emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/named-lambdas db8090ebf1b 1/3: Merge branch 'master' into featur


From: Alan Mackenzie
Subject: feature/named-lambdas db8090ebf1b 1/3: Merge branch 'master' into feature/named-lambdas
Date: Sat, 7 Oct 2023 14:03:45 -0400 (EDT)

branch: feature/named-lambdas
commit db8090ebf1b09dfb66139f5722518f03a5ba09ee
Merge: aba3d13ea8d 53162eff8dd
Author: Alan Mackenzie <acm@muc.de>
Commit: Alan Mackenzie <acm@muc.de>

    Merge branch 'master' into feature/named-lambdas
---
 .clangd                                            |     5 +
 .dir-locals.el                                     |     2 +-
 .gitattributes                                     |    32 +-
 .gitignore                                         |    49 +
 .mailmap                                           |     5 +-
 ChangeLog.1                                        |    14 +-
 ChangeLog.4                                        |   735 +-
 ChangeLog.android                                  |  7278 +++++++
 INSTALL                                            |    17 +-
 Makefile.in                                        |    64 +-
 README                                             |     5 +
 admin/MAINTAINERS                                  |     6 +
 admin/emake                                        |    47 +-
 admin/git-bisect-start                             |    21 +-
 admin/make-tarball.txt                             |     7 +
 admin/merge-gnulib                                 |    16 +-
 admin/notes/bugtracker                             |    27 +-
 admin/notes/copyright                              |     2 +-
 admin/notes/unicode                                |    29 +-
 admin/upload-manuals                               |    13 +-
 autogen.sh                                         |    13 +
 build-aux/config.guess                             |    33 +-
 build-aux/config.sub                               |    37 +-
 build-aux/git-hooks/pre-commit                     |    11 +
 build-aux/git-hooks/prepare-commit-msg             |     4 +
 build-aux/make-info-dir                            |     2 +-
 build-aux/makecounter.sh                           |    43 +
 build-aux/ndk-build-helper-1.mk                    |   112 +
 build-aux/ndk-build-helper-2.mk                    |   105 +
 .../prepare-commit-msg => ndk-build-helper-3.mk}   |    39 +-
 build-aux/ndk-build-helper-4.mk                    |    39 +
 build-aux/ndk-build-helper.mk                      |    81 +
 build-aux/ndk-module-extract.awk                   |    88 +
 configure.ac                                       |  1574 +-
 cross/Makefile.in                                  |   194 +
 cross/README                                       |     5 +
 cross/langinfo.h                                   |    20 +
 cross/ndk-build/Makefile.in                        |   144 +
 cross/ndk-build/README                             |   353 +
 .../ndk-build/ndk-build-executable.mk              |    33 +-
 cross/ndk-build/ndk-build-shared-library.mk        |   171 +
 cross/ndk-build/ndk-build-static-library.mk        |   142 +
 cross/ndk-build/ndk-build.mk.in                    |    68 +
 cross/ndk-build/ndk-clear-vars.mk                  |    57 +
 .../ndk-build/ndk-prebuilt-shared-library.mk       |    33 +-
 .../ndk-build/ndk-prebuilt-static-library.mk       |    33 +-
 cross/ndk-build/ndk-resolve.mk                     |   162 +
 src/verbose.mk.in => cross/verbose.mk.android      |    34 +-
 doc/emacs/ChangeLog.1                              |    36 +-
 doc/emacs/Makefile.in                              |     2 +
 doc/emacs/ack.texi                                 |     2 +-
 doc/emacs/android.texi                             |   793 +
 doc/emacs/custom.texi                              |     7 +-
 doc/emacs/dired.texi                               |    30 +-
 doc/emacs/emacs.texi                               |    21 +
 doc/emacs/files.texi                               |    19 +-
 doc/emacs/frames.texi                              |    24 +
 doc/emacs/input.texi                               |   179 +
 doc/emacs/mini.texi                                |     4 +-
 doc/emacs/package.texi                             |     8 +-
 doc/emacs/search.texi                              |    25 +-
 doc/emacs/sending.texi                             |    22 +
 doc/emacs/trouble.texi                             |     4 +-
 doc/emacs/windows.texi                             |     7 +
 doc/lispref/ChangeLog.1                            |     8 +-
 doc/lispref/commands.texi                          |   426 +-
 doc/lispref/display.texi                           |    23 +-
 doc/lispref/elisp.texi                             |     1 +
 doc/lispref/files.texi                             |    22 +-
 doc/lispref/frames.texi                            |   596 +-
 doc/lispref/functions.texi                         |     2 +-
 doc/lispref/keymaps.texi                           |    45 +-
 doc/lispref/loading.texi                           |     4 +-
 doc/lispref/minibuf.texi                           |    50 +-
 doc/lispref/modes.texi                             |     4 -
 doc/lispref/os.texi                                |    95 +-
 doc/lispref/processes.texi                         |    23 +-
 doc/lispref/searching.texi                         |    10 +-
 doc/lispref/text.texi                              |    19 +-
 doc/lispref/variables.texi                         |     4 +-
 doc/misc/calc.texi                                 |     4 +-
 doc/misc/cl.texi                                   |    13 +-
 doc/misc/eglot.texi                                |     7 +-
 doc/misc/erc.texi                                  |    40 +-
 doc/misc/eshell.texi                               |   132 +-
 doc/misc/eww.texi                                  |    13 +-
 doc/misc/gnus.texi                                 |     4 +-
 doc/misc/org.org                                   |    15 +-
 doc/misc/ses.texi                                  |    15 +-
 doc/misc/smtpmail.texi                             |     6 +-
 doc/misc/texinfo.tex                               |   257 +-
 doc/misc/tramp.texi                                |    51 +-
 doc/misc/transient.texi                            |   131 +-
 doc/misc/url.texi                                  |    10 +-
 etc/AUTHORS                                        |    49 +-
 etc/ChangeLog.1                                    |    30 +-
 etc/DEBUG                                          |   200 +-
 etc/EGLOT-NEWS                                     |     6 +
 etc/ERC-NEWS                                       |   126 +-
 etc/HISTORY                                        |     5 +-
 etc/MACHINES                                       |    20 +
 etc/NEWS                                           |   291 +-
 etc/NEWS.28                                        |     2 +-
 etc/NEWS.29                                        |   742 +-
 etc/PROBLEMS                                       |   228 +-
 etc/TODO                                           |    13 +
 etc/images/alt.pbm                                 |   Bin 0 -> 85 bytes
 etc/images/ctrl.pbm                                |   Bin 0 -> 104 bytes
 etc/images/hyper.pbm                               |   Bin 0 -> 123 bytes
 etc/images/last-page.pbm                           |   Bin 0 -> 81 bytes
 etc/images/last-page.xpm                           |   122 +
 etc/images/meta.pbm                                |   Bin 0 -> 104 bytes
 etc/images/shift.pbm                               |   Bin 0 -> 169 bytes
 etc/images/super.pbm                               |   Bin 0 -> 123 bytes
 etc/refcards/orgcard.tex                           |     2 +-
 etc/themes/deeper-blue-theme.el                    |     8 +-
 etc/themes/leuven-dark-theme.el                    |    10 +-
 etc/themes/leuven-theme.el                         |     8 +-
 etc/themes/manoj-dark-theme.el                     |     4 +-
 etc/themes/whiteboard-theme.el                     |     8 +-
 exec/Makefile.in                                   |   140 +
 exec/README                                        |     3 +
 exec/config-mips.m4.in                             |    42 +
 {build-aux => exec}/config.guess                   |    30 +-
 {build-aux => exec}/config.sub                     |    58 +-
 exec/configure.ac                                  |   537 +
 exec/deps.mk                                       |    21 +
 exec/exec.c                                        |  1168 ++
 exec/exec.h                                        |   201 +
 exec/exec1.c                                       |    94 +
 exec/install-sh                                    |   541 +
 exec/loader-aarch64.s                              |   187 +
 exec/loader-armeabi.s                              |   204 +
 exec/loader-mips64el.s                             |   234 +
 exec/loader-mipsel.s                               |   236 +
 exec/loader-x86.s                                  |   203 +
 exec/loader-x86_64.s                               |   195 +
 exec/mipsel-user.h                                 |    42 +
 exec/mipsfpu.c                                     |   289 +
 exec/mipsfpu.h                                     |    82 +
 exec/test.c                                        |   105 +
 exec/trace.c                                       |  1432 ++
 java/AndroidManifest.xml.in                        |   230 +
 java/INSTALL                                       |  1028 +
 java/Makefile.in                                   |   344 +
 java/README                                        |  1048 +
 java/debug.sh                                      |   368 +
 java/emacs.keystore                                |   Bin 0 -> 2776 bytes
 java/org/gnu/emacs/EmacsActivity.java              |   473 +
 java/org/gnu/emacs/EmacsApplication.java           |    92 +
 java/org/gnu/emacs/EmacsClipboard.java             |    47 +
 java/org/gnu/emacs/EmacsContextMenu.java           |   389 +
 java/org/gnu/emacs/EmacsCursor.java                |    47 +
 java/org/gnu/emacs/EmacsDesktopNotification.java   |   175 +
 java/org/gnu/emacs/EmacsDialog.java                |   416 +
 java/org/gnu/emacs/EmacsDialogButtonLayout.java    |   152 +
 java/org/gnu/emacs/EmacsDirectoryEntry.java        |    33 +
 java/org/gnu/emacs/EmacsDocumentsProvider.java     |   578 +
 java/org/gnu/emacs/EmacsDrawLine.java              |    79 +
 java/org/gnu/emacs/EmacsDrawPoint.java             |    34 +
 java/org/gnu/emacs/EmacsDrawRectangle.java         |   120 +
 java/org/gnu/emacs/EmacsDrawable.java              |    32 +
 java/org/gnu/emacs/EmacsFillPolygon.java           |    80 +
 java/org/gnu/emacs/EmacsFillRectangle.java         |   116 +
 java/org/gnu/emacs/EmacsFontDriver.java            |   173 +
 java/org/gnu/emacs/EmacsGC.java                    |   121 +
 java/org/gnu/emacs/EmacsHandleObject.java          |    59 +
 java/org/gnu/emacs/EmacsHolder.java                |    30 +
 java/org/gnu/emacs/EmacsInputConnection.java       |   698 +
 .../emacs/EmacsLauncherPreferencesActivity.java    |    31 +
 java/org/gnu/emacs/EmacsMultitaskActivity.java     |    29 +
 java/org/gnu/emacs/EmacsNative.java                |   316 +
 java/org/gnu/emacs/EmacsNoninteractive.java        |   203 +
 java/org/gnu/emacs/EmacsOpenActivity.java          |   540 +
 java/org/gnu/emacs/EmacsPixmap.java                |   192 +
 java/org/gnu/emacs/EmacsPreferencesActivity.java   |   168 +
 java/org/gnu/emacs/EmacsSafThread.java             |  1711 ++
 java/org/gnu/emacs/EmacsSdk11Clipboard.java        |   284 +
 java/org/gnu/emacs/EmacsSdk23FontDriver.java       |   114 +
 java/org/gnu/emacs/EmacsSdk7FontDriver.java        |   539 +
 java/org/gnu/emacs/EmacsSdk8Clipboard.java         |   147 +
 java/org/gnu/emacs/EmacsService.java               |  1811 ++
 java/org/gnu/emacs/EmacsSurfaceView.java           |   223 +
 java/org/gnu/emacs/EmacsThread.java                |    82 +
 java/org/gnu/emacs/EmacsView.java                  |   846 +
 java/org/gnu/emacs/EmacsWindow.java                |  1445 ++
 .../gnu/emacs/EmacsWindowAttachmentManager.java    |   206 +
 java/res/drawable/emacs.png                        |   Bin 0 -> 13462 bytes
 java/res/drawable/emacs_background.xml             |    42 +
 java/res/drawable/emacs_foreground.xml             |    39 +
 java/res/drawable/emacs_wrench.png                 |   Bin 0 -> 24996 bytes
 java/res/layout/sdk8_notifications_view.xml        |    33 +
 java/res/mipmap-v26/emacs_icon.xml                 |    23 +
 java/res/mipmap/emacs_icon.png                     |   Bin 0 -> 13462 bytes
 java/res/values-v11/style.xml                      |    24 +
 java/res/values-v14/style.xml                      |    25 +
 java/res/values-v19/bool.xml                       |    22 +
 java/res/values-v24/bool.xml                       |    22 +
 java/res/values-v29/style.xml                      |    32 +
 java/res/values/bool.xml                           |    23 +
 java/res/values/strings.xml                        |    45 +
 java/res/values/style.xml                          |    26 +
 java/res/xml/preferences.xml                       |    30 +
 lib-src/ChangeLog.1                                |     4 +-
 lib-src/Makefile.in                                |    23 +-
 lib-src/asset-directory-tool.c                     |   289 +
 lib-src/emacsclient.c                              |     2 +
 lib/Makefile.in                                    |    28 +-
 lib/boot-time-aux.h                                |   317 +
 lib/boot-time.c                                    |   287 +
 lib/boot-time.h                                    |    44 +
 lib/diffseq.h                                      |    24 +-
 lib/getdelim.c                                     |   143 +
 lib/getline.c                                      |    27 +
 lib/gnulib.mk.in                                   |   145 +-
 lib/mini-gmp.c                                     |    11 +-
 lib/nproc.c                                        |     4 +-
 lib/readutmp.h                                     |   334 +
 lib/stdalign.in.h                                  |    49 +
 lib/time.in.h                                      |    81 +
 lisp/ChangeLog.13                                  |    40 +-
 lisp/ChangeLog.14                                  |    36 +-
 lisp/ChangeLog.15                                  |     2 +-
 lisp/ChangeLog.16                                  |     3 +-
 lisp/ChangeLog.17                                  |    22 +-
 lisp/ChangeLog.3                                   |   150 +-
 lisp/ChangeLog.4                                   |     8 +-
 lisp/ChangeLog.5                                   |     2 +-
 lisp/ChangeLog.6                                   |     2 +-
 lisp/ChangeLog.7                                   |     4 +-
 lisp/Makefile.in                                   |     4 -
 lisp/align.el                                      |    65 +-
 lisp/arc-mode.el                                   |     8 +-
 lisp/auth-source-pass.el                           |     8 +-
 lisp/auth-source.el                                |   109 +-
 lisp/battery.el                                    |    80 +
 lisp/bindings.el                                   |    20 +-
 lisp/bookmark.el                                   |     6 +-
 lisp/button.el                                     |    30 +-
 lisp/calc/calc.el                                  |    96 +-
 lisp/calendar/appt.el                              |     3 +-
 lisp/calendar/cal-move.el                          |     6 +-
 lisp/calendar/calendar.el                          |    20 +-
 lisp/calendar/holidays.el                          |     4 +-
 lisp/calendar/lunar.el                             |     2 +-
 lisp/cedet/mode-local.el                           |     2 +-
 lisp/cedet/semantic/db-ebrowse.el                  |     3 +-
 lisp/cedet/semantic/decorate/include.el            |     2 +-
 lisp/cedet/semantic/fw.el                          |     5 +-
 lisp/cedet/semantic/grammar.el                     |     9 +-
 lisp/comint.el                                     |     7 +-
 lisp/completion.el                                 |     8 +-
 lisp/cus-edit.el                                   |    95 +-
 lisp/cus-face.el                                   |     2 +-
 lisp/cus-start.el                                  |     1 +
 lisp/cus-theme.el                                  |    34 +-
 lisp/custom.el                                     |     2 +-
 lisp/desktop.el                                    |     2 +-
 lisp/dired-aux.el                                  |   203 +-
 lisp/dired-x.el                                    |     6 +-
 lisp/dired.el                                      |   192 +-
 lisp/doc-view.el                                   |    74 +-
 lisp/elec-pair.el                                  |    14 +
 lisp/emacs-lisp/advice.el                          |     1 +
 lisp/emacs-lisp/byte-opt.el                        |    69 +-
 lisp/emacs-lisp/bytecomp.el                        |   162 +-
 lisp/emacs-lisp/checkdoc.el                        |    13 +-
 lisp/emacs-lisp/cl-generic.el                      |     8 +-
 lisp/emacs-lisp/cl-indent.el                       |     2 +-
 lisp/emacs-lisp/cl-macs.el                         |    23 +-
 lisp/emacs-lisp/comp.el                            |    82 +-
 lisp/emacs-lisp/easy-mmode.el                      |    10 +-
 lisp/emacs-lisp/eieio.el                           |     3 +-
 lisp/emacs-lisp/eldoc.el                           |     3 +-
 lisp/emacs-lisp/ert.el                             |     4 +-
 lisp/emacs-lisp/find-func.el                       |     2 +-
 lisp/emacs-lisp/lisp-mnt.el                        |    36 +-
 lisp/emacs-lisp/lisp-mode.el                       |     9 +-
 lisp/emacs-lisp/loaddefs-gen.el                    |     3 +-
 lisp/emacs-lisp/macroexp.el                        |    97 +-
 lisp/emacs-lisp/map.el                             |    33 +-
 lisp/emacs-lisp/oclosure.el                        |     2 +-
 lisp/emacs-lisp/package-vc.el                      |   201 +-
 lisp/emacs-lisp/package.el                         |   135 +-
 lisp/emacs-lisp/pp.el                              |     7 +-
 lisp/emacs-lisp/rx.el                              |   788 +-
 lisp/emacs-lisp/shortdoc.el                        |    20 +-
 lisp/emacs-lisp/subr-x.el                          |    15 +-
 lisp/emacs-lisp/timer.el                           |     9 +-
 lisp/emulation/cua-base.el                         |     2 +-
 lisp/emulation/viper-cmd.el                        |    10 +-
 lisp/epa-file.el                                   |     9 +-
 lisp/epg.el                                        |     3 +-
 lisp/erc/erc-backend.el                            |    23 +-
 lisp/erc/erc-button.el                             |    49 +-
 lisp/erc/erc-common.el                             |     9 +-
 lisp/erc/erc-compat.el                             |     2 +-
 lisp/erc/erc-fill.el                               |   195 +-
 lisp/erc/erc-goodies.el                            |     4 +-
 lisp/erc/erc-log.el                                |     9 +
 lisp/erc/erc-match.el                              |    46 +-
 lisp/erc/erc-networks.el                           |    29 +-
 lisp/erc/erc-nicks.el                              |     8 +-
 lisp/erc/erc-speedbar.el                           |   112 +-
 lisp/erc/erc-stamp.el                              |   202 +-
 lisp/erc/erc-status-sidebar.el                     |   105 +-
 lisp/erc/erc-truncate.el                           |    45 +-
 lisp/erc/erc.el                                    |    65 +-
 lisp/eshell/em-cmpl.el                             |    23 +
 lisp/eshell/em-dirs.el                             |    10 +-
 lisp/eshell/em-glob.el                             |    22 +-
 lisp/eshell/em-hist.el                             |    29 +-
 lisp/eshell/em-ls.el                               |     6 +-
 lisp/eshell/em-pred.el                             |    29 +-
 lisp/eshell/em-smart.el                            |     4 +-
 lisp/eshell/em-term.el                             |     5 +-
 lisp/eshell/em-unix.el                             |   106 +-
 lisp/eshell/esh-arg.el                             |     2 +-
 lisp/eshell/esh-cmd.el                             |   190 +-
 lisp/eshell/esh-io.el                              |   308 +-
 lisp/eshell/esh-proc.el                            |    31 +-
 lisp/eshell/esh-util.el                            |    56 +-
 lisp/eshell/esh-var.el                             |    11 +-
 lisp/face-remap.el                                 |    13 +-
 lisp/faces.el                                      |    88 +-
 lisp/ffap.el                                       |     8 +-
 lisp/files-x.el                                    |     5 +-
 lisp/files.el                                      |    84 +-
 lisp/find-dired.el                                 |     4 +-
 lisp/finder.el                                     |     2 +-
 lisp/format.el                                     |    13 +-
 lisp/frame.el                                      |    88 +-
 lisp/gnus/gmm-utils.el                             |     3 +-
 lisp/gnus/gnus-cite.el                             |     6 -
 lisp/gnus/gnus-score.el                            |    81 +-
 lisp/gnus/gnus-search.el                           |     7 +
 lisp/gnus/gnus-uu.el                               |     3 +-
 lisp/gnus/mail-source.el                           |     2 +-
 lisp/gnus/message.el                               |     5 +-
 lisp/gnus/mm-view.el                               |     2 -
 lisp/gnus/nnmail.el                                |     4 +-
 lisp/gnus/nnmairix.el                              |     3 +-
 lisp/gnus/nnspool.el                               |     6 +-
 lisp/gnus/spam.el                                  |     3 +-
 lisp/help-fns.el                                   |    38 +-
 lisp/help-macro.el                                 |     9 +-
 lisp/help.el                                       |     2 +-
 lisp/hexl.el                                       |     2 +-
 lisp/htmlfontify.el                                |     5 +-
 lisp/ibuf-ext.el                                   |     6 +-
 lisp/ibuf-macs.el                                  |     2 +-
 lisp/ielm.el                                       |     7 +-
 lisp/image-mode.el                                 |     5 +-
 lisp/image/image-dired-external.el                 |     3 +-
 lisp/image/image-dired-util.el                     |   101 +-
 lisp/image/image-dired.el                          |    80 +-
 lisp/image/wallpaper.el                            |     2 +
 lisp/indent-aux.el                                 |    76 +
 lisp/info.el                                       |     2 +-
 lisp/international/characters.el                   |   217 +-
 lisp/international/fontset.el                      |    25 +-
 lisp/international/iso-transl.el                   |     4 +
 lisp/international/mule-cmds.el                    |     6 +-
 lisp/isearch.el                                    |    32 +
 lisp/jit-lock.el                                   |     5 +-
 lisp/jsonrpc.el                                    |     4 +-
 lisp/keymap.el                                     |     4 +-
 lisp/kmacro.el                                     |    17 +-
 lisp/language/chinese.el                           |     5 +
 lisp/language/japanese.el                          |     1 +
 lisp/language/korean.el                            |     1 +
 lisp/language/tibetan.el                           |    32 +-
 lisp/language/vietnamese.el                        |    32 +-
 lisp/ldefs-boot.el                                 |   191 +-
 lisp/loadhist.el                                   |     7 +-
 lisp/loadup.el                                     |   179 +-
 lisp/ls-lisp.el                                    |     2 +-
 lisp/mail/emacsbug.el                              |     4 +
 lisp/mail/footnote.el                              |     7 +-
 lisp/mail/rmail.el                                 |    10 +-
 lisp/mail/supercite.el                             |     3 -
 lisp/man.el                                        |    13 +-
 lisp/menu-bar.el                                   |    49 +-
 lisp/minibuffer.el                                 |    72 +-
 lisp/mouse.el                                      |    60 +-
 lisp/mwheel.el                                     |    12 +-
 lisp/net/ange-ftp.el                               |     2 +-
 lisp/net/browse-url.el                             |    35 +
 lisp/net/dictionary.el                             |    96 +-
 lisp/net/dns.el                                    |     2 +-
 lisp/net/eww.el                                    |    55 +-
 lisp/net/imap.el                                   |     2 +-
 lisp/net/newst-backend.el                          |    12 +-
 lisp/net/newst-plainview.el                        |     4 +-
 lisp/net/newsticker.el                             |     4 +-
 lisp/net/rcirc.el                                  |     2 +-
 lisp/net/shr.el                                    |     9 +-
 lisp/net/tramp-adb.el                              |   220 +-
 lisp/net/tramp-archive.el                          |     6 +-
 lisp/net/tramp-cache.el                            |    26 +-
 lisp/net/tramp-cmds.el                             |    14 +-
 lisp/net/tramp-compat.el                           |    10 +-
 lisp/net/tramp-container.el                        |    51 +-
 lisp/net/tramp-crypt.el                            |   142 +-
 lisp/net/tramp-gvfs.el                             |   263 +-
 lisp/net/tramp-integration.el                      |     3 +
 lisp/net/tramp-message.el                          |   580 +
 lisp/net/tramp-rclone.el                           |   102 +-
 lisp/net/tramp-sh.el                               |   929 +-
 lisp/net/tramp-smb.el                              |   331 +-
 lisp/net/tramp-sshfs.el                            |   103 +-
 lisp/net/tramp-sudoedit.el                         |    74 +-
 lisp/net/tramp.el                                  |   922 +-
 lisp/net/trampver.el                               |     2 +
 lisp/notifications.el                              |    11 +-
 lisp/nxml/nxml-maint.el                            |     2 +-
 lisp/nxml/nxml-mode.el                             |    14 +-
 lisp/nxml/rng-util.el                              |    23 +-
 lisp/obsolete/iswitchb.el                          |     2 +-
 lisp/obsolete/terminal.el                          |     4 +-
 lisp/org/ob-C.el                                   |     2 +-
 lisp/org/ob-tangle.el                              |     7 +-
 lisp/org/ol-bibtex.el                              |     5 +-
 lisp/org/org-clock.el                              |    15 +
 lisp/org/org-colview.el                            |     1 +
 lisp/org/org-ctags.el                              |     4 +-
 lisp/org/org-element.el                            |    10 +-
 lisp/org/org-faces.el                              |     2 +-
 lisp/org/org-fold-core.el                          |    58 +-
 lisp/org/org-mouse.el                              |     6 +-
 lisp/org/org-num.el                                |     1 +
 lisp/org/org-table.el                              |     2 +
 lisp/org/org-version.el                            |     4 +-
 lisp/org/org.el                                    |    27 +-
 lisp/org/ox-beamer.el                              |     9 +-
 lisp/org/ox-publish.el                             |     3 +-
 lisp/org/ox-texinfo.el                             |     3 -
 lisp/org/ox.el                                     |     7 +-
 lisp/pcmpl-unix.el                                 |     2 +-
 lisp/pcomplete.el                                  |    21 +-
 lisp/pixel-scroll.el                               |   113 +-
 lisp/play/cookie1.el                               |     2 +-
 lisp/play/doctor.el                                |     5 +
 lisp/play/dunnet.el                                |     5 +
 lisp/play/gamegrid.el                              |     4 +-
 lisp/printing.el                                   |     3 +-
 lisp/proced.el                                     |    11 +-
 lisp/progmodes/asm-mode.el                         |     4 +-
 lisp/progmodes/c-ts-mode.el                        |   119 +-
 lisp/progmodes/cc-cmds.el                          |    35 +
 lisp/progmodes/cc-defs.el                          |    29 +-
 lisp/progmodes/cc-langs.el                         |    29 +-
 lisp/progmodes/cc-mode.el                          |    28 +-
 lisp/progmodes/cmake-ts-mode.el                    |    52 +-
 lisp/progmodes/compile.el                          |    13 +-
 lisp/progmodes/cperl-mode.el                       |   596 +-
 lisp/progmodes/csharp-mode.el                      |    12 +-
 lisp/progmodes/eglot.el                            |    52 +-
 lisp/progmodes/elisp-mode.el                       |    62 +-
 lisp/progmodes/f90.el                              |     1 -
 lisp/progmodes/gud.el                              |     2 +-
 lisp/progmodes/idlw-help.el                        |     1 -
 lisp/progmodes/idlwave.el                          |    11 +-
 lisp/progmodes/js.el                               |    34 +-
 lisp/progmodes/make-mode.el                        |   128 +-
 lisp/progmodes/prog-mode.el                        |     2 +
 lisp/progmodes/project.el                          |   178 +-
 lisp/progmodes/python.el                           |    67 +-
 lisp/progmodes/ruby-mode.el                        |     6 +-
 lisp/progmodes/sh-script.el                        |     7 +-
 lisp/progmodes/sql.el                              |    21 +-
 lisp/progmodes/typescript-ts-mode.el               |    82 +-
 lisp/progmodes/vera-mode.el                        |     2 +-
 lisp/progmodes/vhdl-mode.el                        |     2 +-
 lisp/progmodes/xref.el                             |     3 +-
 lisp/replace.el                                    |    10 +-
 lisp/saveplace.el                                  |    14 +-
 lisp/server.el                                     |    29 +-
 lisp/shell.el                                      |     9 +-
 lisp/simple.el                                     |   237 +-
 lisp/speedbar.el                                   |     7 +-
 lisp/sqlite-mode.el                                |     2 +-
 lisp/startup.el                                    |    26 +-
 lisp/strokes.el                                    |     7 +-
 lisp/subr.el                                       |   192 +-
 lisp/tab-bar.el                                    |   176 +-
 lisp/tab-line.el                                   |   259 +-
 lisp/tar-mode.el                                   |   187 +-
 lisp/term.el                                       |    24 +-
 lisp/term/AT386.el                                 |     2 +-
 lisp/term/android-win.el                           |   237 +
 lisp/textmodes/artist.el                           |     4 +-
 lisp/textmodes/bibtex.el                           |     2 +-
 lisp/textmodes/conf-mode.el                        |     7 +-
 lisp/textmodes/ispell.el                           |     1 +
 lisp/textmodes/less-css-mode.el                    |     2 +-
 lisp/textmodes/paragraphs.el                       |     1 -
 lisp/textmodes/reftex-cite.el                      |     4 +-
 lisp/textmodes/reftex-global.el                    |     6 +-
 lisp/textmodes/reftex-index.el                     |    10 +-
 lisp/textmodes/reftex-ref.el                       |     3 +-
 lisp/textmodes/reftex-sel.el                       |     2 +-
 lisp/textmodes/reftex-toc.el                       |    33 +-
 lisp/textmodes/reftex-vars.el                      |     1 -
 lisp/textmodes/reftex.el                           |    54 +-
 lisp/textmodes/remember.el                         |     2 +-
 lisp/textmodes/rst.el                              |     5 +-
 lisp/textmodes/sgml-mode.el                        |     1 -
 lisp/textmodes/tex-mode.el                         |     6 +-
 lisp/textmodes/text-mode.el                        |     6 +
 lisp/textmodes/toml-ts-mode.el                     |     4 +-
 lisp/thingatpt.el                                  |     7 +-
 lisp/time.el                                       |     3 +-
 lisp/tool-bar.el                                   |   273 +-
 lisp/tooltip.el                                    |     4 +-
 lisp/touch-screen.el                               |  1596 ++
 lisp/transient.el                                  |   245 +-
 lisp/type-break.el                                 |    25 +-
 lisp/uniquify.el                                   |    38 +-
 lisp/use-package/bind-key.el                       |     1 +
 lisp/use-package/use-package-core.el               |    32 +-
 lisp/userlock.el                                   |    19 +-
 lisp/vc/cvs-status.el                              |     4 +-
 lisp/vc/diff.el                                    |     2 +-
 lisp/vc/ediff-util.el                              |     2 +-
 lisp/vc/smerge-mode.el                             |     4 -
 lisp/vc/vc-dir.el                                  |     3 +-
 lisp/vc/vc-git.el                                  |     7 +-
 lisp/vc/vc-hg.el                                   |     1 -
 lisp/vc/vc-hooks.el                                |     3 +-
 lisp/vc/vc-rcs.el                                  |     5 +-
 lisp/vc/vc.el                                      |    17 +-
 lisp/version.el                                    |    80 +-
 lisp/wdired.el                                     |    20 +-
 lisp/wid-edit.el                                   |   156 +-
 lisp/windmove.el                                   |    10 +-
 lisp/window.el                                     |    26 +-
 lisp/woman.el                                      |     6 +-
 m4/clock_time.m4                                   |    32 +-
 m4/getdelim.m4                                     |   114 +
 m4/getline.m4                                      |   111 +
 m4/gnulib-comp.m4                                  |    37 +
 m4/mktime.m4                                       |     3 +-
 m4/nanosleep.m4                                    |     6 +-
 m4/ndk-build.m4                                    |   479 +
 m4/printf-posix-rpl.m4                             |    26 +
 m4/readutmp.m4                                     |   121 +
 m4/stdalign.m4                                     |    10 +-
 m4/stdint.m4                                       |     7 +-
 m4/time_h.m4                                       |    35 +-
 m4/time_r.m4                                       |     4 +-
 m4/timegm.m4                                       |     3 +-
 make-dist                                          |     3 +-
 msdos/sed1v2.inp                                   |    17 +
 msdos/sed3v2.inp                                   |     1 +
 msdos/sedlibcf.inp                                 |     1 +
 msdos/sedlibmk.inp                                 |    17 +-
 nt/gnulib-cfg.mk                                   |    23 +-
 nt/inc/ms-w32.h                                    |    12 -
 src/ChangeLog.12                                   |     2 +-
 src/ChangeLog.3                                    |    36 +-
 src/Makefile.in                                    |   112 +-
 src/alloc.c                                        |    85 +-
 src/android-asset.h                                |   422 +
 src/android-emacs.c                                |   178 +
 src/android.c                                      |  6903 +++++++
 src/android.h                                      |   325 +
 src/androidfns.c                                   |  3271 +++
 src/androidfont.c                                  |  1104 +
 src/androidgui.h                                   |   754 +
 src/androidmenu.c                                  |   844 +
 src/androidselect.c                                |   803 +
 src/androidterm.c                                  |  6526 ++++++
 src/androidterm.h                                  |   481 +
 src/androidvfs.c                                   |  7399 +++++++
 src/buffer.c                                       |    22 +
 src/buffer.h                                       |    11 +
 src/bytecode.c                                     |    46 +-
 src/callint.c                                      |     6 +-
 src/callproc.c                                     |   122 +-
 src/character.c                                    |    44 +-
 src/charset.c                                      |     7 +-
 src/cmds.c                                         |     3 +-
 src/coding.c                                       |    27 +-
 src/coding.h                                       |     4 +-
 src/comp.c                                         |     4 +-
 src/conf_post.h                                    |    10 +
 src/data.c                                         |    21 +-
 src/dired.c                                        |    74 +-
 src/dispextern.h                                   |    68 +-
 src/dispnew.c                                      |    69 +-
 src/doc.c                                          |    77 +-
 src/editfns.c                                      |    24 +-
 src/emacs-module.c                                 |   162 +-
 src/emacs.c                                        |   187 +-
 src/epaths.in                                      |    22 +
 src/eval.c                                         |     6 +-
 src/fileio.c                                       |   652 +-
 src/filelock.c                                     |   240 +-
 src/fns.c                                          |    10 +-
 src/font.c                                         |    44 +-
 src/font.h                                         |    14 +
 src/fontset.c                                      |    27 +
 src/frame.c                                        |    80 +-
 src/frame.h                                        |   130 +-
 src/fringe.c                                       |    23 +-
 src/haiku_io.c                                     |     2 +
 src/haiku_select.cc                                |   146 +-
 src/haiku_support.cc                               |    58 +-
 src/haiku_support.h                                |     7 +
 src/haikuselect.c                                  |   125 +
 src/haikuselect.h                                  |    25 +-
 src/haikuterm.c                                    |    17 +-
 src/image.c                                        |  1180 +-
 src/inotify.c                                      |    19 +-
 src/keyboard.c                                     |   638 +-
 src/keyboard.h                                     |    21 +-
 src/keymap.c                                       |     6 +-
 src/lisp.h                                         |    49 +-
 src/lread.c                                        |   327 +-
 src/marker.c                                       |    26 +
 src/menu.c                                         |    20 +-
 src/msdos.c                                        |     9 +-
 src/nsfns.m                                        |    33 +-
 src/nsimage.m                                      |     4 +
 src/pdumper.c                                      |    28 +-
 src/pgtkterm.c                                     |    45 +-
 src/pgtkterm.h                                     |     1 -
 src/print.c                                        |    20 +-
 src/process.c                                      |    64 +-
 src/regex-emacs.c                                  |    26 +-
 src/scroll.c                                       |     7 +
 src/search.c                                       |    15 +-
 src/sfnt.c                                         | 19975 +++++++++++++++++++
 src/sfnt.h                                         |  2003 ++
 src/sfntfont-android.c                             |   792 +
 src/sfntfont.c                                     |  3991 ++++
 src/sfntfont.h                                     |    78 +
 src/sound.c                                        |     4 +-
 src/sysdep.c                                       |   267 +-
 src/sysstdio.h                                     |     2 -
 src/term.c                                         |   151 +-
 src/termhooks.h                                    |    34 +-
 src/terminal.c                                     |     2 +
 src/textconv.c                                     |  1976 +-
 src/textconv.h                                     |    52 +-
 src/treesit.c                                      |     4 +
 src/verbose.mk.in                                  |    11 +
 src/w32.c                                          |     3 +-
 src/w32fns.c                                       |     4 +-
 src/w32font.c                                      |     2 +-
 src/w32proc.c                                      |     2 +-
 src/w32term.c                                      |     2 +-
 src/w32term.h                                      |     2 +-
 src/window.c                                       |     5 +-
 src/window.h                                       |    36 +-
 src/xdisp.c                                        |   527 +-
 src/xfaces.c                                       |    65 +-
 src/xfns.c                                         |    61 +-
 src/xmenu.c                                        |    17 +-
 src/xterm.c                                        |   751 +-
 src/xterm.h                                        |    69 +-
 test/Makefile.in                                   |     3 +
 test/lisp/align-resources/align-post.c             |     3 -
 test/lisp/align-resources/align-post.java          |     9 -
 test/lisp/align-resources/align-pre.c              |     3 -
 test/lisp/align-resources/align-pre.java           |     9 -
 test/lisp/align-resources/align-regexp.erts        |    13 +
 test/lisp/align-resources/c-mode.erts              |    23 +
 test/lisp/align-resources/conf-toml-mode.erts      |    45 +
 test/lisp/align-resources/css-mode.erts            |    23 +
 test/lisp/align-resources/java-mode.erts           |    23 +
 test/lisp/align-resources/latex-mode.erts          |    29 +
 test/lisp/align-resources/python-mode.erts         |    29 +
 test/lisp/align-tests.el                           |    51 +-
 test/lisp/auth-source-tests.el                     |    20 +
 test/lisp/calc/calc-tests.el                       |     4 +-
 test/lisp/calculator-tests.el                      |     6 +
 test/lisp/cedet/semantic/bovine/gcc-tests.el       |   109 +-
 test/lisp/cus-edit-tests.el                        |    42 +
 test/lisp/dired-tests.el                           |     4 +-
 .../warn-make-process-missing-keyword-arg.el       |     3 +
 .../warn-make-process-missing-keyword-value.el     |     3 +
 .../warn-make-process-repeated-keyword-arg.el      |     3 +
 .../warn-make-process-unknown-keyword-arg.el       |     4 +
 test/lisp/emacs-lisp/bytecomp-tests.el             |   107 +-
 test/lisp/emacs-lisp/cl-macs-tests.el              |    17 +
 test/lisp/emacs-lisp/lisp-mode-tests.el            |    23 +
 test/lisp/emacs-lisp/macroexp-tests.el             |    16 +
 test/lisp/emacs-lisp/map-tests.el                  |    59 +
 test/lisp/emacs-lisp/nadvice-tests.el              |    34 +-
 test/lisp/emacs-lisp/rx-tests.el                   |   132 +-
 test/lisp/epg-tests.el                             |    11 +-
 test/lisp/erc/erc-button-tests.el                  |    28 +
 test/lisp/erc/erc-fill-tests.el                    |    77 +
 test/lisp/erc/erc-scenarios-base-renick.el         |     4 +-
 test/lisp/erc/erc-scenarios-log.el                 |     1 +
 test/lisp/erc/erc-scenarios-match.el               |    95 +-
 test/lisp/erc/erc-scenarios-status-sidebar.el      |    13 +-
 test/lisp/erc/erc-stamp-tests.el                   |    29 +-
 test/lisp/erc/erc-tests.el                         |    66 +-
 .../erc/resources/base/netid/bouncer/barnet.eld    |    12 +-
 .../erc/resources/base/netid/bouncer/foonet.eld    |    12 +-
 test/lisp/erc/resources/base/reconnect/options.eld |    10 +-
 .../base/renick/queries/bouncer-barnet.eld         |    14 +-
 .../base/renick/queries/bouncer-foonet.eld         |    12 +-
 test/lisp/erc/resources/erc-scenarios-common.el    |     3 +-
 .../erc/resources/fill/snapshots/merge-wrap-01.eld |     1 +
 .../resources/fill/snapshots/stamps-left-01.eld    |     1 +
 .../erc/resources/services/auth-source/libera.eld  |    10 +-
 test/lisp/eshell/em-dirs-tests.el                  |    23 +
 test/lisp/eshell/em-extpipe-tests.el               |    25 +-
 test/lisp/eshell/em-glob-tests.el                  |    64 +
 test/lisp/eshell/em-hist-tests.el                  |    43 +-
 test/lisp/eshell/em-unix-tests.el                  |    68 +
 test/lisp/eshell/esh-io-tests.el                   |    10 +
 test/lisp/eshell/esh-util-tests.el                 |    31 +
 test/lisp/eshell/esh-var-tests.el                  |    46 +
 test/lisp/eshell/eshell-tests.el                   |     9 +-
 test/lisp/files-tests.el                           |    16 +-
 test/lisp/help-fns-tests.el                        |    23 +-
 test/lisp/image/image-dired-util-tests.el          |    17 +-
 test/lisp/minibuffer-tests.el                      |     2 +-
 test/lisp/net/tramp-archive-tests.el               |    29 +-
 test/lisp/net/tramp-tests.el                       |    86 +-
 test/lisp/proced-tests.el                          |    28 +-
 .../progmodes/cperl-mode-resources/sub-names.pl    |    25 +
 test/lisp/progmodes/cperl-mode-tests.el            |    33 +
 test/lisp/progmodes/python-tests.el                |    12 +-
 test/lisp/server-tests.el                          |    12 +-
 test/lisp/textmodes/conf-mode-tests.el             |    26 +-
 test/lisp/uniquify-tests.el                        |    79 +-
 test/lisp/wid-edit-tests.el                        |    31 +
 test/manual/image-tests.el                         |    14 +-
 .../comp-resources/comp-test-funcs-dyn2.el}        |    29 +-
 test/src/comp-tests.el                             |    14 +-
 test/src/data-tests.el                             |    25 +
 test/src/filelock-tests.el                         |    14 +-
 test/src/keyboard-tests.el                         |     5 +
 test/src/regex-emacs-tests.el                      |    16 +
 test/src/syntax-tests.el                           |     3 +-
 test/src/treesit-tests.el                          |     1 +
 742 files changed, 117135 insertions(+), 9282 deletions(-)

diff --git a/.clangd b/.clangd
new file mode 100644
index 00000000000..131d0af5843
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,5 @@
+---
+If:
+    PathMatch: "src/.*\.c"
+CompileFlags:
+    Add: [-Wno-unused-macros, -include=config.h]
diff --git a/.dir-locals.el b/.dir-locals.el
index 0bcded4b5d1..e087aa89cd1 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -12,7 +12,7 @@
  (c-mode . ((c-file-style . "GNU")
             (c-noise-macro-names . ("INLINE" "NO_INLINE" 
"ATTRIBUTE_NO_SANITIZE_UNDEFINED"
                                     "UNINIT" "CALLBACK" "ALIGN_STACK" 
"ATTRIBUTE_MALLOC"
-                                    "ATTRIBUTE_DEALLOC_FREE"))
+                                    "ATTRIBUTE_DEALLOC_FREE" "ANDROID_EXPORT" 
"TEST_STATIC"))
             (electric-quote-comment . nil)
             (electric-quote-string . nil)
             (indent-tabs-mode . t)
diff --git a/.gitattributes b/.gitattributes
index d9288b27d0f..39a82cc0efc 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -17,13 +17,11 @@
 # You should have received a copy of the GNU General Public License
 # along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-# A few files use CRLF endings, even on non-Microsoft platforms.
+# A few UTF-8-compatible text files use CRLF endings,
+# even on non-Microsoft platforms.
 # Do not warn about trailing whitespace with these files.
 *.bat whitespace=cr-at-eol
 admin/charsets/mapfiles/PTCP154 whitespace=cr-at-eol
-leim/MISC-DIC/cangjie-table.b5 whitespace=cr-at-eol
-leim/MISC-DIC/cangjie-table.cns whitespace=cr-at-eol
-leim/MISC-DIC/pinyin.map whitespace=cr-at-eol
 test/manual/etags/c-src/dostorture.c whitespace=cr-at-eol
 test/manual/etags/cp-src/c.C whitespace=cr-at-eol
 test/manual/etags/html-src/algrthms.html whitespace=cr-at-eol
@@ -31,19 +29,41 @@ test/manual/etags/html-src/algrthms.html 
whitespace=cr-at-eol
 # The todo-mode file format includes trailing whitespace.
 *.tod[aorty] -whitespace=blank-at-eol
 
+# The following text files use encodings incompatible with UTF-8.
+# They should not be treated as text when diffing, as that could
+# cause the output to mix encodings.
+*.tit -diff
+admin/charsets/mapfiles/cns2ucsdkw.txt -diff
+leim/MISC-DIC/CTLau* -diff
+leim/MISC-DIC/cangjie-table.* -diff
+leim/MISC-DIC/pinyin.map -diff
+leim/MISC-DIC/ziranma.cin -diff
+leim/SKK-DIC/SKK-JISYO.L -diff
+src/msdos.c -diff
+test/lisp/gnus/mm-decode-resources/win1252-multipart.bin -diff
+
 # Some files should not be treated as text when diffing or merging.
+*.bmp binary
 *.cur binary
+*.gif binary
 *.gpg binary
 *.gz binary
 *.icns binary
 *.ico binary
+*.jpg binary
+*.kbx binary
+*.key binary
 *.pbm binary
 *.pdf binary
 *.pif binary
 *.png binary
 *.sig binary
 *.tiff binary
+*.webp binary
+*.zip binary
 etc/e/eterm-color binary
+etc/e/eterm-direct binary
+java/emacs.keystore binary
 
 # Git's builtin diff hunk header styles.
 *.ad[abs] diff=ada
@@ -96,3 +116,7 @@ build-aux/msys-to-w32 diff=shell
 build-aux/update-subdirs diff=shell
 lib-src/rcs2log diff=shell
 /make-dist diff=shell
+
+# This file contains in-line diffs, which can include trailing
+# whitespace.
+java/INSTALL -whitespace
diff --git a/.gitignore b/.gitignore
index b09a0c030b3..0a68121009d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,23 @@ src/config.h
 src/epaths.h
 src/emacs-module.h
 
+# Built by recursive call to `configure'.
+*.android
+!INSTALL.android
+!verbose.mk.android
+
+# Built by `javac'.
+java/install_temp/*
+java/*.apk*
+java/*.dex
+java/org/gnu/emacs/*.class
+
+# Built by `aapt'.
+java/org/gnu/emacs/R.java
+
+# Built by `config.status'.
+java/AndroidManifest.xml
+
 # C-level sources built by 'make'.
 lib/alloca.h
 lib/assert.h
@@ -70,8 +87,10 @@ lib/limits.h
 lib/malloc/*.gl.h
 lib/signal.h
 lib/std*.h
+lib/math.h
 !lib/std*.in.h
 !lib/stdio-impl.h
+!lib/_Noreturn.h
 lib/string.h
 lib/sys/
 lib/time.h
@@ -81,6 +100,19 @@ src/globals.h
 src/lisp.mk
 src/verbose.mk
 
+# Stuff built during cross compilation
+cross/lib/*
+cross/src/*
+cross/lib-src/*
+cross/sys/*
+cross/config.status
+cross/*.bak
+cross/etc/DOC
+
+cross/ndk-build/Makefile
+cross/ndk-build/ndk-build.mk
+cross/ndk-build/*.o
+
 # Lisp-level sources built by 'make'.
 *cus-load.el
 *loaddefs.el
@@ -186,6 +218,7 @@ ID
 # Executables.
 *.exe
 a.out
+lib-src/asset-directory-tool
 lib-src/be-resources
 lib-src/blessmail
 lib-src/ctags
@@ -208,6 +241,7 @@ nextstep/GNUstep/Emacs.base/Resources/Info-gnustep.plist
 src/bootstrap-emacs
 src/emacs
 src/emacs-[0-9]*
+src/sfnt
 src/Emacs
 src/temacs
 src/dmpstruct.h
@@ -338,3 +372,18 @@ lib-src/seccomp-filter-exec.pfc
 # GDB history
 .gdb_history
 _gdb_history
+
+# Files ignored in exec/.
+exec/aclocal.m4
+exec/config.status
+exec/loader
+exec/test
+exec/exec1
+exec/deps/*
+exec/aclocal.m4
+exec/autom4te.cache
+exec/config.h
+exec/config.h.in
+exec/config-mips.m4
+exec/configure
+exec/*.s.s
diff --git a/.mailmap b/.mailmap
index 8454eb9154c..f14f086b2e5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -51,8 +51,7 @@ David M. Koppelman <koppel@ece.lsu.edu>
 Deniz Dogan <deniz@dogan.se> <deniz.a.m.dogan@gmail.com>
 Dick R. Chiang <dick.r.chiang@gmail.com>
 Dick R. Chiang <dick.r.chiang@gmail.com> dickmao <none>
-Earl Hyatt <ej32u@protonmail.com>
-Earl Hyatt <ej32u@protonmail.com> <okamsn@protonmail.com>
+Earl Hyatt <okamsn@protonmail.com> <ej32u@protonmail.com>
 Edward M. Reingold <reingold@emr.cs.iit.edu>
 Eli Zaretskii <eliz@gnu.org> <eliz@is.elta.co.il>
 Emilio C. Lopes <eclig@gmx.net>
@@ -61,7 +60,7 @@ Era Eriksson <era+emacs@iki.fi> <era+emacsbugs@iki.fi>
 Eric Ludlam <zappo@gnu.org>
 Eric Ludlam <zappo@gnu.org> <eric@siege-engine.com>
 Eric Ludlam <zappo@gnu.org> <ericludlam@gmail.com>
-Eric S. Raymond <esr@thyrsus.com> <esr@snark.thyrsus.com>
+Eric S. Raymond <esr@thyrsus.com>
 Etienne Prud’homme <e.e.f.prudhomme@gmail.com>
 Fabián Ezequiel Gallina <fgallina@gnu.org> <fgallina@cuca>
 Fabián Ezequiel Gallina <fgallina@gnu.org> <galli.87@gmail.com>
diff --git a/ChangeLog.1 b/ChangeLog.1
index fcfae7c3883..654cb43d4dc 100644
--- a/ChangeLog.1
+++ b/ChangeLog.1
@@ -7582,7 +7582,7 @@
 
        * INSTALL.CVS: Clarify why `make bootstrap' sometimes fails.
 
-2008-06-08  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-06-08  Eric S. Raymond  <esr@thyrsus.com>
 
        * INSTALL.CVS: Indicate when "cvs update -d" may be needed.
 
@@ -9672,7 +9672,7 @@
        * configure.in: New entry for HP/UX-11.
 
        * Makefile.in (SOURCES): Replace GETTING.GNU.SOFTWARE with FTP.
-       From Eric S. Raymond <esr@golux.thyrsus.com>.
+       From Eric S. Raymond <esr@thyrsus.com>.
 
 2001-10-28  Eli Zaretskii  <eliz@is.elta.co.il>
 
@@ -13896,7 +13896,7 @@
        places.  Implement them by setting C_SWITCH_X_SITE and
        LD_SWITCH_X_SITE in src/config.h.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * make-dist: Don't distribute etc/Old files.
 
@@ -13907,7 +13907,7 @@
 
        * make-dist: Fix typo.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * make-dist: Corrected typo, fixed it to discard = and TAGS files
        in some cases where it should but didn't seen to.
@@ -13924,11 +13924,11 @@
        * configure: Recognize rs6000-ibm-aix32 and rs6000-ibm-aix, and
        make rs6000-ibm-aix default to -aix32.
 
-1993-03-17  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * Makefile.in: Added `Developer's configuration' section.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * Makefile.in: Add commented-out variable settings for developer's
        configuration.
@@ -14299,7 +14299,7 @@
        return handler, make a copy of it, not a symbolic link to it; that
        way, it will work on systems that don't have symbolic links.
 
-1992-08-14  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-08-14  Eric S. Raymond  (esr@thyrsus.com)
 
        * make-dist: Taught it about vcdiff and rcs2log, added --newer
        option for generating incremental distributions.  Stopped it from
diff --git a/ChangeLog.4 b/ChangeLog.4
index b8efa20cdf9..372b03b32b2 100644
--- a/ChangeLog.4
+++ b/ChangeLog.4
@@ -1,3 +1,732 @@
+2023-07-30  Eli Zaretskii  <eliz@maintain0p.gnu.org>
+
+       * Version 29.1 released.
+
+2023-07-29  Vincenzo Pupillo  <v.pupillo@gmail.com>
+
+       Update CMake support due to upstream changes (bug#64922)
+
+       A recent change in tree-sitter-cmake grammar support for CMake (commit
+       fe9b5e0), now put arguments are wrapped in a new argument_list node.
+       To support the old and new version of the grammar, a new function was
+       added on which string syntax highlighting now depends.
+
+       * lisp/progmodes/cmake-ts-mode.el
+       (cmake-ts-mode--font-lock-compatibility-fe9b5e0): Indent helper
+       function to handle different tree-sitter-cmake version.
+       * lisp/progmodes/cmake-ts-mode.el
+       (cmake-ts-mode--font-lock-settings): Use the new function to handle
+       the new argument_list node.
+
+2023-07-24  Theodor Thornhill  <theo@thornhill.no>
+
+       Remove nullptr named node from c++-ts-mode (bug#64818)
+
+       The nullptr node was changed from a named node to an unnamed node
+       upstream[0], which caused font locking to break. As this is a small
+       enough regression, no compat code is required.
+
+       * lisp/progmodes/c-ts-mode.el (c-ts-mode--font-lock-settings): Remove
+       node no longer in use.
+
+       [0]:
+       
https://github.com/tree-sitter/tree-sitter-c/commit/c75868f8b508ae32a0c8490da91bb31b2b96430e
+
+2023-07-24  Theodor Thornhill  <theo@thornhill.no>
+
+       Make compat check also check typescript
+
+       * lisp/progmodes/typescript-ts-mode.el
+       (tsx-ts-mode--font-lock-compatibility-bb1f97b):
+       Add argument so that we run the 'treesit-query-capture' when the
+       language is 'typescript', not only 'tsx'.
+
+       * lisp/progmodes/typescript-ts-mode.el
+       (typescript-ts-mode--font-lock-settings): Use supplied argument.
+
+2023-07-23  Eli Zaretskii  <eliz@gnu.org>
+
+       Bump Emacs version
+
+       * README:
+       * configure.ac:
+       * nt/README.W32:
+       * msdos/sed2v2.inp: Bump Emacs version to 29.1.
+
+2023-07-23  Eli Zaretskii  <eliz@gnu.org>
+
+       Update AUTHORS and ChangeLog.4
+
+       * ChangeLog.4:
+       * etc/AUTHORS: Update for Emacs 29.1 release.
+
+2023-07-22  Vincenzo Pupillo  <v.pupillo@gmail.com>
+
+       Update JSX support due to upstream changes (bug#64647)
+
+       A recent change in tree-sitter-javascript grammar support for
+       JSX (commit bb1f97b), changed two things:
+       1. renamed nested_identifier to member_expression
+       2. removed jsx_fragment, jsx_text is used instead
+
+       * lisp/progmodes/js.el (js-jsx--treesit-indent-compatibility-bb1f97b):
+       Indent helper function to handle different tree-sitter-javascript
+       version.
+       * lisp/progmodes/js.el (js--treesit-indent-rules): Use the new
+       function to handle both jsx_fragment and jsx_text.
+       * lisp/progmodes/js.el 
(js-jsx--treesit-font-lock-compatibility-bb1f97b):
+       Font lock helper function for handle different tree-sitter-javascript
+       version.
+       * lisp/progmodes/js.el (js--treesit-font-lock-settings): Use the new
+       function to handle both nested_identifier and member_expression.
+
+2023-07-22  Vincenzo Pupillo  <v.pupillo@gmail.com>
+
+       Update TSX support due to upstream changes (bug#64647)
+
+       A recent change in tree-sitter-typescript grammar support for
+       TSX (commit b893426), changed two things:
+       1. renamed nested_identifier to member_expression
+       2. removed jsx_fragment, jsx_text is used instead
+
+       * lisp/progmodes/typescript-ts-mode.el
+       (tsx-ts-mode--indent-compatibility-b893426): Indent helper function
+       to handle different tree-sitter-typescript version.
+       * lisp/progmodes/typescript-ts-mode.el
+       (typescript-ts-mode--indent-rules): use the new function to handle
+       both jsx_fragment and jsx_text.
+       * lisp/progmodes/typescript-ts-mode.el
+       (tsx-ts-mode--font-lock-compatibility-bb1f97b): Font lock helper
+       function for handle different tree-sitter-typescript version.
+       * lisp/progmodes/typescript-ts-mode.el
+       (typescript-ts-mode--font-lock-settings): Use the new function to
+       handle both nested_identifier and member_expression.
+
+2023-07-21  Philipp Stephani  <phst@google.com>
+
+       Unbreak build with CHECK_STRUCTS.
+
+       The hash for the Lisp_Overlay structure has changed due to the 
(comment-only)
+       commit 7ac947f34c745c61f8acc1fe2452a2c720d57a0d.
+
+       * src/pdumper.c (dump_overlay): Update struct hash.
+
+2023-07-21  Mattias Engdegård  <mattiase@acm.org>
+
+       Revert "Improve commentary in nsfns.m"
+
+       This reverts commit 3af27a4b815906c2ee38cbaf3a765289b3df061a,
+       because it missed the point completely.
+       Please talk to the original author next time.
+
+2023-07-20  Eli Zaretskii  <eliz@gnu.org>
+
+       Fix NetBSD build --with-sound
+
+       * src/sound.c (alsa_write): Use ESTRPIPE only if defined.
+       (Bug#64698)
+
+2023-07-20  Mauro Aranda  <maurooaranda@gmail.com>
+
+       Fix typo in pre-filter for underline property
+
+       * lisp/cus-face.el (custom-face-attributes): Fix typo (Bug#64347)
+
+2023-07-20  Po Lu  <luangruo@yahoo.com>
+
+       Improve commentary in nsfns.m
+
+       * src/nsfns.m (lispString): Avoid C++ comment and make the
+       commentary actually relevant to the reason `make_string' is
+       used.
+
+2023-07-20  Po Lu  <luangruo@yahoo.com>
+
+       Fix typos and ommissions in cus-edit.el
+
+       * lisp/cus-edit.el (custom-display): Add missing display types.
+
+2023-07-18  Mattias Engdegård  <mattiase@acm.org>
+
+       Convert NUL-containing NSString objects to Lisp strings correctly
+
+       This cures the inability to paste text containing NUL from other
+       applications on macOS, introduced by mistake in 7e3c2b553f
+       (bug#64697).
+
+       * src/nsfns.m ([NSString lispString]): Use make_string instead of
+       build_string which relies on NUL-termination.
+
+2023-07-15  Spencer Baugh  <sbaugh@catern.com>
+
+       Ignore quit while getting interprogram paste in kill-new
+
+       On X, if the current selection owner is not responding to selection
+       requests, the user may want to take ownership of the selection.  The
+       obvious way to do this is to kill some text (which a user might also
+       be doing just as part of normal editing at the time the selection
+       owner becomes nonresponsive).  However, if
+       save-interprogram-paste-before-kill is non-nil, then killing text will
+       hang until the user quits, and this quit will abort the entire
+       kill-new, preventing the user from taking ownership of the selection.
+
+       Now instead if the user quits while we are attempting to retrieve the
+       selection from hanging owner, we will proceed to take ownership of the
+       selection as normal, resolving the problem.
+
+       (One example of a selction owner that might not be responding to
+       selection requests is another instance of Emacs itself; while Emacs is
+       blocked in call-process or Lisp execution, it currently does not
+       respond to selection requests.)
+
+       * lisp/simple.el (kill-new): Ignore quit while getting interprogram
+       paste (bug#64423)
+
+2023-07-15  Wang Diancheng  <dianchengwang@gmail.com>  (tiny change)
+
+       * lisp/progmodes/gdb-mi.el: Fix interactive invocation of 'gud-go'.
+
+       Bug#64590.
+
+2023-07-15  Valtteri Vuorikoski  <vuori@notcom.org>  (tiny change)
+
+       Fix NetBSD build with and without ncurses
+
+       * configure.ac (netbsd): Don't set TERMINFO=no unless the termcap
+       library is either -ltermcap or -lcurses.  This prevents aborts
+       because on recent versions of NetBSD libtermcap is actually a
+       symlink to libterminfo. (Bug#64577)
+
+2023-07-14  YugaEgo  <yet@ego.team>
+
+       Improve documentation of 'enable-local-variables' in Emacs manual
+
+       * doc/emacs/custom.texi (Safe File Variables): Document ':all'.
+       (Bug#64621)
+
+2023-07-13  Jim Porter  <jporterbugs@gmail.com>
+
+       Add documentation about remote access in Eshell
+
+       Do not merge to master.  This is a backport of 438921161ac.
+
+       * doc/misc/eshell.texi
+       (Invocation): Mention the '*' prefix.
+       (Remote Access): New section...
+       (Commands): ... link to it.
+
+2023-07-13  Jim Porter  <jporterbugs@gmail.com>
+
+       Document some missing Eshell commands
+
+       Do not merge to master.  This is a backport of f7a899d7ca0.
+
+       * doc/misc/eshell.texi (Built-ins): Document 'eshell-debug'.
+       (Extra built-in commands): Document 'count', 'ff', and 'gf'.
+
+2023-07-13  Jim Porter  <jporterbugs@gmail.com>
+
+       Document optional Eshell modules
+
+       Do not merge to master.  This is a backport of 77f13edab0f.
+
+       * doc/misc/eshell.texi (Built-ins): Move disabled-by-default commands
+       to...
+       (Tramp extensions, Extra built-in commands): ...here
+       (Optional modules, Key rebinding, Smart scrolling): Add documentation.
+       (Bug and ideas): Documentation is no longer incomplete!
+
+2023-07-13  Jim Porter  <jporterbugs@gmail.com>
+
+       Correct the Eshell documentation about how to write new modules
+
+       * doc/misc/eshell.texi (Writing a module): Fix the documentation.
+       'eshell-defgroup' doesn't exist anymore.
+
+       Do not merge to master.  This is a backport of 77f13edab0f.
+
+2023-07-13  Jim Porter  <jporterbugs@gmail.com>
+
+       Restructure Eshell extension modules documentation
+
+       This adds a section for documenting all the optional modules.
+
+       Do not merge to master.  This is a backport of f2981a1681d.
+
+       * doc/misc/eshell.texi (Extension modules): Move explanation about
+       writing modules to...
+       (Writing a module): ... here.
+       (Module testing): Remove.  Testing an Eshell module doesn't require
+       any special documentation.
+       (Key binding, Smart scrolling, Electric forward slash): Move under...
+       (Optional modules): ... here.
+       (Directory handling, Terminal emulation): Remove.  These modules are
+       enabled by default, and so are documented above.
+       (Tramp extensions, Extra built-in commands): New sections.
+
+2023-07-13  Jens Schmidt  <jschmidt4gnu@vodafonemail.de>
+
+       Replace duplicate text from epa.texi by a reference
+
+       * doc/misc/auth.texi (GnuPG and EasyPG Assistant Configuration):
+       Replace duplicate text from epa.texi by a reference to
+       that. (Bug#64154)
+
+2023-07-13  Jens Schmidt  <jschmidt4gnu@vodafonemail.de>
+
+       Add basic usage information and fix references
+
+       * doc/misc/epa.texi (Top): Add menu entry for new node GnuPG Pinentry.
+       (Quick Start): Add information on and reference to basic GnuPG
+       configuration.
+       (Encrypting/decrypting gpg files): Add usage information.
+       (GnuPG version compatibility): Update version information.
+       (GnuPG Pinentry): Add new node.
+       (Caching Passphrases): Describe mandatory gpg-agent usage for GnuPG
+       2.0 and later.
+       (Overview, Encrypting/decrypting gpg files, GnuPG version compatibility)
+       (Caching Passphrases, Bug Reports): Fix references, terminology,
+       mark-up, and index entries.  (Bug#64154)
+
+2023-07-13  Jens Schmidt  <jschmidt4gnu@vodafonemail.de>
+
+       Add concept index, title-case structure titles
+
+       * doc/misc/epa.texi (Top, Overview, Commands, Key management)
+       (Cryptographic operations on regions, Cryptographic operations on files)
+       (Dired integration, Mail-mode integration)
+       (Encrypting/decrypting gpg files, Querying a key server)
+       (GnuPG version compatibility, Caching Passphrases)
+       (GNU Free Documentation License): Add concept index, title-case
+       structure titles.  (Bug#64154)
+
+2023-07-10  Eli Zaretskii  <eliz@gnu.org>
+
+       Fix show-paren-mode when the parentheses is partially visible
+
+       * lisp/paren.el (show-paren-function): Support the case where the
+       open paren is partially visible, but enough so to not consider it
+       "off-screen".  (Bug#64547)
+
+2023-07-08  Gregory Heytings  <gregory@heytings.org>
+
+       Merge branch 'scratch/bug64391' into emacs-29
+
+2023-07-08  Michael Albinus  <michael.albinus@gmx.de>
+
+       * lisp/net/tramp.el (tramp-get-buffer-string): Stabilize.
+
+2023-07-08  Michael Albinus  <michael.albinus@gmx.de>
+
+       Fix stale cache in Tramp (don't merge)
+
+       * lisp/net/tramp-sh.el (tramp-do-copy-or-rename-file-directly):
+       Flush cache in time.
+
+2023-07-07  Mattias Engdegård  <mattiase@acm.org>
+
+       Mark failing icalendar test as unstable (bug#56241)
+
+       * test/lisp/calendar/icalendar-tests.el
+       (icalendar-export-bug-56241-dotted-pair):
+       This test started failing early July 2023 in multiple branches at
+       once without any change to the code and is likely sensitive to the
+       current date.  Tag it to keep it quiet for now.
+
+2023-07-06  Gregory Heytings  <gregory@heytings.org>
+
+       Simplify after adding internal function to enter a labeled restriction
+
+       * src/editfns.c: (Finternal__labeled_narrow_to_region): Merge the
+       code of Finternal__label_restriction into this function.
+       (Finternal__label_restriction): Remove this function.
+       (syms_of_editfns): Remove the 'outermost-restriction' buffer local
+       variable, which is not used anymore, and the symbol of
+       'internal--label-restriction'.
+       (Fwiden): Remove the call to reset the 'outermost-restriction'
+       buffer local variable.
+
+2023-07-06  Gregory Heytings  <gregory@heytings.org>
+
+       Add internal function to enter a labeled restriction
+
+       * src/editfns.c (Finternal__labeled_narrow_to_region): New
+       function.  A specific function is necessary to avoid unnecessary
+       slowdowns when 'narrow-to-region'/'widen' are called in a loop.
+       (Fnarrow_to_region): Remove the call to Fset, which has been moved
+       into Finternal__labeled_narrow_to_region.
+       (labeled_narrow_to_region): Use the new function.
+       (syms_of_editfns): Add the symbol of the new function.
+
+       * lisp/subr.el (internal--with-restriction): Use the new function.
+
+2023-07-04  Robert Pluim  <rpluim@gmail.com>
+
+       Improve natnump shortdoc
+
+       * lisp/emacs-lisp/shortdoc.el (number): Make it clear that zero
+       satisfies 'natnump'.  Move 'natnump' next to 'cl-plusp' to highlight
+       the difference between them.
+
+2023-07-04  Robert Pluim  <rpluim@gmail.com>
+
+       correct info documentation of benchmark-call
+
+       * doc/lispref/debugging.texi (Profiling): 'benchmark-call' is a defun,
+       not a macro.
+
+2023-07-03  Juri Linkov  <juri@linkov.net>
+
+       * lisp/progmodes/grep.el (rgrep): Fix docstring.
+
+       Instead of the incorrect key `M-c' produce the right key
+       with \\<read-regexp-map>\\[read-regexp-toggle-case-fold].
+
+2023-07-03  Spencer Baugh  <sbaugh@catern.com>
+
+       Include a help-echo for flymake's modeline counters
+
+       This helps clarify what each of these numbers mean.  This is inspired
+       by 'compilation-mode-line-errors' which does the same.
+       * lisp/progmodes/flymake.el (flymake--mode-line-counter): Add
+       help-echo to mode line properties.  (Bug#64424)
+
+2023-07-02  Eli Zaretskii  <eliz@gnu.org>
+
+       Avoid errors in completion due to 'completion-regexp-list'
+
+       * doc/lispref/minibuf.texi (Basic Completion):
+       * src/minibuf.c (syms_of_minibuf) <completion-regexp-list>:
+       Document that global non-nil settings of 'completion-regexp-list'
+       are not safe.
+
+       * lisp/minibuffer.el (completion-pcm--merge-completions): Avoid
+       errors in 'try-completion' when PREFIX is nil.  (Bug#64351)
+
+2023-07-01  Alan Mackenzie  <acm@muc.de>
+
+       C Mode: Don't fontify foo globally as type due to "struct foo"
+
+       This fixes bug#64322.
+
+       * lisp/progmodes/cc-langs.el (c-typeless-decl-kwds): Make the
+       entry for c-mode nil.
+
+2023-07-01  Mattias Engdegård  <mattiase@acm.org>
+
+       * lisp/rect.el (rectangle--duplicate-right): Fix rectangle dup bug.
+
+       This is a necessary adjustment to changes to rect.el in Emacs 29.
+
+2023-07-01  Spencer Baugh  <sbaugh@janestreet.com>
+
+       Add project command entries to the menu-bar
+
+       This will make it easier for new users to learn these increasingly
+       important and useful commands.  (Bug#63469)
+
+       * lisp/menu-bar.el (menu-bar-file-menu): Add project-dired and
+       project-find-file entries.
+       (menu-bar-search-menu): Add project-find-regexp entry.
+       (menu-bar-replace-menu): Add project-query-replace regexp entry.
+       (menu-bar-shell-commands-menu): Add project-shell entry.
+       (menu-bar-buffers-menu-command-entries): Add project-switch-to-buffer
+       and project-list-buffer entries.
+       (menu-bar-project-menu): Add.
+       (menu-bar-tools-menu): Add "Project" submenu, and also project-compile
+       entry and change text for compile entry.
+
+2023-06-30  Eli Zaretskii  <eliz@gnu.org>
+
+       Improve documentation of registers
+
+       * doc/lispref/text.texi (Registers): Document buffers in
+       registers.  Mention "frameset" as another name for "frame
+       configuration".
+       * doc/emacs/regs.texi (Registers, Configuration Registers)
+       (File and Buffer Registers): Clarify and improve wording.  Add
+       cross-references and indexing.
+       (Configuration Registers): Rename the section to a more accurate
+       name.  (Bug#64354)
+
+       * lisp/register.el (jump-to-register, point-to-register)
+       (register-alist, frame-configuration-to-register): Doc fixes.
+       (Bug#64353)
+
+2023-06-29  Andrew G Cohen  <cohen@andy.bu.edu>
+
+       Use a temporary buffer in nnagent-request-set-mark (bug#64117)
+
+       Commit cb12a84f2c519a48dd87453c925e3bc36d9944db inadvertently removed
+       the use of a temporary buffer in nnagent-request-set-mark.  Bug and
+       fix reported by Jens Schmidt <jschmidt4gnu@vodafonemail.de>
+
+       * lisp/gnus/nnagent.el (nnagent-request-set-mark): Restore the use of
+       a temporary buffer that was inadvertently removed.
+
+       (cherry picked from commit 5075d752773c31d959272a7e2b73b1dc38ba184c)
+
+2023-06-29  john muhl  <jm@pub.pink>  (tiny change)
+
+       Support cons cell as value of ':line-width' box attribute
+
+       * lisp/calculator.el (calculator-need-3-lines): Support
+       values of a face's ':line-width' box attribute that are
+       cons cells.  (Bug#64344)
+
+2023-06-29  Yuan Fu  <casouri@gmail.com>
+
+       Add missing calls to treesit_record_change in editfns.c
+
+       These should be all that are missing.  See the next commit for detail.
+
+       * src/editfns.c (Ftranslate_region_internal):
+       (Ftranspose_regions): Call treesit_record_change.
+
+2023-06-29  Yuan Fu  <casouri@gmail.com>
+
+       Call treesit_record_change in subst-char-in-region (bug#64329)
+
+       * src/editfns.c (Fsubst_char_in_region): Call treesit_record_change in
+       the else branch.
+
+2023-06-29  Juri Linkov  <juri@linkov.net>
+
+       * lisp/misc.el (duplicate-line-final-position): New defcustom 
(bug#64185).
+
+       * lisp/misc.el (duplicate-line): Use it.
+
+       * test/lisp/misc-tests.el (misc--duplicate-line): Add tests for
+       duplicate-line-final-position.
+
+       Don't merge to master.
+
+2023-06-29  Daniel Martín  <mardani29@yahoo.es>
+
+       Make js-beginning-of-defun return non-nil on success
+
+       The docstring of 'beginning-of-defun-function' says that the
+       function shall return non-nil when it found the beginning
+       of a defun.  This is specially important because the calling
+       code decides when to move point depending on the return value.
+       * lisp/progmodes/js.el (js-beginning-of-defun)
+       (js--beginning-of-defun-flat): Return non-nil when the beginning
+       of a defun is found.  (Bug#64283)
+
+       * test/lisp/progmodes/js-tests.el (js-mode-end-of-defun): Add a unit
+       test.
+
+2023-06-27  Yuan Fu  <casouri@gmail.com>
+
+       Tree-sitter use with-silent-modifications like jit-lock (bug#64321)
+
+       * lisp/treesit.el (treesit--font-lock-notifier): Use
+       with-silent-modifications when marking modified text to be fontified
+       by jit-lock.  This is what jit-lock itself does.
+
+2023-06-27  Stephen Berman  <stephen.berman@gmx.net>
+
+       Fix todo-mode.el Commentary and a doc string (bug#64298)
+
+       * lisp/calendar/todo-mode.el: Explicitly note in the Commentary
+       that the Todo mode user manual is a separate Info manual in the
+       Emacs installation.
+       (todo-always-add-time-string): Replace doc string, which was
+       mistakenly retained in the initial merge of this version of
+       todo-mode.el, by a correct description of this user option.
+
+2023-06-27  Stephen Berman  <stephen.berman@gmx.net>
+
+       Prevent truncation of todo-mode categories sexp
+
+       * lisp/calendar/todo-mode.el (todo-delete-file)
+       (todo-move-category, todo-convert-legacy-files)
+       (todo-update-categories-sexp, todo-check-format):
+       Bind print-length and print-level to nil before using prin1
+       and related functions, to avoid truncating the todo categories
+       sexp and possibly corrupting the file format.
+
+2023-06-27  Stephen Berman  <stephen.berman@gmx.net>
+
+       Avoid making todo-mode buffers manually editable
+
+       * lisp/calendar/todo-mode.el (todo-add-category)
+       (todo-move-category, todo-edit-item--header)
+       (todo-set-item-priority, todo-move-item, todo-item-undone)
+       (todo-archive-done-item, todo-set-category-number): Restrict the
+       scope of nil buffer-read-only to the function calls that change
+       buffer text, thereby preventing todo mode buffers from becoming
+       manually editable and hence possibly corrupted when the minibuffer
+       is in use.
+
+2023-06-26  Michael Albinus  <michael.albinus@gmx.de>
+
+       Fix Tramp mount-spec (don't merge)
+
+       * lisp/net/tramp-fuse.el (tramp-fuse-mounted-p): The mount-spec
+       could contain an optional trailing slash.  (Bug#64278)
+
+2023-06-25  Daniel Semyonov  <daniel@dsemy.com>
+
+       Fix VC package build when doc file isn't in a subdir
+
+       * lisp/emacs-lisp/package-vc.el (package-vc--build-documentation):
+       Expand 'file' before attempting to get its directory.  (Bug#64242)
+
+2023-06-25  Michael Albinus  <michael.albinus@gmx.de>
+
+       Fix type check in tramp-get-buffer-string
+
+       * lisp/net/tramp.el (tramp-get-buffer-string): Check, that BUFFER
+       is really a bufferp.
+
+2023-06-25  Richard M. Stallman  <rms@gnu.org>
+
+       Clarify list terminology
+
+       * doc/lispintro/emacs-lisp-intro.texi (Lists diagrammed):
+       Mention "cons cell".  Add index entries.
+       (car & cdr): Simplify etymology of `car' and `cdr'.
+       Explain why for some purposes they are better than `first' and `rest'.
+       Mention cons cells.
+
+       (cherry picked from commit 188c90c7c111dbbdc3edd29c23b59ade26f97bfd)
+
+2023-06-24  Mattias Engdegård  <mattiase@acm.org>
+
+       * lisp/emacs-lisp/shortdoc.el: More and better `substring` examples.
+
+       Suggested by Juri Linkov.
+
+2023-06-24  Eli Zaretskii  <eliz@gnu.org>
+
+       Fix "C-x RET r" when the new encoding is UTF
+
+       * src/fileio.c (Finsert_file_contents): Update point of
+       'conversion_buffer' before decoding the last block.  (Bug#64253)
+
+2023-06-23  Dmitry Gutov  <dmitry@gutov.dev>
+
+       Fix "vc-print-log does not erase buffer" and associated problems
+
+       * lisp/vc/vc.el (vc-deduce-fileset): Make sure to retain the
+       buffer switch (if it did), bug#63949.
+
+2023-06-23  Theodor Thornhill  <theo@thornhill.no>
+
+       Add selector_expression indentation rule
+
+       * lisp/progmodes/go-ts-mode.el (go-ts-mode--indent-rules): New rule.
+
+2023-06-22  Eli Zaretskii  <eliz@gnu.org>
+
+       Add "nixd" LSP server to Eglot
+
+       * lisp/progmodes/eglot.el (eglot-server-programs): Add "nixd".
+       Patch by Brian Leung <leungbk@posteo.net>.  (Bug#64214)
+
+2023-06-22  Eli Zaretskii  <eliz@gnu.org>
+
+       Fix building --with-native-compilation=aot from release tarball
+
+       * lisp/Makefile.in (%.eln): Pattern rule for AOT native
+       compilation.
+       (compile-eln-targets, compile-eln-aot): New targets for AOT native
+       compilation.
+
+       * src/Makefile.in (../native-lisp): If NATIVE_COMPILATION_AOT is
+       set, also native-compile all the other Lisp files.
+       (Bug#64167)
+
+2023-06-21  Alan Mackenzie  <acm@muc.de>
+
+       Fix bug#64152 (Minibuffer sometimes goes "modal")
+
+       In particular, when a frame has no minibuffer and is using that
+       of a different "normal" frame, C-x 5 o, etc., and GUI
+       operations fail.
+
+       Fix by partially reverting the commit from 2022-07-07 15:38:09
+       +0000 "Remove obscure, obsolete code from do_switch_frame".  As
+       a consequent change, also revert the commit from 2022-07-08
+       20:19:03 +0000 "Remove now unused parameter TRACK from
+       do_switch_frame".
+
+       * src/frame.c (do_switch_frame): Restore the TRACK parameter.
+       Restore the code which redirects the frame focus when a new
+       frame gets selected.
+
+       * src/frame.c (Fselect_frame, Fhandle_switch_frame)
+       (delete_frame)
+       * src/keyboard.c (quit_throw_to_read_char)
+       * src/lisp.h (do_switch_frame prototype)
+       * src/minibuf.c (read_minibuf_unwind)
+       * src/window.c (Fset_window_configuration): Restore the TRACK
+       argument to do_switch_frame.
+
+       * src/xterm.c (x_try_restore_frame): Add a zero TRACK argument
+       to do_switch_frame.
+
+2023-06-21  Eli Zaretskii  <eliz@gnu.org>
+
+       Disable target-async by default in gdb-mi.el
+
+       * lisp/progmodes/gdb-mi.el (gdb-non-stop-setting): Disable until
+       bug#63084 is fixed.  (Bug#64186)
+
+2023-06-20  Filipp Gunbin  <fgunbin@fastmail.fm>
+
+       Revert "Fix parsing of dn line if WITHDN is non-nil"
+
+       This reverts commits 71b27779a9a and d2246b26275, because they change
+       the return value of "ldap-search" in an incompatible way.  The fix
+       (a different one) will be done on master instead (bug#64089).
+
+2023-06-20  Eli Zaretskii  <eliz@gnu.org>
+
+       Don't truncate filenames with "emacs.el" in them
+
+       * lisp/emacs-lisp/find-func.el (find-function-search-for-symbol):
+       Avoid false positives when looking for "emacs.el" matches the
+       likes of "emacs.elpa".  (Bug#64143)
+
+2023-06-20  Eli Zaretskii  <eliz@gnu.org>
+
+       Improve documentation of 'minibuffer-message'
+
+       * doc/lispref/minibuf.texi (Minibuffer Misc): Clarify that
+       'minibuffer-message' behaves like 'message' if called from a
+       buffer that is not a minibuffer.
+
+       * lisp/minibuffer.el (minibuffer-message)
+       (set-minibuffer-message, clear-minibuffer-message): Doc fixes.
+       (Bug#64165)
+
+2023-06-19  João Távora  <joaotavora@gmail.com>
+
+       Eglot: again fix positions of coinciding inlay hint overlays (bug#64101)
+
+       This bug originated from the previous fix, and is reproducible on non
+       Mac OS platforms, as long as the very latest version (at time of
+       writing) of the rust-analyzer server is used.
+
+       * lisp/progmodes/eglot.el (eglot--update-hints-1): Reverse
+       priorities when pegging overlays after (i.e. when before-string is
+       used).
+
+2023-06-18  Eli Zaretskii  <eliz@gnu.org>
+
+       * lisp/ldefs-boot.el: Regenerate.
+
+2023-06-18  Eli Zaretskii  <eliz@gnu.org>
+
+       Bump Emacs version to 29.0.92
+
+       * README:
+       * configure.ac:
+       * nt/README.W32:
+       * msdos/sed2v2.inp: Bump version to 29.0.92.
+
 2023-06-18  Mattias Engdegård  <mattiase@acm.org>
 
        Describe primarily the Emacs s-exp dialect for treesit queries
@@ -115497,7 +116226,7 @@
 
        Do not quote lambda expressions
 
-       http://emacs.stackexchange.com/a/3596
+       https://emacs.stackexchange.com/a/3596
 
        Quoting lambda expressions is at best redundant and at worst
        detrimental; this commit removes all use of the sharp-quote to reduce
@@ -116231,7 +116960,7 @@
 
        This change follows the regexp for require on emacs truck. See line
        2327 on font-lock.el in the following patch.
-       http://bzr.savannah.gnu.org/lh/emacs/trunk/revision/111821
+       https://bzr.savannah.gnu.org/lh/emacs/trunk/revision/111821
 
 2013-09-04  John Wiegley  <johnw@newartisans.com>
 
@@ -116651,7 +117380,7 @@
 
 This file records repository revisions from
 commit f2ae39829812098d8269eafbc0fcb98959ee5bb7 (exclusive) to
-commit 8f62e7b85f69bb4026e9cf2971668b0d77077792 (inclusive).
+commit 7d1737071fba1fd83039aac34f34f6b90c9579b8 (inclusive).
 See ChangeLog.3 for earlier changes.
 
 ;; Local Variables:
diff --git a/ChangeLog.android b/ChangeLog.android
new file mode 100644
index 00000000000..f56c0469408
--- /dev/null
+++ b/ChangeLog.android
@@ -0,0 +1,7278 @@
+2023-08-07  Po Lu  <luangruo@yahoo.com>
+
+       * nt/mingw-cfg.site: Remove additions for Gnulib printf.
+
+       * m4, lib: Update from Gnulib.
+
+       * msdos/sedlibmk.inp: Remove variables deleted as part of previous
+       change.
+
+       * admin/merge-gnulib (GNULIB_MODULES): Remove vasprintf and
+       printf-posix.
+
+2023-08-06  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (readDirectoryEntry): Fix
+       potential NULL dereference.
+
+       * java/org/gnu/emacs/EmacsSafThread.java (openDocument1): If
+       initially opening with rwt, verify the file descriptor is really
+       writable; if not, resort to rw and truncating the file descriptor
+       by hand instead.
+
+       * src/androidvfs.c (NATIVE_NAME (ftruncate)): New function.
+       Truncate file descriptor and return whether that was successful.
+
+       * src/androidvfs.c (android_saf_tree_chmod): Repair file access
+       permissions allowed within FLAGS.
+
+2023-08-05  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/commands.texi (Touchscreen Events): Fix typo.
+
+       * lisp/subr.el (y-or-n-p): Don't call set-text-conversion-style
+       when not present.
+
+2023-08-04  Po Lu  <luangruo@yahoo.com>
+
+       * ChangeLog.android: New file.
+
+2023-08-04  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidvfs.c (android_verify_jni_string): Move to
+       android.c.
+
+       * src/android.c (android_verify_jni_string): New function.
+       (android_build_string): Forgo encoding menu text if TEXT is a
+       multibyte string that's also a valid JNI string.
+
+       * src/android.h: Update prototypes.
+
+       * java/org/gnu/emacs/EmacsService.java (getDocumentTrees): Don't
+       encode some characters that need not be escaped within file
+       names.
+
+2023-08-03  Po Lu  <luangruo@yahoo.com>
+
+       * src/fileio.c (check_vfs_filename): Revert earlier change.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidvfs.c (unix_vfs_ops, android_unix_chmod, afs_vfs_ops)
+       (android_afs_chmod, content_vfs_ops, android_content_chmod)
+       (authority_vfs_ops, android_authority_chmod, saf_root_vfs_ops)
+       (android_saf_root_chmod, saf_tree_vfs_ops, android_saf_tree_chmod)
+       (saf_file_vfs_ops, saf_new_vfs_ops, android_saf_new_chmod)
+       (root_vfs_ops): Add `chmod' to the list of functions implemented
+       by each vnode.
+       (android_fchmodat): New function.
+
+       * src/fileio.c (Fset_file_modes): Use `emacs_fchmodat'.
+
+       * src/lisp.h:
+       * src/sysdep.c (emacs_fchmodat): Delegate to android_fchmodat on
+       Android.
+
+       * java/org/gnu/emacs/EmacsSafThread.java (CacheToplevel)
+       (EmacsSafThread, DocIdEntry, getCache, pruneCache)
+       (cacheDirectoryFromCursor, run, documentIdFromName1)
+       (statDocument1, openDocumentDirectory1, openDocument1): Introduce
+       a file status cache and populate it with files within directories
+       as they are opened.
+
+       * java/org/gnu/emacs/EmacsService.java (createDocument)
+       (createDirectory, moveDocument): Invalidate the file status cache
+       wherever needed.
+
+       * src/fileio.c (check_vfs_filename):
+       (Fset_file_modes): Permit `set-file-modes' to silently fail
+       on asset and content files.
+
+2023-08-02  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android, What is Android?, Android
+       Startup, Android File System, Android Environment,Android
+       Windowing, Android Fonts, Android Troubleshooting): Improve
+       section titles.
+       (Android Windowing): Describe the relation between keyboard
+       modifiers reported by Android and those in key events.
+
+       * java/org/gnu/emacs/EmacsWindow.java (onKeyDown, onKeyUp):
+       Clear META_SYM_ON and META_META_MASK when retrieving ASCII
+       characters.
+
+       * src/androidgui.h: Add ANDROID_META_MASK.
+
+       * src/androidterm.c (android_android_to_emacs_modifiers)
+       (android_emacs_to_android_modifiers): Transform META to Alt, and
+       vice versa.
+
+2023-08-01  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android File System): Describe how to
+       access real files named /assets or /contents if so required.
+
+       * java/org/gnu/emacs/EmacsService.java (validAuthority):
+       * src/android.c (android_init_emacs_service):
+       * src/android.h: New function.
+
+       * src/androidvfs.c (android_saf_valid_authority_p): New
+       function.  Wrap the Java function.
+       (android_saf_root_stat, android_saf_root_access): Don't return
+       success if no authority by vp->authority's name exists.
+       (android_saf_tree_from_name): Check validity of string data
+       before giving it to JNI.
+
+       * src/sfnt.c (CHECK_STACK_AVAILABLE): New macro.
+       (PUSH, PUSH_UNCHECKED): Always define to unchecked versions,
+       even if TEST.
+       (PUSH2_UNCHECKED): New macro.
+       (NPUSHB, NPUSHW, PUSHB, PUSHW): Check the number of remaining
+       stack elements once.
+       (stack_overflow_test_args): Expect zero stack arguments.
+
+       * src/android.c (ANDROID_THROW): Remove unused macro.
+
+2023-07-31  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (ISECT): Micro-optimize this instruction.
+
+       * src/sfntfont.c (sfnt_parse_style): Avoid GC safety problem.
+       (sfnt_parse_style): Fix misworded commentary.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative):
+       * java/org/gnu/emacs/EmacsNoninteractive.java (main):
+       * java/org/gnu/emacs/EmacsService.java (run):
+       * java/org/gnu/emacs/EmacsThread.java (run):
+       * src/android.c (initEmacs, setEmacsParams): Set
+       `android_api_level' within setEmacsParams, not in initEmacs.
+
+       * src/androidvfs.c: Pacify compiler warnings.
+
+       * java/org/gnu/emacs/EmacsService.java (renameDocument): Don't
+       catch UnsupportedOperationException; handle ENOSYS in
+       android_saf_rename_document instead.
+       (moveDocument): New function.
+
+       * lisp/subr.el (y-or-n-p): Always change the text conversion
+       style.
+
+       * src/android.c (android_init_emacs_service)
+       (android_exception_check_4): New function.
+
+       * src/android.h: Update Java function table.
+
+       * src/androidvfs.c (android_saf_rename_document): Handle ENOSYS
+       here by setting errno to EXDEV.
+       (android_saf_move_document): New function.
+       (android_document_id_from_name): Take const `dir_name'.
+       (android_saf_tree_rename): Use delete-move-rename to implement
+       cross-directory renames.
+
+2023-07-30  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsSafThread.java
+       (postInvalidateCacheDir):
+       * java/org/gnu/emacs/EmacsService.java (renameDocument): New
+       functions.
+
+       * src/android.c (android_init_emacs_service):
+       * src/android.h (struct android_emacs_service): Link to new JNI
+       function.
+
+       * src/androidvfs.c (android_saf_rename_document): New function.
+       (android_saf_tree_rename): Implement in terms of that function
+       if possible.
+
+2023-07-29  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsSafThread.java (statDocument1):
+
+       * src/androidvfs.c (android_afs_stat, android_content_stat)
+       (android_saf_root_stat): Report search permissions for files.
+
+       * src/androidvfs.c: Improve commentary.
+
+2023-07-29  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsSafThread.java (postInvalidateCache):
+       New argument cacheName.  Remove that file from the cache.
+       (accessDocument1): Consult the storage cache as well.
+
+       * java/org/gnu/emacs/EmacsService.java (deleteDocument): New
+       argument NAME.
+
+       * src/android.c (android_init_emacs_service): Add new argument.
+       * src/androidvfs.c (android_saf_delete_document)
+       (android_saf_tree_rmdir, android_saf_file_unlink): Pass name of
+       file being deleted to `deleteDocument'.
+
+2023-07-29  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidvfs.c (android_saf_exception_check): Describe
+       exceptions earlier.
+
+       * java/org/gnu/emacs/EmacsSafThread.java (DocIdEntry)
+       (getCacheEntry, CacheEntry, documentIdFromName1): Fix earlier
+       change.
+
+       * java/org/gnu/emacs/EmacsSafThread.java (DocIdEntry)
+       (getCacheEntry, CacheEntry): Use `uptimeMillis' as the basis for
+       cache expiration.
+
+       * java/org/gnu/emacs/EmacsSafThread.java (EmacsSafThread, getCache)
+       (pruneCache1, pruneCache, cacheChild, cacheDirectoryFromCursor)
+       (documentIdFromName1, openDocumentDirectory1): Implement the
+       cache referred to by the commentary.
+
+       * java/org/gnu/emacs/EmacsService.java (deleteDocument):
+       Invalidate the cache upon document removal.
+
+       * src/androidvfs.c (android_saf_exception_check)
+       (android_document_id_from_name): Correctly preserve or set errno
+       in error cases.
+
+2023-07-28  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsSafThread.java (documentIdFromName1):
+       Fix query argument placeholder string.
+
+       * src/androidvfs.c (android_document_id_from_name): Don't return
+       0 if an SAF exception occurs.
+
+       * src/androidselect.c (Fandroid_get_clipboard): Don't return
+       data if clipboard is empty.  Reported by Johan Widén
+       <j.e.widen@gmail.com>.
+
+2023-07-28  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Document Providers): Say that
+       quitting is now possible.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
+       functions `safSyncAndReadInput', `safync' and `safPostRequest'.
+
+       * java/org/gnu/emacs/EmacsSafThread.java: New file.  Move
+       cancel-able SAF operations here.
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService): Allow
+       quitting from most SAF operations.
+
+       * src/androidvfs.c (android_saf_exception_check): Return EINTR if
+       OperationCanceledException is received.
+       (android_saf_stat, android_saf_access)
+       (android_document_id_from_name, android_saf_tree_opendir_1)
+       (android_saf_file_open): Don't allow reentrant calls from async
+       input handlers.
+       (NATIVE_NAME): Implement new synchronization primitives for JNI.
+       (android_vfs_init): Initialize new class.
+
+       * src/dired.c (open_directory): Handle EINTR from opendir.
+
+       * src/sysdep.c: Describe which operations may return EINTR on
+       Android.
+
+2023-07-28  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsDirectoryEntry.java
+       (EmacsDirectoryEntry): Make class final.
+
+       * java/org/gnu/emacs/EmacsService.java (accessDocument)
+       (openDocumentDirectory, openDocument, createDocument): Throw
+       access and IO error exceptions instead of catching them.
+       (createDirectory, deleteDocument): New functions.
+
+       * src/android.c (android_init_emacs_service): Add new functions.
+
+       * src/android.h (struct android_emacs_service): Likewise.
+
+       * src/androidvfs.c (android_saf_exception_check): New function.
+       Translate between Java exceptions and errno values.
+       (android_saf_stat, android_saf_access, android_saf_delete_document)
+       (struct android_saf_tree_vnode, android_document_id_from_name)
+       (android_saf_tree_name, android_saf_tree_rmdir)
+       (android_saf_tree_opendir_1, android_saf_tree_opendir)
+       (android_saf_file_open, android_saf_file_unlink)
+       (android_saf_new_open, android_saf_new_mkdir): Implement missing
+       VFS operations and derive errno values from the type of any
+       exceptions thrown.
+       (android_vfs_init): Initialize exception classes.
+       (android_mkdir, android_fstat): Remove trailing whitespace.
+
+2023-07-27  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Document Providers): Improve
+       wording of paragraph clarifying limits on subprocesses.
+
+       * java/org/gnu/emacs/EmacsService.java (getDocumentTrees): Use
+       Java standard US-ASCII coding standard instead of the
+       undocumented ``ASCII'' alias.
+       (decodeFileName): Remove unused function.
+       (documentIdFromName):
+       * src/android.c (android_init_emacs_service): Take a String for
+       NAME instead of a byte array.
+
+       * src/androidvfs.c (android_verify_jni_string): New function.
+       (android_document_id_from_name): Verify that STRING is a valid
+       Modified UTF-8 string.
+
+       * src/androidvfs.c (android_afs_initial)
+       (android_content_get_directory_name, android_saf_tree_name)
+       (android_saf_tree_from_name, android_vfs_init): Silence compiler
+       warnings.
+
+       * src/android.c (android_run_in_emacs_thread): Behave more
+       robustly if SIGIO arrives too late Emacs for Emacs to check for
+       signals, but too early to preempt a long running syscall.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onActivityResult):
+
+       * src/androidvfs.c (android_renameat_noreplace, android_rename):
+       Free vdst using vdst->ops, not vp->ops.
+
+2023-07-27  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (ANDROID_STUBIFY): Add androidvfs.o when building
+       libemacs.so.
+
+       * doc/emacs/android.texi (Android): Add `Android Document
+       Providers'.
+       (Android Startup): Update the location of the content identifier
+       directory.
+       (Android File System): Describe access to document provider
+       directories.
+       (Android Document Providers): New node.
+
+       * doc/emacs/emacs.texi (Top): Update the menu for the Android
+       appendix.
+
+       * java/Makefile.in (filename, install_temp/assets/build_info):
+       Make directory-tree depend on build_info.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onActivityResult): New
+       function.  When a document tree is accepted, persist access to it.
+
+       * java/org/gnu/emacs/EmacsDirectoryEntry.java
+       (EmacsDirectoryEntry): New struct.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (checkReadableOrCopy):
+       Use EmacsService.buildContentName.
+
+       * java/org/gnu/emacs/EmacsService.java (getEmacsView)
+       (openContentUri)
+       (checkContentUri): Remove excessive debug logging.
+       (buildContentName, getDocumentAuthorities, requestDirectoryAccess)
+       (getDocumentTrees, decodeFileName, documentIdFromName, getTreeUri)
+       (statDocument, accessDocument, openDocumentDirectory)
+       (readDirectoryEntry)
+       (openDocument, createDocument): New functions.
+
+       * lib-src/asset-directory-tool.c: Improve commentary by
+       illustrating the difference between directory and ordinary files.
+
+       * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags)
+       (struct android_emacs_service, android_extract_long)
+       (android_scan_directory_tree, android_is_directory)
+       (android_get_asset_name, android_url_encode)
+       (android_content_name_p)
+       (android_get_content_name, android_check_content_access)
+       (android_fstat)
+       (android_fstatat, android_file_access_p)
+       (android_hack_asset_fd_fallback)
+       (android_detect_ashmem, android_hack_asset_fd)
+       (android_close_on_exec)
+       (android_open, android_close, android_fclose)
+       (android_create_lib_link)
+       (android_faccessat, struct android_dir, android_opendir)
+       (android_dirfd)
+       (android_readdir, android_closedir)
+       (android_lookup_asset_directory_fd)
+       (android_exception_check_3, android_get_current_api_level)
+       (android_open_asset, android_close_asset, android_asset_read_quit)
+       (android_asset_read, android_asset_lseek, android_asset_fstat):
+       Move content and asset related functions to androidvfs.c.
+       (android_init_emacs_service): Obtain handles for new JNI
+       functions.
+       (initEmacsParams): Initialize the VFS layer.
+       (android_request_directory_access): New function.
+       (android_display_toast): Remove unused function.
+
+       * src/android.h (android_get_current_api_level): Assume that
+       this function never returns less than __ANDROID_API__.
+       (struct android_emacs_service): Move `struct
+       android_emacs_service' here.
+
+       * src/androidfns.c (Fandroid_request_directory_access): New
+       interactive function.
+       (syms_of_androidfns): Register new subr.
+
+       * src/androidvfs.c (struct android_vdir, struct android_vops)
+       (struct android_vnode, struct android_special_vnode)
+       (enum android_vnode_type, struct android_cursor_class)
+       (struct emacs_directory_entry_class)
+       (struct android_parcel_file_descriptor_class)
+       (android_init_cursor_class, android_init_entry_class)
+       (android_init_fd_class, android_vfs_canonicalize_name)
+       (struct android_unix_vnode, struct android_unix_vdir)
+       (unix_vfs_ops)
+       (android_unix_name, android_unix_vnode, android_unix_open)
+       (android_unix_close, android_unix_unlink, android_unix_symlink)
+       (android_unix_rmdir, android_unix_rename, android_unix_stat)
+       (android_unix_access, android_unix_mkdir, android_unix_readdir)
+       (android_unix_closedir, android_unix_dirfd, android_unix_opendir)
+       (android_extract_long, android_scan_directory_tree)
+       (android_is_directory, android_init_assets)
+       (android_hack_asset_fd_fallback, android_detect_ashmem)
+       (android_hack_asset_fd, struct android_afs_vnode)
+       (struct android_afs_vdir, struct android_afs_open_fd, afs_vfs_ops)
+       (android_afs_name, android_afs_initial, android_close_on_exec)
+       (android_afs_open, android_afs_close, android_afs_unlink)
+       (android_afs_symlink, android_afs_rmdir, android_afs_rename)
+       (android_afs_stat, android_afs_access, android_afs_mkdir)
+       (android_afs_readdir, android_afs_closedir, android_afs_dirfd)
+       (android_afs_opendir, android_afs_get_directory_name)
+       (struct android_content_vdir, content_vfs_ops)
+       (content_directory_contents, android_content_name)
+       (android_content_open, android_content_close)
+       (android_content_unlink, android_content_symlink)
+       (android_content_rmdir, android_content_rename)
+       (android_content_stat, android_content_access)
+       (android_content_mkdir, android_content_readdir)
+       (android_content_closedir, android_content_dirfd)
+       (android_content_opendir, android_content_get_directory_name)
+       (android_content_initial, android_get_content_name)
+       (android_check_content_access, struct android_authority_vnode)
+       (authority_vfs_ops, android_authority_name)
+       (android_authority_open)
+       (android_authority_close, android_authority_unlink)
+       (android_authority_symlink, android_authority_rmdir)
+       (android_authority_rename, android_authority_stat)
+       (android_authority_access, android_authority_mkdir)
+       (android_authority_opendir, android_authority_initial)
+       (struct android_saf_root_vnode, struct android_saf_root_vdir)
+       (saf_root_vfs_ops, android_saf_root_name, android_saf_root_open)
+       (android_saf_root_close, android_saf_root_unlink)
+       (android_saf_root_symlink, android_saf_root_rmdir)
+       (android_saf_root_rename, android_saf_root_stat)
+       (androqid_saf_root_access, android_saf_root_mkdir)
+       (android_saf_root_readdir, android_saf_root_closedir)
+       (android_saf_root_dirfd, android_saf_root_opendir)
+       (android_saf_root_initial, android_saf_root_get_directory)
+       (android_saf_stat, android_saf_access)
+       (struct android_saf_tree_vnode, struct android_saf_tree_vdir)
+       (saf_tree_vfs_ops, android_document_id_from_name)
+       (android_saf_tree_name, android_saf_tree_open)
+       (android_saf_tree_close, android_saf_tree_unlink)
+       (android_saf_tree_symlink, android_saf_tree_rmdir)
+       (android_saf_tree_rename, android_saf_tree_stat)
+       (android_saf_tree_access, android_saf_tree_mkdir)
+       (android_saf_tree_opendir_1, android_saf_tree_readdir)
+       (android_saf_tree_closedir, android_saf_tree_dirfd)
+       (android_saf_tree_opendir, android_saf_tree_from_name)
+       (android_saf_tree_get_directory, android_saf_file_vnode)
+       (saf_file_vfs_ops, android_saf_file_name, android_saf_file_open)
+       (android_saf_file_unlink, android_saf_file_rmdir)
+       (android_saf_file_opendir, android_close_parcel_fd)
+       (android_saf_new_vnode, android_saf_new_name)
+       (android_saf_new_open)
+       (android_saf_new_unlink, android_saf_new_symlink)
+       (android_saf_new_rmdir, android_saf_new_rename)
+       (android_saf_new_stat, android_saf_new_access)
+       (android_saf_new_mkdir, android_saf_new_opendir, root_vfs_ops)
+       (special_vnodes, android_root_name, android_name_file)
+       (android_vfs_init, android_open, android_unlink, android_symlink)
+       (android_rmdir, android_mkdir, android_renameat_noreplace)
+       (android_rename, android_fstat, android_fstatat_1)
+       (android_fstatat)
+       (android_faccessat, android_fdopen, android_close, android_fclose)
+       (android_open_asset, android_close_asset, android_asset_read_quit)
+       (android_asset_read, android_asset_lseek, android_asset_fstat)
+       (android_opendir, android_dirfd, android_readdir)
+       (android_closedir): Move file system emulation routines here.
+       Introduce a new ``VFS'' layer for translating between
+       Emacs-specific file names and the various disparate interfaces
+       for accessing files on Android.
+
+       * src/callproc.c (delete_temp_file):
+       * src/charset.c (load_charset_map_from_file):
+       * src/dired.c:
+       * src/emacs.c (Fkill_emacs):
+       * src/fileio.c (check_mutable_filename, Fcopy_file)
+       (Fmake_directory_internal, Fdelete_directory_internal)
+       (Fdelete_file, Frename_file, Fadd_name_to_file)
+       (Fmake_symbolic_link, file_accessible_directory_p)
+       (Fset_file_modes)
+       (Fset_file_times, write_region):
+       * src/filelock.c (get_boot_time, rename_lock_file)
+       (create_lock_file, current_lock_owner, unlock_file):
+       * src/image.c (slurp_file, png_load_body, jpeg_load_body):
+       * src/keyboard.c (Fopen_dribble_file):
+       * src/lisp.h:
+       * src/lread.c (Fload):
+       * src/process.c (handle_child_signal):
+       * src/sysdep.c (init_standard_fds, emacs_fopen, emacs_fdopen)
+       (emacs_unlink, emacs_symlink, emacs_rmdir, emacs_mkdir)
+       (emacs_renameat_noreplace, emacs_rename):
+       * src/term.c (Fresume_tty, init_tty): Use and add new wrappers
+       for fopen, fdopen, unlink, symlink, rmdir, mkdir,
+       renameat_norepalce and rename.
+
+2023-07-23  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android File System): Document where the
+       app library directory can probably be found.
+
+       * src/android.c (android_create_lib_link): New function.  Try to
+       symlink `lib' in the directory holding the files directory to the
+       app library directory.
+       (setEmacsParams): Call that function if Emacs is being initialized
+       from an application context.
+
+2023-07-22  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/touch-screen.el (touch-screen-drag): If
+       touch-screen-word-select, also keep the initial word within the
+       region while scrolling.
+
+       * src/window.h (WINDOW_MENU_BAR_P): Check for external menu bars
+       using HAVE_WINDOW_SYSTEM && HAVE_EXT_MENU_BAR instead of hard
+       coding X without Xt or GTK.
+
+       * doc/lispref/commands.texi (Key Sequence Input): Describe which
+       events receive imaginary prefix keys.
+       * lisp/touch-screen.el (touch-screen-translate-touch): Consider
+       `vertical-line' a virtual function key.
+       (function-key-map): Translate events on vertical window borders.
+
+       * etc/NEWS: Announce `current-key-remap-sequence'.
+
+       * src/androidfns.c (Fx_create_frame): Default
+       Qvertical_scroll_bars to Qnil, but set scroll-bar-width and
+       scroll-bar-height.
+
+2023-07-21  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/commands.texi (Key Sequence Input): Document new
+       argument to `read-key-sequence' etc.
+
+       * lisp/help-macro.el (make-help-screen):
+       * lisp/subr.el (read-key, read-char-choice-with-read-key): Disable
+       text conversion and display the OSK before reading a key sequence.
+
+       * lisp/touch-screen.el (touch-screen-window-selection-changed):
+       Only cancel the minibuffer OSK timer.
+       (touch-screen-handle-point-up): Update comment accordingly.
+
+       * src/keyboard.c (command_loop_1, read_menu_command)
+       (read_key_sequence, read_key_sequence_vs, Fread_key_sequence)
+       (Fread_key_sequence_vector): New arg DISABLE_TEXT_CONVERSION.
+       All callers changed.
+
+       * lisp/touch-screen.el (touch-screen-translate-touch): Check if a
+       prefix is specified separately from prefix being non-nil.  Accept
+       `nil' as an imaginary prefix key.
+       (function-key-map): Register translation functions on the tab bar,
+       tab lines and internal border.
+
+       * lisp/touch-screen.el (touch-screen-preview-select): Avoid
+       unnecessary redisplays.
+       (touch-screen-drag): Scroll at window margins using window
+       scrolling functions instead of relying on redisplay to recenter
+       the window around point.
+
+       * doc/emacs/input.texi (Touchscreens): Document
+       `touch-screen-preview-select'.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Fix typo in the
+       descriptions of two touch screen events.
+
+       * lisp/dired.el (dired-insert-set-properties): Adjust for changes
+       to file end computation.
+
+       * lisp/minibuffer.el (clear-minibuffer-message): Don't clear
+       minibuffer message if dragging.
+
+       * lisp/touch-screen.el (touch-screen-current-tool): Fix doc
+       string.
+       (touch-screen-preview-select): New function.
+       (touch-screen-drag): Call it if point changes.
+
+2023-07-20  Po Lu  <luangruo@yahoo.com>
+
+       * exec/trace.c (handle_readlinkat): Adjust commentary to match
+       behavior.
+
+       * src/android.c (android_get_keysym_name): NULL terminate
+       *NAME_RETURN.
+
+       * lisp/international/mule-cmds.el (set-coding-system-map): Don't
+       display `set-terminal-coding-system' on Android.
+
+       * lisp/cus-edit.el (custom-display): Add `android' display type.
+
+       * src/android.c (struct android_event_queue): Don't make
+       unnecessarily volatile.
+
+       * lisp/touch-screen.el (touch-screen-handle-touch): Don't restart
+       dragging if point is at ZV and the tap falls on a different row.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New
+       field `title'.
+       (addSubmenu): New arg TITLE.  Set that field.
+       (expandTo): Set MENU's header title if it's a context menu.
+
+       * src/androidmenu.c (android_init_emacs_context_menu): Adjust
+       signature of `createContextMenu'.
+       (android_menu_show): Use TITLE instead of pane titles if there's
+       only one pane.
+
+       * doc/emacs/dired.texi (Marks vs Flags): Document command bound
+       to `touchscreen-hold'.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Describe
+       `touch-screen-inhibit-drag'.
+
+       * etc/NEWS: Improve description of changes to touch screen
+       support.
+
+       * lisp/dired-aux.el (dired-do-chxxx, dired-do-chmod)
+       (dired-do-print, dired-do-shell-command, dired-do-compress-to)
+       (dired-do-create-files, dired-do-rename, dired-do-isearch)
+       (dired-do-isearch-regexp, dired-do-search)
+       (dired-do-query-replace-regexp, dired-do-find-regexp)
+       (dired-vc-next-action): Disable ``click to select'' after
+       running this command.
+
+       * lisp/dired.el (dired-insert-set-properties): Attach
+       click-to-select keymap to file names if necessary.
+       (dired-mode-map): Bind `touchscreen-hold' to click to select
+       mode.
+       (dired-post-do-command): New function.
+       (dired-do-delete): Call it.
+       (dired-mark-for-click, dired-enable-click-to-select-mode): New
+       functions.
+       (dired-click-to-select-mode): New minor mode.
+
+       * lisp/touch-screen.el (touch-screen-current-tool): Fix doc
+       string.
+       (touch-screen-inhibit-drag): New function.
+
+2023-07-19  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_infer_deltas): Improve commentary.
+
+       * lisp/touch-screen.el (touch-screen-handle-point-up): If what is
+       `restart-drag' (meaning that a drag has been restarted but the
+       touchpoint has not moved), translate the release into a regular
+       mouse click to deactivate the region.
+
+       * build-aux/makecounter.sh (curcount): Rename `counter' to
+       `emacs_shortlisp_counter'.
+
+       * doc/emacs/input.texi (Touchscreens): Document
+       `touch-screen-extend-selection'.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document
+       `touchscreen-restart-drag'.
+
+       * lisp/touch-screen.el (touch-screen-extend-selection): New user
+       option.
+       (touch-screen-restart-drag): New function.
+       (touch-screen-handle-point-update): Handle `restart-drag'
+       gestures.
+       (touch-screen-handle-touch): Check if the prerequisites for
+       extending a previous drag gesture are met, and generate such
+       events if so.
+       (touch-screen-translate-touch): Update doc string.
+
+       * src/Makefile.in (otherobj): Remove BUILD_COUNTER_OBJ.
+       ($(lispsource)/international/charprop.el):
+       (%.elc): Don't depend on bootstrap-emacs if cross configuring for
+       Android.
+       (libemacs.so): Directly depend on and link with BUILD_COUNTER_OBJ.
+
+       * build-aux/makecounter.sh: New script.
+
+       * src/Makefile.in (abs_top_builddir): New variable.
+       (BUILD_COUNTER_OBJ): Define to build-counter.o
+       if compiling for Android.
+       (build-counter.c): New target.  Generate this file using
+       makecounter.sh upon changes to lisp.mk or shortlisp.
+       (lisp.mk): Make and load relative to abs_top_builddir.
+       (emacs$(EXEEXT)): Adjust acordingly.
+       (mostlyclean): Remove build-counter.c.
+
+2023-07-18  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/touch-screen.el (touch-screen-handle-point-update)
+       (touch-screen-handle-point-up): Fix typos.
+
+       * lisp/touch-screen.el (touch-screen-handle-point-update): Fix
+       typo.
+
+       * src/keyboard.c (make_lispy_event): Return nil if no menu item
+       is found.
+
+       * lisp/touch-screen.el (touch-screen-hold)
+       (touch-screen-handle-point-up): Don't select inactive minibuffer
+       windows.
+       (touch-screen-handle-point-update): Improve detection of left
+       and right edges.
+
+       * lisp/touch-screen.el (touch-screen-handle-touch): Fix treatment
+       of stray update events.
+
+       * src/frame.c (syms_of_frame): Default to nil if HAVE_ANDROID.
+
+       * src/keyboard.c (make_lispy_event): Fix botched merge.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Describe
+       treatment of canceled touch sequences during touch event
+       translation.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Update JNI
+       prototypes.
+
+       * java/org/gnu/emacs/EmacsWindow.java (motionEvent): Set
+       cancelation flag in events sent where appropriate.
+
+       * lisp/touch-screen.el (touch-screen-handle-point-update):
+       Improve treatment of horizontal scrolling near window edges.
+       (touch-screen-handle-touch): Don't handle point up if the touch
+       sequence has been canceled.
+
+       * src/android.c (sendTouchDown, sendTouchUp, sendTouchMove): New
+       argument `flags'.
+
+       * src/androidgui.h (enum android_touch_event_flags): New enum.
+       (struct android_touch_event): New field `flags'.
+
+       * src/androidterm.c (handle_one_android_event): Report
+       cancelation in TOUCHSCREEN_END_EVENTs.
+
+       * src/keyboard.c (make_lispy_event): Fix botched merge.
+
+2023-07-17  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document meaning
+       of `mouse-1-menu-command'.
+
+       * lisp/mouse.el (minor-mode-menu-from-indicator): New arg EVENT.
+       Give it to popup-menu.
+       (mouse-minor-mode-menu): Use posn specified within EVENT.
+
+       * lisp/touch-screen.el (touch-screen-handle-touch): Fix
+       interactive translation.  Treat commands labeled
+       `mouse-1-menu-command' like ordinary keymaps.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document
+       changes to simple translation.
+
+       * lisp/touch-screen.el (touch-screen-handle-point-up): Generate
+       `down-mouse-1' if the current gesture is `mouse-1-menu'.
+       (touch-screen-handle-touch): If binding is a keymap, set state to
+       `mouse-1-menu' and wait for point to be released before generating
+       down-mouse-1.
+
+       * lisp/tab-bar.el (tab-bar-map): Don't bind touch-screen-drag.
+
+       * lisp/touch-screen.el (touch-screen-drag): Extend the region
+       based on the position of the mark, not the position of point
+       relative to EVENT.
+       (touch-screen-translate-touch): Don't generate virtual function
+       keys for non-mouse events.
+       (function-key-map): Remove redundant definitions.
+
+       * src/keyboard.c (read_key_sequence): Don't generate *-bar prefix
+       keys for mock input (such as input from function key translation.)
+
+       * doc/emacs/input.texi (Touchscreens): Document the new feature
+       for people who have trouble dragging to word boundaries.
+
+       * lisp/touch-screen.el (touch-screen-word-select): New defcustom.
+       (touch-screen-word-select-bounds)
+       (touch-screen-word-select-initial-word): New variable
+       definitions.
+       (touch-screen-hold): If `touch-screen-word-select', select the
+       word around EVENT.
+       (touch-screen-drag): If `touch-screen-word-select', extend the
+       region to the next word boundary if the character under point
+       constitutes a word.
+       (touch-screen-handle-point-update, touch-screen-handle-touch)
+       (touch-screen-translate-touch): Fix doc strings and fill
+       comments.
+
+2023-07-16  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (displayToast):
+       * src/android.c (android_init_emacs_service): Remove unused
+       function.
+
+       * lisp/touch-screen.el (touch-screen-handle-point-up): Correctly
+       compute posn point.
+       (touch-screen-translate-touch): Update doc string.
+       (function-key-map): Define touch screen translation functions
+       within the internal border.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Improve
+       documentation.
+
+       * lisp/tab-bar.el (tab-bar-map): Bind `[tab-bar
+       touchscreen-hold]'.
+
+       * lisp/touch-screen.el (touch-screen-hold, touch-screen-drag):
+       New functions.
+       (touch-screen-handle-timeout): Generate a `touchscreen-hold'
+       event instead.
+       (touch-screen-handle-point-update): Generate a
+       `touchscreen-drag' event upon dragging.
+       (touch-screen-translate-touch): Cancel touch screen timer upon
+       exit.
+
+       * src/keyboard.c (access_keymap_keyremap): Take unsigned int
+       start and end instead.
+
+       * doc/emacs/emacs.texi (Top):
+       * doc/emacs/input.texi (Other Input Devices): Correctly
+       capitalize subsection name.
+       (Touchscreens): Document additional translation.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document that
+       `touchscreen-end' events now have prefix keys.  Also, describe
+       mouse emulation and `touchscreen-scroll' events.
+
+       * doc/lispref/keymaps.texi (Translation Keymaps): Document
+       `current-key-remap-sequence'.
+
+       * lisp/touch-screen.el (touch-screen-translate-prompt): New
+       function.
+       (touch-screen-scroll): New command.  Bind to `touchscreen-scroll'.
+       (touch-screen-handle-point-update, touch-screen-handle-point-up)
+       (touch-screen-handle-touch): Refactor to actually translate touch
+       screen event sequences, as opposed to looking up commands and
+       executing them.
+       (touch-screen-translate-touch): New function.  Bind in
+       function-key-map to all touch screen events.
+       (touch-screen-drag-mode-line-1, touch-screen-drag-mode-line)
+       (touch-screen-tap-header-line): Remove special commands for
+       dragging the mode line and clicking on the header line.
+
+       * lisp/wid-edit.el (widget-button-click): Adjust accordingly.
+
+       * src/keyboard.c (access_keymap_keyremap): Bind
+       `current-key-remap-sequence' to the key sequence being remapped.
+       (keyremap_step): Give fkey->start and fkey->end to
+       access_keymap_keyremap.
+       (head_table): Add imaginary prefix to touchscreen-end events as
+       well.
+       (syms_of_keyboard): New variable Vcurrent_key_remap_sequence.
+
+2023-07-15  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android): Add new node to menu.
+       (Android Environment): Add footnote pointing to new node.
+       (Android Software): New node.
+
+       * doc/emacs/emacs.texi (Top): Add new node to menu.
+
+       * java/AndroidManifest.xml.in (manifest): Fix location of
+       sharedUserId property.
+
+       * java/INSTALL: Improve documentation of shared user ID
+       support.
+
+2023-07-14  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (ANDROID_SHARED_USER_NAME): New variable.
+       Substitute it.
+
+       * java/AndroidManifest.xml.in: Set `sharedUserLabel' if a shared
+       user ID is enabled.
+
+       * java/res/values/strings.xml (shared_user_name): New string
+       resource.
+
+       * src/android.c (android_blit_copy): Don't check for overflow
+       where not required.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (getSurroundingText): Don't print debug information if DEBUG_IC is
+       off.
+
+       * lisp/calc/calc.el (calc): Fix typo.
+
+2023-07-13  Po Lu  <luangruo@yahoo.com>
+
+       * etc/NEWS: Announce the new tool bar.
+
+       * etc/images/last-page.xpm:
+       * etc/images/last-page.pbm: New images.  Mirrored from
+       next-page.xpm.
+
+       * lisp/doc-view.el (doc-view-menu): Use `doc-view-new-search'
+       instead of an anonymous function.
+       (doc-view-tool-bar-map): New keymap.
+       (doc-view-search): Update the tool bar after initiating a search.
+       (doc-view-new-search): New command.
+       (doc-view-mode): Set the tool bar map appropriately.
+
+       Restore hardware acceleration, as a small degree of tearing is
+       preferable towards large slowdowns on some specific devices.  The
+       slight tearing remains, but a workaround for the GPU texture
+       remaining partially updated has been introduced.
+
+       * java/AndroidManifest.xml.in:
+       * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): Don't
+       change hardware acceleration state.
+
+       * java/org/gnu/emacs/EmacsNative.java (notifyPixelsChanged): New
+       function.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
+       New field `bitmapChanged'.
+       (copyToFrontBuffer): Signal that the bitmap has changed.
+       (onDraw): If the bitmap has changed, increment the generation ID.
+
+       * src/android.c (JNICALL): Implement new function.
+
+2023-07-13  Po Lu  <luangruo@yahoo.com>
+
+       Disable hardware acceleration on Android.  It serves no purpose
+       and causes tearing.  Uploading the bitmap to the GPU takes about
+       as long as it does to incrementally update the surface in
+       software.
+
+       * java/AndroidManifest.xml.in: Disable hardware acceleration.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): Make
+       lastClosedMenu static.
+
+       * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): Enable
+       hardware acceleration within alert dialogs.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (onDraw): Describe
+       why hardware acceleration is disabled.
+
+       * java/org/gnu/emacs/EmacsWindow.java (run): Remove redundant
+       call.
+
+2023-07-12  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_run_select_thread): Fix typo.
+
+       * src/android.c (android_run_select_thread): Correctly return the
+       set of ready read and write descriptors on __ANDROID_API__ < 16
+       systems.
+       (android_select): Clear the set of ready file descriptors if
+       events from the event queue are present despite pselect failing.
+
+       * src/androidterm.c (android_android_to_emacs_modifiers)
+       (android_emacs_to_android_modifiers): Fix statement precedence
+       bugs.
+
+       * src/doc.c (doc_close): Remove unused macro.
+
+       * java/org/gnu/emacs/EmacsWindow.java (whatButtonWasIt): Handle
+       back and forward buttons along with styluses.
+
+       * src/doc.c (close_file_unwind_android_fd): New function.
+       (get_doc_string, Fsnarf_documentation): Don't create a temporary
+       fd if it can be avoided.
+
+2023-07-11  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Ignore cross/etc/DOC.
+
+       * configure.ac: Make the directory `cross/etc'.
+
+       * cross/Makefile.in (CLEAN_SUBDIRS): Clean files inside `etc' as
+       well.
+
+       * java/Makefile.in (install_temp): Copy cross/etc/DOC to the
+       package if it is available.
+
+       * src/Makefile.in (SOME_MACHINE_OBJECTS): Add androidselect.c,
+       sfntfont-android.c and sfntfont.c.
+       (libemacs.so): Depend on $(etc)/DOC.
+
+       * src/sfnt.c (sfnt_fill_span): Correctly clip span to raster
+       width, ensuring that the last pixel is filled.
+       (main): Adjust test sizes.
+
+       * java/org/gnu/emacs/EmacsView.java (onGenericMotionEvent): Call
+       onGenericMotionEvent.
+
+       * java/org/gnu/emacs/EmacsWindow.java (Coordinate): New fields
+       `button' and `id'.
+       (<init>): Add new arguments to the construtor.
+       (whatButtonWasIt): Return 0 if the button state has not changed.
+       (buttonForEvent): New function.
+       (figureChange): Return the Coordinate object associated to EVENT.
+       Determine whether or not EVENT was accompanied by a change to the
+       button state, and ascertain which button that was.
+       (motionEvent): New function.
+       (onGenericMotionEvent, onTouchEvent): Factor out touch and mouse
+       event delivery to motionEvent.
+
+2023-07-10  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (browseUrl): New argument
+       SEND.  Choose from a list of applications that want to share the
+       URL if true.
+
+       * lisp/net/browse-url.el (browse-url-android-share): New user
+       option.
+       (browse-url-default-android-browser): Respect said user option.
+
+       * src/android.c (android_init_emacs_service, android_browse_url):
+       Expose new option.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidselect.c (Fandroid_browse_url): Likewise.
+
+       * lib/vasnprintf.c:
+       * m4/printf.m4:
+       * m4/vasnprintf.m4: Update from Gnulib.
+
+2023-07-09  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsWindow.java (eventModifiers)
+       (motionEventModifiers): New functions.
+       (onKeyDown, onKeyUp, onFocusChanged, onSomeKindOfMotionEvent):
+       Don't record the previous modifier mask; instead, always use the
+       modifier state specified in the event.
+
+       * src/androidterm.c (handle_one_android_event): Don't dispatch
+       button release events when a popup is active.
+
+       * java/org/gnu/emacs/EmacsService.java (onStartCommand): Fix typo
+       in notification message.
+       * java/org/gnu/emacs/EmacsWindow.java (onFocusChanged): Reset the
+       recorded modifier state upon a change to the window focus.
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate): Fix typo.
+
+       * java/org/gnu/emacs/EmacsDrawPoint.java (perform): Don't fill an
+       extra pixel.
+       * java/org/gnu/emacs/EmacsService.java (onCreate): Make sure
+       scaledDensity is always at least 160 dpi.
+
+2023-07-08  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsDrawLine.java (perform): Symmetrically
+       adjust coordinates to cover the last pixel.  Then, fill the left
+       most pixel of the line.
+
+       * java/org/gnu/emacs/EmacsService.java (DEBUG_IC)
+       (DEBUG_THREADS): Improve commentary.
+
+       * src/androidterm.c (handle_one_android_event): Signal completion
+       of IME events that have lost their frames.
+       (requestCursorUpdates): Don't set an edit counter as this event
+       won't be passed to the text conversion machinery.
+
+       * src/android.c (android_blit_xor, android_check_query_urgent)
+       (android_run_in_emacs_thread, android_update_extracted_text): Fix
+       whitespace.
+
+2023-07-07  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (copyToFrontBuffer):
+       Use fallback bit blit function on Android 7.0 as well, as crashes
+       have been observed in drawBitmap.
+
+2023-07-07  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/tool-bar.el (modifier-bar-modifier-list): New variable.
+       (modifier-bar-button): New function.
+       (tool-bar-event-apply-alt-modifier)
+       (tool-bar-event-apply-super-modifier)
+       (tool-bar-event-apply-hyper-modifier)
+       (tool-bar-event-apply-shift-modifier)
+       (tool-bar-event-apply-control-modifier)
+       (tool-bar-event-apply-meta-modifier): Factor out modifier bar
+       logic to that function, and redisplay the tool bar where
+       necessary.
+       (modifier-bar-available-p): New function.
+       (modifier-bar-mode): Disable tool bar items corresponding to
+       modifier keys that've already been pressed.
+
+       * etc/images/shift.pbm: Regenerate using the same font as the
+       other modifier button bitmaps.
+
+2023-07-06  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsNative.java (scaledDensity): Announce
+       new argument `scaledDensity'.
+
+       * java/org/gnu/emacs/EmacsNoninteractive.java (main): Specify new
+       argument.
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate): Compute an
+       adjusted DPI for the font size based on the ratio between density
+       and scaledDensity.
+       (run): Specify that adjusted density.
+
+       * src/android.c (setEmacsParams): Set
+       `android_scaled_pixel_density'.
+
+       * src/android.h (android_scaled_pixel_density) New variable.
+
+       * src/androidterm.c (android_term_init): Set `font_resolution'.
+
+       * src/androidterm.h (struct android_display_info): New field.
+
+       * src/font.c (font_pixel_size, font_find_for_lface)
+       (font_open_for_lface, Ffont_face_attributes, Fopen_font): Use
+       FRAME_RES instead of FRAME_RES_Y.
+
+       * src/frame.h (FRAME_RES): New macro.  Use this to convert between
+       font point and pixel sizes as opposed to FRAME_RES_Y.
+
+       * src/w32font.c (fill_in_logfont):
+       * src/xfaces.c (Fx_family_fonts, set_lface_from_font): Use
+       FRAME_RES instead of FRAME_RES_Y.  (bug#64444)
+
+2023-07-05  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Environment): Document that
+       Emacs also receives READ_EXTERNAL_STORAGE by default on old
+       versions of Android.
+       * java/AndroidManifest.xml.in: Request READ_EXTERNAL_STORAGE.
+       (bug#64445)
+
+       * doc/emacs/emacs.texi (Emacs and Android): Fix menu.
+
+       * doc/emacs/android.texi (Android): Fix discrepancies between menu
+       and sectioning.
+
+       * java/org/gnu/emacs/EmacsService.java (detectMouse): Don't use
+       function that is not present on Android 4.0.
+
+       * doc/lispref/commands.texi (Misc Events): Correctly index
+       `set-text-conversion-style'.
+
+       * lisp/tool-bar.el (tool-bar-event-apply-alt-modifier)
+       (tool-bar-event-apply-super-modifier)
+       (tool-bar-event-apply-hyper-modifier)
+       (tool-bar-event-apply-shift-modifier)
+       (tool-bar-event-apply-control-modifier)
+       (tool-bar-event-apply-meta-modifier): Pass t when restoring text
+       conversion style.
+
+       * src/keyboard.c (restore_reading_key_sequence): New function.
+       (read_key_sequence): Set `reading_key_sequence' where necessary.
+
+       * src/keyboard.h: Declare variable.
+
+       * src/textconv.c (check_postponed_buffers): New function.
+       (Fset_text_conversion_style): New argument.  If set, and a key
+       sequence is being read, postpone resetting the IME until the key
+       sequence is read.
+       (syms_of_textconv): Clear new variable and add staticpro.
+
+       * src/textconv.h: Update prototypes.
+
+2023-07-04  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/frames.texi (Tool Bars): Describe modifier bars.
+
+       * doc/lispref/keymaps.texi (Extended Menu Items, Tool Bar):
+       Document changes to tool bar menu item handling and secondary tool
+       bars.
+
+       * etc/NEWS: Announce changes.
+
+       * lisp/simple.el (event-apply-modifier): Correctly apply Ctrl and
+       Shift modifiers to lower case ASCII key events that already have
+       other modifiers applied.
+
+       * lisp/tool-bar.el (tool-bar--cache-key)
+       (tool-bar--secondary-cache-key): New defsubsts.
+       (tool-bar--flush-cache): Flush secondary tool bar cache.
+       (tool-bar-make-keymap): Include secondary tool bar if necessary.
+       (tool-bar-make-keymap-1): New arg MAP.  Generate a keymap for that
+       map if specified, else default to tool-bar-map.
+       (set-text-conversion-style, tool-bar-apply-modifiers)
+       (overriding-text-conversion-style)
+       (tool-bar-event-apply-alt-modifier)
+       (tool-bar-event-apply-super-modifier)
+       (tool-bar-event-apply-hyper-modifier)
+       (tool-bar-event-apply-shift-modifier)
+       (tool-bar-event-apply-control-modifier)
+       (tool-bar-event-apply-meta-modifier, modifier-bar-mode): New
+       functions.
+
+       * src/dispextern.h (enum tool_bar_item_idx): Add
+       TOOL_BAR_ITEM_WRAP.
+
+       * src/frame.c (make_frame): Clear new field `tool_bar_wraps_p'.
+
+       * src/frame.h (struct frame): New field `tool_bar_wraps_p'.
+
+       * src/keyboard.c (parse_tool_bar_item): Handle QCwrap properties
+       in tool bar menu items.
+       (syms_of_keyboard): New defsym QCwrap.
+
+       * src/xdisp.c (build_desired_tool_bar_string): Clear
+       f->tool_bar_wraps_p and set it appropriately.  Insert new line
+       characters in the tool bar string upon encountering a wrap
+       character.
+       (display_tool_bar_line): Stop at EOB, not line end.  Reseat on the
+       next line upon encountering EOL characters.
+       (redisplay_tool_bar): Allow rows to be different heights if
+       explicit new lines are present upon the tool bar string.
+
+       * src/sfnt.c (sfnt_decompose_compound_glyph): Pacify warning.
+
+2023-06-30  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_query_tree): Correctly return children.
+
+2023-06-27  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Environment): Improve wording.
+
+       * doc/emacs/android.texi (Android Environment): Fix typos.
+
+       * src/android.c (android_exception_check)
+       (android_exception_check_1)
+       (android_exception_check_2)
+       (android_exception_check_nonnull)
+       (android_exception_check_nonnull_1): Tell the compiler to expect
+       that the object is non-NULL, or that no exception has been thrown.
+
+       * exec/loader-mips64el.s (rest_of_exec): Fix typo in comment.
+
+2023-06-26  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/commands.texi (Touchscreen Events): Fix typo.
+
+2023-06-26  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/calc/calc.el (calc-mode, calc): Make sure the on-screen
+       keyboard is not hidden when a Calc buffer is created or a Calc
+       Trail window is being created for the first time.
+
+       * lisp/touch-screen.el (touch-screen-window-selection-changed):
+       Take touch-screen-display-keyboard in to account.
+
+       * src/sfnt.c (sfnt_decompose_compound_glyph)
+       (sfnt_interpret_compound_glyph_1): Reset `defer_offsets' before
+       processing each component.
+       (sfnt_lerp_half): Avoid undefined shift of negative value.
+       (sfnt_compute_tuple_scale): Pacify compiler warning.
+
+2023-06-23  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform):
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java (Sdk7FontEntity):
+       (hasChar): Clean up dead stores.
+
+       * src/android.c (android_wc_lookup_string): Fix typo.
+       (android_wc_lookup_string): Check that GetStringChars returns
+       non-NULL, not if it throws an exception.
+
+2023-06-21  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidfns.c (android_set_tool_bar_position)
+       (frame_geometry):
+       * src/androidterm.c (android_flash)
+       (android_clear_under_internal_border): Synchronize with X.
+
+2023-06-20  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidfns.c (android_frame_parm_handlers): Fix typo.
+       (android_set_tool_bar_position): New function.
+       (android_frame_parm_handlers): Add new frame param handler.
+
+2023-06-19  Po Lu  <luangruo@yahoo.com>
+
+       * lib-src/Makefile.in (seccomp-filter$(EXEEXT)): Link with Gnulib.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView, dimensionsLock):
+       New field.
+       (<init>): Create new lock object.
+       (handleDirtyBitmap, onLayout, onAttachedToWindow): Make sure
+       measuredWidth and measuredHeight are extracted and set atomically.
+       Send Expose upon layout changes if the view has grown.
+
+       * exec/Makefile.in (clean): Add `exec1'.
+
+2023-06-18  Po Lu  <luangruo@yahoo.com>
+
+       * src/window.h (struct window): Improve documentation of
+       `last_mark'.
+
+       * src/xdisp.c (mark_window_display_accurate_1): Don't set
+       `last_mark' to -1 if the mark is inactive.
+
+       * lisp/textmodes/conf-mode.el (conf-mode-initialize): Set
+       text-conversion-style.
+
+2023-06-17  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate, run): Don't
+       initialize signal mask here.
+
+       * java/org/gnu/emacs/EmacsApplication.java (onCreate): Do it
+       here instead.
+
+       * src/android.c (JNICALL): Restore previous signal masks.
+
+       * java/README: More documentation.
+
+2023-06-16  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_write_event, JNICALL)
+       (android_run_in_emacs_thread): Don't rely on raise to call
+       deliver_process_signal.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity):
+       * java/org/gnu/emacs/EmacsApplication.java (findDumpFile):
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu)
+       (addSubmenu, display):
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java
+       (getNotificationUri, queryChildDocuments, deleteDocument):
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform):
+       * java/org/gnu/emacs/EmacsFillRectangle.java (perform):
+       * java/org/gnu/emacs/EmacsOpenActivity.java (readEmacsClientLog)
+       (checkReadableOrCopy):
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java
+       (EmacsSdk7FontDriver):
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
+       * java/org/gnu/emacs/EmacsView.java (EmacsView):
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyUp):
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+       (EmacsWindowAttachmentManager): Remove various unused arguments
+       and variables, dead stores, and make minor cleanups and
+       performance improvements.
+
+       * src/androidmenu.c (FIND_METHOD_STATIC, android_menu_show):
+       Adjust accordingly.
+
+2023-06-15  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection, beginBatchEdit, reset, endBatchEdit): Keep
+       track of the number of batch edits and return an appropriate
+       value.
+       (takeSnapshot): Implement function.
+
+       * java/org/gnu/emacs/EmacsNative.java (takeSnapshot): New
+       function.
+
+       * java/org/gnu/emacs/EmacsService.java (resetIC): Improve
+       debugging output.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection):
+       Call `reset' to clear the UI side batch edit count.
+
+       * src/androidterm.c (struct android_get_surrounding_text_context):
+       New fields `conversion_start' and `conversion_end'.
+       (android_get_surrounding_text): Return the conversion region.
+       (android_get_surrounding_text_internal, NATIVE_NAME): Factor out
+       `getSurroundingText'.
+       (takeSnapshot): New function.
+
+2023-06-14  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection): Reimplement as an InputConnection, not
+       BaseInputConnection.
+
+       * src/androidterm.c (performEditorAction): Sync prior to sending
+       keyboard events.
+
+2023-06-13  Po Lu  <luangruo@yahoo.com>
+
+       * etc/NEWS: Fix typo.
+
+       * lisp/gnus/gnus-score.el (gnus-read-char): New function.
+       (gnus-summary-increase-score): Use it to display a dialog box on
+       Android, where input methods have trouble with plain old
+       read-char.
+
+2023-06-12  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): Resolve
+       dialog button style and use it instead.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView)
+       (showOnScreenKeyboard, hideOnScreenKeyboard): Don't synchronize.
+
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow)
+       (toggleOnScreenKeyboard): Revert to calling IMM functions from the
+       main thread.
+
+       * src/android.c (struct android_event_container)
+       (android_pselect_nfds, android_pselect_readfds)
+       (android_pselect_writefds, android_pselect_exceptfds)
+       (android_pselect_timeout): Don't make volatile.
+       (android_wait_event): Run queries if necessary.
+
+2023-06-11  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/net/tramp.el (tramp-encoding-shell):
+       * lisp/obsolete/terminal.el (terminal-emulator):
+       * lisp/term.el (term-exec-1):
+       * lisp/textmodes/artist.el (artist-figlet-get-font-list):
+       * src/android.c (JNICALL): Where /bin/sh was previously used, use
+       /system/bin/sh on Android.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
+       Document member variables.
+       (onDraw): Use separate Paint object on the UI thread.
+
+       * src/textconv.c (really_commit_text, really_set_composing_text)
+       (really_delete_surrounding_text): Run modification hooks when
+       deleting text.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView)
+       (showOnScreenKeyboard, hideOnScreenKeyboard)
+       (onCheckIsTextEditor): Make synchronized.
+
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow)
+       (toggleOnScreenKeyboard): Don't post to the main thread.
+
+2023-06-10  Po Lu  <luangruo@yahoo.com>
+
+       * src/keyboard.c (handle_input_available_signal): Don't generate
+       instructions not available in arm mode.
+
+       * src/android.c (android_select, android_check_query)
+       (android_check_query_urgent, android_answer_query)
+       (android_answer_query_spin, android_begin_query)
+       (android_end_query)
+       (android_run_in_emacs_thread): Use finer grained memory
+       synchronization semantics.
+
+       * src/androidterm.c (android_get_selection): Use the current
+       selection, not its value at the time of the last redisplay.
+
+       * src/keyboard.c (handle_input_available_signal): Place memory
+       barrier.
+
+       * src/textconv.c (really_commit_text)
+       (really_set_composing_text): Improve behavior of certain
+       fontification mechanisms by inheriting text properties from
+       surrounding text.
+
+       * src/android.c (android_select): Clear `android_urgent_query'.
+       (android_check_query): Make static.  Clear `android_urgent_query'.
+       (android_check_query_urgent): New function; work like
+       `android_check_query', but only answer urgent queries.
+       (android_answer_query, android_end_query): Clear urgent query
+       flag.
+       (android_run_in_emacs_thread): Initially wait two seconds for the
+       query to run from the keyboard loop; upon a timeout, set
+       `android_urgent_query' to true and wait for it to run while
+       reading async input.
+
+       * src/android.h: Update prototypes.
+
+       * src/keyboard.c (handle_async_input): Call
+       `android_check_query_urgent'.
+
+2023-06-09  Po Lu  <luangruo@yahoo.com>
+
+       * src/textconv.c (really_commit_text)
+       (handle_pending_conversion_events): Fix minor typos.
+
+       * src/androidterm.c (handle_one_android_event): Don't answer
+       queries here; just rely on the event interrupting android_select.
+       This avoids exposing buffer contents to input methods while a
+       command is being executed.
+
+       * src/textconv.c (TEXTCONV_DEBUG, really_commit_text)
+       (really_finish_composing_text, really_set_composing_text)
+       (really_set_composing_region, really_delete_surrounding_text)
+       (really_set_point_and_mark, get_extracted_text): Add debugging
+       printouts.
+
+       * lisp/progmodes/cc-mode.el (c-initialize-cc-mode): Always add
+       text conversion hooks.
+
+       * src/android.c (android_get_gc_values): Remove redundancy.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
+       function `setupSystemThread'.
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate): Block all
+       signals except for SIGBUS and SIGSEGV in the UI thread.
+
+       * src/android.c (setupSystemThread): New function.
+
+       * java/org/gnu/emacs/EmacsThread.java (run): Correct check
+       against extraStartupArgument when an initial file is specified.
+
+2023-06-08  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu):
+       Make subclasses final.
+
+       * java/org/gnu/emacs/EmacsDialog.java (display1): Check if an
+       instance of EmacsOpenActivity is open; if it is, try using it to
+       display the pop up dialog.
+
+       * java/org/gnu/emacs/EmacsDialogButtonLayout.java
+       (EmacsDialogButtonLayout): Make final.
+
+       * java/org/gnu/emacs/EmacsHolder.java (EmacsHolder<T>): Likewise.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity):
+       New field `currentActivity'.
+       (onCreate, onDestroy, onWindowFocusChanged, onPause): Set that
+       field as appropriate.
+
+       * src/android.c (android_is_special_directory): New function.
+       (android_get_asset_name, android_content_name_p)
+       (android_get_content_name):
+       * src/android.h (android_is_special_directory)
+       (JNI_STACK_ALIGNMENT_PROLOGUE):
+       * src/fileio.c (check_mutable_filename):
+       * src/filelock.c (WTMP_FILE, make_lock_file_name):
+       * src/inotify.c (IN_ONLYDIR, Finotify_add_watch): Factor out
+       checks against asset and content directories to that function.
+
+2023-06-07  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Startup): Fix reference to non
+       existent node.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit)
+       (endBatchEdit, commitCompletion, commitText)
+       (deleteSurroundingText)
+       (finishComposingText, getSelectedText, getTextAfterCursor)
+       (getTextBeforeCursor, setComposingText, setComposingRegion)
+       (performEditorAction, performContextMenuAction, getExtractedText)
+       (setSelection, sendKeyEvent, deleteSurroundingTextInCodePoints)
+       (requestCursorUpdates): Ensure that the input connection is up to
+       date.
+       (getSurroundingText): New function.
+
+       * java/org/gnu/emacs/EmacsNative.java (getSurroundingText): Export
+       new C function.
+
+       * java/org/gnu/emacs/EmacsService.java (resetIC): Invalidate
+       previously created input connections.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView)
+       (onCreateInputConnection): Signify that input connections are
+       now up to date.
+
+       * src/androidterm.c (struct android_get_surrounding_text_context):
+       New structure.
+       (android_get_surrounding_text, NATIVE_NAME):
+       * src/textconv.c (get_surrounding_text):
+       * src/textconv.h: New functions.
+
+2023-06-06  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/simple.el (analyze-text-conversion): Remove old workaround.
+
+2023-06-06  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (display): Use
+       `EmacsHolder' instead of `Holder'.
+
+       * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): Use
+       `EmacsDialogButtonLayout' to ensure that buttons are wrapped
+       properly.
+       (display): Adjust for new holder class.
+
+       * java/org/gnu/emacs/EmacsDialogButtonLayout.java
+       (EmacsDialogButtonLayout, onMeasure, onLayout): New functions.
+
+       * java/org/gnu/emacs/EmacsDrawLine.java:
+       * java/org/gnu/emacs/EmacsFillPolygon.java: Remove redundant
+       imports.
+
+       * java/org/gnu/emacs/EmacsHolder.java (EmacsHolder<T>):
+       * java/org/gnu/emacs/EmacsService.java (class Holder<T>)
+       (getEmacsView, EmacsService): Rename `Holder' to `EmacsHolder'
+       and make it public.
+
+2023-06-06  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/simple.el (undo-auto-amalgamate): Update doc string to
+       describe new amalgamating commands.
+       (analyze-text-conversion): Make this an amalgamating command by
+       default, unless a new line has been inserted.  Also, shorten the
+       undo boundary timer.
+
+       * src/textconv.c (really_commit_text)
+       (really_set_composing_text): Correctly report ephemeral deletions.
+       (syms_of_textconv): Fix doc strings.
+
+2023-06-05  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidterm.c (android_handle_ime_event): Clear batch edit
+       state, in case the previous input method forgot to do so.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
+       function clearInputFlags.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection):
+       Stop reporting changes after a new input method connection is
+       established.
+
+       * src/androidterm.c (android_handle_ime_event): Implement that
+       change.
+       (JNICALL): New function.
+
+2023-06-04  Po Lu  <luangruo@yahoo.com>
+
+       * src/keyboard.c: Fix build without window system
+
+       * configure.ac: Tune pty detection for Android.
+
+       * java/debug.sh (gdbserver_cmd, is_root): Prefer TCP again.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function
+       `queryAndSpin'.
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService)
+       (icBeginSynchronous, icEndSynchronous, viewGetSelection): New
+       synchronization functions.
+       (resetIC, updateCursorAnchorInfo): Call those instead.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection):
+       Call viewGetSelection.
+
+       * src/android.c (JNICALL, android_answer_query_spin): New
+       functions.
+
+2023-06-03  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/bindings.el (global-map): Bind cut, copy and paste.
+
+       * src/androidterm.c (JNICALL): Use key.
+
+       * src/textconv.c (really_commit_text)
+       (really_set_composing_text): Delete text between mark and point if
+       the mark is active.  Don't record changes if the text is empty.
+
+       * src/androidterm.c (struct android_get_extracted_text_context):
+       New field `mark_active'.
+       (android_get_extracted_text): Set that field.
+       (struct android_extracted_text_class): New field `flags'.
+       (android_build_extracted_text): New argument `mark_active'.  Set
+       flags appropriately.
+       (NATIVE_NAME, android_update_selection): Likewise.
+
+       * src/textconv.c (get_extracted_text): New argument
+       `mark_active'.  Set it if the mark is active.
+
+       * src/textconv.h: Update prototypes.
+
+       * etc/MACHINES: Fix reference to obsolete file.
+
+2023-06-02  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/emacs-lisp/eldoc.el ("back-to-indentation"): Register touch
+       screen and text conversion commands.
+
+       * lisp/progmodes/cc-cmds.el (c-post-text-conversion): New
+       function.
+
+       * lisp/progmodes/cc-mode.el (c-initialize-cc-mode): Add it as the
+       `post-texxt-conversion-hook'.
+
+       * lisp/simple.el (post-text-conversion-hook): New hook.
+       (analyze-text-conversion): Run it until success before trying post
+       insert functions.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection): Apply workarounds on Vivo devices as well.
+
+       * src/android.c (sendKeyPress, sendKeyRelease): Clear counter.
+
+       * src/androidgui.h (struct android_key_event): New field
+       `counter'.
+
+       * src/androidterm.c (handle_one_android_event): Generate barriers
+       as appropriate.
+       (JNICALL): Set `counter'.
+
+       * src/frame.h (enum text_conversion_operation):
+       * src/textconv.c (detect_conversion_events)
+       (really_set_composing_text, handle_pending_conversion_events_1)
+       (handle_pending_conversion_events, textconv_barrier):
+       * src/textconv.h: Implement text conversion barriers and fix
+       various typos.
+
+2023-06-01  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (browseUrl): If uri's
+       scheme is `file', rewrite it into a content URI.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection, performContextMenuAction): New function.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative)
+       (performContextMenuAction): New function.
+
+       * src/android.c (android_get_gc_values): Implement more
+       efficiently.
+
+       * src/androidterm.c (android_handle_ime_event): Pass through
+       `update' argument to `finish_composing_text'.  Fix thinko.
+
+       * src/textconv.c (really_finish_composing_text)
+       (really_set_composing_text, really_set_composing_region)
+       (handle_pending_conversion_events_1, finish_composing_text): New
+       argument `update'.  Notify IME of conversion region changes if
+       set.
+
+       * src/textconv.h: Update structs and prototypes.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection): Add compatibility adjustments for Samsung
+       devices.
+
+       * src/androidterm.c (struct android_get_extracted_text_context):
+       New field `start_offset' and `end_offset'.  Delete `offset'.
+       (android_get_extracted_text, android_build_extracted_text):
+       Replace `offset' with new args `start_offset' and `end_offset'.
+       (NATIVE_NAME): Set `start_offset' and `end_offset'.
+       (android_update_selection): Likewise.
+
+       * src/textconv.c (get_extracted_text): Likewise.
+
+       * src/textconv.h: Update prototypes.
+
+2023-05-31  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac: Pass through `--enable-check-lisp-object-type' on
+       Android.
+
+       * src/alloc.c (android_make_lisp_symbol):
+       * src/android.c:
+       * src/androidfns.c (android_set_no_focus_on_map)
+       (android_set_no_accept_focus):
+       * src/androidfont.c (androidfont_match, androidfont_open_font):
+       * src/androidselect.c (Fandroid_get_clipboard)
+       (Fandroid_get_clipboard_targets):
+       * src/keyboard.c (make_lispy_event, syms_of_keyboard):
+       * src/sfntfont.c (sfnt_enum_font_1, sfntfont_list_1):
+       * src/textconv.c (really_set_point_and_mark): Fix Lisp_Object and
+       integer screw-ups.
+
+       * doc/emacs/input.texi (Other Input Devices, Touchscreens)
+       (On-Screen Keyboards):
+       * doc/lispref/commands.texi (Misc Events):
+       * src/android.c (android_faccessat): Improve word choices and
+       commentary.
+
+       * lisp/touch-screen.el (touch-screen-handle-scroll): Make
+       precision scrolling work better with horizontal movement.
+
+       * src/android.c (android_copy_area): Pacify compiler warning.
+
+       * exec/exec.c (insert_args): New argument `arg3'.  Replace argv[1]
+       with that argument.
+       (exec_0): Pass file name of script to `insert_args'.
+
+       * doc/emacs/android.texi (What is Android?, Android Startup)
+       (Android File System, Android Environment, Android Windowing)
+       (Android Troubleshooting): Improve wording and various other
+       issues.
+
+       * java/debug.sh (is_root): Go back to using unix sockets; allow
+       adb to forward them correctly.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (getExtractedText): Don't print text if NULL.
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService): New field
+       `imSyncInProgress'.
+       (updateIC): If an IM sync might be in progress, avoid deadlocks.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection):
+       Set `imSyncInProgress' across synchronization point.
+
+       * src/android.c (android_check_query): Use __atomic_store_n.
+       (android_answer_query): New function.
+       (android_begin_query): Set `android_servicing_query' to 2.  Check
+       once, and don't spin waiting for query to complete.
+       (android_end_query): Use __atomic_store_n.
+       (android_run_in_emacs_thread): Compare-and-exchange flag.  If
+       originally 1, fail.
+
+       * src/textconv.c (really_set_composing_text): Clear conversion
+       region if text is empty.
+
+2023-05-29  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_blit_copy, android_blit_xor): Fix typos.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function
+       `blitRect'.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): Use
+       it on Android 8.x.
+
+       * src/android.c (blitRect): Implement new function.
+       (android_neon_mask_line): Fix iteration over remainder.
+       (android_blit_copy): Be more paranoid.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java: Remove file.
+
+       * java/org/gnu/emacs/EmacsService.java (copyArea): Delete
+       function.
+
+       * src/android.c (struct android_emacs_service)
+       (android_init_emacs_service): Remove `copy_area'.
+       (android_create_gc, android_change_gc, android_get_gc_values):
+       Record new GC values.
+       (android_neon_mask_line): New function.
+       (android_blit_copy, android_blit_xor): New functions.
+       (android_copy_area): Implement in C.
+       (android_lock_bitmap): Accept drawables instead of windows.
+
+       * src/android.h: Adjust prototype for `android_lock_bitmap'.
+
+       * src/androidgui.h (struct android_gc): Record last known GC
+       values.
+
+2023-05-27  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService)
+       (checkEmacsThread): New function.
+       (fillPolygon, drawRectangle, drawLine, drawPoint, copyArea)
+       (clearArea):
+       * java/org/gnu/emacs/EmacsThread.java (EmacsThread):
+       * java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers): Call
+       where appropriate.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers):
+       Remove unnecessary documentation.  `damageRegion' is only changed
+       from the Emacs thread.
+
+2023-05-26  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Troubleshooting): Document
+       `debug-init' option.
+
+       * java/AndroidManifest.xml.in
+       (EmacsLauncherPreferencesActivity): New activity.  Export on
+       systems older than Android 7.0.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate): Adjust for
+       string startup argument.
+
+       * java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java: New
+       file.
+
+       * java/org/gnu/emacs/EmacsPreferencesActivity.java
+       (EmacsPreferencesActivity): Don't make final.
+       (startEmacsQ): Give start-up argument as an argument, not as a
+       boolean.
+       (startEmacsDebugInit): New function.
+       (onCreate): Register new listener; make final.
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate): Pass
+       extraStartupArgument.
+
+       * java/org/gnu/emacs/EmacsThread.java (EmacsThread): Rename
+       startDashQ to extraStartupArgument.
+       (run): Adjust accordingly.
+
+       * java/res/values-v24/bool.xml:
+       * java/res/values/bool.xml:
+       * java/res/values/strings.xml: New files.
+
+       * java/res/xml/preferences.xml: Add new option.  Move string
+       resources around.
+
+2023-05-24  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_decompose_compound_glyph): Allow decomposing up
+       to 16 nested components.
+       (CALL, LOOPCALL): Correctly error if no fdef storage exists.
+       (sfnt_interpret_run): New label `next_instruction', for CALL.
+       (sfnt_interpret_compound_glyph_1): Allow decomposing up to 16
+       nested components.  Prevent crash if there are no end points or
+       points.
+       (sfnt_read_cvar_table): Prevent assigning uninitialized values.
+       (sfnt_vary_simple_glyph): Update commentary.
+
+2023-05-23  Po Lu  <luangruo@yahoo.com>
+
+       * exec/exec.c (exec_0): Use strcpy.
+
+2023-05-20  Po Lu  <luangruo@yahoo.com>
+
+       * exec/trace.c (handle_clone_prepare, handle_clone): When
+       !REENTRANT, use malloc to allocate tracees after running out of
+       static ones.
+
+       * java/org/gnu/emacs/EmacsView.java (swapBuffers): Restore missing
+       damage rect code.
+       (onDetachedFromWindow): Remove redundant synchronization.
+
+2023-05-19  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/touch-screen.el (touch-screen-tap-header-line): New
+       function.
+       ([header-line touchscreen-begin]): Define to
+       `touch-screen-tap-header-line'.
+
+2023-05-18  Po Lu  <luangruo@yahoo.com>
+
+       * make-dist (possibly_non_vc_files): Add Android-specific files.
+
+       * doc/emacs/frames.texi (Tab Bars): Explain how to interact with
+       the tab bar from a touch screen.
+
+       * doc/emacs/input.texi (Touchscreens): Document exactly what a
+       ``long press'' is.
+
+       * doc/emacs/windows.texi (Tab Line): Likewise.
+
+       * lisp/tab-line.el (tab-line-tab-map, tab-line-add-map)
+       (tab-line-tab-close-map, tab-line-left-map, tab-line-right-map):
+       Bind `touchscreen-begin' to each command.
+       (tab-line-track-tap, tab-line-event-start): New functions.
+       (tab-line-hscroll-right, tab-line-hscroll-left, tab-line-new-tab)
+       (tab-line-select-tab, tab-line-close-tab): Use them.
+
+2023-05-16  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/menu-bar.el (popup-menu-normalize-position): Normalize
+       `touchscreen-begin' events correctly.
+
+       * lisp/tab-bar.el (tab-bar-mouse-context-menu): New argument POSN.
+       Use it if specified.
+       (touch-screen-track-tap, tab-bar-handle-timeout)
+       (tab-bar-touchscreen-begin): New functions.
+       (tab-bar-map): Bind [tab-bar touchscreen-begin].
+
+       * lisp/touch-screen.el (touch-screen-track-drag): Fix doc
+       string.
+
+       * src/dispextern.h: Export `get_tab_bar_item_kbd'.
+
+       * src/keyboard.c (coords_in_tab_bar_window): New function.
+       (make_lispy_event): Adjust touchscreen begin event mouse
+       position list for tab bar.
+
+       * src/xdisp.c (tab_bar_item_info): Allow CLOSE_P to be NULL.
+       (get_tab_bar_item): Adjust doc string.
+       (get_tab_bar_item_kbd): New function.
+
+2023-05-15  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac: Also disable enable_year2038.
+
+       * msdos/sed1v2.inp: Fix removal of ANDROID_BUILD_CFLAGS.
+       * msdos/sedlibmk.inp: Clear DIR_HAS_FD_MEMBER and LOCALE_FR_UTF8.
+
+2023-05-14  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java
+       (notifyChangeByName): New function.
+       (queryDocument1): Set FLAG_SUPPORTS_MOVE where necessary.
+       (moveDocument): Implement new function.
+
+2023-05-08  Po Lu  <luangruo@yahoo.com>
+
+       * java/Makefile.in (install_temp/assets/version): Fix generation
+       in out of tree builds.
+
+2023-05-07  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (requestCursorUpdates):
+       * java/org/gnu/emacs/EmacsNative.java (requestCursorUpdates):
+       * java/org/gnu/emacs/EmacsService.java (updateCursorAnchorInfo):
+       New functions.
+
+       * src/android.c (struct android_emacs_service)
+       (android_init_emacs_service): Add new method.
+       (android_update_cursor_anchor_info): New function.
+
+       * src/androidfns.c (android_set_preeditarea): New function.
+
+       * src/androidgui.h (enum android_ime_operation): New operation
+       `REQUEST_CURSOR_UPDATES'.
+       (struct android_ime_event): Document new meaning of `length'.
+
+       * src/androidterm.c (android_request_cursor_updates): New
+       function.
+       (android_handle_ime_event): Handle new operations.
+       (handle_one_android_event, android_draw_window_cursor): Update
+       the preedit area if needed, like on X.
+       (requestCursorUpdates): New function.
+
+       * src/androidterm.h (struct android_output): New field
+       `need_cursor_updates'.
+
+2023-05-06  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (LIBGMP_CFLAGS): Avoid non portable test
+       expression.
+
+       * cross/verbose.mk.android (AM_V_CC): Get rid of badly aligned
+       ANDROID_CC messages.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java (syncAfterCommit)
+       (extractAbsoluteOffsets): Add workarounds for several kinds of
+       machines.
+       (commitText, getExtractedText): Likewise.
+
+       * src/textconv.c (really_commit_text): Improve definition of
+       POSITION.
+       (get_extracted_text): Default to providing at least 4 characters.
+
+2023-05-05  Po Lu  <luangruo@yahoo.com>
+
+       * exec/exec.h (struct exec_tracee): New field `new_child'.  Also
+       make `waiting_for_syscall' a bitfield.
+
+       * exec/trace.c (PTRACE_GETEVENTMSG): New declaration.
+       (MAX_TRACEES): Bump to 4096.
+       (handle_clone_prepare): New function.
+       (handle_clone): If required, set `new_child' and wait for a ptrace
+       event describing the parent to arrive.
+       (after_fork): Clear new field.
+       (exec_waitpid): Upon a ptrace event describing a clone, create the
+       child's tracee if it doesn't already exist.  Otherwise, copy over
+       the parent's cmdline and start running it.
+
+       * doc/emacs/android.texi (Android Environment): Document lossage
+       with SIGSTOP.
+
+       * exec/exec.c (exec_0): Check X_OK on file being opened.  Also
+       handle /proc/self/exe.
+
+       * exec/trace.c (SYS_SECCOMP): Define when not present.
+
+2023-05-04  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Environment): Describe how to
+       turn off process killing.
+
+       * exec/trace.c (check_signal): New function.
+       (handle_exec, process_system_call): Handle signal-delivery-stop
+       while waiting synchronously for syscall completion.
+
+2023-05-03  Po Lu  <luangruo@yahoo.com>
+
+       * exec/config.h.in: Update from new automatically generated
+       headers.
+
+       * exec/configure.ac: Check for siginfo_t.si_syscall.
+
+       * exec/trace.c (exec_waitpid): If SIGSYS is received, and caused
+       by seccomp, drop it should the call number be the invalid system
+       call used by Emacs.
+
+       * exec/configure.ac: Use system extensions.
+       (HAVE_PROCESS_VM): Define if process_vm_readv and
+       process_vm_writev are available.
+
+       * exec/trace.c (read_memory, user_copy): Implement in terms of
+       process_vm if possible.
+
+       * exec/loader-mipsel.s (__start): Remove extraneous debugging
+       code.
+
+       * exec/Makefile.in: (.PHONY): Add `bootstrap-clean' and
+       `extraclean'.
+       (bootstrap-clean): New rule.
+
+       * java/Makefile.in (FIND_DELETE): New substitution.
+       (clean): Use FIND_DELETE.
+
+2023-05-02  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Environment): Improve doc.
+
+       * exec/config.h.in (__bool_true_false_are_defined):
+       * exec/configure.ac (REENTRANT): New definitions.
+       (READLINKAT_SYSCALL, READLINK_SYSCALL): New defines.  Set on all
+       hosts.
+
+       * exec/exec.c (MIN, MAX): Remove redundant declarations.  Move to
+       config.h.
+       (exec_0): Copy name of executable into NAME when !REENTRANT.
+
+       * exec/exec.h (struct exec_tracee): New struct `exec_file'.
+
+       * exec/trace.c (remove_tracee, handle_exec, handle_readlinkat)
+       (process_system_call, after_fork): Handle readlinkat system calls.
+
+       * exec/Makefile.in (.SUFFIXES): Include ., then `srcdir'.
+
+       * exec/loader-aarch64.s (_start):
+       * exec/loader-armeabi.s (_start):
+       * exec/loader-mips64el.s (__start):
+       * exec/loader-mipsel.s (__start):
+       * exec/loader-x86.s (_start):
+       * exec/loader-x86_64.s (_start): Get basename of opened exec file
+       and make it the command name.  Fix envp skipping on x86 and
+       various leaks.
+
+       * exec/configure.ac: Check for declarations of stpcpy and stpncpy.
+
+       * exec/exec.c (stpcpy, stpncpy): Use replacements if declarations
+       are not present; this happens when a new Android NDK is building
+       for an old version of Android.
+
+2023-05-01  Po Lu  <luangruo@yahoo.com>
+
+       * exec/config.h.in: Update config.h.in.
+
+       * exec/configure.ac: Check for stpcpy and stpncpy.
+
+       * exec/exec.c (rpl_stpcpy, rpl_stpncpy): Define replacements when
+       they are not present on the system.
+       (process_program_header): Fill comment.
+
+       * src/term.c (syms_of_term): Pretend Android uses TERMINFO.
+
+       * exec/exec.c (format_pid): New function.
+       (exec_0): Make cwd relative file names relative to /proc/pid/cwd.
+
+       * exec/trace.c (handle_exec): Handle EINTR.
+
+       (process_system_call): Report failure without clobbering x0.
+
+       * README: Describe `exec' directory.
+
+       * lisp/subr.el (use-dialog-box-p): Always prefer dialog boxes.
+
+       * src/android.c (android_write_event, JNICALL): Raise SIGIO on key
+       press and window action events.
+
+       * exec/trace.c (process_system_call): Save and restore x0, x1 and
+       x2 regs after replacing them with an invalid file descriptor.
+
+       * Makefile.in (extraclean): Clean in exec as well.
+
+       * configure.ac: Fix detection of absolute srcdir.  Also, pass
+       CFLAGS.
+
+       * exec/Makefile.in: (.c.o): Add -I. so config.h can be found.
+       (.s.o): Don't create m4 temporary in srcdir.
+
+       * exec/config-mips.m4.in (DADDI2, DADDI3): New macros.  Define to
+       substitute if as cannot assemble daddi.
+
+       * exec/configure.ac (user_h): Look for user.h in asm/ as well.
+       Use new user.h.  Also look in ptrace.h on arm systems.  Check if
+       as supports daddi on mips64.
+
+       * exec/exec.c (check_interpreter): Fix char signedness bug.
+
+       * exec/loader-mips64el.s (__start): Use DADDI2 and DADDI3 for two-
+       and 3-operand daddi.
+
+       * exec/mipsel-user.h: Don't include sgidefs.h.
+
+       * java/INSTALL: Document that m4 is now required.
+
+       * src/android.c (android_rewrite_spawn_argv): Add missing NULL.
+
+       * doc/emacs/android.texi (Android Environment): Document
+       `android-use-exec-loader'.
+
+       * exec/exec1.c (main): Set program group of child process.
+
+       * src/android.c (android_rewrite_spawn_argv): New function.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfns.c (syms_of_androidfns): New variable
+       `android_use_exec_loader'.
+
+       * src/callproc.c (emacs_spawn): Rewrite the argument vector to use
+       exec1 if necessary.
+
+       * .gitignore: Add exec/configure.
+
+2023-04-30  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: New files.
+
+       * Makefile.in (mostlyclean_dirs): Add libexec, if its Makefile
+       exists.
+
+       * autogen.sh (do_git): Autoreconf in exec as well.
+
+       * configure.ac: Configure libexec on Android.
+
+       * exec/Makefile.in:
+       * exec/README:
+       * exec/config-mips.m4.in:
+       * exec/config.guess:
+       * exec/config.h.in:
+       * exec/config.sub:
+       * exec/configure:
+       * exec/configure.ac:
+       * exec/deps.mk:
+       * exec/exec.c (MIN, struct exec_open_command)
+       (struct exec_map_command, struct exec_jump_command)
+       (write_open_command, write_load_command, process_interpreter_1)
+       (process_interpreter, process_program_header, insert_args)
+       (exec_0):
+       * exec/exec.h (_EXEC_H_, struct elf_header_32)
+       (struct program_header_32, struct dt_entry_32)
+       (struct elf_header_64, struct program_header_64)
+       (struct dt_entry_64, struct exec_tracee):
+       * exec/exec1.c (main):
+       * exec/install-sh (scriptversion):
+       * exec/loader-aarch64.s (_start):
+       * exec/loader-armeabi.s (_start):
+       * exec/loader-mips64el.s (__start):
+       * exec/loader-mipsel.s (__start):
+       * exec/loader-x86.s (_start):
+       * exec/loader-x86_64.s (_start):
+       * exec/mipsel-user.h (_MIPSEL_USER_H_):
+       * exec/mipsfpu.c (MIPS_ABI_FP_ANY, fpu_reqs, valid_abi_p)
+       (fp_mode_for_abi, cpu_supports_fr0_p, determine_fpu_mode):
+       * exec/mipsfpu.h (_MIPSFPU_H_, FP_FR0):
+       * exec/test.c (print_usage, main):
+       * exec/trace.c (MAX_TRACEES, aarch64_set_regs, read_memory)
+       (user_alloca, user_copy, remove_tracee, handle_clone)
+       (syscall_trap_p, handle_exec, process_system_call, tracing_execve)
+       (after_fork, find_tracee, exec_waitpid, exec_init): New files.
+       * java/Makefile.in (CROSS_EXEC_BINS): Add exec1 and loader.
+       ($(CROSS_EXEC_BINS) &): New target.
+
+2023-04-29  Po Lu  <luangruo@yahoo.com>
+
+       * build-aux/ndk-build-helper.mk (TARGET_ARCH): Define variable.
+
+       * configure.ac (ENABLE_CHECKING, CHECK_STRUCTS)
+       (GC_CHECK_STRING_OVERRUN, GC_CHECK_STRING_FREE_LIST, GLYPH_DEBUG)
+       (GC_CHECK_STRING_BYTES): Enable checking correctly on Android.
+
+       * java/README: Fix typos.
+
+       * m4/ndk-build.m4 (ndk_run_test): Pass target arch.
+
+       * src/android.c (android_get_content_name, android_close)
+       (android_fclose, android_check_string): Fix various typos caught
+       by checking.
+
+       * src/charset.c (load_charset_map_from_file): Call emacs_fclose,
+       not fclose.
+
+       * src/image.c (image_set_transform): Fix thinko.
+       (png_load_body, jpeg_load_body, gif_load): Call emacs_fclose, not
+       fclose.  Use open instead of fdopen.
+
+       * src/xfaces.c (Fx_load_color_file): Likewise.
+
+2023-04-27  Po Lu  <luangruo@yahoo.com>
+
+       * src/image.c (image_create_bitmap_from_data): Fix typo in
+       preprocessor conditionals.
+
+       * doc/emacs/android.texi (Android File System, Android Windowing):
+       Make Emacs manual more portable.
+
+       * doc/lispref/commands.texi (Misc Events):
+       * doc/lispref/frames.texi (Accessing Selections, X Selections):
+       Fix pieces of the Info manual.
+
+2023-04-26  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/play/doctor.el (text-conversion-style, doctor-mode):
+       * lisp/play/dunnet.el (text-conversion-style, dun-mode): Set
+       `text-conversion-style' to `action'.
+
+2023-04-13  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Fonts): Update documentation.
+
+       * doc/lispref/frames.texi (Accessing Selections, X Selections):
+       Fix typos.
+
+       * src/sfntfont-android.c (system_font_directories)
+       (init_sfntfont_android): Add `/product/fonts'.
+
+2023-04-08  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/elisp.texi (Top):
+       * doc/lispref/frames.texi (Frames): Add ``Accessing Selections''
+       to menu.
+       (Accessing Selections, X Selections, Other Selections): New nodes.
+
+2023-04-06  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Windowing): Update selection
+       restrictions.
+
+       * java/org/gnu/emacs/EmacsClipboard.java (EmacsClipboard): New
+       functions `getClipboardTargets' and `getClipboardData'.
+
+       * java/org/gnu/emacs/EmacsSdk11Clipboard.java
+       (EmacsSdk11Clipboard, getClipboardTargets, getClipboardData):
+       Implement these virtual functions defined in EmacsClipboard.
+
+       * java/org/gnu/emacs/EmacsSdk8Clipboard.java: Stub out new
+       functions.
+
+       * lisp/term/android-win.el (android-get-clipboard-1): Implement
+       MIME type targets.
+
+       * src/android.c (android_exception_check)
+       (android_exception_check_1, android_exception_check_2): Fix
+       punctuation in warning message.
+       (android_exception_check_nonnull_1): New function.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidselect.c (struct android_emacs_clipboard): New
+       methods.
+       (android_init_emacs_clipboard): Initialize new methods.
+       (Fandroid_get_clipboard_targets, android_xfree_inside)
+       (Fandroid_get_clipboard_data): New functions.
+       (syms_of_androidselect): Define new subrs.
+
+2023-04-04  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/subr.el (read-char-from-minibuffer): Don't use undefined
+       variable.  Reported by Robert Pluim.
+
+2023-04-03  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_normalize_vector): Don't rely on undefined
+       sign extension semantics.
+
+2023-03-30  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfntfont.c: Adjust font cache size constants.
+
+       * src/sfnt.c (GETINFO): Fix typo.
+
+       * src/sfnt.h: Fix typo.
+
+       * src/sfnt.c (sfnt_make_interpreter): New argument `fvar'.  Set
+       axis count.
+       (SCANCTRL): Implement selector bit 8.
+       (GXAXIS): New instruction.
+       (SFVTPV): Validate graphics state after changing freedom vector.
+       (sfnt_line_to_vector): Implement `original'.
+       (sfnt_move): Remove redundant division.
+       (sfnt_interpret_run): Implement distortable font related GXAXIS
+       instruction (0x91).
+       (sfnt_vary_interpreter): Set naxis and norm_coords.
+       (sfnt_make_test_interpreter, pushb_test_args, pushw_test_args)
+       (sfnt_name_instruction, main): Adjust accordingly.
+
+       * src/sfnt.h (struct sfnt_interpreter):
+       * src/sfntfont.c (sfntfont_setup_interpreter, sfntfont_open): Set
+       up distortion information.
+
+2023-03-29  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Fonts): Document distortable
+       font replacement rules.
+
+       * src/sfntfont.c (sfnt_replace_fonts_p): New function.
+       (sfnt_enum_font_1): Call it.
+
+       * src/sfnt.c (sfnt_move_x, sfnt_move_y, sfnt_move): Set N flags
+       and don't forget to set N points too.
+       (sfnt_read_avar_table): Fix sequencing problem.
+
+       * src/sfntfont.c (sfntfont_setup_interpreter): Don't create
+       interpreter for blatently broken fonts.
+       (sfntfont_open): Avoid specifying redundant blends.
+
+       * src/sfnt.c (sfnt_validate_gs): Fix validation of projection
+       vector.
+
+2023-03-28  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_vary_compound_glyph):
+       * src/sfntfont.c (sfntfont_get_glyph)
+       (sfntfont_get_glyph_outline): Avoid clobbering offset size flag
+       when varying compound glyph.
+
+       * src/sfnt.c (sfnt_vary_simple_glyph, sfnt_vary_compound_glyph):
+       Fix application of intermediate tuples.
+       * src/sfntfont.c (sfntfont_open): Set xlfd name after applying
+       distortion.
+
+       * src/sfnt.h (SFNT_ROUND_FIXED):
+       * src/sfntfont.c (sfntfont_probe_widths):
+       (sfntfont_measure_pcm): Round lbearing properly.
+       (sfnt_open_tables): Fix typos in non-HarfBuzz code.
+
+       * src/androidterm.c (android_draw_image_glyph_string): Restore
+       potentially clobbered GC clipping.
+
+       * src/sfnt.c (sfnt_large_integer_add, sfnt_multiply_divide_round)
+       (sfnt_mul_fixed_round): New functions.
+       (sfnt_build_glyph_outline): Take unscaled glyph metrics.
+       (sfnt_prepare_raster, sfnt_vary_simple_glyph)
+       (sfnt_vary_compound_glyph, sfnt_vary_interpreter): Use rounding
+       multiplication to scale deltas.
+       (main): Adjust tests.
+
+       * src/sfntfont.c (sfntfont_get_glyph_outline)
+       (sfntfont_probe_widths, sfntfont_open, sfntfont_measure_pcm)
+       (sfntfont_draw): More minor fixes to variable fonts.
+
+2023-03-27  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_normalize_blend): Don't crash when axis
+       variations are not present.
+
+       * configure.ac (HAVE_OTF_GET_VARIATION_GLYPHS): Check for
+       `hb_font_set_var_named_instance'.
+
+       * src/sfnt.c (main): Update tests.
+
+       * src/sfntfont-android.c (Fandroid_enumerate_fonts): Blacklist
+       bad font.
+
+       * src/sfntfont.c (struct sfnt_font_tables, struct sfnt_font_desc)
+       (sfnt_decode_instance_name, sfnt_weight_descriptions)
+       (sfnt_enum_font_1, sfntfont_list_1, sfntfont_desc_to_entity)
+       (sfntfont_list, struct sfntfont_get_glyph_outline_dcontext)
+       (sfntfont_get_glyph, sfntfont_get_glyph_outline)
+       (struct sfnt_font_info, sfnt_close_tables, sfnt_open_tables)
+       (sfntfont_open, sfntfont_measure_pcm, sfntfont_close)
+       (sfntfont_draw, sfntfont_begin_hb_font, syms_of_sfntfont)
+       (mark_sfntfont): Handle variable fonts correctly.
+
+       * src/sfnt.c (sfnt_build_glyph_outline): Take scale, not head
+       and pixel size.
+       (sfnt_scale_metrics_to_pixel_size): Delete function.
+       (sfnt_get_scale): New function.
+       (main): Update tests.
+
+       * src/sfnt.h: Update prototypes.
+
+       * src/sfntfont.c (struct sfnt_outline_cache)
+       (sfntfont_get_glyph_outline, struct sfnt_font_info)
+       (sfntfont_open): Save scale in font information and use it.
+       (sfntfont_measure_instructed_pcm): Delete function.
+       (sfntfont_measure_pcm): Make this the only ``measure pcm''
+       function.
+       (sfntfont_draw): Rely on sfntfont_get_glyph_outline for the scale.
+       (struct sfnt_font_tables): New structure.
+       (struct sfnt_font_desc): New field `tables'.
+       (struct sfnt_font_info): New field `desc'.
+       (sfntfont_setup_interpreter): Drop fd arguments and don't try to
+       load interpreter tables.
+       (sfnt_open_tables, sfnt_close_tables): New functions.
+       (sfnt_reference_font_tables, sfnt_dereference_font_tables): New
+       functions.
+       (sfntfont_open, sfntfont_close): Implement in terms of those
+       functions in order to share tables.
+
+2023-03-26  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_table_names): Add avar.
+       (sfnt_read_glyph): Clear distortion fields.
+       (sfnt_build_glyph_outline): Calculate the outline origin point.
+       (sfnt_prepare_raster): Apply the origin point to the X axis
+       offset.
+       (sfnt_scale_metrics_to_pixel_size): New function.
+       (sfnt_build_instructed_outline): Use instructed origin phantom
+       point to determine the outline origin.
+       (sfnt_compute_phantom_points): Apply origin and advance
+       distortion.
+       (struct sfnt_variation_axis, struct sfnt_instance)
+       (struct sfnt_fvar_table, sfnt_read_fvar_table)
+       (struct sfnt_gvar_table, sfnt_read_gvar_table)
+       (sfnt_read_avar_table, struct sfnt_blend, sfnt_init_blend)
+       (sfnt_free_blend, sfnt_normalize_blend, struct sfnt_tuple_header)
+       (struct sfnt_gvar_glyph_header, sfnt_read_packed_deltas)
+       (sfnt_compute_tuple_scale, sfnt_read_cvar_table)
+       (sfnt_infer_deltas_1, sfnt_vary_simple_glyph, sfnt_infer_deltas)
+       (sfnt_vary_glyph, sfnt_vary_compound_glyph)
+       (sfnt_vary_interpreter): New functions.  Add structs to
+       sfntfont.h.
+       (struct sfnt_test_dcontext, sfnt_test_get_glyph, main): Test
+       distortable font handling.
+
+       * src/sfnt.h (SFNT_ENABLE_HINTING, enum sfnt_table)
+       (struct sfnt_glyph, struct sfnt_glyph_outline, struct sfnt_raster)
+       (struct sfnt_default_uvs_table, struct sfnt_unicode_value_range)
+       (struct sfnt_nondefault_uvs_table, struct sfnt_uvs_mapping)
+       (struct sfnt_mapped_variation_selector_record)
+       (struct sfnt_table_offset_rec, struct sfnt_uvs_context)
+       (struct sfnt_mapped_table, struct sfnt_variation_axis)
+       (struct sfnt_instance, struct sfnt_fvar_table)
+       (struct sfnt_short_frac_correspondence)
+       (struct sfnt_short_frac_segment, struct sfnt_avar_table)
+       (struct sfnt_tuple_variation, struct sfnt_cvar_table)
+       (struct sfnt_gvar_table, struct sfnt_blend)
+       (struct sfnt_metrics_distortion): Update prototypes.
+
+       * src/sfntfont.c (sfntfont_get_glyph_outline)
+       (sfntfont_measure_pcm): Adjust calls.
+
+2023-03-24  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_table_names): Add fvar, gvar, cvar.
+       (sfnt_read_maxp_table): Call xmalloc, not malloc.
+       (sfnt_read_simple_glyph): Avoid use-after-free if simple is
+       invalid.
+       (sfnt_fill_span): Fix max coverage.
+       (sfnt_normalize_vector): Fail if magnitude is zero.
+       (sfnt_measure_distance): Fix opcode order.
+       (sfnt_dot_fix_14): Fix implementation.
+       (struct sfnt_variation_axis, struct sfnt_instance)
+       (struct sfnt_fvar_table, struct sfnt_gvar_table)
+       (sfnt_read_fvar_table, sfnt_read_gvar_table, struct sfnt_blend)
+       (sfnt_init_blend, sfnt_free_blend, sfnt_normalize_blend)
+       (struct sfnt_tuple_header, struct sfnt_gvar_glyph_header)
+       (sfnt_read_packed_points, sfnt_read_packed_deltas)
+       (sfnt_compute_tuple_scale, sfnt_infer_deltas_1, sfnt_infer_deltas)
+       (sfnt_vary_glyph): Add WIP variation glyph implementation.
+
+       * src/sfnt.h (enum sfnt_table, struct sfnt_simple_glyph):
+       Likewise.
+
+2023-03-20  Po Lu  <luangruo@yahoo.com>
+
+       * java/INSTALL: Fix typo.
+
+       * configure.ac: Add support for HarfBuzz on Android.
+
+       * java/INSTALL: Document where to get Emacs with HarfBuzz.
+
+       * lisp/subr.el (overriding-text-conversion-style, y-or-n-p):
+       Correctly set text conversion style if y-or-n-p is called inside
+       the minibuffer.
+
+       * src/sfnt.c (sfnt_read_cmap_format_8)
+       (sfnt_read_cmap_format_12): Fix typos.
+       (sfnt_read_24, sfnt_read_cmap_format_14): New function.
+       (sfnt_read_cmap_table_1, sfnt_read_cmap_table): Handle format 14
+       cmap tables.
+       (sfnt_read_default_uvs_table, sfnt_read_nondefault_uvs_table)
+       (sfnt_compare_table_offsets, sfnt_create_uvs_context)
+       (sfnt_free_uvs_context, sfnt_compare_uvs_mapping)
+       (sfnt_variation_glyph_for_char, sfnt_map_table, sfnt_unmap_table)
+       (sfnt_read_table, sfnt_test_uvs): New functions.
+       (main): Add UVS tests.
+
+       * src/sfnt.h (struct sfnt_cmap_format_14)
+       (struct sfnt_variation_selector_record)
+       (struct sfnt_default_uvs_table, struct sfnt_unicode_value_range)
+       (struct sfnt_nondefault_uvs_table, struct sfnt_uvs_mapping)
+       (struct sfnt_mapped_variation_selector_record)
+       (struct sfnt_table_offset_rec, struct sfnt_uvs_context)
+       (struct sfnt_mapped_table): New structures.  Update prototypes.
+
+       * src/sfntfont-android.c (android_sfntfont_driver): Register
+       HarfBuzz callbacks where required.
+
+       * src/sfntfont.c (sfntfont_select_cmap): Look for a format 14
+       table.  Save it in new arg FORMAT14.
+       (sfntfont_read_cmap): Adjust accordingly.
+       (struct sfnt_font_info): New field `uvs'.  New fields `hb_font',
+       `fd' and `directory'.
+       (sfntfont_open): Open uvs context.  Under HarfBuzz, don't close
+       the fd or subtable, but save them in the font info instead.
+       (sfntfont_close): Free UVS context.  Close font fd and table
+       directory and HarfBuzz font.
+       (sfntfont_draw): Handle case where s->padding_p.
+       (sfntfont_get_variation_glyphs): New function.
+       (sfntfont_unmap_blob, sfntfont_get_font_table)
+       (sfntfont_begin_hb_font): New functions.
+
+       * src/sfntfont.h: Update prototypes.
+
+       * src/textconv.c (Fset_text_conversion_style): Fix doc string.
+
+2023-03-18  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsView.java (onAttachedToWindow): Send
+       measured width and height in exposures again.
+
+       * src/androidterm.c (handle_one_android_event): Don't log expose
+       events.
+
+       * src/android.c (android_run_select_thread): New flag.  Use it
+       rather than the rc of pselect and errno to determine whether or
+       not it has been interrupted.
+       (android_handle_sigusr1): Set said flag.
+
+       * java/org/gnu/emacs/EmacsView.java (prepareForLayout): New
+       function.  Call this prior to mapping the view.
+       (onGlobalLayout): New function.  Register as global layout
+       listener.
+
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow)
+       (notifyContentRectPosition): New function.  Use specified
+       xPosition and yPosition when reporting the offsets of children of
+       the root window.
+
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+       (registerWindow): Specify activity launch bounds if necessary.
+
+       * src/androidterm.c (handle_one_android_event): Send
+       MOVE_FRAME_EVENT where necessary.
+
+2023-03-17  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidfns.c (Fx_server_vendor, Fx_server_version): New
+       functions.
+       (syms_of_androidfns): Define new functions.
+
+       * src/androidterm.c (android_set_build_fingerprint)
+       (syms_of_androidterm): Set new variable
+       Vandroid_build_manufacturer.
+
+       * src/xfns.c (Fx_server_vendor, Fx_server_version): Update doc
+       strings.
+
+       * src/fileio.c (emacs_fd_to_int): Don't define on WINDOWSNT.
+
+       * src/image.c (image_create_bitmap_from_data): Don't abort if
+       !defined HAVE_ANDROID.
+
+       * configure.ac:
+       * m4/ndk-build.m4 (ndk_INIT, ndk_LATE): Avoid AC_REQUIRE magic.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New
+       field `lastGroupId'.
+       (Item): New field `isRadio'.
+       (addItem): New arg `isRadio'.
+       (inflateMenuItems): Apply an empty radio button group if required.
+
+       * src/androidmenu.c (android_init_emacs_context_menu): Adjust
+       accordingly.
+       (android_menu_show): Likewise.
+
+       * java/org/gnu/emacs/EmacsView.java (cancelPopupMenu): Dismiss
+       context menu correctly.
+       (isOpaque): New function.
+
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java: Make
+       consumer list public.
+
+       * configure.ac: Add missing precious variable.
+
+2023-03-16  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/frame.el (android-detect-mouse):
+       * lisp/term/android-win.el (android-get-connection): Add function
+       declarations.
+
+       * configure.ac: Remove unnecessary escape.
+
+       * configure.ac (AUTO_DEPEND, ANDROID_STUBIFY, ANDROID_LDFLAGS):
+       * lib/Makefile.in (ANDROID_CFLAGS, ANDROID_BUILD_CFLAGS)
+       (ALL_CFLAGS):
+       * lib/gnulib.mk.in (AM_DEFAULT_VERBOSITY):
+       * msdos/sed1v2.inp:
+       * msdos/sedlibmk.inp:
+       * src/Makefile.in (ANDROID_OBJ, EMACS_CFLAGS): Make those
+       variables precious.  Rename ANDROID_CFLAGS substitution to
+       ANDROID_BUILD_CFLAGS.
+
+       * nt/mingw-cfg.site: Suppress build of Gnulib printf.
+
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java (queryRoots): Add
+       icon to document root.
+
+       * lisp/loadup.el (current-load-list): Set to empty load list after
+       startup.
+
+       * src/lread.c (build_load_history): Revert earlier changes.
+
+2023-03-15  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac: Improve portability.
+
+2023-03-15  Robert Pluim  <rpluim@gmail.com>
+
+       * src/fileio.c (Finsert_file_contents):
+       * src/window.c (replace_buffer_in_windows): Call Fboundp, not
+       boundp.
+
+2023-03-15  Po Lu  <luangruo@yahoo.com>
+
+       * cross/Makefile.in (lib/gnulib.mk): Edit out build-aux stuff.
+       * m4/ndk-build.m4: Also look for cross ranlib.
+
+       * src/sfntfont.c (sfntfont_close): Fix warning w/o mmap.
+
+       * lib-src/asset-directory-tool.c (main_2): Port to systems without
+       htole32.
+
+2023-03-15  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (XCONFIGURE): Disable NS.
+
+       * cross/Makefile.in (lib-src/config.h, lib/libgnu.a)
+       (src/android-emacs): Port sed invocation to Mac OS without GNU
+       sed.
+
+2023-03-15  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/commands.texi (Misc Events): Document variable
+       `disable-inhibit-text-conversion'.
+
+       * java/org/gnu/emacs/EmacsDialog.java (display1): Try an activity
+       that is certain to be focused first.
+
+       * lisp/touch-screen.el (touch-screen-track-tap)
+       (touch-screen-track-drag): Bind `disable-inhibit-text-conversion'.
+
+       * src/keyboard.c (read_key_sequence): Only disable text conversion
+       if an actual function or numeric key is found in the key sequence.
+       (syms_of_keyboard): New variable
+       `disable-inhibit-text-conversion'.
+
+       * src/lread.c (read_filtered_event): Check new variable.
+
+       * src/textconv.c (textconv_query): Remove unused label.
+
+       * nt/gnulib-cfg.mk: Omit new gnulib modules.
+
+2023-03-14  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/minibuffer.el (minibuffer-setup-on-screen-keyboard): Handle
+       cases where last-event-frame is a kbd macro.
+       * src/keyboard.c (lispy_function_keys): Remove duplicates.
+
+       * msdos/sed1v2.inp:
+       * msdos/sed3v2.inp:
+       * msdos/sedlibcf.inp:
+       * msdos/sedlibmk.inp: Update for Android port and new Gnulib
+       modules.
+
+       * java/org/gnu/emacs/EmacsWindow.java (figureChange): Detect mice
+       on up events as well.
+       (onSomeKindOfMotionEvent): Work past framework bug.
+
+       * src/androidterm.c (android_perform_conversion_query):
+       * src/textconv.c (textconv_query):
+       * src/textconv.h (TEXTCONV_SKIP_ACTIVE_REGION): Remove unused
+       code.
+
+       * doc/emacs/android.texi (Android Windowing): Document how to
+       display dialogs when Emacs is in the background.
+
+       * java/org/gnu/emacs/EmacsDialog.java (display1): Use system
+       dialogs if possible.
+
+2023-03-13  Po Lu  <luangruo@yahoo.com>
+
+       * etc/NEWS: Announce new option.
+
+       * lisp/menu-bar.el (menu-bar-close-window): New option.
+       (kill-this-buffer, kill-this-buffer-enabled-p): Adjust
+       accordingly.
+
+       * src/keyboard.c (lispy_function_keys): Add more silly keys.
+
+       * src/android.c (android_check_string, android_build_string):
+       Reduce consing when building menu bar strings.
+
+       * etc/MACHINES (Android): Update with more recent information.
+
+       * doc/emacs/android.texi (Android Startup): Document changes to
+       emacsclient wrapper.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity)
+       (startEmacsClient): Open EmacsActivity if the service is not
+       running.
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate):
+       * java/org/gnu/emacs/EmacsThread.java (run): Pass any file to open
+       to Emacs.
+
+       * lisp/term/android-win.el (handle-args-function): Implement.
+
+       * src/image.c (image_create_bitmap_from_file, image_find_image_fd)
+       (close_android_fd, slurp_file): Return and use `struct
+       android_fd_or_asset' on Android.
+       (xbm_load, xpm_load, pbm_load, png_load_body, jpeg_load_body)
+       (webp_load, svg_load): Adjust accordingly.
+
+2023-03-12  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_get_screen_width)
+       (android_get_screen_height, android_get_mm_width)
+       (android_get_mm_height, android_detect_mouse): Correctly handle
+       Java exceptions.
+
+       * src/android.c (android_check_if_event): New function.
+
+       * src/androidgui.h: Update prototypes.
+
+       * src/androidterm.c (android_event_is_for_frame): New function.
+       (android_reset_conversion): Free and unqueue all text conversion
+       events for the given frame.
+
+       * src/androidterm.c (NATIVE_NAME, JNICALL)
+       (android_build_extracted_text, android_update_selection): Use
+       0-based indices for Android buffer positions.  Also, report
+       surrounding text relative to the region, not to the cursor.
+
+       * src/textconv.c (textconv_query): Accept new values of position.
+       (really_set_composing_text): Use ephemeral last point.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (onCancel): New
+       function.
+       (displayFailureDialog): Handle dialog cancellation.
+
+       * src/sfntfont.c (sfnt_parse_languages): Look for SLNG tag if
+       DLNG is not present.
+
+       * src/androidgui.h (enum android_modifier_mask): New modifier
+       ANDROID_SUPER_MASK.
+
+       * src/androidterm.c (android_android_to_emacs_modifiers)
+       (android_emacs_to_android_modifiers): Add new modifier.
+
+       * src/androidterm.c (syms_of_androidterm): Initialize
+       Vandroid_build_fingerprint in case GC happens.
+
+       * src/emacs-module.c (module_reset_handlerlist): Fix macro
+       conflict.
+
+       * src/emacs-module.c (MODULE_HANDLE_NONLOCAL_EXIT)
+       (module_make_global_ref, module_free_global_ref)
+       (module_make_function, module_get_function_finalizer)
+       (module_set_function_finalizer, module_make_interactive)
+       (module_funcall, module_intern, module_type_of)
+       (module_extract_integer, module_make_integer, module_extract_float)
+       (module_make_float, module_copy_string_contents)
+       (module_make_string, module_make_unibyte_string)
+       (module_make_user_ptr, module_get_user_ptr, module_set_user_ptr)
+       (module_get_user_finalizer, module_set_user_finalizer)
+       (module_vec_set, module_vec_get, module_vec_size)
+       (module_process_input, module_extract_time, module_make_time)
+       (module_extract_big_integer, module_make_big_integer)
+       (module_open_channel, module_reset_handlerlist): Adjust as
+       recommended by Paul Eggert <eggert@cs.ucla.edu>.
+
+       * configure.ac: Take option `--with-shared-user-id' and give it to
+       AndroidManifest.xml.in.
+
+       * java/AndroidManifest.xml.in: Substitute that into the
+       application info.
+
+       * java/INSTALL (BUILDING WITH A SHARED USER ID): New section.
+
+2023-03-11  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (with_mailutils): Default to off on Android.
+       (HAVE_MAILUTILS, with_mailutils, ANDROID_SDK_8_OR_EARLIER)
+       (XCONFIGURE): Fix POP and mailutils configuration on Android.
+
+       * java/Makefile.in:
+       * src/callproc.c (syms_of_callproc): Avoid using built-in movemail
+       when --with-mailutils.
+
+       * src/android.c (android_resolve_handle)
+       (android_resolve_handle2): Don't perform checking done by CheckJNI
+       by default.
+       (android_copy_area): Check for errors here because CopyArea can
+       perform a lot of consing.
+       (android_define_cursor): Remove redundant code.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onContextMenuClosed):
+       Process submenu closing normally if it happens more than 300 ms
+       after a submenu item was selected.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu)
+       (onMenuItemClick, display1): Give `wasSubmenuSelected' different
+       values depending on how the submenu was selected.
+
+       * lib/gnulib.mk.in: Update from gnulib.
+
+       * configure.ac: Default modules to on.  Remove check for
+       __attribute__((cleanup)).  However, keep the new `ifavailable'
+       value for systems without dlopen.
+
+       * src/emacs-module.c (MODULE_HANDLE_NONLOCAL_EXIT): Don't rely on
+       cleanup attribute and correctly reset handlerlist upon longjmp.
+       (MODULE_INTERNAL_CLEANUP): New macro.
+       (module_make_global_ref, module_free_global_ref)
+       (module_make_function, module_get_function_finalizer)
+       (module_set_function_finalizer, module_make_interactive)
+       (module_funcall, module_intern, module_type_of)
+       (module_extract_integer, module_make_integer, module_extract_float)
+       (module_make_float, module_copy_string_contents)
+       (module_make_string, module_make_unibyte_string)
+       (module_make_user_ptr, module_get_user_ptr, module_set_user_ptr)
+       (module_get_user_finalizer, module_set_user_finalizer)
+       (module_vec_set, module_vec_get, module_vec_size)
+       (module_process_input, module_extract_time, module_make_time)
+       (module_extract_big_integer, module_make_big_integer)
+       (module_open_channel): Call MODULE_INTERNAL_CLEANUP prior to
+       returning.
+
+       * lisp/term/android-win.el (x-pointer-arrow, x-pointer-left-ptr)
+       (x-pointer-left-side, x-pointer-sb-h-double-arrow)
+       (x-pointer-sb-v-double-arrow, x-pointer-watch, x-pointer-xterm)
+       (x-pointer-invisible): New constants.
+
+       * src/androidterm.c (android_show_hourglass)
+       (android_hide_hourglass): New functions.
+       (android_toggle_visible_pointer, android_define_frame_cursor):
+       Define or don't define hourglass cursor if x->hourglass.
+       (android_redisplay_interface): Add new functions.
+
+       * src/androidterm.h (struct android_output): New field
+       `hourglass'.
+
+2023-03-10  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Windowing): Document how to pass
+       multimedia keys to the system.
+
+       * java/org/gnu/emacs/EmacsNative.java
+       (shouldForwardMultimediaButtons): New function.
+
+       * java/org/gnu/emacs/EmacsView.java (onKeyDown, onKeyMultiple)
+       (onKeyUp): Check that function.
+
+       * java/org/gnu/emacs/EmacsWindow.java (defineCursor): Handle cases
+       where cursor is NULL.
+
+       * src/android.c (NATIVE_NAME): New function.
+
+       * src/androidfns.c (syms_of_androidfns): New variable.
+
+       * src/keyboard.c (lispy_function_keys): Add volume keys.
+
+       * java/org/gnu/emacs/EmacsCursor.java: New file.
+
+       * java/org/gnu/emacs/EmacsWindow.java (defineCursor): New
+       function.
+
+       * src/android.c (struct android_emacs_cursor): New struct.
+       (android_init_emacs_cursor): New function.
+       (initEmacs): Call it.
+       (android_create_font_cursor, android_define_cursor)
+       (android_free_cursor): New functions.
+
+       * src/android.h (enum android_handle_type): Add cursor handle
+       type.
+
+       * src/androidfns.c (Fx_create_frame, android_create_tip_frame)
+       (enum mouse_cursor, struct mouse_cursor_types, mouse_cursor_types)
+       (struct mouse_cursor_data, android_set_mouse_color)
+       (syms_of_androidfns):
+
+       * src/androidgui.h (enum android_cursor_shape):
+
+       * src/androidterm.c (make_invisible_cursor)
+       (android_toggle_invisible_pointer, android_free_frame_resources)
+       (android_define_frame_cursor):
+
+       * src/androidterm.h (struct android_display_info)
+       (struct android_output): Port mouse cursor code over from X.
+
+       * java/org/gnu/emacs/EmacsNative.java: Add missing dependency.
+
+       * lisp/battery.el (battery-status-function): Don't look for /sys
+       or /proc* on Android.  Explain why.
+
+       * java/org/gnu/emacs/EmacsService.java (queryBattery19): New
+       function.
+       (queryBattery): Call it on old systems.  Also, return AC line
+       status and temperature.
+
+       * lisp/battery.el (battery-android): Implement more format
+       directives.
+
+       * src/android.c (android_query_battery): Handle new status fields.
+
+       * src/android.h (struct android_battery_state): Add `plugged' and
+       `temperature'.
+
+       * src/androidfns.c (Fandroid_query_battery): Return new fields.
+
+2023-03-09  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_destroy_handle): Handle OOM errors in
+       android_destroy_handle.
+
+       * textconv.c: Remove out-of-date comment.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onContextMenuClosed):
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu)
+       (onMenuItemClick, run):
+
+       * java/org/gnu/emacs/EmacsDialog.java (onClick, createDialog)
+       (onDismiss): Take menu event serial, and pass it along in context
+       menu events.
+
+       * java/org/gnu/emacs/EmacsNative.java (sendContextMenu): New
+       argument.
+
+       * src/android.c (sendContextMenu): Pass serial number in event.
+
+       * src/androidgui.h (struct android_menu_event): New field
+       `menu_event_serial'.
+
+       * src/androidmenu.c (android_init_emacs_dialog): Adjust method
+       declarations.
+       (android_menu_show, android_dialog_show):
+
+       * src/androidterm.c (handle_one_android_event): Expect serial in
+       context menu events.
+
+       * src/androidterm.h: Update prototypes.
+
+       * configure.ac (HAVE_WEBP): Disable WebPGetInfo check when
+       REALLY_ANDROID.
+
+       * java/debug.sh (is_root): Port to Android versions which don't
+       support `chmod +x'.
+
+       * src/android.c (android_content_name_p): Disable before API level
+       19.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (addItem): New argument
+       `tooltip'.
+
+       * src/android.c (android_build_string): Convert the text to
+       UTF-16, and create the Java string using that.
+       (android_build_jstring): Update comment.
+
+       * src/androidmenu.c (android_init_emacs_context_menu): Add String
+       argument to `addItem'.
+
+       (android_menu_show): Correctly pass help strings in regular menu
+       items.
+
+       * src/sfnt.c (_sfnt_swap16, _sfnt_swap32): Avoid reserved names.
+
+       * src/android.c (android_set_input_focus): Don't call method on
+       window using service class.
+
+       * src/sfnt.c (ODD): Use PUSH_UNCHECKED.
+
+2023-03-08  Po Lu  <luangruo@yahoo.com>
+
+       * src/fileio.c (Fcopy_file): On Android, ignore ENOSYS and ENOTSUP
+       when restoring file times, as many old kernel versions encountered
+       on Android devices omit support for the relevant system calls.
+
+       * src/androidterm.c (android_build_extracted_text): Return NULL if
+       text class not initialized.
+       (android_update_selection): Check that EXTRACTED is not NULL.
+
+       * doc/emacs/android.texi (Android File System): Document what
+       `temp~unlinked' means in the temporary files directory.
+
+       * java/org/gnu/emacs/EmacsService.java (updateExtractedText): New
+       function.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Ask
+       the input method nicely to not display the extracted text UI.
+
+       * src/android.c (struct android_emacs_service): New method
+       `updateExtractedText'.
+       (android_hack_asset_fd_fallback): Improve naming convention.  Fix
+       typo.
+       (android_init_emacs_service): Add new method.
+       (android_update_extracted_text): New function.
+       (android_open_asset): Fix typo.
+
+       * src/androidgui.h: Update prototypes.
+
+       * src/androidterm.c (struct android_get_extracted_text_context):
+       New field `flags'.
+       (android_get_extracted_text): Set flags on the frame's output
+       data.
+       (android_build_extracted_text): New function.
+       (getExtractedText): Move out class structures.
+       (android_update_selection): Send updates to extracted text if the
+       input method asked for them.
+       (android_reset_conversion): Clear extracted text flags.
+
+       * src/androidterm.h (struct android_output): New fields for
+       storing extracted text data.
+
+       * src/sfnt.c (sfnt_read_cmap_table): Don't allocate too big data.
+       Also, free elements of (*data), not offsets into data itself.
+
+2023-03-07  Po Lu  <luangruo@yahoo.com>
+
+       * java/Makefile.in (install_temp/assets/build_info): New rule.
+       (emacs.apk-in): Depend on that file.
+
+       * lisp/version.el (android-read-build-system)
+       (android-read-build-time): New functions.
+       (emacs-build-system, emacs-build-time): Use those functions on
+       Android, as dumping is done after installation on Android.
+
+       * src/fileio.c (Finsert_file_contents):
+
+       * src/window.c (replace_buffer_in_windows): Don't call functions
+       if they are not defined, which can happen during loadup.
+
+       * java/org/gnu/emacs/EmacsWindow.java (onSomeKindOfMotionEvent):
+       Dismiss splurious LeaveNotify events from button presses.
+
+       * src/android.c (android_change_window_attributes)
+       (android_change_gc, android_set_clip_rectangles)
+       (android_reparent_window, android_clear_window)
+       (android_map_window, android_unmap_window, android_resize_window)
+       (android_move_window, android_swap_buffers)
+       (android_fill_rectangle, android_copy_area)
+       (android_fill_polygon, android_draw_rectangle, android_draw_point)
+       (android_draw_line, android_clear_area, android_bell)
+       (android_set_input_focus, android_raise_window)
+       (android_lower_window, android_set_dont_focus_on_map)
+       (android_set_dont_accept_focus, android_get_keysym_name)
+       (android_toggle_on_screen_keyboard, android_restart_emacs)
+       (android_display_toast, android_update_ic, android_reset_ic)
+       (android_set_fullscreen): Optimize by specifying the class
+       explicitly when calling a method.
+
+       * src/lread.c (lread_fd, file_tell, infile, skip_dyn_bytes)
+       (skip_dyn_eof, readbyte_from_stdio, safe_to_load_version)
+       (close_infile_unwind, close_file_unwind_android_fd): New function.
+       (Fload, Flocate_file_internal, openp): New argument PLATFORM.  All
+       callers changed.
+       (skip_lazy_string): Add optimized versions of various functions
+       for accessing Android assets.
+
+2023-03-06  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function
+       requestSelectionUpdate.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection):
+       Call it instead of losing if getting the current selection fails.
+
+       * src/android-asset.h (AAsset_seek): Define stub.
+
+       * src/android.c (android_open): Take mode_t MODE instead of int.
+       (android_open_asset, android_close_asset, android_asset_read_quit)
+       (android_asset_read, android_asset_lseek, android_asset_fstat):
+       New functions.
+
+       * src/android.h (struct android_fd_or_asset): Update prototypes.
+
+       * src/androidgui.h (enum android_ime_operation): Add new operation
+       to update the selection position.
+
+       * src/androidterm.c (android_handle_ime_event): Handle new
+       operation.
+       (requestSelectionUpdate): New function.
+
+       * src/fileio.c (close_file_unwind_emacs_fd): New function.
+       (Fcopy_file, union read_non_regular, read_non_regular)
+       (Finsert_file_contents): Use optimized codepath to insert Android
+       asset files.
+
+       * src/frame.h (enum text_conversion_operation): New operation.
+
+       * src/textconv.c (really_request_point_update)
+       (handle_pending_conversion_events_1, request_point_update): New
+       functions.
+
+       * src/textconv.h: Update prototypes.
+
+       * src/conf_post.h: Avoid macro redeclaration.
+
+       * java/org/gnu/emacs/EmacsService.java (sync): Delete function.
+
+       * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Erase
+       with window background.
+       (onDetachedFromWindow): Only recycle bitmap if non-NULL.
+
+       * java/org/gnu/emacs/EmacsWindow.java (background): New field.
+       (changeWindowBackground): Set it.
+
+       * src/android.c (struct android_emacs_service): Remove `sync'.
+       (android_init_emacs_service): Likewise.
+       (android_sync): Delete function.
+
+       * src/androidfns.c (android_create_tip_frame): Set frame
+       background color correctly.
+       (Fx_show_tip): Make the tip frame visible.
+
+       * src/androidgui.h: Update prototypes.
+
+       * src/androidterm.c (handle_one_android_event): Handle tooltip
+       movement correctly.
+
+2023-03-05  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate):
+       * java/org/gnu/emacs/EmacsContextMenu.java:
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java (getMimeType):
+       * java/org/gnu/emacs/EmacsDrawLine.java (perform):
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform):
+       * java/org/gnu/emacs/EmacsFillPolygon.java:
+       * java/org/gnu/emacs/EmacsFontDriver.java:
+       * java/org/gnu/emacs/EmacsHandleObject.java:
+       * java/org/gnu/emacs/EmacsInputConnection.java:
+       * java/org/gnu/emacs/EmacsMultitaskActivity.java:
+       * java/org/gnu/emacs/EmacsNative.java:
+       * java/org/gnu/emacs/EmacsNoninteractive.java (main):
+       * java/org/gnu/emacs/EmacsOpenActivity.java (startEmacsClient):
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java:
+       * java/org/gnu/emacs/EmacsSdk8Clipboard.java:
+       * java/org/gnu/emacs/EmacsService.java (onCreate):
+       * java/org/gnu/emacs/EmacsView.java (onLayout):
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow):
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java: Remove
+       redundant includes.  Reorganize some functions, remove duplicate
+       `getLibDir' functions, and remove unused local variables.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (onCreate): Don't set
+       the style here.
+
+       * java/res/values-v11/style.xml:
+       * java/res/values-v14/style.xml:
+       * java/res/values-v29/style.xml:
+       * java/res/values/style.xml: Define styles for the emacsclient
+       wrapper.
+
+       * src/keyboard.c (read_key_sequence): Don't disable text
+       conversion if use_mouse_menu or if a menu bar prefix key is being
+       displayed.
+
+       * etc/PROBLEMS: Document problem with default monospace font.
+
+       * src/fileio.c (check_mutable_filename): Check if the file is a
+       constituent of /content as well.
+       (Fcopy_file, Fdelete_directory_internal, Fdelete_file)
+       (Frename_file, Fadd_name_to_file, Fmake_symbolic_link)
+       (Fset_file_modes, Fset_file_times, Ffile_newer_than_file_p)
+       (write_region): Adjust accordingly.
+       (Fset_visited_file_modtime): Remove unnecessary restriction.
+
+       * src/filelock.c (make_lock_file_name): Don't interlock files
+       under /assets and /content.
+
+       * src/inotify.c (Finotify_add_watch): Fix typo.
+
+       * cross/Makefile.in (config.status): Depend on
+       top_builddir/config.status instead.
+
+       * configure.ac: Fix another typo.
+
+       * cross/Makefile.in (builddir): Define.
+
+       * cross/README: Update.
+
+       * cross/lib: Delete.  Make configure generate it instead.
+
+       * .gitignore: Simplify cross/lib rule.
+
+       * admin/merge-gnulib (avoided_flags): Stop copying to cross/lib.
+
+       * configure.ac: Link gnulib source and header files to cross/lib.
+
+       * cross/Makefile.in (LIB_SRCDIR): Make relative to builddir.
+       (maintainer-clean): Merge with distclean.  Remove links created
+       by configure.
+
+2023-03-04  Po Lu  <luangruo@yahoo.com>
+
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk: Specify right ELF
+       format for 64 bit executables.
+
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk: Ensure nasm
+       generates ELF objects.
+
+       * src/sfnt.c (sfnt_fill_span): Specifically handle spans that span
+       a single pixel by computing the coverage in the center.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field
+       `lastClosedMenu'.
+       (onContextMenuClosed): Don't send event if a menu is closed twice
+       in a row.  Also, clear wasSubmenuSelected immediately.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java: Display submenus
+       manually in Android 6.0 and earlier.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateContextMenu)
+       (popupMenu): Adjust accordingly.
+
+       * configure.ac: Check for __ctype_get_mb_cur_max.  Then see if
+       MB_CUR_MAX is defined to it, and define REPLACEMENT_MB_CUR_MAX if
+       so and it does not link.
+
+       * java/INSTALL: Update documentation.
+
+       * src/conf_post.h (MB_CUR_MAX): Define replacement if
+       necessary.
+
+       * m4/ndk-build.m4 (ndk_INIT): Fix typo.
+
+       * configure.ac: Call ndk_LATE after gl_EARLY.
+
+       * cross/ndk-build/Makefile.in (NDK_BUILD_CXX): New variable.
+
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk: Use it.
+
+       * java/INSTALL: Describe how to build C++ dependencies.
+
+       * m4/ndk-build.m4 (ndk_LATE): New macro.
+       (ndk_INIT): Try to find a suitable C++ compiler.
+       (ndk_CHECK_MODULES): Make sure the C++ compiler works before
+       allowing C++ dependencies.
+
+       * cross/ndk-build/Makefile.in (NDK_BUILD_CFLAGS_CXX): New
+       variable.
+
+       * cross/ndk-build/ndk-build-shared-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1)))):
+       * cross/ndk-build/ndk-build-static-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1)))): Use it to
+       build C++ code.
+
+2023-03-03  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (ANDROID_SDK_8_OR_EARLIER): Pass through
+       `--with-ndk-cxx-shared'.
+
+       * m4/ndk-build.m4 (ndk_INIT): Fix quoting of $CC.
+
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk: Include
+       ndk-resolve.mk in srcdir.
+
+       * cross/ndk-build/README: Update accordingly.
+
+       * build-aux/ndk-build-helper.mk: Define in terms of BUILD_AUXDIR.
+
+       * m4/ndk-build.m4 (ndk_INIT): Find right build-aux directory.
+       Remove uses of unportable shell constructs.
+
+       * java/org/gnu/emacs/EmacsService.java (checkContentUri): Improve
+       debug output.
+
+       * lisp/files.el (basic-save-buffer): Check whether or not file
+       itself exists before checking for the existence of the directory
+       containing it.
+
+       * src/android.c (android_open): Don't forget to set errno after
+       open_content_uri fails.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate): Add view tree
+       observer.
+       (onGlobalLayout): Sync fullscreen state.
+       (syncFullscreenWith): Improve visibility flag setting.
+
+       * src/textconv.c (select_window): New function.
+       (textconv_query, restore_selected_window, really_commit_text)
+       (really_set_composing_text, really_set_composing_region)
+       (really_delete_surrounding_text, really_set_point_and_mark)
+       (get_extracted_text): Call it instead of Fselect_window to avoid
+       selecting the mini window if it is no longer active.
+
+2023-03-02  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/input.texi (On-Screen Keyboards): Fix indexing.
+
+       * INSTALL: Document where to find Android installation
+       instructions.
+
+       * configure.ac (CHECK_LISP_OBJECT_TYPE): Pacify
+       -Wsuggest-attribute=noreturn only on Android.
+
+       * cross/ndk-build/README: New file.
+
+       * doc/emacs/android.texi (Android):
+       * doc/emacs/emacs.texi (Top):
+       * doc/emacs/input.texi (Other Input Devices): Untabify menus.
+
+       * etc/NEWS: Move INSTALL.android to java/INSTALL.
+
+       * java/INSTALL: New file.
+
+       * java/README:
+
+       * src/coding.c (from_unicode_buffer): Make Android specific code
+       only build on Android.
+
+       * INSTALL.android: Remove file.
+
+       * configure.ac: Make cross/* and related directories.
+
+       * cross/Makefile.in (src/verbose.mk, lib/libgnu.a)
+       (src/config.h): Stop making directories here.
+       (lib-src/config.h): New config.h rule.
+       ($(LIBSRC_BINARIES)): Add it.
+       (clean): Don't remove CLEAN_SUBDIRS, but clean inside.
+
+       * src/android.c (android_alloc_id): Return correct values upon
+       wraparound.
+
+       * java/Makefile.in ($(CLASS_FILES) &): Touch all class files, even
+       those the Java compiler elected not to rebuild.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onWindowFocusChanged):
+       Restore fullscreen state here.
+       (onResume): And not here.
+
+       * doc/emacs/android.texi (Android):
+       * doc/emacs/emacs.texi (Top, GNU Free Documentation License):
+       Rearrange menu and sectioning.
+
+       * doc/emacs/android.texi (Android Windowing): Reword
+       documentation.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity):
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu):
+       * java/org/gnu/emacs/EmacsFontDriver.java (EmacsFontDriver):
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java
+       (EmacsSdk7FontDriver):
+       * java/org/gnu/emacs/EmacsService.java (queryBattery):
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): Make
+       functions final and classes static where possible.  This
+       subsequently brings a small speed-up as the JVM can optimize
+       vtable dispatch away at run-time.
+
+       * src/android.c (struct android_emacs_service): New method
+       `display_toast'.
+       (android_init_emacs_service): Load new method.
+       (android_display_toast): New function.
+
+       * src/android.h (android_display_toast): Export that new function.
+
+       * src/androidfns.c (Fandroid_detect_mouse):
+       * src/androidselect.c (Fandroid_clipboard_owner_p)
+       (Fandroid_set_clipboard, Fandroid_get_clipboard)
+       (Fandroid_browse_url): Prevent crashes when called from
+       libandroid-emacs.so, where the Emacs service object is not
+       present.
+
+       * src/androidterm.c (handle_one_android_event): Fix out of date
+       commentary.
+
+2023-03-01  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (JAVA_PUSH_LINT): Push to WARN_JAVAFLAGS instead of
+       JAVAFLAGS.
+       (cross/lib): Always AS_MKDIR_P.
+
+       * cross/Makefile.in (srcdir): New variable.
+       (LIB_SRCDIR): Take realpath relative to srcdir, not.
+       (src/verbose.mk): Depend on verbose.mk.android in srcdir.
+       (lib/Makefile): Edit srcdir and VPATH to LIB_SRCDIR.
+       (src/Makefile): Edit -I$$(top_srcdir) to -I../$(srcdir)/lib,
+       instead of ommitting it.
+       (clean): Allow ndk-build clean to fail.
+
+       * java/Makefile.in (builddir): New variable.
+       (WARN_JAVAFLAGS): Likewise.
+       (JAVAFLAGS): Define in terms of WARN_JAVAFLAGS.
+       (SIGN_EMACS, SIGN_EMACS_V2): Use emacs.keystore relative to
+       srcdir.  Allow inclusion of ndk-build.mk to fail.
+       (install_temp, emacs.apk-in)
+       (../config.status): Depend relative to top_srcdir.
+       (AndroidManifest.xml, $(APK_NAME)): Likewise.
+       (RESOURCE_FILE, CLASS_FILES, classes.dex): Output class files to
+       $(srcdir); these are arch independents, so this is okay.
+
+2023-03-01  Po Lu  <luangruo@yahoo.com>
+
+       * cross/Makefile.in: Remove outdated comment.
+
+       * src/Makefile.in (.PHONY): Clean android-emacs and libemacs.so,
+       not emacs.so and aemacs.
+
+       * doc/emacs/android.texi (Android File System): Document new
+       behavior of starting a subprocess from /assets.
+
+       * java/org/gnu/emacs/EmacsWindow.java (onSomeKindOfMotionEvent):
+       Don't use isFromSource where not present.
+
+       * src/androidterm.c (android_scroll_run): Avoid undefined behavior
+       writing to bitfields.
+
+       * src/callproc.c (get_current_directory): When trying to run a
+       subprocess inside /assets, run it from the home directory instead.
+
+       * java/AndroidManifest.xml.in: Specify @style/EmacsStyle.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate): Stop setting
+       the theme here.
+
+       * java/res/values-v11/style.xml:
+       * java/res/values-v14/style.xml:
+       * java/res/values-v29/style.xml:
+       * java/res/values/style.xml: Extract style resources into
+       res/values.
+
+       * java/Makefile.in (ETAGS, clean): New rules to generate tags.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity):
+       * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication):
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu):
+       * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea):
+       * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog):
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java
+       (EmacsDocumentsProvider):
+       * java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine):
+       * java/org/gnu/emacs/EmacsDrawPoint.java (EmacsDrawPoint):
+       * java/org/gnu/emacs/EmacsDrawRectangle.java
+       (EmacsDrawRectangle):
+       * java/org/gnu/emacs/EmacsFillPolygon.java (EmacsFillPolygon):
+       * java/org/gnu/emacs/EmacsFillRectangle.java
+       (EmacsFillRectangle):
+       * java/org/gnu/emacs/EmacsGC.java (EmacsGC):
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection):
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative):
+       * java/org/gnu/emacs/EmacsNoninteractive.java
+       (EmacsNoninteractive):
+       * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity):
+       * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap):
+       * java/org/gnu/emacs/EmacsPreferencesActivity.java
+       (EmacsPreferencesActivity):
+       * java/org/gnu/emacs/EmacsSdk11Clipboard.java
+       (EmacsSdk11Clipboard):
+       * java/org/gnu/emacs/EmacsSdk23FontDriver.java
+       (EmacsSdk23FontDriver):
+       * java/org/gnu/emacs/EmacsSdk8Clipboard.java
+       (EmacsSdk8Clipboard):
+       * java/org/gnu/emacs/EmacsService.java (EmacsService):
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
+       * java/org/gnu/emacs/EmacsView.java (EmacsView):
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow):
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+       (EmacsWindowAttachmentManager): Make classes final where
+       appropriate.  The Java virtual machine is capable of removing
+       vtable dispatch when a virtual function call is being performed on
+       an instance of a final class.
+
+       * src/android.c (android_query_tree, android_get_geometry)
+       (android_translate_coordinates, android_query_battery): Correctly
+       verify the results of calls to JNI Get<Type>ArrayElements
+       functions.
+       (android_exception_check_nonnull): New function.
+
+       * src/androidselect.c (Fandroid_get_clipboard): Also check the
+       return values of calls to JNI array extraction functions.
+
+2023-02-28  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (main):
+
+       * src/sfntfont.c (sfntfont_get_glyph_outline): Remove outdated
+       commentary.
+
+2023-02-26  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (struct android_emacs_window): New methods.
+       (android_init_emacs_window): Add new methods.
+       (android_lookup_method): Delete now-unused function.
+       (android_change_window_attributes, android_reparent_window)
+       (android_map_window, android_unmap_window, android_resize_window)
+       (android_move_window, android_set_input_focus)
+       (android_raise_window, android_lower_window, android_get_geometry)
+       (android_translate_coordinates, android_set_dont_focus_on_map)
+       (android_set_dont_accept_focus): Don't look up the class and
+       method each time when calling a function; that's just wasteful.
+
+       * cross/lib/unistd.in.h:
+       * lib/gnulib.mk.in:
+       * m4/gnulib-comp.m4: Update from gnulib.
+
+       * doc/lispref/commands.texi (Misc Events): Update documentation.
+
+       * java/org/gnu/emacs/EmacsService.java (onStartCommand): Improve
+       notification message.
+
+       * src/android.c (android_hack_asset_fd): Detect if ashmem is
+       available dynamically.
+       (android_detect_ashmem): New function.
+
+       * src/textconv.c (record_buffer_change): Use markers to
+       represent BEG and END instead.
+       (syms_of_textconv): Update doc string.
+
+2023-02-25  Po Lu  <luangruo@yahoo.com>
+
+       * java/debug.sh (is_root): Fix tee detection again for old systems
+       which don't return exit codes from adb shell.
+
+       * src/android.c (android_run_select_thread, initEmacs):
+       * src/android.h:
+       * src/androidterm.c: Apply stack alignment to all JNICALL
+       functions.
+
+       * src/android.c (android_open): Clean up unused variables.
+
+       * java/org/gnu/emacs/EmacsNoninteractive.java (main): Port to
+       Android 2.2.
+
+       * src/android-asset.h (AAsset_openFileDescriptor): Delete stub
+       function.
+
+       * src/android.c (android_check_compressed_file): Delete function.
+       (android_open): Stop trying to find compressed files or to use the
+       system provided file descriptor.  Explain why.
+
+       * doc/emacs/android.texi (Android Startup, Android File System)
+       (Android Environment, Android Windowing, Android Troubleshooting):
+       Improve documentation; fix typos.
+
+       * doc/lispref/commands.texi (Misc Events): Likewise.
+
+       * java/org/gnu/emacs/EmacsService.java (queryBattery): New
+       function.
+
+       * lisp/battery.el (battery-status-function): Set appropriately
+       for Android.
+       (battery-android): New function.
+
+       * src/android.c (struct android_emacs_service): New method
+       `query_battery'.
+       (android_check_content_access): Improve exception checking.
+       (android_init_emacs_service): Look up new method.
+       (android_destroy_handle, android_create_window)
+       (android_init_android_rect_class, android_init_emacs_gc_class)
+       (android_set_clip_rectangles)
+       (android_create_pixmap_from_bitmap_data, android_fill_polygon)
+       (android_get_image, android_put_image, android_bell)
+       (android_set_input_focus, android_raise_window)
+       (android_lower_window, android_query_tree, android_get_geometry)
+       (android_translate_coordinates, android_wc_lookup_string)
+       (android_damage_window, android_build_string)
+       (android_build_jstring, android_exception_check_1)
+       (android_exception_check_2): New functions.
+       (android_browse_url): Improve exception handling.  Always use
+       android_exception_check and don't leak local refs.
+       (android_query_battery): New function.
+
+       * src/android.h (struct android_battery_state): New struct.
+
+       * src/androidfns.c (Fandroid_query_battery, syms_of_androidfns):
+       New functions.
+
+       * src/androidfont.c (androidfont_from_lisp, DO_SYMBOL_FIELD)
+       (DO_CARDINAL_FIELD, androidfont_list, androidfont_match)
+       (androidfont_draw, androidfont_open_font)
+       (androidfont_close_font):
+       * src/androidselect.c (Fandroid_set_clipboard)
+       (Fandroid_get_clipboard):
+       * src/sfnt.c (sfnt_map_glyf_table):
+       * src/sfntfont.c (sfntfont_free_outline_cache)
+       (sfntfont_free_raster_cache, sfntfont_close): Allow calling font
+       close functions twice.
+
+2023-02-24  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (JAVA_PUSH_LINT): New macro.
+       (JAVAFLAGS): New variable.  Check for various lint flags and
+       macros and enable them.
+
+       * java/Makefile.in (ANDROID_ABI):
+
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java: Remove compiler
+       warning.
+
+       * lisp/frame.el (display-symbol-keys-p):
+       * lisp/simple.el (normal-erase-is-backspace-setup-frame): Return
+       appropriate values on Android.
+
+       * src/inotify.c (Finotify_add_watch): Handle asset files by
+       returning nil.
+
+       * src/keyboard.c (lispy_function_keys): Add missing delete key.
+
+2023-02-23  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/loadup.el: Update commentary.
+
+       * src/androidterm.c (syms_of_androidterm): Define
+       Vx_toolkit_scroll_bars.
+
+       * src/xterm.c (syms_of_xterm): Update doc string.
+
+       * INSTALL.android:
+       * build-aux/ndk-build-helper-1.mk:
+       (NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES)
+       (NDK_CXX_FLAG_$(LOCAL_MODULE)):
+       * build-aux/ndk-build-helper-2.mk:
+       (NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES)
+       (NDK_CXX_FLAG_$(LOCAL_MODULE)):
+       * cross/ndk-build/ndk-build-shared-library.mk (objname)
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1))))
+       (ALL_OBJECT_FILES$(LOCAL_MODULE)):
+       * cross/ndk-build/ndk-build-static-library.mk (objname)
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1))))
+       (ALL_OBJECT_FILES$(LOCAL_MODULE)):
+       (ALL_SOURCE_FILES): Update ImageMagick build instructions and C++
+       module detection.
+
+       * src/android.c (android_run_select_thread): Fix typos.
+       (android_run_select_thread): Lock select_mutex before signalling
+       condition variable.
+       (android_select): Unlock event queue mutex prior to waiting for
+       it.
+
+2023-02-22  Po Lu  <luangruo@yahoo.com>
+
+       * cross/ndk-build/ndk-build-shared-library.mk: Fix typo.
+
+       * src/image.c (imagemagick_load_image): Check HAVE_DECL_xxx.
+
+       * INSTALL.android: Document ImageMagick and caveats.
+
+       * build-aux/ndk-build-helper-1.mk (NDK_SO_NAMES):
+       * build-aux/ndk-build-helper-2.mk (NDK_A_NAMES):
+       * build-aux/ndk-build-helper.mk (TARGET_ARCH_ABI): Define
+       architecture and don't respect explicitly specified library names.
+       * configure.ac: Enable ImageMagick and lcms2 on Android.
+       * cross/ndk-build/ndk-build-shared-library.mk (objname)
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1))))
+       (ALL_OBJECT_FILES$(LOCAL_MODULE)):
+       * cross/ndk-build/ndk-build-static-library.mk (objname)
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1))))
+       (NDK_CFLAGS, ALL_SOURCE_FILES): Handle source files whose names
+       begin with $(LOCAL_PATH).
+
+       * cross/ndk-build/ndk-clear-vars.mk: Don't undefine; clear
+       variables instead.
+
+       * m4/ndk-build.m4 (ndk_SEARCH_MODULE): Redirect make stderr to
+       config.log.
+
+       * src/androidmenu.c (android_menu_show): Fix typo.
+
+       * doc/emacs/input.texi (On-Screen Keyboards): Document changes to
+       text conversion.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java (getExtractedText):
+       * src/keyboard.c (read_key_sequence): Disable text conversion
+       after reading prefix key.
+
+       * src/textconv.c (get_extracted_text): Fix returned value when
+       request length is zero.
+
+       * configure.ac: Per title.
+
+       * INSTALL.android: Port to MIPS.
+
+       * configure.ac (modules): Default to ifavailable.  Write actual
+       test for __attribute__((cleanup)).
+
+       * m4/ndk-build.m4: Recognize mips and mips64.
+
+       * src/emacs-module.c: Remove broken HAS_ATTRIBUTE test.
+
+2023-02-21  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (addSubmenu)
+       (inflateMenuItems): Handle tooltips correctly.
+
+       * src/android.c (android_scan_directory_tree): Fix limit
+       generation for root directory.
+
+       * src/androidmenu.c (android_init_emacs_context_menu)
+       (android_menu_show): Implement menu item help text on Android 8.0
+       and later.
+
+       * admin/merge-gnulib (GNULIB_MODULES): Add gnulib module stpncpy.
+
+       * m4, lib: Update from Gnulib.
+
+       * src/android.c: Include string.h.
+
+       * doc/emacs/android.texi (Android Startup): Document `content'
+       special directory.
+
+       * java/debug.sh (is_root): Improve /bin/tee detection.
+
+       * java/org/gnu/emacs/EmacsNative.java (dup): New function.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (checkReadableOrCopy)
+       (onCreate): Create content directory names when the file is not
+       readable.
+
+       * java/org/gnu/emacs/EmacsService.java (openContentUri)
+       (checkContentUri): New functions.
+
+       * src/android.c (struct android_emacs_service): New methods.
+       (android_content_name_p, android_get_content_name)
+       (android_check_content_access): New function.
+       (android_fstatat, android_open): Implement opening content URIs.
+       (dup): Export to Java.
+       (android_init_emacs_service): Initialize new methods.
+       (android_faccessat): Implement content file names.
+
+2023-02-20  Po Lu  <luangruo@yahoo.com>
+
+       * INSTALL.android: Explain where to get tree-sitter.
+
+       * configure.ac: Add support for dynamic modules and tree-sitter.
+
+       * doc/emacs/android.texi (Android Windowing):
+
+       * java/org/gnu/emacs/EmacsSdk11Clipboard.java
+       (ownsClipboard): Enhance the treatment of the clipboard and the
+       documentation addressing that subject.
+
+2023-02-20  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidfont.c (androidfont_list_family): Don't
+       unconditionally initialize the Android font driver.
+
+       * src/fontset.c (fontset_find_font): Add compatibility test to
+       registry strangeness case.
+
+       * src/sfnt.c (sfnt_read_cmap_table): Don't read subtable data if
+       DATA is NULL.
+
+       * src/sfntfont.c (struct sfnt_font_desc): New field `registry'.
+       (sfnt_registry_for_subtable): New function.
+       (sfntfont_identify_cmap): Move above sfnt_grok_registry.
+       (sfnt_grok_registry): New function.
+       (sfnt_enum_font_1): Call it.
+       (sfntfont_registries_compatible_p): New function.
+       (sfntfont_list_1): Check registry compatibility.
+       (sfntfont_registry_for_desc): New function.
+       (mark_sfntfont): Mark desc->registry.
+
+       * java/Makefile.in ($(CLASS_FILES)): Depend on the Java compiler's
+       internal dependency tracking.
+
+       * src/fontset.c (fontset_find_font): Work around TrueType
+       performance problem.
+
+       * cross/Makefile.in (src/libemacs.so): Depend on libgnu.a.
+
+       * cross/ndk-build/ndk-build.mk.in (NDK_BUILD_MODULES)
+       (NDK_BUILD_SHARED, NDK_BUILD_STATIC): Define group rule to build
+       all files so that they are built within one make process.
+
+       * java/Makefile.in: Reorganize cross compilation and make sure
+       there is only one make subprocess for each subdirectory of cross.
+
+       * cross/Makefile.in (.PHONY):
+       * java/Makefile.in (.PHONY):
+       * src/Makefile.in (libemacs.so): Avoid calling ndk-build from two
+       places at once.  Build android-emacs separately from libemacs.so.
+
+       * cross/Makefile.in ($(top_builddir)/lib/libgnu.a):
+       * java/Makefile.in (CROSS_LIBS): Explicitly depend on Gnulib to
+       prevent it from being built at the same time from different jobs.
+
+       * src/sfntfont.c (sfntfont_close): Don't unlink font if mmap is
+       not available.
+
+       * INSTALL.android: Say where building Emacs is supported.
+
+       * doc/emacs/android.texi (Android Startup): Describe how to
+       connect via ADB.
+
+       * java/org/gnu/emacs/EmacsNative.java (getSelection): Return array
+       of ints.
+
+       * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection):
+       Adjust accordingly.
+
+       * src/androidterm.c (struct android_get_selection_context): New
+       field `mark'.
+       (android_get_selection): Set the mark field as appropriate.
+       (getSelection): Adjust accordingly.
+
+       * lisp/play/gamegrid.el (gamegrid-setup-default-font): Clamp font
+       size at eight.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (checkReadableOrCopy):
+       New function.
+       (onCreate): If the file specified is not readable from C, read it
+       into a temporary file and ask Emacs to open that.
+
+       * doc/emacs/android.texi (Android Windowing): Document what new
+       frame parameters are now supported.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field
+       `isFullscreen'.
+       (detachWindow, attachWindow): Sync fullscreen state.
+       (onWindowFocusChanged): Add more logging.
+       (onResume): Restore previous fullscreen state.
+       (syncFullscreen): New function.
+
+       * java/org/gnu/emacs/EmacsWindow.java (setFullscreen): New
+       function.
+
+       * src/android.c (struct android_emacs_window): Add new method.
+       (android_init_emacs_window): Look up new method.
+       (android_set_fullscreen): New function.
+
+       * src/androidgui.h:
+       * src/androidterm.c (android_fullscreen_hook): Implement
+       accordingly.
+
+       * lisp/subr.el (overriding-text-conversion-style, y-or-n-p):
+       Disable text conversion when reading from minibuffer.
+
+       * src/androidfns.c (android_make_monitor_attribute_list): New
+       function.
+       (Fandroid_display_monitor_attributes_list): Call it to set
+       monitor_frames, which avoids a NULL pointer dereference.
+       Reported by Angelo Graziosi <angelo.g0@libero.it>.
+
+2023-02-18  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/loadup.el: Fix merge typos.
+
+       * doc/emacs/input.texi (On-Screen Keyboards): Document
+       `touch-screen-always-display'.
+
+       * doc/lispref/commands.texi (Misc Events): Improve documentation
+       of text conversion events.
+
+       * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog, display1):
+       Reorder buttons to make more sense.
+
+       * lisp/elec-pair.el (electric-pair-analyze-conversion): New
+       function.
+
+       * lisp/simple.el (analyze-text-conversion): Improve integration
+       with electric pair modes.
+
+       * lisp/term.el (term-mode): Always display the onscreen keyboard.
+
+       * lisp/touch-screen.el (touch-screen-display-keyboard)
+       (touch-screen-handle-point-up): Respect new options.
+
+       * src/textconv.c (really_set_composing_text): Stop widenining
+       unnecessarily.
+       (really_delete_surrounding_text): Really delete surrounding text.
+       Give text conversion analyzers the buffer text.
+       (syms_of_textconv): Update doc string.
+
+       * INSTALL.android: Clarify build instructions.
+
+       * src/textconv.c (struct complete_edit_check_context): New
+       structure.
+       (complete_edit_check): New function.
+       (handle_pending_conversion_events_1): If the window is known, then
+       ensure that any editing failures are reported to the input method.
+
+       * configure.ac: Fix typo.  Check for madvise.
+
+       * lisp/international/fontset.el (script-representative-chars):
+       Improve detection of CJK fonts.
+
+       * src/pdumper.c (dump_discard_mem): Use madvise if possible.
+
+       * src/sfnt.c (sfnt_map_glyf_table, sfnt_unmap_glyf_table): New
+       functions.
+
+       * src/sfnt.h (struct sfnt_glyf_table): New field.
+
+       * src/sfntfont.c (struct sfnt_font_info, sfntfont_open)
+       (sfntfont_close, sfntfont_detect_sigbus): Map fonts into memory if
+       possible.
+
+       * src/sfntfont.h: Update prototypes.
+
+       * src/sysdep.c (handle_sigbus, init_sigbus, init_signals):
+       Initialize SIGBUS correctly.
+
+2023-02-17  Po Lu  <luangruo@yahoo.com>
+
+       * src/androidterm.c (android_get_selection): Use ephemeral last
+       point.
+
+       * src/textconv.c (report_selected_window_change): Set
+       w->ephemeral_last_point to the window's point now.
+
+       * java/Makefile.in (install_temp/assets/version): New generated
+       file.
+
+       * lisp/loadup.el: Set Emacs versions appropriately prior to
+       dumping on Android.
+
+       * lisp/mail/emacsbug.el (emacs-build-description): Insert Android
+       build fingerprint.
+
+       * lisp/version.el (emacs-repository-version-android)
+       (emacs-repository-get-version, emacs-repository-get-branch):
+       Implement for Android.
+
+       * src/androidterm.c (android_set_build_fingerprint): New function.
+       (syms_of_androidterm): New variable `android-build-fingerprint'.
+
+       * src/android.c (android_exception_check): Fix typo.
+       (android_exception_check): Print more detailed information.
+
+       * java/org/gnu/emacs/EmacsService.java (nameKeysym): Implement
+       stub on Android 3.0 and earlier.
+
+       * INSTALL.android: Document that Android 2.2 is now supported)
+       (with caveats.
+
+       * configure.ac (ANDROID_MIN_SDK, ANDROID_SDK_18_OR_EARLIER)
+       (SYSTEM_TYPE, ANDROID_STUBIFY, SIZEOF_LONG): Correctly detect
+       things missing on Android 2.2.
+
+       * java/Makefile.in (ANDROID_JAR, JARSIGNER_FLAGS):
+       * java/debug.sh (jdb, gdbserver, line):
+       * java/org/gnu/emacs/EmacsApplication.java (findDumpFile):
+       * java/org/gnu/emacs/EmacsService.java (onCreate):
+       * java/org/gnu/emacs/EmacsThread.java (run): Run parameter
+       initialization on main thread.
+
+       * src/android-asset.h (struct android_asset_manager)
+       (struct android_asset, AAssetManager_fromJava, AAssetManager_open)
+       (AAsset_close, android_asset_create_stream)
+       (android_asset_read_internal, AAsset_openFileDescriptor)
+       (AAsset_getLength, AAsset_getBuffer, AAsset_read): New file.
+       Write substitutes for functions that aren't present within the NDK
+       on Android 2.2.
+
+       * src/android.c: Arrange to include android-asset.h if the minimum
+       supported Android version is 2.2.
+       (android_user_full_name, android_hack_asset_fd)
+       (android_check_compressed_file): Implement for Android 2.2.
+
+       * src/process.c (Fprocess_send_eof): Don't call tcdrain if
+       unavailable.
+
+       * src/sfntfont-android.c (system_font_directories): Fix compiler
+       warning.
+
+       * src/sfntfont.c (sfntfont_read_cmap): Correctly test rc of
+       emacs_open.
+
+       * src/textconv.c (handle_pending_conversion_events_1): Mark buffer
+       UNINIT.
+
+2023-02-16  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Fonts):
+       * doc/emacs/input.texi (On-Screen Keyboards):
+       * doc/lispref/commands.texi (Misc Events): Update documentation.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java (setSelection): New
+       function.
+       * java/org/gnu/emacs/EmacsSurfaceView.java
+       (reconfigureFrontBuffer): Make bitmap references weak references.
+
+       * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Don't
+       clear surfaceView bitmap.
+
+       * lisp/comint.el (comint-mode): Set text-conversion-style to
+       `action' so on screen keyboards' Return buttons send an actual key
+       press event.
+
+       * lisp/international/fontset.el (script-representative-chars)
+       (setup-default-fontset): Improve detection of CJK fonts.
+
+       * lisp/isearch.el (set-text-conversion-style): New variable.
+       (isearch-mode, isearch-done): Save and restore the text conversion
+       style.
+
+       * lisp/minibuffer.el (minibuffer-mode): Set an appropriate text
+       conversion style.
+
+       * lisp/simple.el (analyze-text-conversion): Run
+       post-self-insert-hook properly.
+
+       * lisp/subr.el (read-char-from-minibuffer): Disable text
+       conversion when reading character.
+
+       * src/androidterm.c (show_back_buffer): Don't check that F is not
+       garbaged.
+       (android_update_selection, android_reset_conversion): Use the
+       ephemeral last point and handle text conversion being disabled.
+
+       * src/buffer.c (syms_of_buffer): Convert old style DEFVAR.
+
+       * src/keyboard.c (kbd_buffer_get_event): Handle text conversion
+       first.
+
+       * src/lisp.h: Update prototypes.
+
+       * src/lread.c (read_filtered_event): Temporarily disable text
+       conversion.
+
+       * src/sfnt.c (sfnt_decompose_glyph_1, sfnt_decompose_glyph_2): New
+       functions.
+       (sfnt_decompose_glyph, sfnt_decompose_instructed_outline):
+       Refactor contour decomposition to those two functions.
+       (main): Update tests.
+
+       * src/sfntfont-android.c (system_font_directories): Add empty
+       field.
+       (Fandroid_enumerate_fonts, init_sfntfont_android): Enumerate fonts
+       in a user fonts directory.
+
+       * src/sfntfont.c (struct sfnt_font_desc): New field `num_glyphs'.
+       (sfnt_enum_font_1): Set num_glyphs and avoid duplicate fonts.
+       (sfntfont_glyph_valid): New function.
+       (sfntfont_lookup_char, sfntfont_list_1): Make sure glyphs found
+       are valid.
+
+       * src/textconv.c (sync_overlay, really_commit_text)
+       (really_set_composing_text, really_set_composing_region)
+       (really_delete_surrounding_text, really_set_point_and_mark)
+       (handle_pending_conversion_events_1)
+       (handle_pending_conversion_events, conversion_disabled_p)
+       (disable_text_conversion, resume_text_conversion)
+       (Fset_text_conversion_style, syms_of_textconv): Update to respect
+       new options.
+
+       * src/window.h (GCALIGNED_STRUCT): New field
+       `ephemeral_last_point'.
+
+       * src/xdisp.c (mark_window_display_accurate_1): Set it.
+
+2023-02-15  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/input.texi (On-Screen Keyboards):
+       * doc/lispref/commands.texi (Misc Events): Improve documentation
+       of text conversion stuff.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit)
+       (endBatchEdit, commitCompletion, commitText)
+       (deleteSurroundingText)
+       (finishComposingText, getSelectedText, getTextAfterCursor)
+       (EmacsInputConnection, setComposingRegion, performEditorAction)
+       (getExtractedText): Condition debug code on DEBUG_IC.
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService, updateIC):
+       Likewise.
+
+       * lisp/bindings.el (global-map):
+       * lisp/electric.el (global-map): Make `text-conversion'
+       `analyze-text-conversion'.
+
+       * lisp/progmodes/prog-mode.el (prog-mode): Enable text conversion
+       in input methods.
+
+       * lisp/simple.el (analyze-text-conversion): New function.
+
+       * lisp/textmodes/text-mode.el (text-conversion-style)
+       (text-mode): Likewise.
+
+       * src/androidterm.c (android_handle_ime_event): Handle
+       set_point_and_mark.
+       (android_sync_edit): Give Emacs 100 ms instead.
+       (android_perform_conversion_query): Skip the active region, not
+       the conversion region.
+       (getSelectedText): Implement properly.
+       (android_update_selection): Expose mark to input methods.
+       (android_reset_conversion): Handle `text-conversion-style'.
+
+       * src/buffer.c (init_buffer_once, syms_of_buffer): Add buffer
+       local variable `text-conversion-style'.
+
+       * src/buffer.h (struct buffer, bset_text_conversion_style): New
+       fields.
+
+       * src/emacs.c (android_emacs_init): Call syms_of_textconv.
+
+       * src/frame.h (enum text_conversion_operation): Rename
+       TEXTCONV_SET_POINT.
+
+       * src/lisp.h (syms_of_textconv): Export syms_of_textconv.
+
+       * src/marker.c (set_marker_internal): Force redisplay when the
+       mark is set and the buffer is visible on builds that use text
+       conversion.  Explain why.
+
+       * src/textconv.c (copy_buffer): Fix copying past gap.
+       (get_mark): New function.
+       (textconv_query): Implement new flag.
+       (sync_overlay): New function.  Display conversion text in an
+       overlay.
+       (record_buffer_change, really_commit_text)
+       (really_set_composing_text, really_set_composing_region)
+       (really_delete_surrounding_text, really_set_point)
+       (handle_pending_conversion_events_1, decrement_inside)
+       (handle_pending_conversion_events, textconv_set_point)
+       (get_extracted_text, register_textconv_interface): Various fixes
+       and improvements.
+
+       * src/textconv.h (struct textconv_interface): Update
+       documentation.
+
+       * src/window.h (GCALIGNED_STRUCT): New field `prev_mark'.
+
+       * src/xdisp.c (mark_window_display_accurate_1): Handle prev_mark.
+
+       * java/debug.sh: Run gdbserver directly if possible.
+
+       * src/androidterm.c (android_handle_ime_event): Pacify compiler
+       warnings.
+
+       * src/textconv.c (really_set_composing_text)
+       (handle_pending_conversion_events, get_extracted_text): Fix
+       reentrancy problems and uses of uninitialized values.
+
+       * configure.ac (HAVE_TEXT_CONVERSION): Define on Android.
+
+       * doc/emacs/input.texi (On-Screen Keyboards): Document ``text
+       conversion'' slightly.
+
+       * doc/lispref/commands.texi (Misc Events): Document new
+       `text-conversion' event.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (display): Use
+       `syncRunnable'.
+
+       * java/org/gnu/emacs/EmacsDialog.java (display): Likewise.
+
+       * java/org/gnu/emacs/EmacsEditable.java: Delete file.
+
+       * java/org/gnu/emacs/EmacsInputConnection.java
+       (EmacsInputConnection): Reimplement from scratch.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Add new
+       functions.
+
+       * java/org/gnu/emacs/EmacsService.java (getEmacsView)
+       (getLocationOnScreen, sync, getClipboardManager, restartEmacs):
+       Use syncRunnable.
+       (syncRunnable): New function.
+       (updateIC, resetIC): New functions.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView): New field
+       `inputConnection' and `icMode'.
+       (onCreateInputConnection): Update accordingly.
+       (setICMode, getICMode): New functions.
+
+       * lisp/bindings.el (global-map): Ignore text conversion events.
+
+       * src/alloc.c (mark_frame): Mark text conversion data.
+
+       * src/android.c (struct android_emacs_service): New fields
+       `update_ic' and `reset_ic'.
+       (event_serial): Export.
+       (android_query_sem): New function.
+       (android_init_events): Initialize new semaphore.
+       (android_write_event): Export.
+       (android_select): Check for UI thread code.
+       (setEmacsParams, android_init_emacs_service): Initialize new
+       methods.
+       (android_check_query, android_begin_query, android_end_query)
+       (android_run_in_emacs_thread):
+       (android_update_ic, android_reset_ic): New functions for managing
+       synchronous queries from one thread to another.
+
+       * src/android.h: Export new functions.
+
+       * src/androidgui.h (enum android_event_type): Add input method
+       events.
+       (enum android_ime_operation, struct android_ime_event)
+       (union android_event, enum android_ic_mode): New structs and
+       enums.
+
+       * src/androidterm.c (android_window_to_frame): Allow DPYINFO to be
+       NULL.
+       (android_decode_utf16, android_handle_ime_event)
+       (handle_one_android_event, android_sync_edit)
+       (android_copy_java_string, beginBatchEdit, endBatchEdit)
+       (commitCompletion, deleteSurroundingText, finishComposingText)
+       (getSelectedtext, getTextAfterCursor, getTextBeforeCursor)
+       (setComposingText, setComposingRegion, setSelection, getSelection)
+       (performEditorAction, getExtractedText): New functions.
+       (struct android_conversion_query_context)
+       (android_perform_conversion_query, android_text_to_string)
+       (android_get_selection_context, android_get_selection)
+       (android_get_extracted_text_context, android_get_extracted_text)
+       (android_extracted_text_request_class)
+       (android_extracted_text_class, android_update_selection)
+       (android_reset_conversion, android_set_point)
+       (android_compose_region_changed, android_notify_conversion)
+       (text_conversion_interface): New functions and structures.
+       (android_term_init): Initialize text conversion.
+
+       * src/coding.c (syms_of_coding): Define Qutf_16le on Android.
+
+       * src/frame.c (make_frame): Clear conversion data.
+       (delete_frame): Reset conversion state.
+
+       * src/frame.h (enum text_conversion_operation)
+       (struct text_conversion_action, struct text_conversion_state)
+       (GCALIGNED_STRUCT): Update structures.
+
+       * src/keyboard.c (read_char, readable_events, kbd_buffer_get_event)
+       (syms_of_keyboard): Handle text conversion events.
+
+       * src/lisp.h:
+       * src/process.c: Fix includes.
+
+       * src/textconv.c (enum textconv_batch_edit_flags, textconv_query)
+       (reset_frame_state, detect_conversion_events)
+       (restore_selected_window, really_commit_text)
+       (really_finish_composing_text, really_set_composing_text)
+       (really_set_composing_region, really_delete_surrounding_text)
+       (really_set_point, complete_edit)
+       (handle_pending_conversion_events_1)
+       (handle_pending_conversion_events, start_batch_edit)
+       (end_batch_edit, commit_text, finish_composing_text)
+       (set_composing_text, set_composing_region, textconv_set_point)
+       (delete_surrounding_text, get_extracted_text)
+       (report_selected_window_change, report_point_change)
+       (register_texconv_interface): New functions.
+
+       * src/textconv.h (struct textconv_interface)
+       (TEXTCONV_SKIP_CONVERSION_REGION): Update prototype.
+
+       * src/xdisp.c (mark_window_display_accurate_1):
+       * src/xfns.c (xic_string_conversion_callback):
+       * src/xterm.c (init_xterm): Adjust accordingly.
+
+2023-02-12  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Environment): Mention that Emacs
+       also requests the notifications permission.
+
+       * java/org/gnu/emacs/EmacsEditable.java:
+       * java/org/gnu/emacs/EmacsInputConnection.java: New files.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Load library
+       dependencies in a less verbose fashion.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView): Make imManager
+       public.
+       (onCreateInputConnection): Set InputType to TYPE_NULL for now.
+
+       * java/org/gnu/emacs/EmacsWindow.java (onKeyDown, onKeyUp)
+       (getEventUnicodeChar): Correctly handle key events with strings.
+
+       * lisp/term/android-win.el (android-clear-preedit-text)
+       (android-preedit-text): New special event handlers.
+
+       * src/android.c (struct android_emacs_window): Add function
+       lookup_string.
+       (android_init_emacs_window): Adjust accordingly.
+       (android_wc_lookup_string): New function.
+
+       * src/androidgui.h (struct android_key_event): Improve commentary.
+       (enum android_lookup_status): New enum.
+
+       * src/androidterm.c (handle_one_android_event): Synchronize IM
+       lookup code with X.
+
+       * src/coding.c (from_unicode_buffer): Implement on Android.
+
+       * src/coding.h:
+       * src/sfnt.c: Fix commentary.
+
+2023-02-11  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsActivity.java (onDestroy)
+       (onWindowFocusChanged): Keep track of the last focused activity.
+
+       * java/org/gnu/emacs/EmacsDialog.java (display1): Use it if there
+       is no current focus.
+
+2023-02-10  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Add org/gnu/emacs/R.java.
+
+       * cross/Makefile.in (top_builddir): Include verbose.mk.  Rewrite
+       rules to print nice looking statements.
+
+       * doc/emacs/android.texi (Android, Android Startup)
+       (Android Environment, Android Windowing, Android Fonts):
+       * doc/emacs/emacs.texi (Top): Add an extra ``Android
+       Troubleshooting'' node and move troubleshooting details there.
+
+       * java/Makefile.in: Generate R.java; improve appearance by using
+       verbose.mk.
+
+       * java/org/gnu/emacs/EmacsPreferencesActivity.java: Reimplement in
+       terms of PreferencesActivity.
+
+       * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Avoid
+       flicker.
+
+       * java/res/xml/preferences.xml: New file.
+
+       * src/verbose.mk.in (AM_V_AAPT, AM_V_SILENT): New variables.
+
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java (queryRoots):
+       Implement isChild.
+       (getNotificationUri, notifyChange): New functions.
+       (queryDocument1): Set rename and remove flags.
+       (queryDocument, queryChildDocuments): Allow the requester to
+       detect changes in the directory hierarchy.
+       (createDocument, deleteDocument, removeDocument): Signal changes
+       to the directory hierarchy.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java (perform): Fix typo.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java
+       (reconfigureFrontBuffer): Don't use function only present on
+       Android 8.0 and later.
+
+       * doc/emacs/android.texi (Android Windowing): Remove yet another
+       limitation.
+
+       * java/debug.sh: Make this work on systems which prohibit
+       attaching to app processes from adbd.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java (perform): Avoid creating
+       copies whenever possible.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
+       Remove SurfaceView based implementation and use manual double
+       buffering with invalidate instead.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView, handleDirtyBitmap)
+       (raise, lower, onDetachedFromWindow): Adjust accordingly.
+
+       * java/org/gnu/emacs/EmacsWindow.java (windowUpdated): Remove
+       function.
+
+       * src/sfntfont.c (sfntfont_open): Set font->max_width correctly.
+
+       * src/sfnt.c (IUP_SINGLE_PAIR): If i is initially more than end,
+       make it start.
+       (sfnt_verbose): Handle cases where interpreter->glyph_zone is
+       NULL.
+       (main): Update tests.
+       (sfnt_read_cmap_table): Fix typo.
+       (main): Update tests.
+
+2023-02-09  Po Lu  <luangruo@yahoo.com>
+
+       * java/AndroidManifest.xml.in: Declare the new documents provider.
+
+       * java/README: Describe the meaning of files in res/values.
+
+       * java/org/gnu/emacs/EmacsDocumentsProvider.java: New file.
+
+       * java/res/values-v19/bool.xml:
+       * java/res/values/bool.xml: New files.
+
+       * src/sfnt.c (main): Update tests.
+
+       * src/sfnt.c (sfnt_read_simple_glyph, sfnt_read_compound_glyph)
+       (sfnt_read_glyph): Take size_t offsets.
+       (struct sfnt_compound_glyph_context)
+       (sfnt_expand_compound_glyph_context)
+       (sfnt_decompose_compound_glyph): Take size_t contour offsets.
+       (sfnt_decompose_glyph): Always close contour even if the first
+       point isn't on-curve.
+       (sfnt_build_outline_edges): Fix coding style.
+       (sfnt_interpret_iup): Skip phantom points during IUP.
+       (sfnt_decompose_instructed_outline): Clarify documentation.
+       Always close contour even if the first point isn't on-curve.
+       (struct sfnt_test_dcontext, sfnt_test_move_to, sfnt_test_line_to)
+       (sfnt_test_curve_to, sfnt_transform_f26dot6, sfnt_test_get_glyph)
+       (sfnt_test_free_glyph, sfnt_test_span, sfnt_test_edge_ignore)
+       (sfnt_interpret_compound_glyph_2, sfnt_test_edges, main): Update
+       tests.
+
+       * src/sfnt.h: Export new function.
+
+       * src/sfntfont.c (sfntfont_get_glyph_outline): Handle compound
+       glyphs.
+       (sfntfont_measure_instructed_pcm, sfntfont_measure_pcm)
+       (sfntfont_draw): Update accordingly.
+
+2023-02-08  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (SCFS): Fix order of arguments.
+       (sfnt_normalize_vector): Make sure vx and vy are within a
+       reasonable range.
+       (sfnt_move): Don't move when vectors are orthogonal.
+       (main): Update.
+
+       * doc/emacs/android.texi (Android Startup): Fix typos.
+
+       * src/sfnt.c (sfnt_interpret_msirp): Fix order in which operands
+       to MSIRP are popped.
+       (main): Reduce ppem values.
+
+       * doc/lispref/frames.texi (On-Screen Keyboards): Describe return
+       value of `frame-toggle-on-screen-keyboard'.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged)
+       (surfaceCreated): Remove purposeless synchronization code.  The
+       framework doesn't seem to consult this at all.
+
+       * java/org/gnu/emacs/EmacsView.java (onLayout): Lay out the window
+       after children.
+       (swapBuffers): Properly implement `force'.
+       (windowUpdated): Delete function.
+
+       * lisp/frame.el (frame-toggle-on-screen-keyboard): Return whether
+       or not the on screen keyboard might've been displayed.
+
+       * lisp/minibuffer.el (minibuffer-on-screen-keyboard-timer)
+       (minibuffer-on-screen-keyboard-displayed)
+       (minibuffer-setup-on-screen-keyboard)
+       (minibuffer-exit-on-screen-keyboard): Improve OSK dismissal when
+       there are consecutive minibuffers.
+
+       * lisp/touch-screen.el (touch-screen-window-selection-changed):
+       New function.
+       (touch-screen-handle-point-up): Register it as a window selection
+       changed function.
+
+       * src/android.c (struct android_emacs_window)
+       (android_init_emacs_window): Remove references to `windowUpdated'.
+       (android_window_updated): Delete function.
+
+       * src/android.h (struct android_output): Remove
+       `last_configure_serial'.
+
+       * src/androidterm.c (handle_one_android_event)
+       (android_frame_up_to_date):
+
+       * src/androidterm.h (struct android_output): Remove frame
+       synchronization, as that does not work on Android.
+
+       * src/sfntfont.c (sfntfont_get_glyph_outline): Take new argument
+       STATE and restore it prior to instructing the glyph.
+       (sfntfont_measure_instructed_pcm, sfntfont_measure_pcm)
+       (sfntfont_draw): Adjust accordingly.
+       (sfntfont_measure_instructed_pcm)
+       (sfntfont_measure_pcm): Ceil rbearing value.
+
+       * src/sfnt.c (sfnt_build_glyph_outline): Clear
+       build_outline_context.
+       (sfnt_poly_coverage): Extend coverage map.
+       (sfnt_prepare_raster): Always floor coordinates, since the
+       increase in coverage makes this hack unnecessary.
+       (sfnt_build_outline_edges): Likewise.
+       (sfnt_compare_edges): Remove function.
+       (sfnt_edge_sort): New function.  Since edges are already partially
+       sorted, and there are not many, insertion sort suffices.
+       (sfnt_poly_edges): Use sfnt_edge_sort.
+       (sfnt_fill_span): Stop rounding x0 and x1 to the grid, and make
+       coverage computation static.
+       (sfnt_lookup_glyph_metrics): Fix return code for unscaled metrics.
+       (sfnt_scale_metrics): New function.
+       (SFNT_ENABLE_HINTING): Remove define.
+       (struct sfnt_cvt_table, struct sfnt_fpgm_table)
+       (struct sfnt_prep_table): Move to sfnt.h.
+       (sfnt_read_cvt_table, sfnt_read_fpgm_table, sfnt_read_prep_table):
+       Make TEST_STATIC.
+       (struct sfnt_unit_vector, struct sfnt_interpreter_definition)
+       (struct sfnt_interpreter_zone, struct sfnt_graphics_state):
+       (struct sfnt_interpreter): Move to sfnt.h.
+       (sfnt_make_interpreter): Make TEST_STATIC.
+       (POP, PUSH, DELTAP1, DELTAP2, DELTAP3): When TEST, define to
+       regular push and pop.
+       (sfnt_deltac, sfnt_deltap): Fix order of arguments.
+       (IUP_SINGLE_PAIR): Fix interpolation loop wraparound.
+       (sfnt_interpret_font_program)
+       (sfnt_interpret_control_value_program): Make TEST_STATIC.
+       (struct sfnt_instructed_outline): Move to sfnt.h.
+       (sfnt_build_instructed_outline): Make TEST_STATIC.
+       (sfnt_interpret_simple_glyph, sfnt_x_raster, sfnt_test_raster)
+       (all_tests, sfnt_verbose, main): Improve test code.
+
+       * src/sfnt.h (SFNT_ENABLE_HINTING, struct sfnt_cvt_table)
+       (struct sfnt_fpgm_table, struct sfnt_prep_table)
+       (struct sfnt_unit_vector, struct sfnt_interpreter_definition)
+       (struct sfnt_interpreter_zone, struct sfnt_graphics_state)
+       (struct sfnt_interpreter, struct sfnt_instructed_outline)
+       (PROTOTYPE): New definitions.
+
+       * src/sfntfont-android.c (sfntfont_android_put_glyphs): Make
+       coordinate generation more straightforward.
+
+       * src/sfntfont.c (sfntfont_get_glyph_outline): New arguments
+       INTERPRETER and METRICS.
+       (struct sfnt_font_info): New tables.
+       (sfntfont_setup_interpreter): New function.
+       (sfntfont_open): Avoid memory leak.  Set up interpreter.
+       (sfntfont_measure_instructed_pcm): New function.
+       (sfntfont_measure_pcm): Delegate to measure_instructed_pcm where
+       appropriate.
+       (sfntfont_close): Free new tables.
+       (sfntfont_draw): Scale metrics properly.
+
+2023-02-07  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_name_instruction): Remove junk from instruction
+       table.
+       (sfnt_step_edge, sfnt_step_edge_n)
+       (sfnt_build_outline_edges, sfnt_test_edge, main):
+       * src/sfnt.h (struct sfnt_edge): Stop using error corrected line
+       drawing, as it's actually slower.
+
+       * INSTALL.android: Describe patches for BoringSSL on ARM.
+
+       * src/sfnt.c (sfnt_build_glyph_outline): Remove redundant
+       multiplication.
+       (sfnt_prepare_raster): Update offset calculation for changes.
+       (sfnt_step_edge, sfnt_step_edge_n): Handle bresenham terms.
+       (sfnt_build_outline_edges): Don't subtract floored xmin, just
+       xmin.
+       (sfnt_saturate_short): Make clang generate better code.
+       (sfnt_fill_span): Stop rounding coordinates.
+       (sfnt_poly_span): Poly consecutive on transitions all in one go.
+       (sfnt_lookup_glyph_metrics): Remove redundant multiplication.
+       (struct sfnt_interpreter): New hooks for debugging.
+       (sfnt_large_integer_add): New function.
+       (sfnt_mul_f26dot6_fixed): Round product.
+       (sfnt_make_interpreter): Remove redundant multiplication.
+       (CHECK_STACK_ELEMENTS, POP_UNCHECKED, PUSH_UNCHECKED): New macros.
+       (MOVE, POP, SWAP, CINDEX, RS, RCVT, LT, LTEQ, GT, GTEQ, EQ, NEQ)
+       (EVEN, AND, OR, NOT, ADD, SUB, DIV, MUL, ABS, NEG, FLOOR, CEILING)
+       (GETINFO, ROLL, _MAX, _MIN, ROUND, NROUND, GC, MD): Don't check SP
+       redundantly, especially when pushing an element right after
+       popping one.
+       (sfnt_move_glyph_zone): Don't touch points by passing NULL as
+       flags.
+       (sfnt_direct_move_zp2): Touch P in the directions of the movement.
+       (sfnt_interpret_scfs): Fix coding style.
+       (sfnt_interpret_simple_glyph): Don't round Y coordinates.
+       (sfnt_test_span, sfnt_test_edges, sfnt_debug_edges)
+       (sfnt_test_edge)
+       (sfnt_x_raster, sfnt_test_raster, rcvt_test_args)
+       (deltac1_test_args, deltac2_test_args, deltac3_test_args)
+       (roll_1_test_args, sfnt_run_hook, sfnt_identify_instruction)
+       (sfnt_verbose, main): Improve debug code and tests.
+
+       * src/sfnt.h (struct sfnt_edge): Add bresenham terms.
+
+2023-02-06  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsNative.java
+       (EmacsNative) <static constructor>: Load every native library on
+       which Emacs depends prior to loading libemacs itself.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java (readEmacsClientLog)
+       (startEmacsClient): Don't use redirectError on Android 7.1 and
+       earlier.
+
+       * configure.ac: Pass ANDROID_CFLAGS to ndk_INIT.
+
+       * cross/ndk-build/Makefile.in (NDK_BUILD_CFLAGS):
+       * cross/ndk-build/ndk-build-shared-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1)))):
+       ($$(error Unsupported suffix):
+       * cross/ndk-build/ndk-build-static-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1)))):
+       ($$(error Unsupported suffix): Use NDK_BUILD_CFLAGS.
+
+       * m4/ndk-build.m4 (ndk_INIT): Accept cflags.
+       (ndk_CONFIG_FILES): Export NDK_BUILD_CFLAGS.
+
+       * java/AndroidManifest.xml.in: Prevent the Emacs activity from
+       being overlayed by the emacsclient wrapper.
+       * java/org/gnu/emacs/EmacsOpenActivity.java (run): Likewise.
+       (onCreate): Set an appropriate theme on ICS and up.
+
+       * java/org/gnu/emacs/EmacsWindow.java (onTouchEvent): Handle
+       ACTION_CANCEL correctly.
+
+       * src/sfnt.c (struct sfnt_build_glyph_outline_context)
+       (sfnt_build_glyph_outline, sfnt_fill_span): Improve glyph
+       appearance by rounding coordinate values.
+       (struct sfnt_interpreter): New fields `twilight_original_x',
+       `twilight_original_y'.
+       (sfnt_make_interpreter): Set new fields.
+       (DELTAP1, DELTAP2, DELTAP3, SVTCAy, SPVTL, SFVTL, MD): Implement
+       instructions.
+       (sfnt_save_projection_vector): New argument `dual_only'.  All
+       callers changed.
+       (sfnt_address_zp2, sfnt_address_zp1, sfnt_address_zp0): Obtain
+       original positions in the twilight zone as well.
+       (sfnt_check_zp1, sfnt_interpret_fliprgoff)
+       (sfnt_interpret_fliprgon)
+       (sfnt_interpret_flippt, sfnt_interpret_scfs, sfnt_interpret_miap)
+       (sfnt_interpret_alignrp, sfnt_line_to_vector, P)
+       (sfnt_interpret_msirp, sfnt_interpret_ip, sfnt_interpret_call)
+       (load_point, sfnt_interpret_iup_1, sfnt_interpret_iup)
+       (sfnt_interpret_run, struct sfnt_scaled_outline)
+       (struct sfnt_instructed_outline)
+       (sfnt_decompose_instructed_outline)
+       (sfnt_build_instructed_outline, sfnt_compute_phantom_points)
+       (sfnt_interpret_simple_glyph, all_tests, sfnt_setup_debugger)
+       (sfnt_name_instruction, sfnt_draw_debugger, sfnt_run_hook)
+       (sfnt_verbose, main): Make glyph instructing work.
+
+       * src/sfnt.h (SFNT_POLY_ROUND): New enumerator.
+
+2023-02-05  Po Lu  <luangruo@yahoo.com>
+
+       * INSTALL.android: Explain how to build selinux.
+
+       * configure.ac: Enable selinux on Android.
+
+       * cross/ndk-build/ndk-build-shared-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1))))
+       ($$(error Unsupported suffix))
+       (NDK_CFLAGS_$(LOCAL_MODULE)):
+       * cross/ndk-build/ndk-build-static-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1))))
+       ($$(error Unsupported suffix))
+       (NDK_CFLAGS_$(LOCAL_MODULE)): Correctly handle files with a .cc
+       suffix, and clang-specific asflags.
+
+       * cross/ndk-build/ndk-clear-vars.mk: Handle AOSP extensions
+       LOCAL_ADDITIONAL_DEPENDENCIES,
+       LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH) and LOCAL_IS_HOST_MODULE.
+
+       * doc/emacs/android.texi (Android Startup): Explain emacsclient
+       wrapper.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView): New flag
+       `isCurrentlyTextEditor'.
+       (showOnScreenKeyboard, hideOnScreenKeyboard): Set as appropriate.
+       (onCheckIsTextEditor): Return its value.
+
+       * lisp/touch-screen.el (touch-screen-handle-scroll): Don't ding
+       at buffer limits.
+
+       * m4/ndk-build.m4: Improve doc.
+
+       * src/Makefile.in (LIBSELINUX_CFLAGS): New variable.
+       (EMACS_CFLAGS): Add it.
+
+2023-02-05  Po Lu  <luangruo@yahoo.com>
+
+       * m4, lib: Update from Gnulib.
+
+       * src/sfnt.c (struct sfnt_graphics_state, LOOPCALL, DELTAC3)
+       (PROJECT, SHPIX, sfnt_save_projection_vector, sfnt_check_zp0)
+       (sfnt_dual_project_vector, sfnt_interpret_scfs)
+       (sfnt_round_symmetric, sfnt_interpret_miap)
+       (sfnt_interpret_alignrp_1, sfnt_interpret_alignrp)
+       (sfnt_measure_distance, sfnt_interpret_msirp, sfnt_interpret_ip)
+       (sfnt_interpret_mdap, sfnt_deltap)
+       (sfnt_dual_project_onto_any_vector, sfnt_validate_gs)
+       (sfnt_set_projection_vector, sfnt_interpret_shp)
+       (sfnt_interpret_run, sfnt_check_sloop, main): Check in more WIP
+       font code.
+
+2023-02-04  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android File System):
+
+       * java/AndroidManifest.xml.in: Update with new activity.  Remove
+       Android 10 restrictions through a special flag.
+
+       * java/org/gnu/emacs/EmacsNative.java (getProcName): New
+       function.
+
+       * java/org/gnu/emacs/EmacsOpenActivity.java: New file.
+
+       * java/org/gnu/emacs/EmacsService.java (getLibraryDirection):
+       Remove unused annotation.
+
+       * lib-src/emacsclient.c (decode_options): Set alt_display on
+       Android.
+
+       * src/android.c (android_proc_name): New function.
+       (getProcName): Export via JNI.
+
+       * doc/emacs/android.texi (Android Environment):
+
+       * java/AndroidManifest.xml.in: Add network state permissions.
+
+       * src/sfnt.c (sfnt_multiply_divide_signed)
+       (struct sfnt_interpreter_zone, struct sfnt_graphics_state)
+       (struct sfnt_interpreter, sfnt_mul_f2dot14)
+       (sfnt_interpret_trap, WCVTF)
+       (ALIGNPTS, sfnt_scale_by_freedom_vector, sfnt_interpret_utp)
+       (sfnt_address_zp2, sfnt_address_zp1, sfnt_address_zp0)
+       (sfnt_check_zp2, sfnt_move_zp0, sfnt_move_zp1)
+       (sfnt_move_glyph_zone, sfnt_move_twilight_zone)
+       (sfnt_direct_move_zp2, sfnt_interpret_alignpts)
+       (sfnt_interpret_isect, sfnt_line_to_vector, sfnt_deltac)
+       (sfnt_interpret_mdap, sfnt_interpret_call, sfnt_dot_fix_14)
+       (sfnt_move_x, sfnt_move_y, sfnt_move, sfnt_validate_gs)
+       (sfnt_interpret_shz, sfnt_interpret_shc, sfnt_interpret_shp)
+       (sfnt_interpret_iup_1, sfnt_interpret_iup, sfnt_interpret_run)
+       (sfnt_interpret_font_program)
+       (sfnt_interpret_control_value_program)
+       (sfnt_interpret_simple_glyph, jrot_test_args, jrof_test_args)
+       (all_tests, main): Check in more WIP code.
+
+2023-02-02  Po Lu  <luangruo@yahoo.com>
+
+       * java/AndroidManifest.xml.in: Add new icon.
+
+       * java/Makefile.in (srcdir): New variable.
+       (JAVA_FILES, RESOURCE_FILES): Update variables.
+       (emacs.apk-in): Apply resources.
+
+       * java/README: Describe directory tree.
+
+       * java/res/drawable/emacs.png: New file.
+
+       * src/android.c (android_get_current_api_level): New function.
+
+       * src/android.h: Export it.
+
+       * src/sfntfont-android.c (init_sfntfont_android): Make device API
+       level detection always work.
+
+       * src/sfnt.c (sfnt_multiply_divide_signed): Add MAYBE_UNUSED.
+
+       * src/sfnt.c (xmalloc, xrealloc): Improve behavior upon allocation
+       failures during test.
+       (sfnt_table_names): Add prep.
+       (sfnt_transform_coordinates): Allow applying offsets during
+       coordinate transform.
+       (sfnt_decompose_compound_glyph): Defer offset computation until
+       any component compound glyph is loaded, then apply it during the
+       transform process.
+       (sfnt_multiply_divide): Make available everywhere.  Implement on
+       64 bit systems.
+       (sfnt_multiply_divide_signed): New function.
+       (sfnt_mul_fixed): Fix division overflow.
+       (sfnt_curve_to_and_build_1, sfnt_build_glyph_outline): Remove
+       outdated comment.
+       (sfnt_build_outline_edges): Fix coding style.
+       (sfnt_lookup_glyph_metrics): Allow looking up metrics without
+       scaling.
+       (struct sfnt_cvt_table): Fix type of cvt values.
+       (struct sfnt_prep_table): New structure.
+       (sfnt_read_cvt_table): Read cvt values in terms of fwords, not
+       longs (as Apple's doc seems to say).
+       (sfnt_read_fpgm_table): Fix memory allocation for font program
+       table.
+       (sfnt_read_prep_table): New function.
+       (struct sfnt_interpreter_zone): New structure.
+       (struct sfnt_interpreter_graphics_state): New fields `project',
+       `move', `vector_dot_product'.  Rename to `sfnt_graphics_state'.
+       (struct sfnt_interpreter, sfnt_mul_f26dot6): Stop doing rounding
+       division.
+       (sfnt_init_graphics_state, sfnt_make_interpreter, MOVE, SSW, RAW)
+       (SDS, ADD, SUB, ABS, NEG, WCVTF, _MIN, S45ROUND, SVTCAx)
+       (sfnt_set_srounding_state, sfnt_skip_code)
+       (sfnt_interpret_unimplemented, sfnt_interpret_fdef)
+       (sfnt_interpret_idef, sfnt_interpret_if, sfnt_interpret_else)
+       (sfnt_round_none, sfnt_round_to_grid, sfnt_round_to_double_grid)
+       (sfnt_round_down_to_grid, sfnt_round_up_to_grid)
+       (sfnt_round_to_half_grid, sfnt_round_super, sfnt_validate_gs)
+       (sfnt_interpret_run, sfnt_interpret_font_program)
+       (struct sfnt_test_dcontext, sfnt_test_move_to, sfnt_test_line_to)
+       (sfnt_test_curve_to, sfnt_test_get_glyph, sfnt_test_free_glyph)
+       (sfnt_test_span, sfnt_test_edge_ignore, sfnt_test_edge)
+       (sfnt_test_raster, test_interpreter_profile, test_cvt_values)
+       (test_interpreter_cvt, test_interpreter_head)
+       (sfnt_make_test_interpreter, struct sfnt_interpreter_test)
+       (sfnt_run_interpreter_test, struct sfnt_generic_test_args)
+       (sfnt_generic_check, sfnt_check_srp0, sfnt_check_szp0)
+       (sfnt_check_sloop, struct sfnt_rounding_test_args)
+       (sfnt_check_rounding, sfnt_check_smd, sfnt_check_scvtci)
+       (sfnt_check_sswci, sfnt_check_ssw, sfnt_check_flipon)
+       (sfnt_check_flipoff, npushb_test_args, npushw_test_args)
+       (pushb_test_args, pushw_test_args, stack_overflow_test_args)
+       (stack_underflow_test_args, rtg_test_args)
+       (rtg_symmetric_test_args, rtg_1_test_args)
+       (rtg_1_symmetric_test_args, rthg_test_args, rthg_1_test_args)
+       (rtdg_test_args, rtdg_1_test_args, rtdg_2_test_args)
+       (rtdg_3_test_args, else_test_args, jmpr_test_args, dup_test_args)
+       (pop_test_args, clear_test_args, swap_test_args, depth_test_args)
+       (cindex_test_args, mindex_test_args, raw_test_args)
+       (loopcall_test_args, call_test_args, fdef_test_args)
+       (fdef_1_test_args, endf_test_args, ws_test_args, rs_test_args)
+       (wcvtp_test_args, rcvt_test_args, mppem_test_args, mps_test_args)
+       (debug_test_args, lt_test_args, all_tests, main): Implement more
+       instructions.
+
+       * src/sfnt.h (enum sfnt_table, struct sfnt_glyph_metrics): Add new
+       tables.  Add comment.
+
+2023-01-30  Po Lu  <luangruo@yahoo.com>
+
+       * cross/ndk-build/ndk-build-shared-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1)))):
+       * cross/ndk-build/ndk-build-static-library.mk
+       ($(call objname,$(LOCAL_MODULE),$(basename $(1)))): Revert broken
+       typo fixes.
+
+       * src/sfnt.c (TEST_STATIC): Define ARRAYELTS.
+       (sfnt_table_names): New CVT and FPGM tables.
+       (sfnt_decompose_compound_glyph, sfnt_decompose_glyph)
+       (struct sfnt_large_integer, sfnt_multiply_divide_1)
+       (sfnt_count_leading_zero_bits, sfnt_multiply_divide_2)
+       (sfnt_multiply_divide, sfnt_mul_fixed, sfnt_div_fixed)
+       (sfnt_ceil_fixed, sfnt_build_glyph_outline): Fix fixed point
+       multiplication routines on systems without 64 bit long long
+       type.
+       (SFNT_ENABLE_HINTING, struct sfnt_test_dcontext, sfnt_test_move_to)
+       (sfnt_test_line_to, sfnt_test_curve_to, sfnt_test_get_glyph)
+       (sfnt_test_free_glyph, sfnt_test_span, sfnt_test_edge_ignore)
+       (sfnt_read_cvt_table, sfnt_test_edge, sfnt_test_raster)
+       (sfnt_read_fpgm_table, struct sfnt_unit_vector)
+       (struct sfnt_interpreter_definition)
+       (struct sfnt_interpreter_graphics_state, struct sfnt_interpreter)
+       (sfnt_div_f26dot6, sfnt_mul_f26dot6, sfnt_floor_f26dot6)
+       (sfnt_ceil_f26dot6, sfnt_round_f26dot6, sfnt_init_graphics_state)
+       (sfnt_make_interpreter, enum sfnt_interpreter_run_context)
+       (sfnt_interpret_trap, STACKSIZE, sfnt_set_srounding_state)
+       (sfnt_skip_code, sfnt_interpret_unimplemented, sfnt_interpret_fdef)
+       (sfnt_interpret_idef, sfnt_interpret_if, sfnt_interpret_else)
+       (sfnt_round_none, sfnt_round_to_grid, sfnt_round_to_double_grid)
+       (sfnt_round_down_to_grid, sfnt_round_up_to_grid)
+       (sfnt_round_to_half_grid, sfnt_round_super, sfnt_validate_gs)
+       (sfnt_interpret_run, sfnt_interpret_font_program)
+       (test_interpreter_profile, test_cvt_values, test_interpreter_cvt)
+       (test_interpreter_head, sfnt_make_test_interpreter)
+       (struct sfnt_interpreter_test, sfnt_run_interpreter_test)
+       (struct sfnt_generic_test_args, sfnt_generic_check)
+       (sfnt_check_srp0, sfnt_check_szp0, sfnt_check_sloop)
+       (struct sfnt_rounding_test_args, sfnt_check_rounding)
+       (sfnt_check_smd, sfnt_check_scvtci, sfnt_check_sswci)
+       (sfnt_check_ssw, sfnt_check_flipon, sfnt_check_flipoff)
+       (npushb_test_args, npushw_test_args, pushb_test_args)
+       (pushw_test_args, stack_overflow_test_args)
+       (stack_underflow_test_args, rtg_test_args, rtg_symmetric_test_args)
+       (rtg_1_test_args, rtg_1_symmetric_test_args, rthg_test_args)
+       (rthg_1_test_args, rtdg_test_args, rtdg_1_test_args)
+       (rtdg_2_test_args, rtdg_3_test_args, else_test_args)
+       (jmpr_test_args, dup_test_args, pop_test_args, clear_test_args)
+       (swap_test_args, depth_test_args, cindex_test_args)
+       (mindex_test_args, raw_test_args, loopcall_test_args)
+       (call_test_args, fdef_test_args, fdef_1_test_args, endf_test_args)
+       (ws_test_args, rs_test_args, wcvtp_test_args, rcvt_test_args)
+       (mppem_test_args, mps_test_args, debug_test_args, lt_test_args)
+       (all_tests, main): Check in WIP hinting code.
+
+       * src/sfnt.h (enum sfnt_table): Add `cvt ' and `fpgm' tables.
+
+2023-01-29  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Add missing Gnulib files.
+
+       * INSTALL.android (module_target): Clarify documentation.
+
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk: Fix building Neon
+       objects.
+
+       * java/AndroidManifest.xml.in: Add a version code.
+
+2023-01-28  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (restartEmacs): New
+       function.
+
+       * src/android.c (struct android_emacs_service)
+       (android_init_emacs_service): Add new method.
+       (android_restart_emacs): New function.
+
+       * src/android.h: Update prototypes.
+
+       * src/emacs.c (Fkill_emacs): Call android_restart_emacs whenever
+       appropriate.
+
+       * INSTALL.android: Document how to build with libtiff.
+
+       * build-aux/ndk-build-helper-1.mk (NDK_SO_NAME):
+       * build-aux/ndk-build-helper-2.mk (NDK_A_NAME):
+       * build-aux/ndk-build-helper-4.mk: Decrease number of duplicate
+       dependencies found.
+
+       * configure.ac (ANDROID_SDK_18_OR_EARLIER, XCONFIGURE, PNG_CFLAGS)
+       (HAVE_TIFF): Allow using libtiff on Android.
+
+       * cross/ndk-build/ndk-clear-vars.mk: Undefine additional
+       variables.
+
+       * cross/ndk-build/ndk-resolve.mk: Split CFLAGS resolution from
+       a-name resolution, and do not recursively add archive or shared
+       object names for dependencies of shared libraries.
+
+       * src/Makefile.in (TIFF_CFLAGS): New variable.
+       (EMACS_CFLAGS): Use it.
+
+2023-01-28  Po Lu  <luangruo@yahoo.com>
+
+       * src/image.c (syms_of_image): Fix typo.
+
+       * doc/emacs/android.texi (Android File System): Describe an easier
+       way to disable scoped storage.
+
+       * java/AndroidManifest.xml.in: Add new permission to allow that.
+
+       * java/README: Add more text describing Java.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (Item): New fields
+       `isCheckable' and `isChecked'.
+       (EmacsContextMenu, addItem): New arguments.
+       (inflateMenuItems): Set checked status as appropriate.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java (perform): Disallow
+       operations where width and height are less than or equal to zero.
+
+       * lisp/menu-bar.el (menu-bar-edit-menu): Make
+       execute-extended-command available as a menu item.
+
+       * src/androidmenu.c (android_init_emacs_context_menu)
+       (android_menu_show): Implement menu check boxes.
+
+       * src/menu.c (have_boxes): Treat Android builds as providing menu
+       checkboxes.
+
+2023-01-28  Po Lu  <luangruo@yahoo.com>
+
+       * lisp/term/android-win.el (window-system-initialization): Create
+       default fontset.
+
+       * src/sfntfont.c (sfntfont_read_cmap, sfntfont_open): Fix leaks of
+       file descriptors.
+
+       * m4, lib: Update from Gnulib.
+
+       * INSTALL.android: Document support for gnutls and libgmp.
+
+       * build-aux/ndk-build-helper-1.mk (NDK_SO_NAMES, NDK_INCLUDES)
+       (SYSTEM_LIBRARIES):
+       * build-aux/ndk-build-helper-2.mk: Recursively resolve and add
+       shared library dependencies; even those of static libraries.
+
+       * build-aux/ndk-module-extract.awk: Fix makefile_imports code.
+
+       * configure.ac (ANDROID_SDK_18_OR_EARLIER, XCONFIGURE)
+       (LIBGMP_CFLAGS): Enable GMP and gnutls on Android.
+
+       * cross/ndk-build/Makefile.in (LOCAL_EXPORT_C_INCLUDES):
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-clear-vars.mk:
+       * cross/ndk-build/ndk-resolve.mk (NDK_SYSTEM_LIBRARIES):
+       (NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE)):
+       (NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)): Implement ``LOCAL_ASM_RULE''
+       and ``LOCAL_C_ADDITIONAL_FLAGS'' extensions for libgmp.
+
+       * doc/emacs/input.texi (Touchscreens): Document how to
+       horizontally scroll.
+
+       * java/org/gnu/emacs/EmacsActivity.java (attachWindow): Give the
+       view focus again if necessary.
+       (onPause): Call right super function.
+
+       * java/org/gnu/emacs/EmacsPreferencesActivity.java (onClick):
+       Clear dumpFileName lest Emacs try to load a nonexistent dump file.
+
+       * java/org/gnu/emacs/EmacsView.java (onDetachedFromWindow)
+       (onAttachedToWindow): Call super functions.
+       (onCreateInputConnection): Make sure the IME never obscures Emacs.
+
+       * java/org/gnu/emacs/EmacsWindow.java (onKeyDown, onKeyUp):
+       Improve tracking of quit keys.
+
+       * lisp/isearch.el (isearch-mode): Bring up the onscreen keyboard.
+
+       * lisp/touch-screen.el (touch-screen-current-tool): Add three
+       fields.
+       (touch-screen-handle-scroll): Allow hscrolling as well.
+       (touch-screen-handle-touch): Add additional fields to
+       `touch-screen-current-tool'.
+
+       * src/Makefile.in (LIBGMP_CFLAGS, EMACS_CFLAGS): Add new variable.
+
+       * src/android.c (android_run_select_thread, android_write_event):
+       Use pthread_cond_broadcast because pthread_cond_signal does
+       nothing on some Android versions/devices?
+
+2023-01-26  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/input.texi (On-Screen Keyboards): Fix typo.
+
+       * INSTALL.android: Mention that apksigner is also required.
+
+       * configure.ac: Correctly add cross/Makefile to SUBDIR_MAKEFILES.
+
+       * cross/Makefile.in (config.status): Depend on
+       $(top_srcdir)/config.status.
+
+       * doc/emacs/input.texi (On-Screen Keyboards): Document how to quit
+       without a physical keyboard.
+
+       * java/org/gnu/emacs/EmacsNative.java (quit): New function `quit'.
+
+       * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): New field
+       `lastVolumeButtonPress'.
+       (onKeyDown): Quit if necessary.
+
+       * m4/ndk-build.m4 (ndk_where_cc): Fix search if CC is not a single
+       word.
+
+       * src/android.c (android_open): Remove unused variable.
+       (quit): New function.
+
+       * src/androidmenu.c (android_process_events_for_menu): Allow
+       quitting the menu.
+
+       * src/xterm.c (handle_one_xevent, x_term_init, syms_of_xterm):
+       Implement features illustrated above, so they work on free
+       operating systems.
+
+       * src/xterm.h (struct x_display_info): New fields `quit_keysym',
+       `quit_keysym_time'.
+
+2023-01-26  Po Lu  <luangruo@yahoo.com>
+
+       * INSTALL.android: Document how to install sqlite3.
+
+       * build-aux/ndk-build-helper-1.mk (SYSTEM_LIBRARIES):
+       * build-aux/ndk-build-helper-2.mk (SYSTEM_LIBRARIES): Add liblog
+       and libandroid.
+
+       * configure.ac (SQLITE3_LIBS, HAVE_SQLITE3)
+       (HAVE_SQLITE3_LOAD_EXTENSION): Support on Android.
+       (APKSIGNER): Look for this new required binary.
+
+       * cross/ndk-build/ndk-build-shared-library.mk (objname):
+       * cross/ndk-build/ndk-build-static-library.mk (objname): Avoid
+       duplicate rules by prefixing objects with module type.
+
+       * cross/ndk-build/ndk-build.mk.in (NDK_BUILD_SHARED): Fix
+       definition.
+
+       * cross/ndk-build/ndk-resolve.mk
+       (NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)): Handle new system libraries.
+
+       * doc/emacs/android.texi (Android File System): Document Android
+       10 system restriction.
+
+       * java/AndroidManifest.xml.in: Target Android API 33, not 28.
+
+       * java/Makefile.in (SIGN_EMACS_V2, APKSIGNER): New variables.
+       ($(APK_NAME)): Make sure to apply a ``version 2 signature'' to the
+       package as well.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
+       argument apiLevel.
+
+       * java/org/gnu/emacs/EmacsNoninteractive.java (main):
+       * java/org/gnu/emacs/EmacsThread.java (run): Pass API level.
+
+       * m4/ndk-build.m4 (ndk_package_mape): Add package mapping for
+       sqlite3.
+
+       * src/Makefile.in (SQLITE3_CFLAGS): New substition.
+       (EMACS_CFLAGS): Add that variable.
+
+       * src/android.c (android_api_level): New variable.
+       (initEmacs): Set it.
+       (android_file_access_p): Make static.
+       (android_hack_asset_fd): Adjust for restrictions in Android 29 and
+       later.
+       (android_close_on_exec): New function.
+       (android_open): Adjust to not duplicate file descriptor even if
+       CLOEXEC.
+       (android_faccessat): Use fstatat at-func emulation.
+
+       * src/android.h: Update prototypes.
+
+       * src/dired.c (file_name_completion_dirp):
+       * src/fileio.c (file_access_p, Faccess_file): Now that
+       sys_faccessat takes care of everything, stop calling
+       android_file_access_p.
+
+2023-01-26  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Ignore lib/math.h.
+
+       * INSTALL.android: Update accordingly.
+
+       * build-aux/ndk-build-helper-1.mk:
+       * build-aux/ndk-build-helper-2.mk:
+       * build-aux/ndk-build-helper.mk:
+       * build-aux/ndk-module-extract.awk: Handle C++ modules.
+       * configure.ac: Enable libxml2 on Android.
+
+       * cross/ndk-build/Makefile.in:
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk:
+       * cross/ndk-build/ndk-build.mk.in:
+       * cross/ndk-build/ndk-resolve.mk: Fix dependency resolution of
+       includes.
+
+       * java/org/gnu/emacs/EmacsView.java (popupMenu): Fix minimum SDK
+       version for actual popup menus.
+       * lib/math.h: Delete file.
+
+       * m4/ndk-build.m4 (ndk_SEARCH_MODULE, ndk_CHECK_MODULES): Look for
+       nasm and C++ libraries.
+
+       * src/android.c (faccessat): Rename to `android_faccessat'.
+
+       * src/android.h: Update prototypes.
+
+       * src/dired.c (file_name_completion_dirp):
+       * src/fileio.c (file_access_p, Faccess_file, file_directory_p):
+       * src/lisp.h:
+       * src/lread.c (openp):
+       * src/process.c (allocate_pty): Use sys_faccessat.
+       * src/sysdep.c (sys_faccessat): New function.
+
+2023-01-26  Po Lu  <luangruo@yahoo.com>
+
+       * cross/ndk-build/ndk-build.in: Delete unused file.
+
+2023-01-25  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsDrawLine.java: Fix this again.
+
+       * java/org/gnu/emacs/EmacsNoninteractive.java (main): Port to
+       Android 2.3.3.
+
+       * java/org/gnu/emacs/EmacsSdk11Clipboard.java: Port to Android
+       4.0.3.
+
+       * java/org/gnu/emacs/EmacsService.java (getClipboardManager): New
+       function.
+
+       * src/alloc.c (find_string_data_in_pure): Fix Android alignment
+       issue.
+
+       * src/android-emacs.c (main): Port to Android 4.4.
+
+       * src/android.c (initEmacs): Align stack to 32 bytes, so it ends
+       up aligned to 16 even though gcc thinks the stack is already
+       aligned to 16 bytes.
+
+       * src/callproc.c (init_callproc): Use /system/bin/sh instead of
+       /bin/sh by default.
+
+       * cross/lib/math.h: Delete header.
+
+       * java/Makefile.in (emacs.apk-in): Don't call cp with empty args.
+
+       * java/org/gnu/emacs/EmacsDrawLine.java (perform): Fix for
+       PostScript filling semantics.
+
+       * src/Makefile.in (android-emacs): Build android-emacs directly.
+
+       * doc/emacs/android.texi (Android Startup, Android Environment):
+       Document that restrictions on starting Emacs have been lifted.
+
+       * java/README: Document Java for Emacs developers and how the
+       Android port works.
+
+       * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication)
+       (findDumpFile): New function.
+       (onCreate): Factor out dump file finding functions to there.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Update
+       function declarations.
+
+       * java/org/gnu/emacs/EmacsNoninteractive.java: New class.
+
+       * java/org/gnu/emacs/EmacsService.java (getApkFile)
+       (onCreate): Pass classpath to setEmacsParams.
+
+       * java/org/gnu/emacs/EmacsThread.java (run): Label as an
+       @Override.
+
+       * lisp/loadup.el: Don't dump on Android when noninteractive.
+
+       * lisp/shell.el (shell--command-completion-data): Handle
+       inaccessible directories.
+
+       * src/Makefile.in (android-emacs): Link with Gnulib.
+
+       * src/android-emacs.c (main): Implement to launch app-process and
+       then EmacsNoninteractive.
+
+       * src/android.c (setEmacsParams): New argument `class_path'.
+       Don't set stuff up when running noninteractive.
+
+       * src/android.h (initEmacs): Likewise.
+
+       * src/androidfont.c (init_androidfont):
+       * src/androidselect.c (init_androidselect): Don't initialize when
+       running noninteractive.
+
+       * src/emacs.c (load_pdump): New argument `dump_file'.
+       (android_emacs_init): Give new argument `dump_file' to
+       `load_pdump'.
+
+       * src/sfntfont-android.c (init_sfntfont_android): Don't initialize
+       when running noninteractive.
+
+       * admin/merge-gnulib (GNULIB_MODULES): Add printf-posix and
+       vasprintf-posix.
+
+       * m4, lib: Update from Gnulib.
+
+       * configure.ac (CFLAGS): Add -DHAVE_CONFIG_H.
+
+2023-01-24  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/processes.texi (Subprocess Creation): Document
+       variables containing program names.
+
+       * etc/NEWS: Document new variables.
+
+       * java/Makefile.in (CROSS_BINS): Add missing etags binary.
+
+       * lisp/cedet/semantic/db-ebrowse.el
+       (semanticdb-create-ebrowse-database):
+       * lisp/gnus/mail-source.el (mail-source-movemail-program):
+       * lisp/hexl.el (hexl-program):
+       * lisp/htmlfontify.el (hfy-etags-bin):
+       * lisp/ielm.el (inferior-emacs-lisp-mode):
+       * lisp/mail/rmail.el (rmail-autodetect):
+       (rmail-insert-inbox-text):
+       * lisp/org/org-ctags.el (org-ctags-path-to-ctags):
+       * lisp/progmodes/cperl-mode.el (cperl-etags):
+       * lisp/speedbar.el (speedbar-fetch-etags-command):
+       * lisp/textmodes/reftex-global.el (reftex-create-tags-file): Use
+       new variables.
+
+       * src/callproc.c (syms_of_callproc): Introduce new variables
+       naming binaries redistributed with Emacs.
+
+       * INSTALL.android: Update documentation.
+
+       * build-aux/ndk-build-helper-1.mk: When building shared
+       libraries, do not link libemacs.so with dependent archive files.
+
+       * build-aux/ndk-build-helper-2.mk: Add whole archive dependencies
+       as well.
+
+       * configure.ac (HAVE_JPEG): Enable on Android.
+
+       * cross/ndk-build/ndk-build-shared-library.mk: Link the shared
+       object with archive file dependencies.
+
+       * cross/ndk-build/ndk-build-static-library.mk: Build all code
+       position-independently.
+
+       * cross/ndk-build/ndk-resolve.mk: Separately resolve a names of
+       archive and whole archive dependencies.
+
+       * src/Makefile.in (JPEG_CFLAGS): New variable.
+       (EMACS_CFLAGS): Add it.
+
+2023-01-24  Po Lu  <luangruo@yahoo.com>
+
+       * INSTALL.android: Update.
+
+       * build-aux/ndk-build-helper-1.mk: Fix typo.
+
+       * configure.ac: Enable --with-json on Android.
+
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       (NDK_CFLAGS_$(LOCAL_MODULE)):
+       (LOCAL_MODULE_FILENAME):
+       * cross/ndk-build/ndk-build-static-library.mk:
+       (ALL_OBJECT_FILES$(LOCAL_MODULE)):
+       (LOCAL_MODULE_FILENAME): Recursively resolve dependencies.
+       * cross/ndk-build/ndk-resolve.mk: New function.
+
+       * doc/emacs/android.texi (Android Startup): Document how Emacs is
+       dumped during initial startup.
+
+       * java/Makefile.in (filename): Fix build with multiple shared
+       libraries.
+
+       * java/README: Improve commentary.
+
+       * java/org/gnu/emacs/EmacsApplication.java (onCreate): Look and
+       set dump file.
+
+       * java/org/gnu/emacs/EmacsNative.java (getFingerprint): New
+       function getFingerprint.
+
+       * java/org/gnu/emacs/EmacsPreferencesActivity.java (onCreate):
+       Add option to erase the dump file.
+
+       * java/org/gnu/emacs/EmacsService.java (browseUrl): New function.
+
+       * java/org/gnu/emacs/EmacsThread.java (run): Specify dump file if
+       found.
+
+       * lisp/loadup.el: Always dump during loadup on Android.
+
+       * lisp/net/browse-url.el (browse-url--browser-defcustom-type)
+       (browse-url-default-browser, browse-url-default-android-browser):
+       New browse url type.
+
+       * m4/ndk-build.m4 (ndk_package_map): Map jansson to libjansson.
+
+       * src/android.c (struct android_emacs_service): New method
+       `browse_url'.
+       (getFingerprint): New function.
+       (android_init_emacs_service): Initialize new method.
+       (android_browse_url): New function.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidselect.c (Fandroid_browse_url): New function.
+       (syms_of_androidselect): Define it.
+
+       * src/emacs.c (load_pdump): Eschew excessively elaborate dump file
+       location code on on Android.
+
+       * src/pdumper.c (Fdump_emacs_portable): Allow dumping while
+       interactive on Android.
+       (syms_of_pdumper): New variable `pdumper-fingerprint'.
+
+       * src/sfntfont-android.c (sfntfont_android_composite_bitmap): Fix
+       unused variables.
+
+2023-01-24  Po Lu  <luangruo@yahoo.com>
+
+       * admin/merge-gnulib: Fix paths for rename.
+
+       * lib-src/Makefile.in (DONT_INSTALL, clean): Correctly define
+       asset-directory-tool.
+
+       * cross/Makefile.in (distclean bootstrap-clean): Remove Makefile.
+
+2023-01-24  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Update with new files.  Do not ignore std*.in.h.
+
+       * INSTALL.android: Explain how to build Emacs with external
+       dependencies.
+
+       * Makefile.in (xcompile, cross): Rename to `cross'.
+       (clean_dirs): Clean cross, not xcompile.
+
+       * README: Document new directories.
+
+       * build-aux/ndk-build-helper-1.mk:
+       * build-aux/ndk-build-helper-2.mk:
+       * build-aux/ndk-build-helper-3.mk:
+       * build-aux/ndk-build-helper-4.mk:
+       * build-aux/ndk-build-helper.mk (NDK_BUILD_DIR, my-dir):
+       * build-aux/ndk-module-extract.awk: New files.
+       * configure.ac: Set up libgif, libwebp, and libpng for ndk-build.
+
+       * cross/ndk-build/Makefile.in:
+       * cross/ndk-build/ndk-build-executable.mk:
+       * cross/ndk-build/ndk-build-shared-library.mk:
+       * cross/ndk-build/ndk-build-static-library.mk:
+       * cross/ndk-build/ndk-build.in:
+       * cross/ndk-build/ndk-build.mk.in:
+       * cross/ndk-build/ndk-clear-vars.mk:
+       * cross/ndk-build/ndk-prebuilt-shared-library.mk:
+       * cross/ndk-build/ndk-prebuilt-static-library.mk: New files.
+
+       * doc/emacs/android.texi (Android, Android Environment): Document
+       clipboard support on Android.
+
+       * doc/emacs/emacs.texi (Top): Update menus.
+
+       * etc/MACHINES: Document Android.
+
+       * java/AndroidManifest.xml.in: Respect new `--with-android-debug'
+       option.
+
+       * java/Makefile.in (CROSS_BINS, CROSS_LIBS): Adjust for rename.
+       Include ndk-build.mk.
+       (emacs.apk-in): Depend on shared libraries.  Then, package shared
+       libraries.
+
+       * java/org/gnu/emacs/EmacsClipboard.java: New file.
+
+       * java/org/gnu/emacs/EmacsFontDriver.java: Update comment to say
+       this is unused.
+
+       * java/org/gnu/emacs/EmacsNative.java (sendExpose): New function
+       `sendExpose'.
+
+       * java/org/gnu/emacs/EmacsSdk11Clipboard.java:
+       * java/org/gnu/emacs/EmacsSdk8Clipboard.java: New files.
+
+       * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap)
+       (onDetachedFromWindow): When window is reattached, expose the
+       frame.
+
+       * lib/Makefile.in (VPATH, ALL_CFLAGS): Adjust for rename.
+
+       * lisp/term/android-win.el (android-clipboard-exists-p)
+       (android-get-clipboard, android-set-clipboard)
+       (android-clipboard-owner-p, android-primary-selection)
+       (android-get-clipboard-1, android-get-primary)
+       (android-selection-bounds, android-encode-select-string)
+       (gui-backend-get-selection, gui-backend-selection-exists-p)
+       (gui-backend-selection-owner-p, gui-backend-set-selection): New
+       functions.
+
+       * m4/ndk-build.m4: New file.
+
+       * src/Makefile.in (GIF_CFLAGS, ANDROID_LDFLAGS): New variables.
+       (EMACS_CFLAGS): Add GIF_CFLAGS.  Include ndk-build.mk.
+       (libemacs.so): Depend on and link with required libraries.
+
+       * src/android.c (android_check_compressed_file): New function.
+       (android_open): Work around Android platform bug.
+       (sendExpose): New function.
+       (android_readdir): Set d_type if this is a directory.
+
+       * src/androidgui.h (enum android_event_type)
+       (struct android_expose_event, union android_event): Add expose
+       events.
+
+       * src/androidselect.c: New file.
+
+       * src/androidterm.c (handle_one_android_event) <ANDROID_EXPOSE>:
+       Handle exposures.
+
+       * src/androidterm.h: Update prototypes.
+
+       * src/emacs.c (android_emacs_init): Initialize androidselect.
+
+       * xcompile: Move to cross.
+       * cross: New directory.
+
+2023-01-21  Po Lu  <luangruo@yahoo.com>
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document
+       changes.
+
+       * lisp/touch-screen.el (touch-screen-current-tool): Update doc
+       string.
+       (touch-screen-precision-scroll): New user option.
+       (touch-screen-handle-scroll): Use traditional scrolling by
+       default.
+       (touch-screen-handle-touch): Adust format of
+       touch-screen-current-tool.
+       (touch-screen-track-tap): Don't print waiting for events.
+       (touch-screen-track-drag): Likewise.  Also, don't call UPDATE
+       until threshold is reached.
+       (touch-screen-drag-mode-line-1, touch-screen-drag-mode-line):
+       Improve window dragging.
+
+       * src/fileio.c (Fverify_visited_file_modtime): Fix fs check.
+
+2023-01-21  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android File System): Document that
+       ls-lisp is now used by default.
+
+       * java/org/gnu/emacs/EmacsThread.java (EmacsThread): Name the
+       thread something meaningful.
+
+       * lisp/loadup.el (featurep): Load ls-lisp on Android.
+
+       * lisp/ls-lisp.el (ls-lisp-use-insert-directory-program): Default
+       to off on Android.
+
+       * src/android.c (android_is_directory): New fucntion.
+       (android_fstatat): Handle directories created by
+       `android_opendir'.
+       (android_open): Return meaningful file mode.
+       (struct android_dir): New fields `next', `asset_file' and `fd'.
+       (android_opendir): Populate those fields.
+       (android_dirfd): New function.
+       (android_closedir): Close file descriptor if set.
+       (android_lookup_asset_directory_fd): New function.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfont.c (androidfont_check_init): New function.
+       (androidfont_list, androidfont_match, androidfont_draw)
+       (androidfont_open_font, androidfont_close_font)
+       (androidfont_has_char, androidfont_encode_char)
+       (androidfont_text_extents, androidfont_list_family): Initialize
+       font driver if necessary.
+       (init_androidfont): Don't initialize Java font if necessary.
+
+       * src/dired.c (open_directory): Return android_dirfd if
+       appropriate.
+       (directory_files_internal, file_name_completion_dirp): Implement
+       correctly for Android.
+
+       * src/fileio.c (check_mutable_filename): New function.
+       (Fcopy_file, Fdelete_directory_internal, Fdelete_file)
+       (Frename_file, Fadd_name_to_file, Fmake_symbolic_link)
+       (Fset_file_modes, Fset_file_times, Ffile_newer_than_file_p)
+       (Fverify_visited_file_modtime, Fset_visited_file_modtime): Check
+       that files being written to do not lie in /assets.
+
+       * src/sfntfont-android.c (GET_SCANLINE_BUFFER)
+       (sfntfont_android_u255to256, sfntfont_android_over_8888_1)
+       (sfntfont_android_over_8888, sfntfont_android_composite_bitmap):
+       Optimize for 64-bit ARM devices.
+       (sfntfont_android_put_glyphs): Optimize away memset if background
+       need not be filled.
+
+2023-01-20  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_run_select_thread, android_select)
+       (android_ftruncate):
+       * src/android.h (ftruncate): Fix compilation on Android 16 and up.
+
+       * src/android.c (android_run_select_thread, android_init_events)
+       (android_select): Add alternative android_select implementation
+       for API 16 and lower.
+
+       * src/androidterm.c (handle_one_android_event): Fix
+       use-after-frees.
+
+       * xcompile/lib/gnulib.mk.in: Delete.
+
+2023-01-20  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Don't ignore verbose.mk.android.
+
+       * doc/emacs/Makefile.in (EMACSSOURCES): Add android.texi and
+       input.texi.
+
+       * doc/emacs/android.texi (Android): Document support for the
+       on-screen keyboard.
+       (Android Startup): Document how to start Emacs with -Q on Android.
+       (Android Environment): Document how Emacs circumvents the system
+       ``task killer''.  Document changes to frame deletion behavior.
+
+       * doc/emacs/emacs.texi (Top):
+       * doc/emacs/input.texi (Other Input Devices)
+       (On-Screen Keyboards): Document how to use Emacs with virtual
+       keyboards.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document changes
+       to `touch-screen-track-drag'.
+
+       * doc/lispref/frames.texi (Frames, On-Screen Keyboards): New node.
+
+       * java/AndroidManifest.xml.in: Add settings activity and
+       appropriate OSK adjustment mode.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate): Allow creating
+       Emacs with -Q.
+       (onDestroy): Don't remove if killed by the system.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems): Fix
+       context menus again.
+
+       * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Make all
+       event sending functions return long.
+
+       * java/org/gnu/emacs/EmacsPreferencesActivity.java: New fle.
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService)
+       (onStartCommand, onCreate, startEmacsService): Start as a
+       foreground service if necessary to bypass system restrictions.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
+       * java/org/gnu/emacs/EmacsThread.java (run):
+       * java/org/gnu/emacs/EmacsView.java (onLayout)
+       (onDetachedFromWindow):
+       * java/org/gnu/emacs/EmacsWindow.java (viewLayout):
+       Implement frame resize synchronization.
+
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+       (removeWindowConsumer): Adjust accordingly for changes to frame
+       deletion behavior.
+
+       * lisp/frame.el (android-toggle-on-screen-keyboard)
+       (frame-toggle-on-screen-keyboard): New functions.
+
+       * lisp/minibuffer.el (minibuffer-setup-on-screen-keyboard)
+       (minibuffer-exit-on-screen-keyboard): New functions.
+       (minibuffer-setup-hook, minibuffer-exit-hook): Add new functions
+       to hooks.
+
+       * lisp/touch-screen.el (touch-screen-relative-xy): Accept new
+       value of window `frame'.  Return frame coordinates in that case.
+       (touch-screen-set-point-commands): New variable.
+       (touch-screen-handle-point-up): Respect that variable.
+       (touch-screen-track-drag): Return `no-drag' where appropriate.
+       (touch-screen-drag-mode-line-1, touch-screen-drag-mode-line):
+       Refactor to use `no-drag'.
+
+       * src/android.c (struct android_emacs_window): New methods.  Make
+       all event sending functions return the event serial.
+       (android_toggle_on_screen_keyboard, android_window_updated): New
+       functions.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfns.c (Fandroid_toggle_on_screen_keyboard)
+       (syms_of_androidfns): New functions.
+
+       * src/androidgui.h (struct android_any_event)
+       (struct android_key_event, struct android_configure_event)
+       (struct android_focus_event, struct android_window_action_event)
+       (struct android_crossing_event, struct android_motion_event)
+       (struct android_button_event, struct android_touch_event)
+       (struct android_wheel_event, struct android_iconify_event)
+       (struct android_menu_event): Add `serial' fields.
+
+       * src/androidterm.c (handle_one_android_event)
+       (android_frame_up_to_date):
+
+       * src/androidterm.h (struct android_output): Implement frame
+       resize synchronization.
+
+       * xcompile/verbose.mk.android: New file.
+
+2023-01-19  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Add new files.
+
+       * INSTALL.android: Explain how to build Emacs for ancient versions
+       of Android.
+
+       * admin/merge-gnulib (GNULIB_MODULES): Add getdelim.
+
+       * build-aux/config.guess (timestamp, version):
+       * build-aux/config.sub (timestamp, version): Autoupdate.
+
+       * configure.ac (BUILD_DETAILS, ANDROID_MIN_SDK):
+       (ANDROID_STUBIFY): Allow specifying CFLAGS via ANDROID_CFLAGS.
+       Add new configure tests for Android API version when not
+       explicitly specified.
+
+       * doc/emacs/android.texi (Android): Add reference to ``Other
+       Input Devices''.
+       (Android File System): Remove restrictions on directory-files on
+       the assets directory.
+
+       * doc/emacs/emacs.texi (Top): Add Other Input Devices to menu.
+
+       * doc/emacs/input.texi (Other Input Devices): New node.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document
+       changes to touchscreen input events.
+
+       * doc/lispref/frames.texi (Pop-Up Menus): Likewise.
+
+       * etc/NEWS: Announce changes.
+
+       * java/Makefile.in: Use lib-src/asset-directory-tool to generate
+       an `directory-tree' file placed in /assets.
+
+       * java/debug.sh: Large adjustments to support Android 2.2 and
+       later.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems):
+       * java/org/gnu/emacs/EmacsCopyArea.java (perform):
+       * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog):
+       * java/org/gnu/emacs/EmacsDrawLine.java (perform):
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform):
+       * java/org/gnu/emacs/EmacsDrawable.java (EmacsDrawable):
+       * java/org/gnu/emacs/EmacsFillPolygon.java (perform):
+       * java/org/gnu/emacs/EmacsFillRectangle.java (perform):
+       * java/org/gnu/emacs/EmacsGC.java:
+       * java/org/gnu/emacs/EmacsPixmap.java (destroyHandle):
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java (draw): Avoid
+       redundant canvas saves and restores.
+
+       * java/org/gnu/emacs/EmacsService.java (run):
+       * java/org/gnu/emacs/EmacsView.java (EmacsView):
+       (handleDirtyBitmap):
+       * java/org/gnu/emacs/EmacsWindow.java (changeWindowBackground)
+       (EmacsWindow): Make compatible with Android 2.2 and later.
+
+       * lib-src/Makefile.in (DONT_INSTALL): Add asset-directory-tool
+       on Android.
+       (asset-directory-tool${EXEEXT}): New target.
+
+       * lib-src/asset-directory-tool.c (struct directory_tree, xmalloc)
+       (main_1, main_2, main): New file.
+
+       * lib, m4: Merge from gnulib.  This will be reverted before
+       merging to master.
+
+       * lisp/button.el (button-map, push-button):
+       * lisp/frame.el (display-popup-menus-p): Improve touchscreen
+       support.
+
+       * lisp/subr.el (event-start, event-end): Handle touchscreen
+       events.
+
+       * lisp/touch-screen.el (touch-screen-handle-timeout)
+       (touch-screen-handle-point-update, touch-screen-handle-point-up)
+       (touch-screen-track-tap, touch-screen-track-drag)
+       (touch-screen-drag-mode-line-1, touch-screen-drag-mode-line): New
+       function
+       ([mode-line touchscreen-begin])
+       ([bottom-divider touchscreen-begin]): Bind new events.
+
+       * lisp/wid-edit.el (widget-event-point, widget-keymap)
+       (widget-event-start, widget-button--check-and-call-button)
+       (widget-button-click): Improve touchscreen support.
+
+       * src/alloc.c (make_lisp_symbol): Avoid ICE on Android NDK GCC.
+       (mark_pinned_symbols): Likewise.
+
+       * src/android.c (struct android_emacs_window): New struct.
+       (window_class): New variable.
+       (android_run_select_thread): Add workaround for Android platform
+       bug.
+       (android_extract_long, android_scan_directory_tree): New
+       functions.
+       (android_file_access_p): Use those functions instead.
+       (android_init_emacs_window): New function.
+       (android_init_emacs_gc_class): Update signature of `markDirty'.
+       (android_change_gc, android_set_clip_rectangles): Tell the GC
+       whether or not clip rects were dirtied.
+       (android_swap_buffers): Do not look up method every time.
+       (struct android_dir): Adjust for new directory tree lookup.
+       (android_opendir, android_readdir, android_closedir): Likewise.
+       (android_four_corners_bilinear): Fix coding style.
+       (android_ftruncate): New function.
+
+       * src/android.h: Update prototypes.  Replace ftruncate with
+       android_ftruncate when necessary.
+
+       * src/androidterm.c (handle_one_android_event): Pacify GCC.  Fix
+       touch screen tool bar bug.
+
+       * src/emacs.c (using_utf8): Fix compilation error.
+
+       * src/fileio.c (Ffile_system_info): Return Qnil when fsusage.o
+       is not built.
+
+       * src/filelock.c (BOOT_TIME_FILE): Fix definition for Android.
+
+       * src/frame.c (Fx_parse_geometry): Fix uninitialized variable
+       uses.
+
+       * src/keyboard.c (lispy_function_keys): Fix `back'.
+
+       * src/menu.c (x_popup_menu_1): Handle touch screen events.
+       (Fx_popup_menu): Document changes.
+
+       * src/sfnt.c (main): Improve tests.
+
+       * src/sfntfont-android.c (sfntfont_android_put_glyphs): Fix
+       minor problem.
+       (init_sfntfont_android): Check for
+       HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL.
+
+       * src/sfntfont.c (struct sfnt_font_desc): New fields `adstyle' and
+       `languages'.
+       (sfnt_parse_style): Append tokens to adstyle.
+       (sfnt_parse_languages): New function.
+       (sfnt_enum_font_1): Parse supported languages and adstyle.
+       (sfntfont_list_1): Handle new fields.
+       (sfntfont_text_extents): Fix uninitialized variable use.
+       (syms_of_sfntfont, mark_sfntfont): Adjust accordingly.
+
+2023-01-17  Po Lu  <luangruo@yahoo.com>
+
+       * doc/emacs/android.texi (Android Fonts): Document that TTC format
+       fonts are now supported.
+
+       * doc/emacs/emacs.texi (Top): Fix menus.
+
+       * doc/lispref/commands.texi (Touchscreen Events)
+       (Key Sequence Input): Document changes to touchscreen events.
+
+       * etc/DEBUG: Describe how to debug 64 bit binaries on Android.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java (perform): Explicitly
+       recycle copy bitmap.
+
+       * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog): New class.
+
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Use 5
+       point PolyLine like X, because Android behaves like Postscript on
+       some devices and X elsewhere.
+
+       * java/org/gnu/emacs/EmacsFillRectangle.java (perform): Explicitly
+       recycle copy bitmap.
+
+       * java/org/gnu/emacs/EmacsPixmap.java (destroyHandle): Explicitly
+       recycle bitmap and GC if it is big.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView): Make
+       `bitmapDirty' a boolean.
+       (handleDirtyBitmap): Reimplement in terms of that boolean.
+       Explicitly recycle old bitmap and GC.
+       (onLayout): Fix lock up.
+       (onDetachedFromWindow): Recycle bitmap and GC.
+
+       * java/org/gnu/emacs/EmacsWindow.java (requestViewLayout):
+       Update call to explicitlyDirtyBitmap.
+
+       * src/android.c (android_run_select_thread, android_select):
+       Really fix android_select.
+       (android_build_jstring): New function.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidmenu.c (android_process_events_for_menu): Totally
+       unblock input before process_pending_signals.
+       (android_menu_show): Remove redundant unblock_input and debugging
+       code.
+       (struct android_emacs_dialog, android_init_emacs_dialog)
+       (android_dialog_show, android_popup_dialog, init_androidmenu):
+       Implement popup dialogs on Android.
+
+       * src/androidterm.c (android_update_tools)
+       (handle_one_android_event, android_frame_up_to_date): Allow
+       tapping tool bar items.
+       (android_create_terminal): Add dialog hook.
+       (android_wait_for_event): Adjust call to android_select.
+
+       * src/androidterm.h (struct android_touch_point): New field
+       `tool_bar_p'.
+
+       * src/keyboard.c (read_key_sequence, head_table)
+       (syms_of_keyboard): Prefix touchscreen events with posn.
+
+       * src/keyboard.h (EVENT_HEAD): Handle touchscreen events.
+
+       * src/process.c (wait_reading_process_output): Adjust call to
+       android_select.
+
+       * src/sfnt.c (sfnt_read_table_directory): If the first long turns
+       out to be ttcf, return -1.
+       (sfnt_read_ttc_header): New function.
+       (main): Test TTC support.
+
+       * src/sfnt.h (struct sfnt_ttc_header): New structure.
+       (enum sfnt_ttc_tag): New enum.
+
+       * src/sfntfont-android.c
+       (struct sfntfont_android_scanline_buffer): New structure.
+       (GET_SCANLINE_BUFFER): New macro.  Try to avoid so much malloc
+       upon accessing the scanline buffer.
+       (sfntfont_android_put_glyphs): Do not use SAFE_ALLOCA to allocate
+       the scaline buffer.
+       (Fandroid_enumerate_fonts): Enumerate ttc fonts too.
+
+       * src/sfntfont.c (struct sfnt_font_desc): New field `offset'.
+       (sfnt_enum_font_1): Split out enumeration code from
+       sfnt_enum_font.
+       (sfnt_enum_font): Read TTC tables and enumerate each font therein.
+       (sfntfont_open): Seek to the offset specified.
+
+       * xcompile/Makefile.in (maintainer-clean): Fix depends here.
+
+2023-01-16  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (sfnt_decompose_compound_glyph): Correct treatment of
+       the Y offset in components with ARG_1_AND_2_ARE_WORDS.
+       (main): Update debugging code.
+
+       * doc/emacs/android.texi (Android, Android Environment): Improve
+       documentation.
+
+       * doc/lispref/commands.texi (Touchscreen Events): Document changes
+       to touchscreen support.
+
+       * doc/lispref/display.texi (Defining Faces, Window Systems):
+       * doc/lispref/frames.texi (Frame Layout)
+       (Font and Color Parameters):
+       * doc/lispref/os.texi (System Environment): Document Android in
+       various places.
+
+       * java/org/gnu/emacs/EmacsWindow.java (figureChange): Fix crash.
+
+       * lisp/loadup.el ("touch-screen"): Load touch-screen.el.
+
+       * lisp/pixel-scroll.el: Autoload two functions.
+
+       * lisp/term/android-win.el: Add require 'touch-screen.
+
+       * lisp/touch-screen.el (touch-screen-current-tool)
+       (touch-screen-current-timer, touch-screen-delay)
+       (touch-screen-relative-xy, touch-screen-handle-scroll)
+       (touch-screen-handle-timeout, touch-screen-handle-point-update)
+       (touch-screen-handle-point-up, touch-screen-handle-touch)
+       (global-map, touch-screen): New file.
+
+       * src/android.c (android_run_debug_thread): Fix build on 64 bit
+       systems.
+       (JNICALL, android_put_pixel): Likewise.
+       (android_transform_coordinates, android_four_corners_bilinear)
+       (android_fetch_pixel_bilinear, android_project_image_bilinear)
+       (android_fetch_pixel_nearest_24, android_fetch_pixel_nearest_1)
+       (android_project_image_nearest): New functions.
+
+       * src/androidgui.h (struct android_transform): New structure.
+
+       * src/androidterm.c (android_note_mouse_movement): Remove obsolete
+       TODO.
+       (android_get_scale_factor): New function.
+       (android_draw_underwave): Scale underwave correctly.
+
+       * src/dispextern.h: Support native image transforms on Android.
+
+       * src/image.c (matrix_identity, matrix_rotate)
+       (matrix_mirror_horizontal, matrix_translate): New functions.
+       (image_set_transform): Implement native image transforms on
+       Android.
+       (Fimage_transforms_p): Implement on Android.
+
+       * src/keyboard.c (make_lispy_event, syms_of_keyboard): Handle
+       touch screen menu bar events.
+
+       * src/sfnt.c: Fix typo in comment.
+
+       * src/sfntfont-android.c (sfntfont_android_blend, U255TO256)
+       (sfntfont_android_put_glyphs): Prevent redundant swizzling.
+
+       * src/sfntfont.c (sfntfont_lookup_char): Fix build on 64 bit
+       systems.
+
+2023-01-15  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate): Set the
+       default theme to Theme.DeviceDefault.NoActionBar if possible.
+       (onContextMenuClosed): Add hack for Android bug.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu)
+       (onMenuItemClick): Set flag upon submenu selection.
+       (inflateMenuItems): Set onClickListener for submenus as well.
+       (display1): Clear new flag.
+
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix
+       rectangle bounds.
+
+       * java/org/gnu/emacs/EmacsNative.java (setEmacsParams): New
+       argument for the cache directory.
+
+       * java/org/gnu/emacs/EmacsService.java (onCreate): Pass cache
+       directory.
+       (sync): New function.
+
+       * src/android.c (struct android_emacs_service): New method `sync'.
+       (setEmacsParams, initEmacs): Handle cache directory.
+       (android_init_emacs_service): Initialize new method `sync'.
+       (android_sync): New function.
+
+       * src/androidfns.c (Fx_show_tip): Call both functions.
+
+       * src/androidgui.h: Update prototypes.
+
+       * src/androidmenu.c (struct android_menu_subprefix)
+       (android_free_subprefixes, android_menu_show): Handle submenu
+       prefixes correctly.
+
+       * src/androidterm.c (handle_one_android_event): Clear help echo
+       on MotionNotify like on X.
+
+       * src/menu.c (single_menu_item): Enable submenus on Android.
+
+2023-01-15  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsActivity.java (onContextMenuClosed):
+       New function.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New
+       field `itemAlreadySelected'.
+       (onMenuItemClick): New function.
+       (inflateMenuItems): Attach onClickListener as appropriate.
+       (display1): Clear itemAlreadySelected.
+       (display): Fix runnable synchronization.
+
+       * java/org/gnu/emacs/EmacsNative.java (sendContextMenu): New
+       function.
+
+       * java/org/gnu/emacs/EmacsView.java (popupMenu):
+       (cancelPopupMenu): Set popupactive correctly.
+
+       * src/android.c (android_run_select_thread): Fix android_select
+       again.
+       (android_wait_event): New function.
+
+       * src/android.h: Update prototypes.
+       * src/androidgui.h (enum android_event_type): New
+       `ANDROID_CONTEXT_MENU' event.
+       (struct android_menu_event, union android_event): Add new event.
+
+       * src/androidmenu.c (struct android_emacs_context_menu): New
+       structure.
+       (android_init_emacs_context_menu): Add `dismiss' method.
+       (struct android_dismiss_menu_data): New structure.
+       (android_dismiss_menu, android_process_events_for_menu): New
+       functions.
+       (android_menu_show): Set an actual item ID.
+       (popup_activated): Define when stubify as well.
+       (Fmenu_or_popup_active_p): New function.
+       (syms_of_androidmenu): New function.
+
+       * src/androidterm.c (handle_one_android_event): Handle context
+       menu events.
+
+       * src/androidterm.h (struct android_display_info): New field for
+       menu item ID.
+
+       * src/emacs.c (android_emacs_init): Call syms_of_androidmenu.
+
+       * src/xdisp.c (note_mouse_highlight): Return if popup_activated on
+       Android as well.
+
+2023-01-14  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_run_select_thread, android_select):
+       Handle EINTR in sem_wait and fix sigsets.
+
+       * xcompile/lib/fpending.c (__fpending): Fix gnulib problem.
+
+       * xcompile/lib/fpending.c (__fpending):
+       * xcompile/lib/open.c:
+       * xcompile/lib/unistd.c (_GL_UNISTD_INLINE): Remove Android
+       patches.
+
+2023-01-14  Po Lu  <luangruo@yahoo.com>
+
+       * java/Makefile.in (clean): Fix distclean and bootstrap-clean
+       rules.
+       * java/debug.sh (jdb_port, attach_existing, num_pids, line): Add
+       new options to upload a gdbserver binary to the device.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): Make
+       focusedActivities public.
+
+       * java/org/gnu/emacs/EmacsContextMenu.java: New file.
+
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix
+       bounds computation.
+
+       * java/org/gnu/emacs/EmacsGC.java (markDirty): Expressly provide
+       stroke width.
+
+       * java/org/gnu/emacs/EmacsService.java (getLocationOnScreen)
+       (nameKeysym): New functions.
+
+       * java/org/gnu/emacs/EmacsView.java (<init>): Disable focus
+       highlight.
+       (onCreateContextMenu, popupMenu, cancelPopupMenu): New functions.
+
+       * java/org/gnu/emacs/EmacsWindow.java: Implement a kind of
+       ``override redirect'' window for tooltips.
+
+       * src/android.c (struct android_emacs_service): New method
+       `name_keysym'.
+       (android_run_select_thread, android_init_events):
+       (android_select): Release select thread on semaphores instead of
+       signals to avoid one nasty race on SIGUSR2 delivery.
+       (android_init_emacs_service): Initialize new method.
+       (android_create_window): Handle CW_OVERRIDE_REDIRECT.
+       (android_move_resize_window, android_map_raised)
+       (android_translate_coordinates, android_get_keysym_name)
+       (android_build_string, android_exception_check): New functions.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfns.c (android_set_parent_frame, Fx_create_frame)
+       (unwind_create_tip_frame, android_create_tip_frame)
+       (android_hide_tip, compute_tip_xy, Fx_show_tip, Fx_hide_tip)
+       (syms_of_androidfns): Implement tooltips and iconification
+       reporting.
+
+       * src/androidgui.h (enum android_window_value_mask): Add
+       CWOverrideRedirect.
+       (struct android_set_window_attributes): Add `override_redirect'.
+       (ANDROID_IS_MODIFIER_KEY): Recognize Caps Lock.
+
+       * src/androidmenu.c (struct android_emacs_context_menu): New
+       struct.
+       (android_init_emacs_context_menu, android_unwind_local_frame)
+       (android_push_local_frame, android_menu_show, init_androidmenu):
+       New functions.
+
+       * src/androidterm.c (handle_one_android_event): Fix NULL pointer
+       dereference.
+       (android_fullscreen_hook): Handle fullscreen correctly.
+       (android_draw_box_rect): Fix top line.
+       (get_keysym_name): Implement function.
+       (android_create_terminal): Remove scroll bar stubs and add menu
+       hook.
+
+       * src/androidterm.h: Update prototypes.
+       * src/emacs.c (android_emacs_init): Initialize androidmenu.c.
+       * xcompile/Makefile.in: Fix clean rules.
+
+2023-01-14  Po Lu  <luangruo@yahoo.com>
+
+       * .gitignore: Add new files.
+
+       * INSTALL.android: New file.
+
+       * Makefile.in (clean_dirs): Clean xcompile as well.
+
+       * admin/merge-gnulib (avoided_flags): Import Gnulib into Android
+       directory as well.
+
+       * doc/emacs/android.texi (Android):
+       * doc/emacs/emacs.texi (Top): New node `Android'.
+
+       * java/org/gnu/emacs/EmacsThread.java (run): Use right executable
+       name.
+
+       * lib/Makefile.in (ANDROID_CFLAGS): Use better way to refer to
+       /src.
+       (vpath): Delete ugly block of vpath statements.
+       (mostlyclean): Remove Makefile.android.
+
+       * lib/fpending.c (__fpending):
+       * lib/open.c:
+       * lib/unistd.c (_GL_UNISTD_INLINE): Revert changes to gnulib in
+       lib/.
+
+       * src/android.h:
+       * src/androidterm.c: Fix build.
+       * xcompile/Makefile.in (LIB_SRCDIR, LIBSRC_BINARIES)
+       (src/verbose.mk, PRE_BUILD_DEPS, PHONY): Use gnulib in
+       xcompile/lib/ as opposed to lib/.
+
+       * xcompile/README: Adjust README.
+
+       * xcompile/lib: Check-in Gnulib with patches for Android.
+
+2023-01-13  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsService.java (queryTree): Fix NULL
+       pointer dereference.
+
+       * src/android.c (android_query_tree): Set *nchildren_return.
+
+       * .gitignore: Add AndroidManifest.xml.
+
+       * java/AndroidManifest.xml: Remove file that is now generated.
+
+       * src/frame.c (make_monitor_attribute_list): Allow source to be NULL.
+
+       * configure.ac (ANDROID_MIN_SDK): New variable.
+       (DX): Remove and replace with D8.
+       (XCONFIGURE): Check for the minimum version of Android the cross
+       compiler compiles for.  Generate java/AndroidManifest.xml from
+       java/AndroidManifest.xml.in.  Allow using Zlib on Android.
+
+       * java/AndroidManifest.xml.in: New file.  Use the minimum SDK
+       detected by configure.
+
+       * java/Makefile.in (top_srcdir, version): New variables.
+       (DX, D8): Replace with D8.
+       (ANDROID_MIN_SDK, APK_NAME): New variables.
+       (.PHONY, .PRECIOUS, classes.dex, emacs.apk): Generate $(APK_NAME)
+       rather than `emacs.apk'.
+
+       * java/debug.sh: New option --attach-existing.  Attach to an
+       existing Emacs instance when specified.
+
+       * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field
+       `isPaused'.
+       (invalidateFocus1): Fix infinite recursion.
+       (detachWindow): Deiconify window.
+       (attachWindow): Iconify the window if the activity is paused.
+       (onCreate): Use the ``no title bar'' theme.
+       (onPause, onResume): New functions.
+
+       * java/org/gnu/emacs/EmacsNative.java (sendTouchUp, sendTouchDown)
+       (sendTouchMove, sendWheel, sendIconified, sendDeiconified): New
+       functions.
+
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java (Sdk7Typeface):
+       (list): Remove logging for code that is mostly going to be unused.
+
+       * java/org/gnu/emacs/EmacsService.java (ringBell, queryTree)
+       (getScreenWidth, getScreenHeight, detectMouse): New functions.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView)
+       (surfaceChanged, surfaceCreated, surfaceDestroyed): Add extra
+       debug logging.  Avoid deadlock in surfaceCreated.
+
+       * java/org/gnu/emacs/EmacsView.java (EmacsView): Try very hard to
+       make the SurfaceView respect Z order.  It didn't work.
+       (handleDirtyBitmap): Copy over the contents from the old bitmap.
+       (explicitlyDirtyBitmap): New function.
+       (onLayout): Don't dirty bitmap if unnecessary.
+       (damageRect, swapBuffers): Don't synchronize so hard.
+       (onTouchEvent): Call window.onTouchEvent instead.
+       (moveChildToBack, raise, lower): New functions.
+
+       * java/org/gnu/emacs/EmacsWindow.java (Coordinate): New
+       subclass.
+       (pointerMap, isMapped, isIconified, dontFocusOnMap)
+       (dontAcceptFocus): New fields.
+       (EmacsWindow): Don't immediately register unmapped window.
+       (viewLayout): Send configure event outside the lock.
+       (requestViewLayout): Explicitly dirty the bitmap.
+       (mapWindow): Register the window now.  Respect dontFocusOnMap.
+       (unmapWindow): Unregister the window now.
+       (figureChange, onTouchEvent): New functions.
+       (onSomeKindOfMotionEvent): Handle scroll wheel events.
+       (reparentTo, makeInputFocus, raise, lower, getWindowGeometry)
+       (noticeIconified, noticeDeiconified, setDontAcceptFocus)
+       (setDontFocusOnMap, getDontFocusOnMap): New functions.
+
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+       (registerWindow, detachWindow): Synchronize.
+       (noticeIconified, noticeDeiconified): New functions.
+       (copyWindows): New function.
+
+       * lisp/frame.el (frame-geometry, frame-edges)
+       (mouse-absolute-pixel-position, set-mouse-absolute-pixel-position)
+       (frame-list-z-order, frame-restack, display-mouse-p)
+       (display-monitor-attributes-list): Implement on Android.
+
+       * lisp/mwheel.el (mouse-wheel-down-event, mouse-wheel-up-event)
+       (mouse-wheel-left-event, mouse-wheel-right-event): Define on
+       Android.
+
+       * src/android.c (struct android_emacs_service): New methods
+       `ringBell', `queryTree', `getScreenWidth', `getScreenHeight',
+       and `detectMouse'.
+       (struct android_event_queue, android_init_events)
+       (android_next_event, android_write_event): Remove write limit.
+       (android_file_access_p): Handle directories correcty.
+       (android_close): Fix coding style.
+       (android_fclose): New function.
+       (android_init_emacs_service): Initialize new methods.
+       (android_reparent_window): Implement function.
+       (android_bell, android_set_input_focus, android_raise_window)
+       (android_lower_window, android_query_tree, android_get_geometry)
+       (android_get_screen_width, android_get_screen_height)
+       (android_get_mm_width, android_get_mm_height)
+       (android_detect_mouse)
+       (android_set_dont_focus_on_map, android_set_dont_accept_focus):
+       New functions.
+       (struct android_dir): New structure.
+       (android_opendir, android_readdir, android_closedir): New
+       functions.
+       (emacs_abort): Implement here on Android and poke debuggerd into
+       generating a tombstone.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfns.c (android_set_parent_frame): New function.
+       (android_default_font_parameter): Use sane font size by default.
+       (Fx_display_pixel_width, Fx_display_pixel_height)
+       (Fx_display_mm_width, Fx_display_mm_height)
+       (Fx_display_monitor_attributes_list): Rename to start with
+       `android-'.  Implement.  Fiddle with documentation to introduce
+       Android specific nuances.
+       (Fandroid_display_monitor_attributes_list): New function.
+       (Fx_frame_geometry, frame_geometry): New function.
+       (Fandroid_frame_geometry): Implement correctly.
+       (Fx_frame_list_z_order): Rename to start with `android-'.
+       (android_frame_list_z_order, Fandroid_frame_list_z_order):
+       Implement.
+       (Fx_frame_restack): Rename to start with `android-'.
+       (Fandroid_frame_restack): ``Implement''.
+       (Fx_mouse_absolute_pixel_position): Rename to start with
+       `android-'.
+       (Fandroid_mouse_absolute_pixel_position): ``Implement''.
+       (Fx_set_mouse_absolute_pixel_position): Rename to start with
+       `android-'.
+       (Fandroid_set_mouse_absolute_pixel_position): ``Implement''.
+       (Fandroid_detect_mouse): New function.
+       (android_set_menu_bar_lines): Use FRAME_ANDROID_DRAWABLE when
+       clearing area.
+       (android_set_no_focus_on_map, android_set_no_accept_focus): New
+       functions.
+       (android_frame_parm_handlers): Register new frame parameter
+       handlers.
+       (syms_of_androidfns): Update appropriately.
+
+       * src/androidfont.c (androidfont_draw): Use FRAME_ANDROID_DRAWABLE
+       instead of FRAME_ANDROID_WINDOW.
+
+       * src/androidgui.h (enum android_event_type): New events.
+       (struct android_touch_event, struct android_wheel_event)
+       (struct android_iconify_event): New structures.
+       (union android_event): Add new events.
+
+       * src/androidterm.c (android_clear_frame): Use
+       FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW.
+       (android_flash, android_ring_bell): Implement bell ringing.
+       (android_toggle_invisible_pointer): Don't TODO function that can't
+       be implemented.
+       (show_back_buffer, android_flush_dirty_back_buffer_on): Check if a
+       buffer flip is required before doing the flip.
+       (android_lower_frame, android_raise_frame): Implement functions.
+       (android_update_tools, android_find_tool): New functions.
+       (handle_one_android_event): Handle new iconification, wheel and
+       touch events.
+       (android_read_socket): Implement pending-autoraise-frames.
+       (android_frame_up_to_date): Implement bell ringing.
+       (android_buffer_flipping_unblocked_hook): Check if a buffer flip
+       is required before doing the flip.
+       (android_focus_frame, android_frame_highlight)
+       (android_frame_unhighlight): New function.
+       (android_frame_rehighlight): Implement functions.
+       (android_iconify_frame): Always display error.
+       (android_set_alpha): Update commentary.
+       (android_free_frame_resources): Free frame touch points.
+       (android_scroll_run, android_flip_and_flush)
+       (android_clear_rectangle, android_draw_fringe_bitmap)
+       (android_draw_glyph_string_background, android_fill_triangle)
+       (android_clear_point, android_draw_relief_rect)
+       (android_draw_box_rect, android_draw_glyph_string_bg_rect)
+       (android_draw_image_foreground, android_draw_stretch_glyph_string)
+       (android_draw_underwave, android_draw_glyph_string_foreground)
+       (android_draw_composite_glyph_string_foreground)
+       (android_draw_glyphless_glyph_string_foreground)
+       (android_draw_glyph_string, android_clear_frame_area)
+       (android_clear_under_internal_border, android_draw_hollow_cursor)
+       (android_draw_bar_cursor, android_draw_vertical_window_border)
+       (android_draw_window_divider): Use FRAME_ANDROID_DRAWABLE instead
+       of FRAME_ANDROID_WINDOW for drawing operations.
+
+       * src/androidterm.h (struct android_touch_point): New structure.
+       (struct android_output): New fields.
+       (FRAME_ANDROID_NEED_BUFFER_FLIP): New macro.
+
+       * src/dired.c (emacs_readdir, open_directory)
+       (directory_files_internal_unwind, read_dirent)
+       (directory_files_internal, file_name_completion): Add indirection
+       over readdir and opendir.  Use android variants on Android.
+
+       * src/dispnew.c (Fopen_termscript):
+       * src/fileio.c (fclose_unwind): Use emacs_fclose.
+       (Faccess_file): Call android_file_access_p.
+       (file_accessible_directory_p): Append right suffix to Android
+       assets directory.
+       (do_auto_save_unwind): Use emacs_fclose.
+
+       * src/keyboard.c (lispy_function_keys): Use right function key for
+       page up and page down.
+       (Fopen_dribble_file): Use emacs_fclose.
+
+       * src/lisp.h: New prototype emacs_fclose.
+
+       * src/lread.c (close_infile_unwind): Use emacs_fclose.
+
+       * src/sfnt.c (sfnt_curve_is_flat): Fix area-squared computation.
+       (sfnt_prepare_raster): Compute raster width and height
+       consistently with outline building.
+       (sfnt_build_outline_edges): Use the same offsets used to set offy
+       and offx.
+       (main): Adjust debug code.
+
+       * src/sfntfont-android.c (sfntfont_android_saturate32): Delete
+       function.
+       (sfntfont_android_blend, sfntfont_android_blendrgb): Remove
+       unnecessary debug code.
+       (sfntfont_android_composite_bitmap): Prevent out of bounds write.
+       (sfntfont_android_put_glyphs): Use FRAME_ANDROID_DRAWABLE.
+       (init_sfntfont_android): Initialize Monospace Serif font to
+       something sensible.
+
+       * src/sfntfont.c (sfntfont_text_extents): Clear glyph metrics
+       before summing up pcm.
+       (sfntfont_draw): Use s->font instead of s->face->font.
+
+       * src/sysdep.c (emacs_fclose): Wrap around android_fclose on
+       Android.
+
+       * src/term.c (Fsuspend_tty, delete_tty): Use emacs_fclose.
+       * src/verbose.mk.in (AM_V_DX): Replace with D8 version.
+
+2023-01-11  Po Lu  <luangruo@yahoo.com>
+
+       Bring up the sfnt-android font driver
+       * configure.ac (ANDROID_CFLAGS): Add sfnt-related font objects
+       to ANDROID_OBJ when not building stubs.
+       * lisp/startup.el (android-fonts-enumerated): New variable.
+       (normal-top-level): Set it.  Also enumerate fonts as early as
+       possible upon startup.
+
+       * src/alloc.c (cleanup_vector): Only finalize Android font
+       entities.
+       (garbage_collect): Mark sfntfont.c.
+
+       * src/android.c (struct android_emacs_drawable): New field
+       `damage_rect'.
+       (android_init_emacs_drawable): Initialize
+       Lorg/gnu/emacs/EmacsDrawable;#damageRect(Landroid/graphics/rect;)V.
+       (android_create_gc): Initialize cached GC fields.
+       (android_free_gc): Free cached GC clip rectangles.
+       (android_change_gc): Cache fields as appropriate.
+       (android_set_clip_rectangles): Set cached clip rectangles for
+       easy access from C.
+       (android_get_gc_values): Use cached values.
+       (android_get_image): Remove obsolete comment.
+       (android_lock_bitmap, android_damage_window): New functions that
+       don't parallel anything on X.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfns.c (android_default_font_parameter): Set Droid
+       Sans Mono as the default monospace font.
+       (Fx_create_frame): Register the sfntfont driver.
+
+       * src/androidgui.h (struct android_gc): Add C side caches for
+       clip rectangles and the foreground and background colors.
+
+       * src/androidterm.h: Update prototypes.
+
+       * src/dispextern.h (struct gui_box): New struct.
+       (gui_union_rectangles): New function.
+
+       * src/emacs.c (android_emacs_init): Initialize Android font
+       stuff late.
+       * src/font.c (font_make_entity): Clear `is_android' field on
+       Android.
+       (font_make_entity_android): Set `is_android' field.
+       * src/font.h (struct font_entity): New field `is_android'.
+
+       * src/print.c (print_vectorlike): Don't print private data,
+       which could include Lisp_Misc.
+
+       * src/sfnt.c (sfnt_read_cmap_format_0, sfnt_read_cmap_format_2)
+       (sfnt_read_cmap_format_4, sfnt_read_cmap_format_6)
+       (sfnt_read_cmap_format_8, sfnt_read_cmap_format_12): Remove
+       buggy pragmas.
+       (sfnt_lookup_glyph_4_1): New function.
+       (sfnt_lookup_glyph_4): Handle malformed lookup tables found on
+       Android.
+       (sfnt_lookup_glyph): Fix overflow problems in glyph checks.
+       (sfnt_read_glyph): Handle empty glyphs.  This implements some
+       behavior which everyone else seems to as well, but I can't find
+       documented in the TrueType Reference Manual.
+       (sfnt_free_glyph): Export correctly.
+       (sfnt_transform_coordinates): Make faster.
+       (sfnt_lerp_half): Fix lerping in some cases.
+       (sfnt_decompose_glyph): Handle empty glyphs correctly.  Close
+       contours manually instead of waiting for the edge building
+       process to do that.  This lets curves be handled correctly.
+       (struct sfnt_build_glyph_outline_context): Move internal struct
+       back to sfnt.c.
+       (sfnt_build_append): Fix detection of initial entry.
+       (sfnt_curve_to_and_build_1): Fix De Casteljau implementation.
+       (sfnt_curve_to_and_build): Use fixed point arithmetic to scale
+       outlines.
+       (sfnt_build_glyph_outline): Clear reference counts.  Use fixed
+       point arithmetic.
+       (sfnt_prepare_raster): Align rasters to 4 bytes,
+       SFNT_POLY_ALIGNMENT.  Fix calculation of offx and offy.
+       (sfnt_step_edge_by): Step edge by previously computed step_x.
+       (sfnt_build_outline_edges): Adjust for already closed contours.
+       Ignore edges abandoned after grid fit.  Also precompute step_x
+       to avoid multiplication on each span rastered.
+       (sfnt_poly_edges): Improve alignment.
+       (sfnt_fill_span): Rewrite to avoid control flow in while loop.
+       (sfnt_poly_span): Remove unnecessary code.
+       (sfnt_raster_glyph_outline): Use raster stride instead of width.
+       (sfnt_test_edge, sfnt_test_raster, main): Improve debugging
+       code.
+
+       * src/sfnt.h (struct sfnt_glyph_outline): Add refcount field to
+       outline.
+       (struct sfnt_build_glyph_outline_context): Remove private
+       struct.
+       (struct sfnt_raster): Add refcount field to raster.
+       (struct sfnt_edge): Improve doc.  Add `source_x' field used when
+       built with TEST.
+       (SFNT_CEIL_FIXED): New macro.
+
+       * src/sfntfont-android.c (sfntfont_android_saturate32)
+       (sfntfont_android_scale32, sfntfont_android_mul8x2)
+       (sfntfont_android_blend, U255TO256)
+       (sfntfont_android_composite_bitmap, sfntfont_android_union_boxes)
+       (sfntfont_android_put_glyphs, sfntfont_android_get_cache): New
+       functions.
+       (android_sfntfont_driver): New font driver.
+       (Fandroid_enumerate_fonts): New function.
+       (syms_of_sfntfont_android_for_pdumper, init_sfntfont_android)
+       (syms_of_sfntfont_android): Initialize default fonts, special
+       family mapping and font driver.
+       * src/sfntfont.c (struct sfnt_font_desc): New fields
+       `char_cache', `cmap_invalid' and `subtable'.
+       (sfnt_setup_coding_system): Improve commentary.  Add default
+       branch.  Fix return value.
+       (sfnt_safe_encode_coding_object_1)
+       (sfnt_safe_encode_coding_object_2):
+       (sfnt_safe_encode_coding_object): Use decode_coding_object
+       instead of encode_coding_object.
+       (sfnt_decode_font_string): Adjust for rename.
+       (sfnt_decode_foundry_name): New function.
+       (sfnt_weight_descriptions, sfnt_slant_descriptions)
+       (sfnt_width_descriptions): Fix definitions.
+       (sfnt_parse_style): Make function work.
+       (sfnt_enum_font): Initialize designer, char-cache and subtable
+       platform ID.
+       (sfntfont_charset_for_name, mark_sfntfont)
+       (sfntfont_charset_for_cmap): New functions.
+       (syms_of_sfntfont): New variable `sfnt-default-family-alist'.
+
+       * src/sfntfont.h (_SFNTFONT_H_): Update prototypes.
+
+       * src/xdisp.c (gui_union_rectangles): New function.
+
+2023-01-08  Po Lu  <luangruo@yahoo.com>
+
+       * configure.ac (ANDROID_OBJS): Add sfntfont files.
+
+       * src/sfnt.h:
+       * src/sfntfont-android.c:
+       * src/sfntfont.c:
+       * src/sfntfont.h: New files.
+
+2023-01-08  Po Lu  <luangruo@yahoo.com>
+
+       * src/android.c (android_change_gc): Fix situations where clip
+       rects are cleared.
+       (android_create_pixmap_from_bitmap_data): Fix bitmap data
+       iteration.
+
+       * src/androidfns.c (Fx_show_tip, Fx_hide_tip): Remove annoying
+       errors.
+
+       * src/androidgui.h (enum android_event_type)
+       (struct android_crossing_event)
+       (struct android_motion_event)
+       (struct android_button_event, union android_event): New crossing,
+       motion and button events.
+
+       * src/androidterm.c (android_note_mouse_movement)
+       (mouse_or_wdesc_frame, android_construct_mouse_click)
+       (handle_one_android_event, android_mouse_position)
+       (android_wait_for_event, android_set_window_size_1)
+       (android_bitmap_icon, android_free_frame_resources)
+       (syms_of_androidterm): New functions.  Handle crossing, motion and
+       button events.
+
+       * src/androidterm.h (struct android_display_info): New field
+       `last_mouse_movement_time'.
+       (struct android_output): Remove unused `need_buffer_flip' field.
+
+       * src/emacs.c (android_emacs_init): Initialize sfntfont.
+
+       * src/frame.c (syms_of_frame): Set frame_inhibit_implied_resize
+       to some reasonable value.
+
+       * src/frame.h (GCALIGNED_STRUCT): Set wait_event_type on Android.
+
+       * src/sfnt.c (eassert, TEST_STATIC, available, enum sfnt_table)
+       (sfnt_table_names, SFNT_ENDOF, struct sfnt_table_directory)
+       (sfnt_scaler_type, sfnt_coerce_fixed, struct sfnt_hhea_table)
+       (struct sfnt_cmap_table, enum sfnt_platform_id)
+       (enum sfnt_unicode_platform_specific_id)
+       (enum sfnt_macintosh_platform_specific_id)
+       (enum sfnt_microsoft_platform_specific_id)
+       (struct sfnt_cmap_encoding_subtable)
+       (struct sfnt_cmap_encoding_subtable_data)
+       (struct sfnt_cmap_format_0)
+       (struct sfnt_cmap_format_2_subheader, struct sfnt_cmap_format_2)
+       (struct sfnt_cmap_format_4, struct sfnt_cmap_format_6)
+       (struct sfnt_cmap_format_8_or_12_group, struct sfnt_cmap_format_8)
+       (struct sfnt_cmap_format_12, struct sfnt_maxp_table)
+       (struct sfnt_loca_table_short, struct sfnt_loca_table_long)
+       (struct sfnt_glyf_table, struct sfnt_simple_glyph)
+       (struct sfnt_compound_glyph_component, struct sfnt_compound_glyph)
+       (struct sfnt_glyph, sfnt_read_table_directory, file)
+       (sfnt_read_cmap_table)
+       (sfnt_read_head_table, success, sfnt_read_hhea_table)
+       (sfnt_read_loca_table_short, sfnt_read_loca_table_long)
+       (sfnt_read_maxp_table, sfnt_read_glyf_table)
+       (sfnt_read_compound_glyph, sfnt_read_glyph, struct sfnt_point)
+       (sfnt_expand_compound_glyph_context)
+       (sfnt_decompose_compound_glyph, struct sfnt_glyph_outline)
+       (enum sfnt_glyph_outline_flags)
+       (struct sfnt_build_glyph_outline_context)
+       (sfnt_build_append, sfnt_build_glyph_outline, struct sfnt_raster)
+       (struct sfnt_edge, sfnt_prepare_raster, sfnt_build_outline_edges)
+       (sfnt_raster_glyph_outline): Move structures to sfnt.h.
+       (struct sfnt_long_hor_metric, sfnt_read_hmtx_table)
+       (sfnt_lookup_glyph_metrics, sfnt_read_name_table, sfnt_find_name)
+       (sfnt_read_meta_table, sfnt_find_metadata, sfnt_test_edge_ignore):
+       New functions.
+       (main): Add new tests.
+
+2023-01-08  Po Lu  <luangruo@yahoo.com>
+
+       * java/org/gnu/emacs/EmacsPaintQueue.java
+       * java/org/gnu/emacs/EmacsPaintReq.java: Remove files.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java (perform, paintTo):
+       * java/org/gnu/emacs/EmacsDrawLine.java:
+       * java/org/gnu/emacs/EmacsDrawPoint.java:
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (paintTo):
+       * java/org/gnu/emacs/EmacsDrawable.java:
+       * java/org/gnu/emacs/EmacsFillPolygon.java:
+       * java/org/gnu/emacs/EmacsFillRectangle.java:
+       * java/org/gnu/emacs/EmacsFontDriver.java:
+       * java/org/gnu/emacs/EmacsGC.java:
+       * java/org/gnu/emacs/EmacsNative.java:
+       * java/org/gnu/emacs/EmacsPixmap.java:
+       * java/org/gnu/emacs/EmacsSdk23FontDriver.java:
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java (textExtents1)
+       (textExtents, draw):
+       * java/org/gnu/emacs/EmacsService.java (copyArea):
+       * java/org/gnu/emacs/EmacsSurfaceView.java:
+       * java/org/gnu/emacs/EmacsView.java (onLayout, onFocusChanged):
+       * java/org/gnu/emacs/EmacsWindow.java (run, resizeWindow)
+       (lockCanvas, getBitmap, onKeyDown, onKeyUp)
+       (onActivityDetached): Move rendering to main thread.  Make
+       drawing operations completely static.
+
+       * src/androidmenu.c: New file.
+
+2023-01-07  Po Lu  <luangruo@yahoo.com>
+
+       * src/sfnt.c (xmalloc, xrealloc, xfree, eassert, MIN)
+       (sfnt_table_names, SFNT_ENDOF, struct sfnt_table_directory)
+       (enum sfnt_scaler_type, sfnt_coerce_fixed, struct sfnt_hhea_table)
+       (struct sfnt_cmap_table, enum sfnt_platform_id)
+       (enum sfnt_unicode_platform_specific_id)
+       (enum sfnt_macintosh_platform_specific_id)
+       (enum sfnt_microsoft_platform_specific_id)
+       (struct sfnt_cmap_encoding_subtable)
+       (struct sfnt_cmap_encoding_subtable_data)
+       (struct sfnt_cmap_format_0, struct sfnt_cmap_format_2_subheader)
+       (struct sfnt_cmap_format_2, struct sfnt_cmap_format_4)
+       (struct sfnt_cmap_format_6, struct sfnt_cmap_format_8_or_12_group)
+       (struct sfnt_cmap_format_8, struct sfnt_cmap_format_12)
+       (struct sfnt_maxp_table, struct sfnt_loca_table_short)
+       (struct sfnt_loca_table_long, struct sfnt_glyf_table)
+       (struct sfnt_simple_glyph, struct sfnt_compound_glyph_component)
+       (struct sfnt_compound_glyph, struct sfnt_glyph, _sfnt_swap16)
+       (_sfnt_swap32, sfnt_swap16, sfnt_find_table)
+       (sfnt_read_cmap_format_0, sfnt_read_cmap_format_2)
+       (sfnt_read_cmap_format_4, sfnt_read_cmap_format_6)
+       (sfnt_read_cmap_format_8, sfnt_read_cmap_format_12)
+       (sfnt_read_cmap_table_1, sfnt_read_cmap_table)
+       (sfnt_lookup_glyph_0)
+       (sfnt_lookup_glyph_2, sfnt_bsearch_above, sfnt_compare_uint16)
+       (sfnt_lookup_glyph_4, sfnt_lookup_glyph_6, sfnt_lookup_glyph_8)
+       (sfnt_lookup_glyph_12, sfnt_lookup_glyph, sfnt_read_head_table)
+       (sfnt_read_hhea_table, sfnt_read_loca_table_short)
+       (sfnt_read_loca_table_long, sfnt_read_maxp_table)
+       (sfnt_read_glyf_table, sfnt_read_simple_glyph)
+       (sfnt_read_compound_glyph, sfnt_read_glyph, sfnt_free_glyph)
+       (struct sfnt_point, sfnt_transform_coordinates)
+       (struct sfnt_compound_glyph_context)
+       (sfnt_expand_compound_glyph_context, sfnt_round_fixed)
+       (sfnt_decompose_compound_glyph, sfnt_lerp_half)
+       (sfnt_decompose_glyph, struct sfnt_glyph_outline)
+       (enum sfnt_glyph_outline_flags)
+       (struct sfnt_build_glyph_outline_context, sfnt_build_append)
+       (sfnt_move_to_and_build, sfnt_line_to_and_build, sfnt_mul_fixed)
+       (sfnt_div_fixed, sfnt_ceil_fixed, sfnt_floor_fixed)
+       (sfnt_curve_is_flat, sfnt_curve_to_and_build_1)
+       (sfnt_curve_to_and_build, sfnt_build_glyph_outline)
+       (struct sfnt_raster, struct sfnt_edge, sfnt_poly_coverage)
+       (sfnt_poly_grid_ceil, sfnt_prepare_raster, sfnt_step_edge_by)
+       (sfnt_build_outline_edges, sfnt_compare_edges, sfnt_poly_edges)
+       (sfnt_saturate_short, sfnt_fill_span, sfnt_poly_span)
+       (sfnt_raster_span, sfnt_raster_edge, sfnt_raster_glyph_outline)
+       (struct sfnt_long_hor_metric, struct sfnt_hmtx_table)
+       (struct sfnt_glyph_metrics, sfnt_read_hmtx_table)
+       (sfnt_lookup_glyph_metrics, struct sfnt_test_dcontext)
+       (sfnt_test_move_to, sfnt_test_line_to, sfnt_test_curve_to)
+       (sfnt_test_get_glyph, sfnt_test_free_glyph, sfnt_test_span)
+       (sfnt_test_edge, sfnt_test_raster, main): New file, meant to read
+       TrueType fonts and OpenType fonts using TrueType outlines.
+
+2023-01-02  Po Lu  <luangruo@yahoo.com>
+
+       * Makefile.in (java): Depend on info.
+       (MAKEFILE_NAME, config.status): Remove unneeded changes.
+
+       * configure.ac (BUILD_DETAILS, ANDROID_STUBIFY): Don't require a
+       C++ compiler on Android.
+
+       * java/AndroidManifest.xml <EmacsActivity>: Set launchMode
+       appropriately.
+       <EmacsMultitaskActivity>: New activity.
+
+       * java/Makefile.in (CROSS_BINS): Add Emacsclient to the list of
+       binaries that will be copied into the app package.
+
+       * java/org/gnu/emacs/EmacsActivity.java (onCreate): Use the window
+       attachment manager.
+
+       * java/org/gnu/emacs/EmacsCopyArea.java (paintTo): Implement clip
+       masks correctly.
+
+       * java/org/gnu/emacs/EmacsDrawRectangle.java (getRect, paintTo):
+       Fix damage tracking rectangles.
+
+       * java/org/gnu/emacs/EmacsFontDriver.java (toString): New
+       function.
+       (FontMetrics): Fix signature of textExtents.
+
+       * java/org/gnu/emacs/EmacsMultitaskActivity.java: New file.
+
+       * java/org/gnu/emacs/EmacsNative.java (sendFocusIn, sendFocusOut)
+       (sendWindowAction): New functions.
+
+       * java/org/gnu/emacs/EmacsPaintQueue.java (run): Correct treatment
+       of the clip rectangle list.
+
+       * java/org/gnu/emacs/EmacsPixmap.java: Add constructor for mutable
+       pixmaps.
+
+       * java/org/gnu/emacs/EmacsSdk23FontDriver.java: New file.
+
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java (Sdk7Typeface)
+       (Sdk7FontEntity, Sdk7FontObject, checkMatch, hasChar, encodeChar):
+       Implement text display and fix font metrics semantics.
+
+       * java/org/gnu/emacs/EmacsService.java (EmacsService): Remove
+       availableChildren.
+       (getLibraryDirectory, onCreate): Pass pixel density to Emacs.
+       (clearArea): Fix arguments.  Switch to using the window attachment
+       manager.
+
+       * java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged)
+       (surfaceCreated): Flip buffers on surface attachment.
+
+       * java/org/gnu/emacs/EmacsView.java (swapBuffers): New argument
+       FORCE.  Always swap if it is true.
+       (onKeyMultiple, onFocusChanged): New functions.
+
+       * java/org/gnu/emacs/EmacsWindow.java (destroyHandle, run): Switch
+       to using the window attachment manager.
+
+       * java/org/gnu/emacs/EmacsWindowAttachmentManager.java: New file.
+
+       * lisp/cus-edit.el (custom-button, custom-button-mouse)
+       (custom-button-pressed):
+
+       * lisp/faces.el (tool-bar): Define faces correctly on Android.
+
+       * src/android.c (struct android_emacs_pixmap): Add mutable
+       constructor.
+       (struct android_emacs_drawable): New structure.
+       (android_write_event): Check if event queue hasn't yet been
+       initialized.
+       (android_select): Set errno to EINTR if pselect fails.
+       (android_close): Remove unused debugging code.
+       (android_get_home_directory): New function.
+       (Java_org_gnu_emacs_EmacsNative_setEmacsParams): Set pixel density
+       and compute game path.
+       (android_init_emacs_drawable): New function.
+       (Java_org_gnu_emacs_EmacsNative_sendKeyPress): New argument
+       `unicode_char'.  Pass it in events.
+       (Java_org_gnu_emacs_EmacsNative_sendKeyRelease): Likewise.
+       (Java_org_gnu_emacs_EmacsNative_sendFocusIn)
+       (Java_org_gnu_emacs_EmacsNative_sendFocusOut)
+       (Java_org_gnu_emacs_EmacsNative_sendWindowAction): New functions.
+       (android_resolve_handle): Export function.
+       (android_change_gc): Clear clip rects under the right
+       circumstances.  Set right clip mask field.
+       (android_create_pixmap_from_bitmap_data): Use correct alpha
+       channels.
+       (android_create_pixmap): Create mutable pixmap and avoid redundant
+       color array allocation.
+       (android_create_bitmap_from_data, android_create_image)
+       (android_destroy_image, android_put_pixel, android_get_pixel)
+       (android_get_image, android_put_image, faccessat): New functions.
+
+       * src/android.h: Update prototypes.
+
+       * src/androidfns.c (android_default_font_parameter): Prefer
+       monospace to Droid Sans Mono.
+
+       * src/androidfont.c (struct android_emacs_font_driver): New method
+       `draw'.
+       (struct android_emacs_font_spec): New field `dpi'.
+       (struct androidfont_info): Add font metrics cache.
+       (android_init_font_driver, android_init_font_spec): Adjust
+       accordingly.
+       (androidfont_from_lisp, androidfont_from_java): Handle new fields.
+       (androidfont_draw): Implement function.
+       (androidfont_open_font): Set pixel size correctly.
+       (androidfont_close_font): Free metrics cache.
+       (androidfont_cache_text_extents)
+       (androidfont_check_cached_extents): New functions.
+       (androidfont_text_extents): Cache glyph metrics somewhere for
+       future use.
+       (androidfont_list_family): Implement function.
+
+       * src/androidgui.h (enum android_event_type): New focus and window
+       action events.
+       (enum android_modifier_mask): New masks.
+       (struct android_key_event): New field `unicode_char'.
+       (ANDROID_IS_MODIFIER_KEY): Newmacro.
+       (struct android_focus_event, struct android_window_action_event):
+       New structs.
+       (union android_event): Add new fields.
+       (enum android_image_format, struct android_image): New enums and
+       structs.
+
+       * src/androidterm.c (android_android_to_emacs_modifiers)
+       (android_emacs_to_android_modifiers, android_lower_frame)
+       (android_raise_frame, android_new_focus_frame)
+       (android_focus_changed, android_detect_focus_change): New
+       functions.
+       (handle_one_android_event): Implement focus and key event
+       handling.
+       (android_frame_rehighlight): New function.
+       (android_frame_raise_lower): Implement accordingly.
+       (android_make_frame_invisible): Clear highlight_frame if required.
+       (android_free_frame_resources): Clear x_focus_event_frame if
+       required.
+       (android_draw_fringe_bitmap, android_draw_image_foreground)
+       (android_draw_image_foreground_1)
+       (android_draw_image_glyph_string): Remove unnecessary code.
+       (android_create_terminal, android_term_init): Set the baud rate to
+       something sensible.
+
+       * src/androidterm.h (struct android_bitmap_record): Make structure
+       the same as on X.
+       (struct android_display_info): New focus tracking fields.
+       (struct android_output): Likewise.
+
+       * src/dispextern.h (struct image): Add ximg and mask_img on
+       Android.
+
+       * src/emacs.c (android_emacs_init): Fix argc sorting iteration.
+
+       * src/fileio.c (user_homedir, get_homedir): Implement correctly on
+       Android.
+
+       * src/font.h (PT_PER_INCH): Define correctly on Android.
+
+       * src/fringe.c (X, swap_nibble, init_fringe_bitmap): Swap fringe
+       bitmaps correctly on Android.
+
+       * src/image.c (GET_PIXEL, image_create_bitmap_from_data)
+       (image_create_bitmap_from_file, free_bitmap_record)
+       (image_unget_x_image_or_dc, struct image_type)
+       (prepare_image_for_display, image_clear_image_1)
+       (image_size_in_bytes, x_check_image_size)
+       (x_create_x_image_and_pixmap, x_destroy_x_image)
+       (image_check_image_size, image_create_x_image_and_pixmap_1)
+       (image_destroy_x_image, gui_put_x_image, image_put_x_image)
+       (image_get_x_image, image_unget_x_image)
+       (Create_Pixmap_From_Bitmap_Data, image_pixmap_draw_cross)
+       (MaskForeground, image_types, syms_of_image): Implement all of the
+       above on Android in terms of an API very similar to X.
+
+       * src/keyboard.c (FUNCTION_KEY_OFFSET, lispy_function_keys):
+       Define to something sensible under Android.
+
+       * src/lread.c (build_load_history): Fix problem.
+
+2022-12-31  Po Lu  <luangruo@yahoo.com>
+
+       * .dir-locals.el (c-mode): Add ANDROID_EXPORT noise macro.
+
+       * .gitignore: Add new files to ignore.
+
+       * Makefile.in: Adjust for Android.
+
+       * admin/merge-gnulib: Add new warning.
+
+       * configure.ac: Detect Android.  Run cross-configuration for
+       Android when appropriate.
+
+       * etc/DEBUG: Document how to debug Emacs on Android.
+
+       * java/AndroidManifest.xml:
+       * java/Makefile.in:
+       * java/README:
+       * java/debug.sh:
+       * java/org/gnu/emacs/EmacsActivity.java:
+       * java/org/gnu/emacs/EmacsApplication.java:
+       * java/org/gnu/emacs/EmacsCopyArea.java:
+       * java/org/gnu/emacs/EmacsDrawLine.java:
+       * java/org/gnu/emacs/EmacsDrawPoint.java:
+       * java/org/gnu/emacs/EmacsDrawRectangle.java:
+       * java/org/gnu/emacs/EmacsDrawable.java:
+       * java/org/gnu/emacs/EmacsFillPolygon.java:
+       * java/org/gnu/emacs/EmacsFillRectangle.java:
+       * java/org/gnu/emacs/EmacsFontDriver.java:
+       * java/org/gnu/emacs/EmacsGC.java:
+       * java/org/gnu/emacs/EmacsHandleObject.java:
+       * java/org/gnu/emacs/EmacsNative.java:
+       * java/org/gnu/emacs/EmacsPaintQueue.java:
+       * java/org/gnu/emacs/EmacsPaintReq.java:
+       * java/org/gnu/emacs/EmacsPixmap.java:
+       * java/org/gnu/emacs/EmacsSdk7FontDriver.java:
+       * java/org/gnu/emacs/EmacsService.java:
+       * java/org/gnu/emacs/EmacsSurfaceView.java:
+       * java/org/gnu/emacs/EmacsThread.java:
+       * java/org/gnu/emacs/EmacsView.java:
+       * java/org/gnu/emacs/EmacsWindow.java: New files and classes.
+
+       * lib-src/Makefile.in (srcdir):
+
+       * lib/Makefile.in (VPATH, HAVE_NATIVE_COMP, libgnu_a_SOURCES):
+       (DEPFLAGS): Configure correctly for cross-compiling.
+
+       * lib/faccessat.c:
+
+       * lib/fpending.c (__fpending):
+
+       * lib/open.c:
+
+       * lib/unistd.c (_GL_UNISTD_INLINE): Temporary adjustments to
+       Gnulib.
+
+       * lisp/frame.el (display-graphic-p, display-screens)
+       (display-pixel-height, display-pixel-width, display-mm-height)
+       (display-mm-width, display-backing-store, display-save-under)
+       (display-planes, display-color-cells, display-visual-class):
+       Adjust for new window system `android'.
+
+       * lisp/image/wallpaper.el (x-open-connection): Add declaration.
+
+       * lisp/loadup.el (featurep): Load files for Android.
+
+       * lisp/net/eww.el (eww-form-submit, eww-form-file)
+       (eww-form-checkbox, eww-form-select): Adjust faces for android.
+
+       * lisp/term/android-win.el: New file.
+
+       * src/Makefile.in: Add new targets emacs.so and android-emacs,
+       then adjust for cross compilation.
+
+       * src/alloc.c (cleanup_vector): Clean up Android font entities
+       as well.
+       (garbage_collect): Mark androidterm.
+
+       * src/android-emacs.c (main):
+
+       * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags)
+       (struct android_emacs_service, struct android_emacs_pixmap)
+       (struct android_graphics_point, struct android_event_container)
+       (struct android_event_queue, android_run_select_thread)
+       (android_handle_sigusr1, android_init_events, android_pending)
+       (android_next_event, android_write_event, android_select)
+       (android_run_debug_thread, android_user_full_name)
+       (android_get_asset_name, android_fstat, android_fstatat)
+       (android_file_access_p, android_hack_asset_fd, android_open)
+       (android_close, JNICALL, android_init_emacs_service)
+       (android_init_emacs_pixmap, android_init_graphics_point)
+       (MAX_HANDLE, struct android_handle_entry, android_alloc_id)
+       (android_destroy_handle, android_resolve_handle)
+       (android_resolve_handle2, android_change_window_attributes)
+       (android_create_window, android_set_window_background)
+       (android_destroy_window, android_init_android_rect_class)
+       (android_init_emacs_gc_class, android_create_gc, android_free_gc)
+       (android_change_gc, android_set_clip_rectangles)
+       (android_reparent_window, android_lookup_method)
+       (android_clear_window, android_map_window, android_unmap_window)
+       (android_resize_window, android_move_window, android_swap_buffers)
+       (android_get_gc_values, android_set_foreground)
+       (android_fill_rectangle, android_create_pixmap_from_bitmap_data)
+       (android_set_clip_mask, android_set_fill_style, android_copy_area)
+       (android_free_pixmap, android_set_background)
+       (android_fill_polygon)
+       (android_draw_rectangle, android_draw_point, android_draw_line)
+       (android_create_pixmap, android_set_ts_origin)
+       (android_clear_area):
+
+       * src/android.h (ANDROID_EXPORT):
+
+       * src/androidfns.c (android_display_info_for_name)
+       (check_android_display_info, check_x_display_info, gamma_correct)
+       (android_defined_color, android_decode_color)
+       (android_implicitly_set_name, android_explicitly_set_name)
+       (android_set_tool_bar_lines, android_change_tool_bar_height)
+       (android_set_tab_bar_lines, android_change_tab_bar_height)
+       (android_set_scroll_bar_default_height)
+       (android_set_scroll_bar_default_width, android_icon_verify)
+       (android_icon, android_make_gc, android_free_gcs)
+       (unwind_create_frame, do_unwind_create_frame)
+       (android_default_font_parameter, android_create_frame_window)
+       (Fx_create_frame, Fxw_color_defined_p, Fxw_color_values)
+       (Fxw_display_color_p, Fx_display_grayscale_p)
+       (Fx_display_pixel_width, Fx_display_pixel_height)
+       (Fx_display_planes, Fx_display_color_cells, Fx_display_screens)
+       (Fx_display_mm_width, Fx_display_mm_height)
+       (Fx_display_backing_store, Fx_display_visual_class)
+       (Fx_display_monitor_attributes_list, Fx_frame_geometry)
+       (Fx_frame_list_z_order, Fx_frame_restack)
+       (Fx_mouse_absolute_pixel_position)
+       (Fx_set_mouse_absolute_pixel_position, Fandroid_get_connection)
+       (Fx_display_list, Fx_show_tip, Fx_hide_tip)
+       (android_set_background_color, android_set_border_color)
+       (android_set_cursor_color, android_set_cursor_type)
+       (android_set_foreground_color)
+       (android_set_child_frame_border_width)
+       (android_set_internal_border_width, android_set_menu_bar_lines)
+       (android_set_mouse_color, android_set_title, android_set_alpha)
+       (android_frame_parm_handlers, syms_of_androidfns):
+
+       * src/androidfont.c (struct android_emacs_font_driver)
+       (struct android_emacs_font_spec)
+       (struct android_emacs_font_metrics)
+       (struct android_emacs_font_object, struct android_integer)
+       (struct androidfont_info, struct androidfont_entity)
+       (android_init_font_driver, android_init_font_spec)
+       (android_init_font_metrics, android_init_integer)
+       (android_init_font_object, androidfont_get_cache)
+       (androidfont_from_lisp, androidfont_from_java, androidfont_list)
+       (androidfont_match, androidfont_draw, androidfont_open_font)
+       (androidfont_close_font, androidfont_has_char)
+       (androidfont_encode_char, androidfont_text_extents)
+       (androidfont_list_family, androidfont_driver)
+       (syms_of_androidfont_for_pdumper, syms_of_androidfont)
+       (init_androidfont, android_finalize_font_entity):
+
+       * src/androidgui.h (_ANDROID_GUI_H_, struct android_rectangle)
+       (struct android_point, enum android_gc_function)
+       (enum android_gc_value_mask, enum android_fill_style)
+       (enum android_window_value_mask)
+       (struct android_set_window_attributes, struct android_gc_values)
+       (struct android_gc, enum android_swap_action, enum android_shape)
+       (enum android_coord_mode, struct android_swap_info)
+       (NativeRectangle, struct android_any_event)
+       (struct android_key_event, struct android_configure_event)
+       (union android_event):
+
+       * src/androidterm.c (android_window_to_frame, android_clear_frame)
+       (android_ring_bell, android_toggle_invisible_pointer)
+       (android_update_begin, android_update_end, show_back_buffer)
+       (android_flush_dirty_back_buffer_on, handle_one_android_event)
+       (android_read_socket, android_frame_up_to_date)
+       (android_buffer_flipping_unblocked_hook)
+       (android_query_frame_background_color, android_parse_color)
+       (android_alloc_nearest_color, android_query_colors)
+       (android_mouse_position, android_get_focus_frame)
+       (android_focus_frame, android_frame_rehighlight)
+       (android_frame_raise_lower, android_make_frame_visible)
+       (android_make_frame_invisible)
+       (android_make_frame_visible_invisible, android_fullscreen_hook)
+       (android_iconify_frame, android_set_window_size_1)
+       (android_set_window_size, android_set_offset, android_set_alpha)
+       (android_new_font, android_bitmap_icon, android_free_pixmap_hook)
+       (android_free_frame_resources, android_delete_frame)
+       (android_delete_terminal, android_scroll_run)
+       (android_after_update_window_line, android_flip_and_flush)
+       (android_clear_rectangle, android_reset_clip_rectangles)
+       (android_clip_to_row, android_draw_fringe_bitmap)
+       (android_set_cursor_gc, android_set_mouse_face_gc)
+       (android_set_mode_line_face_gc, android_set_glyph_string_gc)
+       (android_set_glyph_string_clipping)
+       (android_set_glyph_string_clipping_exactly)
+       (android_compute_glyph_string_overhangs)
+       (android_clear_glyph_string_rect)
+       (android_draw_glyph_string_background, android_fill_triangle)
+       (android_make_point, android_inside_rect_p, android_clear_point)
+       (android_draw_relief_rect, android_draw_box_rect)
+       (HIGHLIGHT_COLOR_DARK_BOOST_LIMIT, android_setup_relief_color)
+       (android_setup_relief_colors, android_draw_glyph_string_box)
+       (android_draw_glyph_string_bg_rect, android_draw_image_relief)
+       (android_draw_image_foreground, android_draw_image_foreground_1)
+       (android_draw_image_glyph_string)
+       (android_draw_stretch_glyph_string, android_draw_underwave)
+       (android_draw_glyph_string_foreground)
+       (android_draw_composite_glyph_string_foreground)
+       (android_draw_glyphless_glyph_string_foreground)
+       (android_draw_glyph_string, android_define_frame_cursor)
+       (android_clear_frame_area, android_clear_under_internal_border)
+       (android_draw_hollow_cursor, android_draw_bar_cursor)
+       (android_draw_window_cursor, android_draw_vertical_window_border)
+       (android_draw_window_divider, android_redisplay_interface)
+       (frame_set_mouse_pixel_position, get_keysym_name)
+       (android_create_terminal, android_term_init, syms_of_androidterm)
+       (mark_androidterm):
+
+       * src/androidterm.h (_ANDROID_TERM_H_)
+       (struct android_display_info)
+       (struct android_output, FRAME_ANDROID_OUTPUT, XSCROLL_BAR): New
+       files.
+
+       * src/dired.c (file_attributes): Do not use openat on Android.
+
+       * src/dispextern.h (No_Cursor): Define appropriately on Android.
+       (struct glyph_string, struct face): Make gc field of type struct
+       android_gc on Android.
+
+       * src/dispnew.c (clear_current_matrices, clear_desired_matrices)
+       (adjust_frame_glyphs_for_window_redisplay, free_glyphs)
+       (update_frame, scrolling, char_ins_del_cost, update_frame_line)
+       (init_display_interactive): Disable text terminal support
+       completely on Android.  Fix non-toolkit menus for non-X systems.
+
+       * src/editfns.c (Fuser_full_name): Call android_user_full_name.
+
+       * src/emacs.c (android_emacs_init): Make main this on Android.
+       Prohibit argv sorting from exceeding end of argv.
+
+       * src/epaths.in: Add path definitions for Android.
+
+       * src/fileio.c (file_access_p): Call android_file_access_p.
+       (file_name_directory): Avoid using openat on Android.
+       (Fcopy_file): Adjust to call sys_fstat instead.
+       (file_directory_p, Finsert_file_contents, write_region): Likewise.
+
+       * src/filelock.c:
+
+       * src/fns.c (Flocale_info): Pacify warning on Android.
+
+       * src/font.c (font_make_entity_android): New function.
+
+       * src/font.h:
+
+       * src/frame.c (Fframep, Fwindow_system): Handle new window system
+       `android'.  Update doc strings.
+       (Fmake_terminal_frame): Disable on Android.
+       (gui_display_get_resource): Disable get_string_resource_hook on
+       Android.
+       (syms_of_frame): New defsym `android'.
+
+       * src/frame.h (GCALIGNED_STRUCT): Add new output data for Android.
+       (ENUM_BF): Expand enumerator size.
+       (FRAME_ANDROID_P, FRAME_WINDOW_P, MOUSE_HL_INFO): Add definitions
+       for Android.
+
+       * src/image.c (GET_PIXEL, image_create_bitmap_from_file)
+       (image_create_x_image_and_pixmap_1, image_get_x_image, slurp_file)
+       (lookup_rgb_color, image_to_emacs_colors, image_from_emacs_colors)
+       (image_pixmap_draw_cross, image_disable_image, MaskForeground)
+       (gif_load): Add stubs for Android.
+
+       * src/lisp.h:
+
+       * src/lread.c (safe_to_load_version, maybe_swap_for_eln1, openp):
+
+       * src/pdumper.c (pdumper_load): Call sys_fstat instead of fstat.
+
+       * src/process.c (wait_reading_process_output): Use android_select
+       instead of pselect.
+
+       * src/scroll.c: Disable on Android.
+
+       * src/sysdep.c (widen_foreground_group, reset_sys_modes)
+       (init_signals, emacs_fstatat, sys_fstat): New function.
+       (emacs_open, emacs_open_noquit, emacs_close): Implement
+       differently on Android.
+       (close_output_streams): Disable what is not required on Android.
+
+       * src/term.c (OUTPUT1_IF, encode_terminal_code, string_cost)
+       (string_cost_one_line, per_line_cost, calculate_costs)
+       (struct fkey_table, tty_append_glyph, produce_glyphs)
+       (tty_capable_p, Fsuspend_tty, Fresume_tty, device, init_tty)
+       (maybe_fatal, syms_of_term): Disable text terminal support on
+       Android.
+
+       * src/termhooks.h (enum output_method): Add android output method.
+       (GCALIGNED_STRUCT, TERMINAL_FONT_CACHE): Define for Android.
+
+       * src/terminal.c (Fterminal_live_p): Implement for Android.
+
+       * src/verbose.mk.in (AM_V_GLOBALS): Add JAVAC and DX.
+       * src/xdisp.c (redisplay_internal): Disable text terminals on
+       Android.
+       (display_menu_bar, display_tty_menu_item)
+       (draw_row_with_mouse_face, expose_frame): Make the non toolkit
+       menu bar work on Android.
+
+       * src/xfaces.c (GCGraphicsExposures, x_create_gc, x_free_gc)
+       (Fx_load_color_file): Define for Android.
+
+       * xcompile/Makefile.in:
+       * xcompile/README:
+       * xcompile/langinfo.h (nl_langinfo): New files.
+
+2022-12-29  Po Lu  <luangruo@yahoo.com>
+
+       Development of the Android port starts...
+
+This ChangeLog only chronicles changes constituting the initial
+development of the Android port.  Refer to other ChangeLog files for a
+narrative of unrelated modifications made to Emacs during that time,
+and those made after the Android port was installed.
+
+;; Local Variables:
+;; coding: utf-8
+;; End:
+
+  Copyright (C) 2023 Free Software Foundation, Inc.
+
+  This file is part of GNU Emacs.
+
+  GNU Emacs is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  GNU Emacs is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
diff --git a/INSTALL b/INSTALL
index 2bb8df52dc9..d09216739f6 100644
--- a/INSTALL
+++ b/INSTALL
@@ -4,13 +4,16 @@ Inc.
 See the end of the file for license conditions.
 
 
-This file contains general information on building GNU Emacs.  For
-more information specific to the MS-Windows, GNUstep/macOS, and MS-DOS
-ports, also read the files nt/INSTALL, nextstep/INSTALL, and
-msdos/INSTALL.
-
-For information about building from a Git checkout (rather than an
-Emacs release), read the INSTALL.REPO file first.
+This file contains general information on building GNU Emacs.  If you
+are building an Emacs release tarball on a Unix or a GNU system, the
+instructions in this file should be sufficient.  For other
+configurations, we have additional specialized files:
+
+  . INSTALL.REPO if you build from a Git checkout
+  . nt/INSTALL if you build for MS-Windows
+  . nextstep/INSTALL if you build for GNUstep/macOS
+  . java/INSTALL if you build for Android
+  . msdos/INSTALL if you build for MS-DOS
 
 
 BASIC INSTALLATION
diff --git a/Makefile.in b/Makefile.in
index 729cd4140e5..fdd9353e254 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -106,15 +106,15 @@ top_builddir = @top_builddir@
 
 FIND_DELETE = @FIND_DELETE@
 
-HAVE_NATIVE_COMP = @HAVE_NATIVE_COMP@
-
 USE_STARTUP_NOTIFICATION = @USE_STARTUP_NOTIFICATION@
 
+HAVE_NATIVE_COMP = @HAVE_NATIVE_COMP@
 HAVE_BE_APP = @HAVE_BE_APP@
-
 HAVE_PGTK = @HAVE_PGTK@
 HAVE_GSETTINGS = @HAVE_GSETTINGS@
 
+ANDROID = @ANDROID@
+
 # ==================== Where To Install Things ====================
 
 # Location to install Emacs.app under GNUstep / macOS.
@@ -339,6 +339,10 @@ EMACS_PDMP = `./src/emacs${EXEEXT} --fingerprint`.pdmp
 # Subdirectories to make recursively.
 SUBDIR = $(NTDIR) lib lib-src src lisp
 
+ifeq ($(ANDROID),yes)
+SUBDIR := $(SUBDIR) java
+endif
+
 # The subdir makefiles created by config.status.
 SUBDIR_MAKEFILES_IN = @SUBDIR_MAKEFILES_IN@
 SUBDIR_MAKEFILES = $(patsubst ${srcdir}/%,%,${SUBDIR_MAKEFILES_IN:.in=})
@@ -467,20 +471,20 @@ epaths-force:
          esac; \
        done
        @(gamedir='${gamedir}'; \
-         sed < ${srcdir}/src/epaths.in > epaths.h.$$$$         \
-         -e 's;\(#.*PATH_LOADSEARCH\).*$$;\1 "${standardlisppath}";' \
-         -e 's;\(#.*PATH_REL_LOADSEARCH\).*$$;\1 "${lispdirrel}";'     \
-         -e 's;\(#.*PATH_SITELOADSEARCH\).*$$;\1 "${locallisppath}";' \
-         -e 's;\(#.*PATH_DUMPLOADSEARCH\).*$$;\1 "${buildlisppath}";' \
-         -e '/^#define PATH_[^ ]*SEARCH /s/\([":]\):*/\1/g'            \
-         -e '/^#define PATH_[^ ]*SEARCH /s/:"/"/'                      \
-         -e 's;\(#.*PATH_EXEC\).*$$;\1 "${archlibdir}";'               \
-         -e 's;\(#.*PATH_INFO\).*$$;\1 "${infodir}";'                  \
-         -e 's;\(#.*PATH_DATA\).*$$;\1 "${etcdir}";'                   \
-         -e 's;\(#.*PATH_BITMAPS\).*$$;\1 "${bitmapdir}";'             \
-         -e 's;\(#.*PATH_X_DEFAULTS\).*$$;\1 "${x_default_search_path}";' \
-         -e 's;\(#.*PATH_GAME\).*$$;\1 $(PATH_GAME);'                  \
-         -e 's;\(#.*PATH_DOC\).*$$;\1 "${etcdocdir}";') &&             \
+         sed < ${srcdir}/src/epaths.in > epaths.h.$$$$                         
        \
+         -e 's;\(#define.*PATH_LOADSEARCH\).*$$;\1 "${standardlisppath}";'     
        \
+         -e 's;\(#define.*PATH_REL_LOADSEARCH\).*$$;\1 "${lispdirrel}";'       
        \
+         -e 's;\(#define.*PATH_SITELOADSEARCH\).*$$;\1 "${locallisppath}";'    
        \
+         -e 's;\(#define.*PATH_DUMPLOADSEARCH\).*$$;\1 "${buildlisppath}";'    
        \
+         -e '/^#define PATH_[^ ]*SEARCH /s/\([":]\):*/\1/g'                    
        \
+         -e '/^#define PATH_[^ ]*SEARCH /s/:"/"/'                              
        \
+         -e 's;\(#define.*PATH_EXEC\).*$$;\1 "${archlibdir}";'                 
        \
+         -e 's;\(#define.*PATH_INFO\).*$$;\1 "${infodir}";'                    
        \
+         -e 's;\(#define.*PATH_DATA\).*$$;\1 "${etcdir}";'                     
        \
+         -e 's;\(#define.*PATH_BITMAPS\).*$$;\1 "${bitmapdir}";'               
        \
+         -e 's;\(#define.*PATH_X_DEFAULTS\).*$$;\1 
"${x_default_search_path}";'        \
+         -e 's;\(#define.*PATH_GAME\).*$$;\1 $(PATH_GAME);'                    
        \
+         -e 's;\(#define.*PATH_DOC\).*$$;\1 "${etcdocdir}";') &&               
        \
        ${srcdir}/build-aux/move-if-change epaths.h.$$$$ src/epaths.h
 
 # The w32 build needs a slightly different editing, and it uses
@@ -532,6 +536,12 @@ lisp: src
 lib lib-src lisp nt: Makefile
        $(MAKE) -C $@ all
 
+java: lisp info
+       $(MAKE) -C $@ all
+
+cross: src
+       $(MAKE) -C $@ all
+
 trampolines: src lisp
 ifeq ($(HAVE_NATIVE_COMP),yes)
        $(MAKE) -C lisp trampolines
@@ -569,10 +579,13 @@ $(MAKEFILE_NAME): config.status $(srcdir)/configure \
 # Don't erase these files if make is interrupted while refreshing them.
 .PRECIOUS: Makefile config.status
 
+# Note that calling config.status --recheck is insufficient on Android
+# due to the recursive calls to configure.
+
 config.status: ${srcdir}/configure
-       if [ -x ./config.status ]; then \
+       if [ -x ./config.status ]; then         \
            $(CFG) ./config.status --recheck;   \
-       else                            \
+       else                                    \
            $(CFG) $(srcdir)/configure $(CONFIGURE_FLAGS); \
        fi
 
@@ -987,6 +1000,12 @@ endef
 mostlyclean_dirs = src oldXMenu lwlib lib lib-src nt doc/emacs doc/misc \
   doc/lispref doc/lispintro test
 
+### Add the libexec directory to mostlyclean_dirs if its Makefile has
+### been created.
+ifneq ($(wildcard exec/Makefile),)
+mostlyclean_dirs := $(mostlyclean_dirs) exec
+endif
+
 $(foreach dir,$(mostlyclean_dirs),$(eval $(call 
submake_template,$(dir),mostlyclean)))
 
 mostlyclean: $(mostlyclean_dirs:=_mostlyclean)
@@ -999,7 +1018,8 @@ mostlyclean: $(mostlyclean_dirs:=_mostlyclean)
 ###      with them.
 ###
 ###      Delete '.dvi' files here if they are not part of the distribution.
-clean_dirs = $(mostlyclean_dirs) nextstep admin/charsets admin/unidata
+clean_dirs = $(mostlyclean_dirs) java cross nextstep admin/charsets \
+  admin/unidata
 
 $(foreach dir,$(clean_dirs),$(eval $(call submake_template,$(dir),clean)))
 
@@ -1081,6 +1101,8 @@ extraclean: maintainer-clean
        -[ "${srcdir}" = "." ] || \
          find ${srcdir} '(' -name '*~' -o -name '#*' ')' ${FIND_DELETE}
        -find . '(' -name '*~' -o -name '#*' ')' ${FIND_DELETE}
+       -rm -f ${srcdir}/exec/config-tmp-* ${srcdir}/exec/aclocal.m4 \
+         ${srcdir}/src/config.in ${srcdir}/exec/configure
 
 # The src subdir knows how to do the right thing
 # even when the build directory and source dir are different.
@@ -1094,7 +1116,7 @@ TAGS tags: lib lib-src # src
        $(MAKE) -C doc/lispref tags
        $(MAKE) -C doc/misc tags
 
-CHECK_TARGETS = check check-maybe check-expensive check-all
+CHECK_TARGETS = check check-maybe check-expensive check-all check-byte-compile
 .PHONY: $(CHECK_TARGETS)
 $(CHECK_TARGETS): all
        $(MAKE) -C test $@
diff --git a/README b/README
index 19d5c96e348..694b3aa4c94 100644
--- a/README
+++ b/README
@@ -95,6 +95,11 @@ There are several subdirectories:
 'admin'     holds files used by Emacs developers, and Unicode data files.
 'build-aux' holds auxiliary files used during the build.
 'm4'        holds Autoconf macros used for generating the configure script.
+'java'     holds the Java code for the Emacs port to Android.
+'cross'            holds Makefiles and an additional copy of gnulib used to 
build
+           Emacs for Android devices.
+'exec'     holds the source code to several helper executables used to run
+           user-installed programs on Android.
 
    Building Emacs on non-Posix platforms requires tools that aren't part
 of the standard distribution of the OS.  The platform-specific README
diff --git a/admin/MAINTAINERS b/admin/MAINTAINERS
index 1273e9a976b..cea1aa56cde 100644
--- a/admin/MAINTAINERS
+++ b/admin/MAINTAINERS
@@ -160,6 +160,12 @@ Po Lu
 
        Haiku battery support in lisp/battery.el
 
+Jim Porter
+       Eshell
+           lisp/eshell/*
+           test/lisp/eshell/*
+           doc/misc/eshell.texi
+
 ==============================================================================
 2. Areas that someone is willing to maintain, although he would not
 necessarily mind if someone else was the official maintainer.
diff --git a/admin/emake b/admin/emake
index 0aa1178768d..c9e59d34067 100755
--- a/admin/emake
+++ b/admin/emake
@@ -19,13 +19,20 @@
 
 # This script is meant to be used as ./admin/emake, and will compile
 # the Emacs tree with virtually all of the informational messages
-# removed, and with errors/warnings highlighted in red.  It'll give a
-# quick overview to confirm that nothing has broken, for instance
+# removed, and with errors/warnings highlighted in red.  It will also
+# run the test files belonging to files that have changed.  It'll give
+# a quick overview to confirm that nothing has broken, for instance
 # after doing a "git pull".  It's not meant to be used during actual
 # development, because it removes so much information that commands
 # like `next-error' won't be able to jump to the source code where
 # errors are.
 
+# It has a few options:
+# with --no-color errors/warnings are not highlighted
+# with --no-check test files are not run
+# with --no-fast the FAST=true make variable is not set (see Makefile.in)
+# with --quieter only errors/warnings remain visible
+
 cores=1
 
 # Determine the number of cores.
@@ -96,6 +103,7 @@ GEN.*loaddefs|\
 ^.Read INSTALL.REPO for more|\
 ^Your system has the required tools.|\
 ^Building aclocal.m4|\
+^Building 'aclocal.m4'|\
 ^ Running 'autoreconf|\
 ^You can now run './configure'|\
 ^./configure|\
@@ -129,15 +137,21 @@ The GNU allocators don't work|\
 " | \
 while read
 do
-  C=""
-  (($NOCOLOR == 0)) && [[ "X${REPLY:0:1}" != "X " ]] && C="\033[1;31m"
-  (($NOCOLOR == 0)) && [[ "X${REPLY:0:3}" == "X   " ]] && C="\033[1;31m"
-  if (($QUIETER == 0))
-  then
-      [[ "X$C" == "X" ]] && printf "%s\n" "$REPLY" || printf "$C%s\033[0m\n" 
"$REPLY"
-  else
-      [[ "X$C" == "X" ]] && printf "%-80s\r" "$REPLY" || printf 
"$C%-80s\033[0m\n" "$REPLY"
-  fi
+    C=""
+    E=0
+    [ ! -v L ] && L=80
+    [[ "X${REPLY:0:1}" != "X " ]] && E=1
+    [[ "X${REPLY:0:3}" == "X   " ]] && E=1
+    (($NOCOLOR == 0)) && (($E == 1)) && C="\033[1;31m"
+    (($NOCOLOR == 0)) && (($E == 1)) && C="\033[1;31m"
+    if (($QUIETER == 0))
+    then
+       (($E == 0)) && printf "%s\n" "$REPLY" || printf "${C}%s\033[0m\n" 
"$REPLY"
+    else
+       (($E == 0)) && printf "%-${L}s\r" "$REPLY" || printf 
"${C}%-${L}s\033[0m\n" "$REPLY"
+    fi
+    L=${#REPLY}
+    (($L < 80)) && L=80
 done
 
 # If make failed, exit now with its error code.
@@ -149,4 +163,13 @@ done
 # changed since last time.
 make -j$cores check-maybe 2>&1 | \
     sed -n '/contained unexpected results/,$p' | \
-    grep -E --line-buffered -v "^make"
+    grep -E --line-buffered -v "^make" | \
+while read
+do
+    if (($NOCOLOR == 0))
+    then
+       printf "\033[1;31m%s\033[0m\n" "$REPLY"
+    else
+       printf "%s\n" "$REPLY"
+    fi
+done
diff --git a/admin/git-bisect-start b/admin/git-bisect-start
index 9de4d547323..8eb5328a1a1 100755
--- a/admin/git-bisect-start
+++ b/admin/git-bisect-start
@@ -82,7 +82,7 @@ done
 # SKIP-BRANCH 58cc931e92ece70c3e64131ee12a799d65409100
 
 ## The list below is the exhaustive list of all commits between Dec 1
-## 2016 and Jul 8 2023 on which building Emacs with the default
+## 2016 and Aug 10 2023 on which building Emacs with the default
 ## options, on a GNU/Linux computer and with GCC, fails.  It is
 ## possible (though unlikely) that building Emacs with non-default
 ## options, with other compilers, or on other platforms, would succeed
@@ -1757,3 +1757,22 @@ $REAL_GIT bisect skip $(cat $0 | grep '^# SKIP-SINGLE ' 
| sed 's/^# SKIP-SINGLE
 # SKIP-SINGLE 0a35c991c19a6dd0a707f2baa868f8989242c3ab
 # SKIP-SINGLE e2ee646b162b87e832c8032b9d90577bd21f21f8
 # SKIP-SINGLE 35d2fe176cb438d55552cacbdf25c3692c054d51
+# SKIP-SINGLE de3d8ae71b43f80244c4d813ff1503b8551f0026
+# SKIP-SINGLE a496509cedb17109d0e6297a74e2ff8ed526333c
+# SKIP-SINGLE a6a586ffc1bd302e30d80cb88b06e1e7e1573f63
+# SKIP-SINGLE f5d142f66370b29af58360faeea90d1112756bc5
+# SKIP-SINGLE 46e8ab23eaeb5e453042f430fc016cf9ffc2ac37
+# SKIP-SINGLE eb72569dbef91862a765cd4d9f380220244b4549
+# SKIP-SINGLE c4b77b82decb757af0aff1b7420203fa0805b483
+# SKIP-SINGLE 0ee01457a84e031d490553949a2deacd4865a5bb
+# SKIP-SINGLE 6c68d9bd3a18c74384fc764179fd92a024d6c35d
+# SKIP-SINGLE a46e231a5f27c46933cc53865cee452ad1a0c0d3
+# SKIP-SINGLE c045d5322c2c1658f215bf59d431fcc8f96ffc12
+# SKIP-SINGLE dabb713eb05aff62deb6872a3498327934f18c8d
+# SKIP-SINGLE b8c05636ca4b28a7adc62e82a5fed528b402396d
+# SKIP-SINGLE e72afa9dbf92f45d00c87c90ead364d52f73024f
+# SKIP-SINGLE 9d3aacedf0c217af207d39e390f376914160396b
+# SKIP-SINGLE 6bdbb4cbfc2deb7d3a02e1428768e101f3dbd265
+# SKIP-SINGLE 2752573dfb76873dbe783e89a1fbf01d157c54e3
+# SKIP-SINGLE 62e990db7a2fad16756e019b331c28ad5a5a89fe
+# SKIP-SINGLE 6253e7e74249c7cdfa86723f0b91a1d207cb143e
diff --git a/admin/make-tarball.txt b/admin/make-tarball.txt
index 1cc97c883af..fddf8444067 100644
--- a/admin/make-tarball.txt
+++ b/admin/make-tarball.txt
@@ -426,6 +426,13 @@ Now change to the 'manual' directory and invoke 
upload-manuals:
   If upload-manuals fails, resolve the problems and re-invoke it.
   This requires running make-manuals again, since upload-manuals
   destructively modifies the 'manual' directory where you invoke it.
+
+  If new files fail to be "cvs add"ed, they need to be manually
+  removed from under /path/to/webpages/cvs/checkout before retrying
+  upload-manuals, because if they exist, they will not be handled as
+  "new" files, and will not be "cvs add"ed by the next run of the
+  script.
+
   Also, upload-manuals invokes "cvs commit -f", so if you run it
   several times, some files will be committed more than once even
   though they were not changed in-between.  Suck it up.
diff --git a/admin/merge-gnulib b/admin/merge-gnulib
index eb790933414..fe88d1106ae 100755
--- a/admin/merge-gnulib
+++ b/admin/merge-gnulib
@@ -26,7 +26,7 @@
 GNULIB_URL=https://git.savannah.gnu.org/git/gnulib.git
 
 GNULIB_MODULES='
-  alignasof alloca-opt binary-io byteswap c-ctype c-strcase
+  alignasof alloca-opt binary-io boot-time byteswap c-ctype c-strcase
   canonicalize-lgpl
   careadlinkat close-stream copy-file-range
   count-leading-zeros count-one-bits count-trailing-zeros
@@ -37,7 +37,7 @@ GNULIB_MODULES='
   fchmodat fcntl fcntl-h fdopendir file-has-acl
   filemode filename filevercmp flexmember fpieee
   free-posix fstatat fsusage fsync futimens
-  getloadavg getopt-gnu getrandom gettime gettimeofday gitlog-to-changelog
+  getline getloadavg getopt-gnu getrandom gettime gettimeofday 
gitlog-to-changelog
   ieee754-h ignore-value intprops largefile libgmp lstat
   manywarnings memmem-simple mempcpy memrchr memset_explicit
   minmax mkostemp mktime
@@ -45,19 +45,20 @@ GNULIB_MODULES='
   pathmax pipe2 pselect pthread_sigmask
   qcopy-acl readlink readlinkat regex
   sig2str sigdescr_np socklen stat-time std-gnu11 stdbool stdckdint stddef 
stdio
-  stpcpy strnlen strtoimax symlink sys_stat sys_time
+  stpcpy strnlen strnlen strtoimax symlink sys_stat sys_time
   tempname time-h time_r time_rz timegm timer-time timespec-add timespec-sub
   update-copyright unlocked-io utimensat
   vla warnings year2038
 '
 
 AVOIDED_MODULES='
-  btowc chmod close crypto/af_alg dup fchdir fstat langinfo lock
+  btowc chmod close crypto/af_alg dup fchdir fstat
+  iswblank iswctype iswdigit iswxdigit langinfo lock
   mbrtowc mbsinit memchr mkdir msvc-inval msvc-nothrow nl_langinfo
   openat-die opendir pthread-h raise
   save-cwd select setenv sigprocmask stat stdarg
   threadlib tzset unsetenv utime utime-h
-  wchar wcrtomb wctype-h
+  wchar wcrtomb wctype wctype-h
 '
 
 GNULIB_TOOL_FLAGS='
@@ -114,6 +115,11 @@ for module in $AVOIDED_MODULES; do
   avoided_flags="$avoided_flags --avoid=$module"
 done
 
+# Clean the lib directory as well.
+if [ -e "$src"/lib/Makefile ]; then
+   make -C "$src"/lib maintainer-clean
+fi
+
 "$gnulib_srcdir"/gnulib-tool --dir="$src" $GNULIB_TOOL_FLAGS \
        $avoided_flags $GNULIB_MODULES &&
 rm -- "$src"lib/gl_openssl.h \
diff --git a/admin/notes/bugtracker b/admin/notes/bugtracker
index deb06f552cc..b47061884d6 100644
--- a/admin/notes/bugtracker
+++ b/admin/notes/bugtracker
@@ -39,7 +39,7 @@ tags 123 moreinfo|unreproducible|wontfix|patch|notabug
 
 For a list of all bugs, see https://debbugs.gnu.org/db/pa/lemacs.html
 This is a static page, updated once a day.  There is also a dynamic
-list, generated on request. This accepts various options, eg to see
+list, generated on request. This accepts various options, e.g., to see
 the most recent bugs:
 
 https://debbugs.gnu.org/cgi/pkgreport.cgi?newest=100
@@ -98,7 +98,7 @@ you might want to have a dialog with the owner address, 
outside of
 normal bug reporting.)
 
 ** When reporting a new bug, to send a Cc to another address
-(e.g. bug-cc-mode@gnu.org), do NOT just use a Cc: header.
+(e.g., bug-cc-mode@gnu.org), do NOT just use a Cc: header.
 Instead, use "X-Debbugs-Cc:".  This ensures the Cc address(es) will get a
 mail with the bug report number in.  If you do not do this, each reply
 in the subsequent discussion might end up creating a new bug.
@@ -138,7 +138,8 @@ The "maintainer email address" is "bug-gnu-emacs@gnu.org" 
in most cases.
 
 ** To not get acknowledgment mail from the tracker,
 add an "X-Debbugs-No-Ack:" header (with any value).  If you use Gnus,
-you can add an element to gnus-posting-styles to do this automatically, eg:
+you can add an element to gnus-posting-styles to do this automatically,
+e.g.:
 
 ("gnu-emacs\\(-pretest\\)?-bug"
    ("X-Debbugs-No-Ack" "yes"))
@@ -222,14 +223,14 @@ Mail-Followup-To: 123@debbugs.gnu.org, person-who-closed
 ** Setting bug parameters.
 There are two ways to set the parameters of bugs in the database
 (tags, severity level, etc).  When you report a new bug, you can
-provide a "pseudo-header" at the start of the report, eg:
+provide a "pseudo-header" at the start of the report, e.g.:
 
 Package: emacs
 Version: 23.0.60
 Severity: minor
 
 This can also include tags, or any X-Debbugs- setting.
-Some things (e.g. submitter) don't seem to work here.
+Some things (e.g., submitter) don't seem to work here.
 
 Otherwise, send mail to the control server, control@debbugs.gnu.org.
 At the start of the message body, supply the desired commands, one per
@@ -258,12 +259,12 @@ where VERSION is XX.YY numerical version number, like 
42.1.
 *** To reopen a closed bug:
 reopen 123
 
-*** Bugs can be tagged in various ways (eg wontfix, patch, etc).
+*** Bugs can be tagged in various ways (e.g., wontfix, patch, etc).
 The available tags are:
 patch wontfix moreinfo unreproducible fixed notabug help security confirmed 
easy
 See https://debbugs.gnu.org/Developer#tags
 The list of tags can be prefixed with +, - or =, meaning to add (the
-default), remove, or reset the tags. E.g.:
+default), remove, or reset the tags.  E.g.:
 
 tags 123 + wontfix
 
@@ -310,7 +311,7 @@ This will add a usertag "any-tag-you-like" to bug#1234.  
The tag will
 be associated with the user "emacs".  If you omit the first line,
 the tag will be associated with your email address.
 
-The syntax of the usertags command is the same as that of tags (eg wrt
+The syntax of the usertags command is the same as that of tags (e.g., wrt
 the optional [=+-] argument).
 
 b) In an initial submission, in the pseudo-header:
@@ -340,15 +341,15 @@ than one email address, but it does not seem to work for 
me.)
 **** To find bugs tagged with a specific usertag:
 
 This works just like a normal tags search, but with the addition of a
-"users" field.  Eg:
+"users" field.  E.g.:
 
 https://debbugs.gnu.org/cgi/pkgreport.cgi?users=emacs;tag=calendar
 
 *** To merge bugs:
-Eg when bad replies create a bunch of new bugs for the same report.
-Bugs must all be in the same state (e.g. same package(s) and severity
+e.g., when bad replies create a bunch of new bugs for the same report.
+Bugs must all be in the same state (e.g., same package(s) and severity
 -- see 'reassign' and 'severity' below), but need not have the same
-tags (tags are merged). E.g.:
+tags (tags are merged).  E.g.:
 
 merge 123 124 125 ...
 
@@ -557,7 +558,7 @@ debbugs-submit.  Approved mail is passed on to the tracker.
 tracker, since mail from whitelisted senders goes straight through.)
 
 NOTE: An alternative to this would be to use listhelper AT nongnu.org
-as a moderator address.  Eg the emacs-bug-tracker list uses this.
+as a moderator address.  E.g., the emacs-bug-tracker list uses this.
 It does basic spam processing on the moderator requests and
 automatically rejects the obviously bogus ones.  Someone still has to
 accept the good ones though.  The advantage of this would not be having
diff --git a/admin/notes/copyright b/admin/notes/copyright
index ae09707bac8..a06d92a2c5f 100644
--- a/admin/notes/copyright
+++ b/admin/notes/copyright
@@ -381,7 +381,7 @@ conclude it was written by me."
 
 lisp/term/README
   - had no copyright notice till Feb 2007. ChangeLog.3 suggests it was
-  written by Eric Raymond. When asked by rms on 14 Feb 2007 he said:
+  written by Eric S. Raymond. When asked by rms on 14 Feb 2007 he said:
 
     I don't remember writing it, but it reads like my prose and I believe
     I wrote the feature(s) it's describing.  So I would have been the
diff --git a/admin/notes/unicode b/admin/notes/unicode
index 31c850af8fd..da4736c43c6 100644
--- a/admin/notes/unicode
+++ b/admin/notes/unicode
@@ -72,7 +72,10 @@ characters are those marked with W or F in that file.  
Zero-width
 characters are not taken from EastAsianWidth.txt, they are those whose
 Unicode General Category property is one of Mn, Me, or Cf, and also
 Hangul jungseong and jongseong characters (a.k.a. "Jamo medial vowels"
-and "Jamo final consonants").
+and "Jamo final consonants").  The list of "ambiguous-width
+characters" recorded in the ambiguous-width-chars table (around line
+1400 of characters.el) should also be checked against the latest
+Unicode data in EastAsianWidth.txt.
 
 Any new scripts added by UnicodeData.txt will also need updates to
 script-representative-chars defined in fontset.el, and also the list
@@ -302,6 +305,12 @@ nontrivial changes to the build process.
 
        src/msdos.c
 
+ * iso-latin-1
+
+     This file is used to test Emacs encoding.
+
+        test/lisp/gnus/mm-decode-resources/win1252-multipart.bin
+
  * iso-2022-cn-ext
 
      This file is externally generated from leim/MISC-DIC/cangjie-table.b5
@@ -352,19 +361,27 @@ nontrivial changes to the build process.
      Some of the entries in this list are patterns, and stand for any
      files with the listed extension.
 
+       *.bmp
+       *.cur
+       *.gif
+       *.gpg
        *.gz
        *.icns
        *.ico
+       *.jpg
+       *.kbx
+       *.key
        *.pbm
        *.pdf
+       *.pif
        *.png
        *.sig
+       *.tiff
+       *.webp
+       *.zip
        etc/e/eterm-color
-       etc/package-keyring.gpg
-       msdos/emacs.pif
-       nextstep/GNUstep/Emacs.base/Resources/emacs.tiff
-       nt/icons/hand.cur
-
+       etc/e/eterm-direct
+       java/emacs.keystore
 
 This file is part of GNU Emacs.
 
diff --git a/admin/upload-manuals b/admin/upload-manuals
index 6f44456efb8..a206177a5c1 100755
--- a/admin/upload-manuals
+++ b/admin/upload-manuals
@@ -305,13 +305,14 @@ done
 ## TODO: check for removed manuals.
 
 [ "$clist" ] && (
-    cd $webdir/manual/html_mono
+    cd $webdir/manual
     [ "$new" ] && {
         echo "Adding new files: $new"
         $cvs add $new || die "add error"
         new_manual $new || die
         echo "Remember to add new entries to manual/index.html"
     }
+    cd html_mono
     $cvs commit -m "$message" $clist || die "commit error"
 )
 
@@ -339,9 +340,13 @@ for d in html_node/*; do
     done
 
     stale=
-    for f in $webdir/manual/$d/*.html; do
-        [ -e ${f#$webdir/manual/} ] || stale="$stale ${f##*/}"
-    done
+    # Newly created directory will have no HTML files, so none can be
+    # "stale".  But 'for' returns the original wildcard, so avoid that.
+    ls $webdir/manual/$d/*.html > /dev/null 2>&1 && {
+       for f in $webdir/manual/$d/*.html; do
+            [ -e ${f#$webdir/manual/} ] || stale="$stale ${f##*/}"
+       done
+    }
 
     mv $d/*.html $webdir/manual/$d/
 
diff --git a/autogen.sh b/autogen.sh
index 6127e7b24f4..0d89b7cfc9a 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -256,6 +256,19 @@ Please report any problems with this script to 
bug-gnu-emacs@gnu.org .'
   ## Let autoreconf figure out what, if anything, needs doing.
   ## Use autoreconf's -f option in case autoreconf itself has changed.
   autoreconf -fi -I m4 || exit
+
+  echo "Building 'aclocal.m4' in exec ..."
+
+  # Create a placeholder aclocal.m4 in exec, preventing autoreconf
+  # from running aclocal.
+
+  echo "" > exec/aclocal.m4
+
+  echo "Running 'autoreconf -fi' in exec ..."
+
+  # Now, run autoreconf inside the exec directory to generate its
+  # configure script.
+  autoreconf -fi exec || exit
 fi
 
 
diff --git a/build-aux/config.guess b/build-aux/config.guess
index 354a8ccde42..b187213930f 100755
--- a/build-aux/config.guess
+++ b/build-aux/config.guess
@@ -4,7 +4,7 @@
 
 # shellcheck disable=SC2006,SC2268 # see below for rationale
 
-timestamp='2023-06-23'
+timestamp='2023-07-20'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -976,7 +976,27 @@ EOF
        GUESS=$UNAME_MACHINE-unknown-minix
        ;;
     aarch64:Linux:*:*)
-       GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+       set_cc_for_build
+       CPU=$UNAME_MACHINE
+       LIBCABI=$LIBC
+       if test "$CC_FOR_BUILD" != no_compiler_found; then
+           ABI=64
+           sed 's/^        //' << EOF > "$dummy.c"
+           #ifdef __ARM_EABI__
+           #ifdef __ARM_PCS_VFP
+           ABI=eabihf
+           #else
+           ABI=eabi
+           #endif
+           #endif
+EOF
+           cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | 
sed 's, ,,g'`
+           eval "$cc_set_abi"
+           case $ABI in
+               eabi | eabihf) CPU=armv8l; LIBCABI=$LIBC$ABI ;;
+           esac
+       fi
+       GUESS=$CPU-unknown-linux-$LIBCABI
        ;;
     aarch64_be:Linux:*:*)
        UNAME_MACHINE=aarch64_be
@@ -1042,6 +1062,15 @@ EOF
     k1om:Linux:*:*)
        GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
        ;;
+    kvx:Linux:*:*)
+       GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+       ;;
+    kvx:cos:*:*)
+       GUESS=$UNAME_MACHINE-unknown-cos
+       ;;
+    kvx:mbr:*:*)
+       GUESS=$UNAME_MACHINE-unknown-mbr
+       ;;
     loongarch32:Linux:*:* | loongarch64:Linux:*:*)
        GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
        ;;
diff --git a/build-aux/config.sub b/build-aux/config.sub
index 9865d6ea4d1..6ae25027537 100755
--- a/build-aux/config.sub
+++ b/build-aux/config.sub
@@ -4,7 +4,7 @@
 
 # shellcheck disable=SC2006,SC2268 # see below for rationale
 
-timestamp='2023-06-26'
+timestamp='2023-07-31'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -1206,6 +1206,7 @@ case $cpu-$vendor in
                        | i370 | i*86 | i860 | i960 | ia16 | ia64 \
                        | ip2k | iq2000 \
                        | k1om \
+                       | kvx \
                        | le32 | le64 \
                        | lm32 \
                        | loongarch32 | loongarch64 \
@@ -1214,31 +1215,7 @@ case $cpu-$vendor in
                        | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
                        | m88110 | m88k | maxq | mb | mcore | mep | metag \
                        | microblaze | microblazeel \
-                       | mips | mipsbe | mipseb | mipsel | mipsle \
-                       | mips16 \
-                       | mips64 | mips64eb | mips64el \
-                       | mips64octeon | mips64octeonel \
-                       | mips64orion | mips64orionel \
-                       | mips64r5900 | mips64r5900el \
-                       | mips64vr | mips64vrel \
-                       | mips64vr4100 | mips64vr4100el \
-                       | mips64vr4300 | mips64vr4300el \
-                       | mips64vr5000 | mips64vr5000el \
-                       | mips64vr5900 | mips64vr5900el \
-                       | mipsisa32 | mipsisa32el \
-                       | mipsisa32r2 | mipsisa32r2el \
-                       | mipsisa32r3 | mipsisa32r3el \
-                       | mipsisa32r5 | mipsisa32r5el \
-                       | mipsisa32r6 | mipsisa32r6el \
-                       | mipsisa64 | mipsisa64el \
-                       | mipsisa64r2 | mipsisa64r2el \
-                       | mipsisa64r3 | mipsisa64r3el \
-                       | mipsisa64r5 | mipsisa64r5el \
-                       | mipsisa64r6 | mipsisa64r6el \
-                       | mipsisa64sb1 | mipsisa64sb1el \
-                       | mipsisa64sr71k | mipsisa64sr71kel \
-                       | mipsr5900 | mipsr5900el \
-                       | mipstx39 | mipstx39el \
+                       | mips* \
                        | mmix \
                        | mn10200 | mn10300 \
                        | moxie \
@@ -1733,7 +1710,7 @@ case $os in
             | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
             | sym* |  plan9* | psp* | sim* | xray* | os68k* | v88r* \
             | hiux* | abug | nacl* | netware* | windows* \
-            | os9* | macos* | osx* | ios* \
+            | os9* | macos* | osx* | ios* | tvos* | watchos* \
             | mpw* | magic* | mmixware* | mon960* | lnews* \
             | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
             | aos* | aros* | cloudabi* | sortix* | twizzler* \
@@ -1759,7 +1736,7 @@ case $os in
             | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
             | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
             | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
-            | fiwix* | mlibc* )
+            | fiwix* | mlibc* | cos* | mbr* )
                ;;
        # This one is extra strict with allowed versions
        sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
@@ -1816,6 +1793,10 @@ case $kernel-$os in
                ;;
        *-eabi* | *-gnueabi*)
                ;;
+       none-coff* | none-elf*)
+               # None (no kernel, i.e. freestanding / bare metal),
+               # can be paired with an output format "OS"
+               ;;
        -*)
                # Blank kernel with real OS is always fine.
                ;;
diff --git a/build-aux/git-hooks/pre-commit b/build-aux/git-hooks/pre-commit
index f89d9ca8f8c..2e0dd7dfd7c 100755
--- a/build-aux/git-hooks/pre-commit
+++ b/build-aux/git-hooks/pre-commit
@@ -21,6 +21,14 @@
 LC_ALL=C
 export LC_ALL
 
+# If this is a system where /bin/sh isn't sufficient to
+# run git-sh-setup, use a working shell as a recourse.
+if test -x "/usr/xpg4/bin/sh" && test -z "$POSIX_SHELL"; then
+    POSIX_SHELL=1
+    export POSIX_SHELL
+    exec "/usr/xpg4/bin/sh" `dirname $0`/pre-commit
+fi
+
 exec >&2
 
 . git-sh-setup
@@ -52,6 +60,9 @@ while IFS= read -r new_name; do
     -* | */-*)
       echo "$new_name: File name component begins with '-'."
       exit 1;;
+    ChangeLog.android)
+      # This file is explicitly ok.
+      ;;
     ChangeLog | */ChangeLog)
       echo "$new_name: Please use git commit messages, not ChangeLog files."
       exit 1;;
diff --git a/build-aux/git-hooks/prepare-commit-msg 
b/build-aux/git-hooks/prepare-commit-msg
index 7802dffda43..1520ef3f5f8 100755
--- a/build-aux/git-hooks/prepare-commit-msg
+++ b/build-aux/git-hooks/prepare-commit-msg
@@ -25,6 +25,10 @@ SHA1=$3
 # Prefer gawk if available, as it handles NUL bytes properly.
 if type gawk >/dev/null 2>&1; then
   awk="gawk"
+# Next use /usr/xpg4/bin/awk if available, since the script
+# doesn't support Unix awk.
+elif test -x /usr/xpg4/bin/awk; then
+  awk="/usr/xpg4/bin/awk"
 else
   awk="awk"
 fi
diff --git a/build-aux/make-info-dir b/build-aux/make-info-dir
index 3490b7a31f9..64cf2d43f16 100755
--- a/build-aux/make-info-dir
+++ b/build-aux/make-info-dir
@@ -38,7 +38,7 @@ shift
 
 exec "${AWK-awk}" '
   function detexinfo() {
-    gsub(/@value{emacsname}/, "Emacs")
+    gsub(/@value\{emacsname\}/, "Emacs")
     gsub(/@[^{]*\{/, "")
     gsub(/}/, "")
   }
diff --git a/build-aux/makecounter.sh b/build-aux/makecounter.sh
new file mode 100755
index 00000000000..3bebd288031
--- /dev/null
+++ b/build-aux/makecounter.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Generate or update a C file containing an increasing counter
+# variable.
+#
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of GNU Emacs.  GNU Emacs is free software: you can
+# redistribute it and/or modify it under the terms of the GNU General
+# Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# GNU Emacs is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.         If not, see <https://www.gnu.org/licenses/>.
+
+set -e
+
+curcount=
+if test -f "$1"; then
+    curcount=`cat "$1" | grep = | cut -d= -f2 \
+                 | sed -e 's/;//' -e 's/ //'`
+fi
+
+curcount=`expr 1 + $curcount 2>/dev/null || echo 0`
+
+cat > $1 <<EOF
+/* Generated automatically by makecounter.sh.  Do not edit! */
+
+#include <config.h>
+
+#ifdef HAVE_ANDROID
+#define EXPORT __attribute__ ((visibility ("default")))
+#endif /* HAVE_ANDROID */
+
+#ifdef EXPORT
+EXPORT
+#endif /* EXPORT */
+int emacs_shortlisp_counter = $curcount;
+EOF
diff --git a/build-aux/ndk-build-helper-1.mk b/build-aux/ndk-build-helper-1.mk
new file mode 100644
index 00000000000..2cde5146301
--- /dev/null
+++ b/build-aux/ndk-build-helper-1.mk
@@ -0,0 +1,112 @@
+# ndk-build-helper-1.mk -- Helper for ndk-build.m4.
+# Copyright (C) 2023 Free Software Foundation, Inc.
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# Print out information now defined.  Important details include:
+#   - list of source files to compile.
+#   - module export include directories.
+#   - module export CFLAGS.
+#   - module export LDFLAGS.
+#   - module name.
+
+build_kind = shared
+NDK_SO_NAMES =
+NDK_A_NAMES =
+
+# Record this module's dependencies.  This information is used later
+# on to recurse over libraries.
+NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES := $(LOCAL_STATIC_LIBRARIES) 
$(LOCAL_WHOLE_STATIC_LIBRARIES)
+NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES)
+NDK_$(LOCAL_MODULE)_EXPORT_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) 
$(LOCAL_EXPORT_C_INCLUDES)
+NDK_CXX_FLAG_$(LOCAL_MODULE) :=
+
+$(info Building $(build_kind))
+$(info $(LOCAL_MODULE))
+$(info $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES) 
$(LOCAL_SRC_FILES$(EMACS_ABI))))
+
+ifeq ($(findstring lib,$(LOCAL_MODULE)),lib)
+NDK_SO_NAMES = $(LOCAL_MODULE)_emacs.so
+else
+NDK_SO_NAMES = lib$(LOCAL_MODULE)_emacs.so
+endif
+
+define add-so-name-1
+# Now recurse over this module's dependencies.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module))))
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-so-name-1,$$(module))))
+endef
+
+define add-so-name
+ifeq ($(findstring lib,$(1)),lib)
+NDK_SO_NAME = $(1)_emacs.so
+else
+NDK_SO_NAME = lib$(1)_emacs.so
+endif
+
+ifeq ($$(findstring $$(NDK_SO_NAME),$$(NDK_SO_NAMES)),)
+NDK_SO_NAMES := $$(NDK_SO_NAMES) $$(NDK_SO_NAME)
+
+# Now recurse over this module's dependencies.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module))))
+
+# Recurse over static library dependencies of this shared library.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_STATIC_LIBRARIES) $$(NDK_$(1)_WHOLE_LIBRARIES)),$$(eval $$(call 
add-so-name-1,$$(module))))
+endif
+
+ifneq ($$(findstring stdc++,$$(NDK_$(1)_SHARED_LIBRARIES)),)
+NDK_CXX_FLAG_$(LOCAL_MODULE) := yes
+endif
+endef
+
+# Figure out includes from dependencies as well.
+NDK_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES)
+
+define add-includes
+ifeq ($$(findstring $$(NDK_$(1)_EXPORT_INCLUDES),$$(NDK_INCLUDES)),)
+NDK_INCLUDES += $$(NDK_$(1)_EXPORT_INCLUDES)
+
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)) $$(NDK_$(1)_STATIC_LIBRARIES),$$(eval $$(call 
add-includes,$$(module))))
+
+# Recurse over shared library dependencies of this static library.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module))))
+
+# Recurse over static or shared library dependencies of this static
+# library.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-so-name-1,$$(module))))
+endif
+endef
+
+# Resolve additional dependencies and their export includes based on
+# LOCAL_STATIC_LIBRARIES and LOCAL_SHARED_LIBRARIES.  Static library
+# dependencies can be ignored while building a shared library, as they
+# will be linked in to the resulting shared object file later.
+
+SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ log liblog android 
libandroid
+
+$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), 
$(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module))))
+$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES) 
$(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES)),$(eval $(call 
add-includes,$(module))))
+
+ifneq ($(findstring stdc++,$(LOCAL_SHARED_LIBRARIES)),)
+NDK_CXX_FLAG_$(LOCAL_MODULE) := yes
+endif
+
+$(info $(foreach dir,$(NDK_INCLUDES),-I$(dir)))
+$(info $(LOCAL_EXPORT_CFLAGS))
+
+$(info $(LOCAL_EXPORT_LDFLAGS) $(abspath $(addprefix 
$(NDK_BUILD_DIR)/,$(NDK_A_NAMES))) -L$(abspath $(NDK_BUILD_DIR)) $(foreach 
soname,$(NDK_SO_NAMES),-l:$(soname)))
+$(info $(NDK_SO_NAMES))
+$(info $(NDK_CXX_FLAG_$(LOCAL_MODULE)))
+$(info End)
diff --git a/build-aux/ndk-build-helper-2.mk b/build-aux/ndk-build-helper-2.mk
new file mode 100644
index 00000000000..186f3aec333
--- /dev/null
+++ b/build-aux/ndk-build-helper-2.mk
@@ -0,0 +1,105 @@
+# ndk-build-helper-2.mk -- Helper for ndk-build.m4.
+# Copyright (C) 2023 Free Software Foundation, Inc.
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# Say a static library is being built
+build_kind = static
+NDK_SO_NAMES =
+NDK_A_NAMES =
+
+# Record this module's dependencies.  This information is used later
+# on to recurse over libraries.
+NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES := $(LOCAL_STATIC_LIBRARIES) 
$(LOCAL_WHOLE_STATIC_LIBRARIES)
+NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES)
+NDK_$(LOCAL_MODULE)_EXPORT_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) 
$(LOCAL_EXPORT_C_INCLUDES)
+NDK_CXX_FLAG_$(LOCAL_MODULE) :=
+
+$(info Building $(build_kind))
+$(info $(LOCAL_MODULE))
+$(info $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES) 
$(LOCAL_SRC_FILES$(EMACS_ABI))))
+
+ifeq ($(findstring lib,$(LOCAL_MODULE)),lib)
+NDK_A_NAMES = $(LOCAL_MODULE).a
+else
+NDK_A_NAMES = lib$(LOCAL_MODULE).a
+endif
+
+define add-a-name
+ifeq ($(findstring lib,$(1)),lib)
+NDK_A_NAME = $(1).a
+else
+NDK_A_NAME = lib$(1).a
+endif
+
+ifeq ($$(findstring $$(NDK_A_NAME),$$(NDK_A_NAMES)),)
+NDK_A_NAMES := $$(NDK_A_NAMES) $$(NDK_A_NAME)
+
+# Now recurse over this module's dependencies.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-a-name,$$(module))))
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module))))
+endif
+
+ifneq ($$(findstring stdc++,$$(NDK_$(1)_SHARED_LIBRARIES)),)
+NDK_CXX_FLAG_$(LOCAL_MODULE) := yes
+endif
+endef
+
+define add-so-name
+ifeq ($(findstring lib,$(1)),lib)
+NDK_SO_NAME = $(1)_emacs.so
+else
+NDK_SO_NAME = lib$(1)_emacs.so
+endif
+
+ifeq ($$(NDK_SO_NAMES:$$(NDK_SO_NAME)=),$$(NDK_SO_NAMES))
+NDK_SO_NAMES := $$(NDK_SO_NAMES) $$(NDK_SO_NAME)
+
+# Now recurse over this module's dependencies.
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-a-name,$$(module))))
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module))))
+endif
+endef
+
+# Figure out includes from dependencies as well.
+NDK_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES)
+
+define add-includes
+ifeq ($$(findstring $$(NDK_$(1)_EXPORT_INCLUDES),$$(NDK_INCLUDES)),)
+NDK_INCLUDES += $$(NDK_$(1)_EXPORT_INCLUDES)
+
+$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), 
$$(NDK_$(1)_SHARED_LIBRARIES)) $$(NDK_$(1)_STATIC_LIBRARIES),$$(eval $$(call 
add-includes,$$(module))))
+endif
+endef
+
+# Resolve additional dependencies based on LOCAL_STATIC_LIBRARIES and
+# LOCAL_SHARED_LIBRARIES.
+
+SYSTEM_LIBRARIES = z libz libc c libdl dl libstdc++ stdc++ log liblog android 
libandroid
+
+$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_STATIC_LIBRARIES) 
$(LOCAL_WHOLE_STATIC_LIBRARIES)),$(eval $(call add-a-name,$(module))))
+$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), 
$(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module))))
+$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES) 
$(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_LIBRARIES)),$(eval $(call 
add-includes,$(module))))
+
+ifneq ($(findstring stdc++,$(LOCAL_SHARED_LIBRARIES)),)
+NDK_CXX_FLAG_$(LOCAL_MODULE) := yes
+endif
+
+$(info $(foreach dir,$(NDK_INCLUDES),-I$(dir)))
+$(info $(LOCAL_EXPORT_CFLAGS))
+$(info $(LOCAL_EXPORT_LDFLAGS) $(abspath $(addprefix 
$(NDK_BUILD_DIR)/,$(NDK_A_NAMES))) $(and $(NDK_SO_NAMES), -L$(abspath 
$(NDK_BUILD_DIR)) $(foreach soname,$(NDK_SO_NAMES),-l:$(soname))))
+$(info $(NDK_A_NAMES) $(NDK_SO_NAMES))
+$(info $(NDK_CXX_FLAG_$(LOCAL_MODULE)))
+$(info End)
diff --git a/build-aux/git-hooks/prepare-commit-msg 
b/build-aux/ndk-build-helper-3.mk
old mode 100755
new mode 100644
similarity index 50%
copy from build-aux/git-hooks/prepare-commit-msg
copy to build-aux/ndk-build-helper-3.mk
index 7802dffda43..4d0358d4f77
--- a/build-aux/git-hooks/prepare-commit-msg
+++ b/build-aux/ndk-build-helper-3.mk
@@ -1,8 +1,5 @@
-#!/bin/sh
-# Check the format of GNU Emacs change log entries.
-
-# Copyright 2019-2023 Free Software Foundation, Inc.
-
+# ndk-build-helper-3.mk -- Helper for ndk-build.m4.
+# Copyright (C) 2023 Free Software Foundation, Inc.
 # This file is part of GNU Emacs.
 
 # GNU Emacs is free software: you can redistribute it and/or modify
@@ -18,28 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-COMMIT_MSG_FILE=$1
-COMMIT_SOURCE=$2
-SHA1=$3
+# Say a static library is being built
+build_kind = executable
 
-# Prefer gawk if available, as it handles NUL bytes properly.
-if type gawk >/dev/null 2>&1; then
-  awk="gawk"
-else
-  awk="awk"
-fi
+$(info Building $(build_kind))
+$(info $(LOCAL_MODULE))
+$(info $(addprefix $(ANDROID_MODULE_DIRECTORY),$(LOCAL_SRC_FILES) 
$(LOCAL_SRC_FILES$(EMACS_ABI))))
 
-exec $awk '
-  # Catch the case when someone ran git-commit with -s option,
-  # which automatically adds Signed-off-by.
-  /^Signed-off-by: / {
-    print "'\''Signed-off-by:'\'' in commit message"
-    status = 1
-  }
-  END {
-    if (status != 0) {
-      print "Commit aborted; please see the file 'CONTRIBUTE'"
-    }
-    exit status
-  }
-' <"$COMMIT_MSG_FILE"
+$(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) 
$(LOCAL_EXPORT_C_INCLUDES),-I$(dir)))
+$(info $(LOCAL_EXPORT_CFLAGS))
+$(info $(LOCAL_EXPORT_LDFLAGS))
+$(info End)
diff --git a/build-aux/ndk-build-helper-4.mk b/build-aux/ndk-build-helper-4.mk
new file mode 100644
index 00000000000..a41679c53af
--- /dev/null
+++ b/build-aux/ndk-build-helper-4.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+undefine LOCAL_MODULE
+undefine LOCAL_MODULE_FILENAME
+undefine LOCAL_SRC_FILES
+undefine LOCAL_CPP_EXTENSION
+undefine LOCAL_CPP_FEATURES
+undefine LOCAL_C_INCLUDES
+undefine LOCAL_CFLAGS
+undefine LOCAL_CPPFLAGS
+undefine LOCAL_STATIC_LIBRARIES
+undefine LOCAL_SHARED_LIBRARIES
+undefine LOCAL_WHOLE_STATIC_LIBRARIES
+undefine LOCAL_LDLIBS
+undefine LOCAL_LDFLAGS
+undefine LOCAL_ALLOW_UNDEFINED_SYMBOLS
+undefine LOCAL_ARM_MODE
+undefine LOCAL_ARM_NEON
+undefine LOCAL_DISABLE_FORMAT_STRING_CHECKS
+undefine LOCAL_EXPORT_CFLAGS
+undefine LOCAL_EXPORT_CPPFLAGS
+undefine LOCAL_EXPORT_C_INCLUDES
+undefine LOCAL_EXPORT_C_INCLUDE_DIRS
+undefine LOCAL_EXPORT_LDFLAGS
+undefine LOCAL_EXPORT_LDLIBS
diff --git a/build-aux/ndk-build-helper.mk b/build-aux/ndk-build-helper.mk
new file mode 100644
index 00000000000..05f0af76411
--- /dev/null
+++ b/build-aux/ndk-build-helper.mk
@@ -0,0 +1,81 @@
+# ndk-build-helper.mk -- Helper for ndk-build.m4.
+# Copyright (C) 2023 Free Software Foundation, Inc.
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# This Makefile sets up enough to parse an Android-style Android.mk
+# file and return useful information about its contents.
+
+# See the text under ``NDK BUILD SYSTEM IMPLEMENTATION'' in
+# cross/ndk-build/README for more details.
+
+# TARGET_ARCH_ABI is the ABI that is being built for.
+TARGET_ARCH_ABI := $(EMACS_ABI)
+
+# TARGET_ARCH is the architecture that is being built for.
+TARGET_ARCH := $(NDK_BUILD_ARCH)
+
+# NDK_LAST_MAKEFILE is the last Makefile that was included.
+NDK_LAST_MAKEFILE = $(lastword $(filter %Android.mk,$(MAKEFILE_LIST)))
+
+# local-makefile is the current Makefile being loaded.
+local-makefile = $(NDK_LAST_MAKEFILE)
+
+# Make NDK_BUILD_DIR absolute.
+NDK_BUILD_DIR := $(absname $(NDK_BUILD_DIR))
+
+# Make EMACS_SRCDIR absolute.  This must be absolute, or nested
+# Android.mk files will not be able to find CLEAR_VARS.
+EMACS_SRCDIR := $(absname $(EMACS_SRCDIR))
+
+# my-dir is a function that returns the Android module directory.  If
+# no Android.mk has been loaded, use ANDROID_MODULE_DIRECTORY.
+my-dir = $(or $(and $(local-makefile),$(dir 
$(local-makefile))),$(ANDROID_MODULE_DIRECTORY))
+
+# Return all Android.mk files under the first arg.
+all-makefiles-under = $(wildcard $(1)/*/Android.mk)
+
+# Return all Android.mk files in subdirectories of this Makefile's
+# location.
+all-subdir-makefiles = $(call all-makefiles-under,$(call my-dir))
+
+# These functions are not implemented.
+parent-makefile =
+grand-parent-makefile =
+
+NDK_IMPORTS :=
+
+# Add the specified module (arg 1) to NDK_IMPORTS.
+import-module = $(eval NDK_IMPORTS += $(1))
+
+# Print out module information every time BUILD_SHARED_LIBRARY is
+# called.
+
+BUILD_SHARED_LIBRARY=$(BUILD_AUXDIR)ndk-build-helper-1.mk
+BUILD_STATIC_LIBRARY=$(BUILD_AUXDIR)ndk-build-helper-2.mk
+BUILD_EXECUTABLE=$(BUILD_AUXDIR)ndk-build-helper-3.mk
+CLEAR_VARS=$(BUILD_AUXDIR)ndk-build-helper-4.mk
+
+# Now include Android.mk.
+
+include $(ANDROID_MAKEFILE)
+
+# Finally, print out the imports.
+$(info Start Imports)
+$(info $(NDK_IMPORTS))
+$(info End Imports)
+
+# Dummy target.
+all:
diff --git a/build-aux/ndk-module-extract.awk b/build-aux/ndk-module-extract.awk
new file mode 100644
index 00000000000..6ff30973d67
--- /dev/null
+++ b/build-aux/ndk-module-extract.awk
@@ -0,0 +1,88 @@
+/^Building.+$/ {
+  kind = $2
+}
+
+/^Start Imports$/ {
+  imports = 1
+}
+
+// {
+  if (imports && ++imports > 2)
+    {
+      if (!match ($0, /^End Imports$/))
+       makefile_imports = makefile_imports " " $0
+    }
+  else if (!match ($0, /^End$/) && !match ($0, /^Building.+$/))
+    {
+      if (kind)
+       {
+         if (target_found)
+           cxx_deps = $0
+         else if (ldflags_found)
+           {
+             target = $0
+             target_found = 1
+           }
+         else if (cflags_found)
+           {
+             ldflags = $0
+             ldflags_found = 1
+           }
+         else if (includes_found)
+           {
+             cflags = $0
+             cflags_found = 1
+           }
+         else if (src_found)
+           {
+             includes = $0
+             includes_found = 1
+           }
+         else if (name_found)
+           {
+             src = $0
+             src_found = 1;
+           }
+         else
+           {
+             name = $0
+             name_found = 1
+           }
+       }
+    }
+}
+
+/^End$/ {
+  if (name == MODULE && (kind == "shared" || kind == "static"))
+    {
+      printf "module_name=%s\n", name
+      printf "module_kind=%s\n", kind
+      printf "module_src=\"%s\"\n", src
+      printf "module_includes=\"%s\"\n", includes
+      printf "module_cflags=\"%s\"\n", cflags
+      printf "module_ldflags=\"%s\"\n", ldflags
+      printf "module_target=\"%s\"\n", target
+      printf "module_cxx_deps=\"%s\"\n", cxx_deps
+    }
+
+  src = ""
+  name = ""
+  kind = ""
+  includes = ""
+  cflags = ""
+  ldflags = ""
+  name_found = ""
+  src_found = ""
+  includes_found = ""
+  cflags_found = ""
+  ldflags_found = ""
+  target_found = ""
+}
+
+/^End Imports$/ {
+  imports = ""
+  # Strip off leading whitespace.
+  gsub (/^[ \t]+/, "", makefile_imports)
+  printf "module_imports=\"%s\"\n", makefile_imports
+  makefile_imports = ""
+}
diff --git a/configure.ac b/configure.ac
index 38ff6e18daf..f92339225b5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,6 +26,25 @@ dnl Note this is parsed by (at least) make-dist and 
lisp/cedet/ede/emacs.el.
 AC_INIT([GNU Emacs], [30.0.50], [bug-gnu-emacs@gnu.org], [],
   [https://www.gnu.org/software/emacs/])
 
+if test "$XCONFIGURE" = "android"; then
+  # configure is being called recursively to configure Emacs for
+  # Android!
+  AC_MSG_NOTICE([called to recursively configure Emacs for Android.])
+  # Set CC to ANDROID_CC and CFLAGS to ANDROID_CFLAGS.
+  CC=$ANDROID_CC
+  # Set -Wno-implicit-function-declaration.  Building Emacs for older
+  # versions of Android requires configure tests to fail if the
+  # functions are not defined, as the Android library in the NDK
+  # defines subroutines that are not available in the headers being
+  # used.
+  CFLAGS="$ANDROID_CFLAGS -Werror=implicit-function-declaration"
+  # Don't explicitly enable support for large files unless Emacs is
+  # being built for API 21 or later.  Otherwise, mmap does not work.
+  AS_IF([test "$ANDROID_SDK" -lt "21"], [
+    enable_largefile=no
+    enable_year2038=no])
+fi
+
 dnl Set emacs_config_options to the options of 'configure', quoted for the 
shell,
 dnl and then quoted again for a C string.  Separate options with spaces.
 dnl Add some environment variables, if they were passed via the environment
@@ -126,7 +145,64 @@ MAKE=$ac_cv_path_MAKE
 export MAKE
 
 dnl Canonicalize the configuration name.
+if test "$XCONFIGURE" = "android"; then
+  dnl Set host to whatever Android system Emacs is being configured
+  dnl for.  Determine this by looking at the output of ANDROID_CC.
+
+  AC_MSG_CHECKING([the cross-compiler's target])
+  cc_target=`${CC} -v 2>&1 | sed -n 's/Target: //p'`
+  case "$cc_target" in
+    *android*) host_alias=$cc_target
+    ;;
+    *) AC_MSG_ERROR([The cross compiler does not compile for Android.
+Please verify that you specified the correct compiler in the ANDROID_CC
+variable when you ran configure.])
+    ;;
+  esac
+  AC_MSG_RESULT([$host_alias])
+fi
+
 AC_CANONICAL_HOST
+AC_CANONICAL_BUILD
+
+AS_IF([test "$XCONFIGURE" = "android"],[
+  # Initialize the Android NDK build system.  Make sure to use the
+  # passed through NDK path.
+  # Make sure to pass through the CFLAGS, as older versions of the
+  # NDK require them to be able to find system includes.
+  with_ndk_path="$android_ndk_path"
+  with_ndk_cxx_shared="$android_ndk_cxx_shared"
+  with_ndk_cxx="$android_ndk_cxx"
+  ndk_INIT([$android_abi], [$ANDROID_SDK], [cross/ndk-build],
+           [$ANDROID_CFLAGS])
+
+  # At the same time, configure libexec with the build directory
+  # set to `exec'.
+  AS_MKDIR_P([exec])
+
+  # Enter exec and configure it, using the C compiler as both the
+  # assembler and the linker.  Determine the absolute name of the
+  # source directory.
+  # N.B. that the linker is actually cc, so pass -nostdlib, lest
+  # the crt be linked in.  Likewise for as.
+
+  AS_CASE([$srcdir], [.], [emacs_srcdir=`pwd`],
+    [[[\\/]* | ?:[\\/]*]], [emacs_srcdir=$srcdir],
+    [*], [emacs_srcdir=`pwd`/$srcdir])
+
+  AC_MSG_NOTICE([configuring in `exec'])
+
+  OLDCWD=`pwd`
+  cd exec
+  $CONFIG_SHELL $emacs_srcdir/exec/configure           \
+               --host=$host "CC=$CC" "LD=$CC" "AS=$CC" \
+               "AR=$AR" "CFLAGS=$CFLAGS"
+  emacs_val=$?
+  cd $OLDCWD
+
+  AS_IF([test "$emacs_val" != "0"],
+    [AC_MSG_ERROR([failed to configure in `exec'])])
+])
 
 case $host in
  *-mingw*)
@@ -207,6 +283,13 @@ AC_ARG_WITH([all],
   [with_features=$withval],
   [with_features=yes])
 
+dnl ARCH_INDEPENDENT_CONFIG_FILES(FILE...)
+dnl Like AC_CONFIG_FILES(FILE).  However, do not generate this
+dnl   if configure is being called recursively in preparation
+dnl   for cross-compilation.
+AC_DEFUN([ARCH_INDEPENDENT_CONFIG_FILES], [
+  AS_IF([test "$XCONFIGURE" != "android"], [AC_CONFIG_FILES([$1])])])
+
 dnl OPTION_DEFAULT_OFF(NAME, HELP-STRING)
 dnl Create a new --with option that defaults to being disabled.
 dnl NAME is the base name of the option.  The shell variable with_NAME
@@ -222,7 +305,8 @@ AC_DEFUN([OPTION_DEFAULT_OFF], [dnl
 ])dnl
 
 dnl OPTION_DEFAULT_IFAVAILABLE(NAME, HELP-STRING)
-dnl Create a new --with option that defaults to 'ifavailable'.
+dnl Create a new --with option that defaults to 'ifavailable',
+dnl unless it is overriden by $with_features being equal to 'no'.
 dnl NAME is the base name of the option.  The shell variable with_NAME
 dnl   will be set to either the user's value (if the option is
 dnl   specified; 'yes' for a plain --with-NAME) or to 'ifavailable' (if the
@@ -232,10 +316,12 @@ dnl   characters with "_".
 dnl HELP-STRING is the help text for the option.
 AC_DEFUN([OPTION_DEFAULT_IFAVAILABLE], [dnl
   AC_ARG_WITH([$1],[AS_HELP_STRING([--with-$1],[$2])],[],[dnl
-    m4_bpatsubst([with_$1], [[^0-9a-z]], [_])=ifavailable])dnl
+    AS_IF([test "$with_features" != no],
+      [m4_bpatsubst([with_$1], [[^0-9a-z]], [_])=ifavailable],
+      [m4_bpatsubst([with_$1], [[^0-9a-z]], [_])=no])dnl
+  ])dnl
 ])dnl
 
-
 dnl OPTION_DEFAULT_ON(NAME, HELP-STRING)
 dnl Create a new --with option that defaults to $with_features.
 dnl NAME is the base name of the option.  The shell variable with_NAME
@@ -259,25 +345,25 @@ AC_ARG_WITH([mailutils],
       options are irrelevant; this is the default if GNU Mailutils is
       installed])],
   [],
-  [with_mailutils=$with_features
-   if test "$with_mailutils" = yes; then
-     (movemail --version) >/dev/null 2>&1 || with_mailutils=no
-   fi])
-if test "$with_mailutils" = no; then
-  with_mailutils=
-fi
-AC_SUBST([with_mailutils])
+  [AS_IF([test "$with_features" != "no"],
+   [with_mailutils=yes-unless-android
+    AS_IF([test "x$XCONFIGURE" != "xandroid"],
+     [(movemail --version) >/dev/null 2>&1 || with_mailutils=no],
+     [dnl Don't check for movemail if cross-compiling.
+      dnl instead, default to false.
+      with_mailutils=no])])])
 
 AC_ARG_WITH([pop],
   [AS_HELP_STRING([--with-pop],
      [Support POP mail retrieval if Emacs movemail is used (not recommended,
       as Emacs movemail POP is insecure).  This is the default only on
-      native MS-Windows.])],
+      native MS-Windows and Android.])],
   [],
-  [case $host in
-     *-mingw*) with_pop=yes;;
-     *) with_pop=no-by-default;;
-   esac])
+  dnl Enable movemail POP support on Android as GNU Mailutils is
+  dnl normally unavailable on that platform.
+  [AS_CASE([$host],
+     [*-mingw*|*android*], [with_pop=yes],
+     [with_pop=no-by-default])])
 if test "$with_pop" = yes; then
    AC_DEFINE([MAIL_USE_POP])
 fi
@@ -499,6 +585,31 @@ OPTION_DEFAULT_ON([threads],[don't compile with elisp 
threading support])
 OPTION_DEFAULT_OFF([cygwin32-native-compilation],[use native compilation on 
32-bit Cygwin])
 OPTION_DEFAULT_ON([xinput2],[don't use version 2 of the X Input Extension for 
input])
 OPTION_DEFAULT_OFF([small-ja-dic],[generate a smaller-size Japanese 
dictionary])
+OPTION_DEFAULT_OFF([android],[cross-compile Android application package])
+OPTION_DEFAULT_ON([android-debug],[don't build Emacs as a debug package on 
Android])
+
+# Find out of Android support is enabled and mailutils has defaulted
+# to `yes-unless-android'.  Disable it if so.
+
+AS_IF([test "x$with_mailutils" = "xyes-unless-android"],
+  [AS_IF([test "x$with_android" != "xno"],
+     [with_mailutils=no],
+     [with_mailutils=yes])])
+
+# Clear with_mailutils if it's set to no.
+
+AS_IF([test "$with_mailutils" = no],
+  [with_mailutils=])
+
+AS_IF([test x"$with_mailutils" = xyes],
+  [AC_DEFINE([HAVE_MAILUTILS], [1],
+     [Define to 1 if Emacs was configured with mailutils])])
+
+AC_SUBST([with_mailutils])
+
+AC_ARG_WITH([shared-user-id],
+  [AS_HELP_STRING([--with-shared-user-id=ID],
+    [use the given shared user ID in Android builds])])
 
 AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
  [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, 
w32, no)])],
@@ -615,37 +726,51 @@ do
 done
 IFS="$ac_save_IFS"
 
-if test x$ac_enable_checking != x ; then
-  AC_DEFINE([ENABLE_CHECKING], [1],
-[Define to 1 if expensive run-time data type and consistency checks are 
enabled.])
-fi
-if $CHECK_STRUCTS; then
-  AC_DEFINE([CHECK_STRUCTS], [1],
-    [Define this to check whether someone updated the portable dumper
-     code after changing the layout of a structure that it uses.
-     If you change one of these structures, check that the pdumper.c
-     code is still valid, and update the pertinent hash in pdumper.c
-     by manually copying the hash from the newly-generated dmpstruct.h.])
-fi
-AC_SUBST([CHECK_STRUCTS])
-if test x$ac_gc_check_stringbytes != x ; then
-  AC_DEFINE([GC_CHECK_STRING_BYTES], [1],
-[Define this temporarily to hunt a bug.  If defined, the size of
-   strings is redundantly recorded in sdata structures so that it can
-   be compared to the sizes recorded in Lisp strings.])
-fi
-if test x$ac_gc_check_string_overrun != x ; then
-  AC_DEFINE([GC_CHECK_STRING_OVERRUN], [1],
-[Define this to check for short string overrun.])
-fi
-if test x$ac_gc_check_string_free_list != x ; then
-  AC_DEFINE([GC_CHECK_STRING_FREE_LIST], [1],
-[Define this to check the string free list.])
-fi
-if test x$ac_glyphs_debug != x ; then
-  AC_DEFINE([GLYPH_DEBUG], [1],
-[Define this to enable glyphs debugging code.])
-fi
+# This environment variable is used to signal that checking should be
+# enabled on Android.  When that happens, simply enable checking for
+# the cross-compiled Android binary.
+
+AS_IF([test "x$XCONFIGURE" = "xandroid" \
+       && test "x$android_enable_checking" = "xyes"],
+  [ac_enable_checking=yes])
+
+# There is little point in enabling checking in the build machine if
+# cross-compiling for Android.
+AS_IF([test "$with_android" = no || test -n "$XCONFIGURE"],[
+  if test x$ac_enable_checking != x ; then
+    AC_DEFINE([ENABLE_CHECKING], [1],
+  [Define to 1 if expensive run-time data type and consistency checks are 
enabled.])
+  fi
+  if $CHECK_STRUCTS; then
+    AC_DEFINE([CHECK_STRUCTS], [1],
+      [Define this to check whether someone updated the portable dumper
+       code after changing the layout of a structure that it uses.
+       If you change one of these structures, check that the pdumper.c
+       code is still valid, and update the pertinent hash in pdumper.c
+       by manually copying the hash from the newly-generated dmpstruct.h.])
+  fi
+  AC_SUBST([CHECK_STRUCTS])
+  if test x$ac_gc_check_stringbytes != x ; then
+    AC_DEFINE([GC_CHECK_STRING_BYTES], [1],
+  [Define this temporarily to hunt a bug.  If defined, the size of
+     strings is redundantly recorded in sdata structures so that it can
+     be compared to the sizes recorded in Lisp strings.])
+  fi
+  if test x$ac_gc_check_string_overrun != x ; then
+    AC_DEFINE([GC_CHECK_STRING_OVERRUN], [1],
+  [Define this to check for short string overrun.])
+  fi
+  if test x$ac_gc_check_string_free_list != x ; then
+    AC_DEFINE([GC_CHECK_STRING_FREE_LIST], [1],
+  [Define this to check the string free list.])
+  fi
+  if test x$ac_glyphs_debug != x ; then
+    AC_DEFINE([GLYPH_DEBUG], [1],
+  [Define this to enable glyphs debugging code.])
+  fi
+],[AS_IF([test "x$ac_enable_checking" != x],
+    [android_enable_checking=yes
+     export android_enable_checking])])
 
 dnl The name of this option is unfortunate.  It predates, and has no
 dnl relation to, the "sampling-based elisp profiler" added in 24.3.
@@ -683,6 +808,532 @@ AC_ARG_ENABLE([build-details],
   [test "$enableval" = no && BUILD_DETAILS=--no-build-details])
 AC_SUBST([BUILD_DETAILS])
 
+# JAVA_PUSH_LINT(OPT)
+# -------------------
+# Check if javac supports the diagnostic flag -Xlint:OPT.
+# If it does, add it to WARN_JAVAFLAGS.
+
+AC_DEFUN([JAVA_PUSH_LINT],
+[
+  AC_CACHE_CHECK([whether Java compiler accepts -Xlint:$1],
+    [emacs_cv_javac_knows_lint_$1],
+    AS_IF([rm -f conftest.class
+cat << EOF > conftest.java
+
+class conftest
+{
+
+}
+
+EOF
+("$JAVAC" -Xlint:$1 conftest.java 2>&AS_MESSAGE_LOG_FD) \
+  && rm -f conftest.class], [emacs_cv_javac_knows_lint_$1=yes],
+     [emacs_cv_javac_knows_lint_$1=no]))
+
+  AS_IF([test "$emacs_cv_javac_knows_lint_$1" = "yes"],
+    [WARN_JAVAFLAGS="$WARN_JAVAFLAGS -Xlint:$1"])
+])
+
+# Start Android configuration.  This is done in three steps:
+
+# First, the SDK tools needed to build the Android package on the host
+# are found.
+
+# Then, configure is called inside itself with the NDK C and C++
+# compilers, and the Makefiles generated, along with config.h, are
+# renamed to end with .android.
+
+# Finally, configure continues to configure the Emacs binary that will
+# run on the host.
+
+ANDROID=
+JAVAC=
+AAPT=
+JARSIGNER=
+APKSIGNER=
+ZIPALIGN=
+DX=
+ANDROID_JAR=
+ANDROID_ABI=
+WARN_JAVAFLAGS=
+ANDROID_SHARED_USER_ID=
+ANDROID_SHARED_USER_NAME=
+
+# This is a list of Makefiles that have alternative versions for
+# Android.
+android_makefiles="lib/Makefile lib/gnulib.mk lib-src/Makefile src/Makefile"
+
+# This is whether or not to package mailutils into the executable.
+emacs_use_mailutils=
+
+AC_ARG_VAR([ANDROID_CC], [The Android C cross-compiler.])
+AC_ARG_VAR([SDK_BUILD_TOOLS], [Name of directory holding Android SDK 
build-tools.])
+AC_ARG_VAR([ANDROID_CFLAGS], [Flags given to the Android C cross-compiler.])
+AC_ARG_VAR([JAVAC], [Java compiler path.  Used for Android.])
+AC_ARG_VAR([JARSIGNER], [Java package signer path.  Used for Android.])
+AC_ARG_VAR([APKSIGNER], [Android package signer path.  Used for Android.])
+AC_ARG_VAR([SDK_BUILD_TOOLS], [Path to the Android SDK build tools.])
+
+if test "$with_android" = "yes"; then
+   AC_MSG_ERROR([Please specify the path to the Android.jar file, like so:
+
+  ./configure --with-android=/path/to/android.jar
+
+along with the path to the SDK build-tools (this is the directory with
+tools such as aapt, dx, and aidl):
+
+              SDK_BUILD_TOOLS=/path/to/sdk-build-tools
+
+The cross-compiler should then be specified:
+
+              ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang
+
+In addition, you may pass any special arguments to the cross-compiler
+via the ANDROID_CFLAGS environment variable.])
+elif test "$with_android" = "no" || test "$with_android" = ""; then
+  ANDROID=no
+else
+  AC_CHECK_PROGS([JAVAC], [javac])
+  if test "$JAVAC" = ""; then
+    AC_MSG_ERROR([The Java compiler required to build Emacs was not found.
+Please make sure `javac' can be found on your path, or alternatively specify
+the path to your Java compiler before configuring Emacs, like so:
+
+  JAVAC=/opt/jdk/bin/javac ./configure --with-android])
+  fi
+
+  AC_CHECK_PROGS([JARSIGNER], [jarsigner])
+  if test "$JARSIGNER" = ""; then
+    AC_MSG_ERROR([The Java package signing utility was not found.
+Please make sure `jarsigner' can be found on your path, or alternatively
+specify its location before configuring Emacs, like so:
+
+  JARSIGNER=/opt/jdk/bin/jarsigner ./configure --with-android])
+  fi
+
+  AC_CACHE_CHECK([whether the Java compiler works],
+    [emacs_cv_working_javac],
+    AS_IF([rm -f conftest.class
+cat << EOF > conftest.java
+
+import android.app.Activity;
+import android.os.Bundle;
+
+class conftest extends Activity
+{
+  @Override
+  public void
+  onCreate (Bundle savedInstanceState)
+  {
+    super.onCreate (savedInstanceState);
+  }
+}
+
+EOF
+("$JAVAC" -classpath "$with_android" -target 1.7 -source 1.7 conftest.java \
+  -d . >&AS_MESSAGE_LOG_FD 2>&1) && test -s conftest.class && rm -f 
conftest.class],
+          [emacs_cv_working_javac=yes],
+          [emacs_cv_working_javac=no]))
+
+  if test "$emacs_cv_working_javac" = "no"; then
+    AC_MSG_ERROR([The Java compiler does not work, or you did not specify
+a valid path to android.jar.  See config.log for more details.])
+  fi
+
+  AC_CACHE_CHECK([whether android.jar is new enough],
+    [emacs_cv_android_s_or_later],
+    AS_IF([rm -f conftest.class
+cat << EOF > conftest.java
+
+import android.os.Build;
+
+class conftest
+{
+  private static int test = Build.VERSION_CODES.TIRAMISU;
+}
+
+EOF
+("$JAVAC" -classpath "$with_android" -target 1.7 -source 1.7 conftest.java \
+  -d . >&AS_MESSAGE_LOG_FD 2>&1) && test -s conftest.class && rm -f 
conftest.class],
+          [emacs_cv_android_s_or_later=yes],
+          [emacs_cv_android_s_or_later=no]))
+
+  if test "$emacs_cv_android_s_or_later" = "no"; then
+    AC_MSG_ERROR([Emacs must be built with an android.jar file produced for \
+Android 13 (Tiramisu) or later.])
+  fi
+
+  dnl See if the Java compiler supports the `--release' option which
+  dnl makes it check for and prevent using features introduced after
+  dnl Java 1.7.
+
+  AC_CACHE_CHECK([whether javac accepts --release 7],
+    [emacs_cv_javac_release_7], AS_IF([rm -f conftest.class
+cat << EOF > conftest.java
+
+class conftest
+{
+
+}
+
+EOF
+("$JAVAC" --release 7 conftest.java 2>&AS_MESSAGE_LOG_FD) \
+  && rm -f conftest.class],
+      [emacs_cv_javac_release_7=yes],
+      [emacs_cv_javac_release_7=no]))
+
+  if test "$emacs_cv_javac_release_7" = "yes"; then
+    WARN_JAVAFLAGS="$WARN_JAVAFLAGS --release 7"
+  else
+    dnl If not, just make sure the generated bytecode is correct.
+    WARN_JAVAFLAGS="$WARN_JAVAFLAGS -target 1.7 -source 1.7"
+  fi
+
+  dnl Enable some useful Java linting options.
+  JAVA_PUSH_LINT([deprecation])
+  JAVA_PUSH_LINT([cast])
+  JAVA_PUSH_LINT([divzero])
+  JAVA_PUSH_LINT([nonempty])
+  JAVA_PUSH_LINT([empty])
+  JAVA_PUSH_LINT([finally])
+  JAVA_PUSH_LINT([overrides])
+  JAVA_PUSH_LINT([path])
+  JAVA_PUSH_LINT([serial])
+  JAVA_PUSH_LINT([unchecked])
+
+  # Get the name of the android.jar file.
+  ANDROID_JAR="$with_android"
+
+  # Substitute this into java/Makefile.
+  AC_SUBST([WARN_JAVAFLAGS])
+
+  AC_PATH_PROGS([AAPT], [aapt], [], "${SDK_BUILD_TOOLS}:$PATH")
+  if test "$AAPT" = ""; then
+    AC_MSG_ERROR([The Android asset packaging tool was not found.
+Please verify that the path to the SDK build tools you specified is correct])
+  fi
+
+  AC_PATH_PROGS([APKSIGNER], [apksigner], [], "${SDK_BUILD_TOOLS}:$PATH")
+  if test "$APKSIGNER" = ""; then
+    AC_MSG_ERROR([The Android package signing tool was not found.
+Please verify that the path to the SDK build tools you specified is correct])
+  fi
+
+  AC_PATH_PROGS([D8], [d8], [], "${SDK_BUILD_TOOLS}:$PATH")
+  if test "D8" = ""; then
+    AC_MSG_ERROR([The Android dexer was not found.
+Please verify that the path to the SDK build tools you specified is correct])
+  fi
+
+  AC_PATH_PROGS([ZIPALIGN], [zipalign], [], "${SDK_BUILD_TOOLS}:$PATH")
+  if test "ZIPALIGN" = ""; then
+    AC_MSG_ERROR([The Android ZIP archive alignment utility was not found.
+Please verify that the path to the SDK build tools you specified is correct]);
+  fi
+
+  dnl Now configure Emacs to generate binaries for Android.  After the
+  dnl configuration completes, move the generated Makefiles.
+
+  if test "$ANDROID_CC" = ""; then
+    AC_MSG_ERROR([Please specify the path to the Android cross-compiler
+for your machine.  For example:
+
+  ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang    \\
+    ./configure --with-android])
+  fi
+
+  dnl Obtain the cross compiler's target to find out where binaries go
+  dnl in the resulting package.
+
+  AC_MSG_CHECKING([for the kind of Android system Emacs is being built for])
+  cc_target=`${ANDROID_CC} -v 2>&1 | sed -n 's/Target: //p'`
+  case "$cc_target" in
+[
+    *i[3-6]86*) android_abi=x86
+      ;;
+    *x86_64*) android_abi=x86_64
+      ;;
+    *aarch64*) android_abi=arm64-v8a
+      ;;
+    *arm*v7a*) android_abi=armeabi-v7a
+      ;;
+    *mips64*) android_abi=mips64
+      ;;
+    *mips*) android_abi=mips
+      ;;
+    *arm*) android_abi=armeabi
+      ;;
+]
+    *) AC_MSG_ERROR([configure could not determine the type of Android \
+binary Emacs is being configured for.  Please port this configure script \
+to your Android system, or verify that you specified the correct compiler \
+in the ANDROID_CC variable when you ran configure.
+
+The compiler target is: $cc_target])
+      ;;
+  esac
+  AC_MSG_RESULT([$android_abi])
+
+  ANDROID_ABI=$android_abi
+
+  dnl Obtain the minimum SDK version of the resulting Emacs binary
+  dnl built with this NDK.
+
+  ANDROID_MIN_SDK=8
+  AC_MSG_CHECKING([for the lowest Android version Emacs can run on])
+  [android_sdk=`echo "$cc_target" | grep -oE 'android([0-9][0-9]?)'`]
+
+  if test -n "$android_sdk"; then
+    android_sdk=`echo "$android_sdk" | sed -n 's/android//p'`
+    AC_MSG_RESULT([$android_sdk])
+    ANDROID_MIN_SDK=$android_sdk
+  else
+    # This is probably GCC.
+    [ cat << EOF > conftest.c
+#include <android/api-level.h>
+extern const char *foo;
+
+int
+main (void)
+{
+#if __ANDROID_API__ < 7
+   foo = "emacs_api_6";
+#elif __ANDROID_API__ < 8
+   foo = "emacs_api_7";
+#elif __ANDROID_API__ < 9
+   foo = "emacs_api_8";
+#elif __ANDROID_API__ < 10
+  foo = "emacs_api_9";
+#elif __ANDROID_API__ < 11
+  foo = "emacs_api_10";
+#elif __ANDROID_API__ < 12
+  foo = "emacs_api_11";
+#elif __ANDROID_API__ < 13
+  foo = "emacs_api_12";
+#elif __ANDROID_API__ < 14
+   foo = "emacs_api_13";
+#elif __ANDROID_API__ < 15
+   foo = "emacs_api_14";
+#elif __ANDROID_API__ < 16
+   foo = "emacs_api_15";
+#elif __ANDROID_API__ < 17
+   foo = "emacs_api_16";
+#elif __ANDROID_API__ < 18
+   foo = "emacs_api_17";
+#elif __ANDROID_API__ < 19
+   foo = "emacs_api_18";
+#elif __ANDROID_API__ < 20
+   foo = "emacs_api_19";
+#elif __ANDROID_API__ < 21
+   foo = "emacs_api_20";
+#elif __ANDROID_API__ < 22
+   foo = "emacs_api_21";
+#elif __ANDROID_API__ < 23
+   foo = "emacs_api_22";
+#elif __ANDROID_API__ < 24
+   foo = "emacs_api_23";
+#elif __ANDROID_API__ < 25
+   foo = "emacs_api_24";
+#elif __ANDROID_API__ < 26
+   foo = "emacs_api_25";
+#elif __ANDROID_API__ < 27
+   foo = "emacs_api_26";
+#elif __ANDROID_API__ < 28
+   foo = "emacs_api_27";
+#elif __ANDROID_API__ < 29
+   foo = "emacs_api_28";
+#elif __ANDROID_API__ < 30
+   foo = "emacs_api_29";
+#elif __ANDROID_API__ < 31
+   foo = "emacs_api_30";
+#elif __ANDROID_API__ < 32
+   foo = "emacs_api_31";
+#elif __ANDROID_API__ < 33
+   foo = "emacs_api_32";
+#elif __ANDROID_API__ < 34
+   foo = "emacs_api_33";
+#else
+   foo = "emacs_api_future";
+#endif
+}
+EOF]
+
+    AC_CACHE_VAL([emacs_cv_android_api],
+                [$ANDROID_CC $ANDROID_CFLAGS -c conftest.c -o conftest.o \
+                 && emacs_cv_android_api=`grep -ao -E                    \
+                    "emacs_api_([[0-9][0-9]]?|future)" conftest.o`])
+    android_sdk="$emacs_cv_android_api"
+    rm -rf conftest.c conftest.o
+
+    # If this version of the NDK requires __ANDROID_API__ to be
+    # specified, then complain to the user.
+    if test "$android_sdk" = "emacs_api_future"; then
+       AC_MSG_ERROR([The version of Android to build for was not specified.
+You must tell the Android compiler what version of Android to build for,
+by defining the __ANDROID_API__ preprocessor macro in ANDROID_CC, like so:
+
+    ANDROID_CC="/path/to/ndk/arm-linux-android-gcc -D__ANDROID_API__=8"])
+    fi
+
+    if test -n "$android_sdk"; then
+       android_sdk=`echo $android_sdk | sed -n 's/emacs_api_//p'`
+       AC_MSG_RESULT([$android_sdk])
+       ANDROID_MIN_SDK=$android_sdk
+    else
+      AC_MSG_RESULT([unknown ($cc_target); assuming 8])
+      AC_MSG_ERROR([configure could not determine the versions of Android \
+a binary built with this compiler will run on.  The generated application \
+package will likely install on older systems but crash on startup.])
+      android_sdk=8
+    fi
+  fi
+  AC_SUBST([ANDROID_MIN_SDK])
+
+  # Now tell java/Makefile if Emacs is being built for Android 4.3 or
+  # earlier.
+  ANDROID_SDK_18_OR_EARLIER=
+  if test "$android_sdk" -le "18"; then
+     ANDROID_SDK_18_OR_EARLIER=yes
+  fi
+  AC_SUBST([ANDROID_SDK_18_OR_EARLIER])
+
+  # Likewise for Android 2.2.
+  ANDROID_SDK_8_OR_EARLIER=
+  if test "$android_sdk" -le "8"; then
+     ANDROID_SDK_8_OR_EARLIER=yes
+  fi
+  AC_SUBST([ANDROID_SDK_8_OR_EARLIER])
+
+  # Save confdefs.h and config.log for now.
+  mv -f confdefs.h _confdefs.h
+  mv -f config.log _config.log
+
+  # Make sure these files are removed upon exit.
+  trap "rm -rf _confdefs.h _config.log" 0
+
+  # Figure out what --with-FOO options to pass through.
+  passthrough="$passthrough --with-png=$with_png"
+  passthrough="$passthrough --with-webp=$with_webp"
+  passthrough="$passthrough --with-gif=$with_gif"
+  passthrough="$passthrough --with-json=$with_json"
+  passthrough="$passthrough --with-jpeg=$with_jpeg"
+  passthrough="$passthrough --with-xml2=$with_xml2"
+  passthrough="$passthrough --with-sqlite3=$with_sqlite3"
+  passthrough="$passthrough --with-gnutls=$with_gnutls"
+  passthrough="$passthrough --with-tiff=$with_tiff"
+  passthrough="$passthrough --with-selinux=$with_selinux"
+  passthrough="$passthrough --with-modules=$with_modules"
+  passthrough="$passthrough --with-tree-sitter=$with_tree_sitter"
+  passthrough="$passthrough --with-imagemagick=$with_imagemagick"
+  passthrough="$passthrough --with-lcms2=$with_lcms2"
+  passthrough="$passthrough --with-mailutils=$with_mailutils"
+  passthrough="$passthrough --with-pop=$with_pop"
+  passthrough="$passthrough --with-harfbuzz=$with_harfbuzz"
+
+  # Now pass through some checking options.
+  emacs_val="--enable-check-lisp-object-type=$enable_check_lisp_object_type"
+  passthrough="$passthrough $emacs_val"
+
+  AS_IF([test "x$with_mailutils" = "xyes"], [emacs_use_mailutils=yes])
+  AC_SUBST([emacs_use_mailutils])
+
+  AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC"           \
+         ANDROID_SDK="$android_sdk" android_abi=$android_abi   \
+        android_ndk_path="$with_ndk_path"                      \
+        android_ndk_cxx_shared="$with_ndk_cxx_shared"          \
+        android_ndk_cxx="$android_ndk_cxx"                     \
+        $CONFIG_SHELL $0 $passthrough], [],
+    [AC_MSG_ERROR([Failed to cross-configure Emacs for android.])])
+
+  # Now set ANDROID to yes.
+  ANDROID=yes
+
+  for makefile in $android_makefiles; do
+    AC_MSG_NOTICE([Generating $makefile.android])
+    mv -f "$makefile" "$makefile.android"
+  done
+
+  AC_MSG_NOTICE([Generating src/config.h.android])
+  mv -f src/config.h src/config.h.android
+
+  # Tell AndroidManifest.xml whether or not Emacs should be built
+  # debug.
+  ANDROID_DEBUGGABLE=false
+  if test "$with_android_debug" = "yes"; then
+    ANDROID_DEBUGGABLE=true
+  fi
+  AC_SUBST([ANDROID_DEBUGGABLE])
+
+  # Move confdefs.h back now that the recursive call to configure is
+  # complete.
+  mv -f _confdefs.h confdefs.h
+
+  # Move the Android config.log to config.log.android.  */
+  mv -f config.log config.log.android
+
+  # And _config.log back.
+  mv -f _config.log config.log
+fi
+
+AC_SUBST([ANDROID])
+AC_SUBST([JAVAC])
+AC_SUBST([AAPT])
+AC_SUBST([D8])
+AC_SUBST([ZIPALIGN])
+AC_SUBST([ANDROID_JAR])
+AC_SUBST([ANDROID_ABI])
+
+if test "$XCONFIGURE" = "android"; then
+  ANDROID=yes
+
+  # Enable cross compiling.
+  cross_compiling=yes
+fi
+
+AC_SUBST([XCONFIGURE])
+
+if test "$ANDROID" = "yes"; then
+  # When --with-android is specified, almost all build options must be
+  # disabled, both within the recursive invocation of configure and
+  # outside.
+  with_xpm=no
+
+  # Some of these dependencies are now supported within Android, so
+  # they can be enabled.
+  if test "$XCONFIGURE" != "android"; then
+    with_png=no
+    with_webp=no
+    with_gif=no
+    with_json=no
+    with_jpeg=no
+    with_xml2=no
+    with_sqlite3=no
+    with_gnutls=no
+    with_tiff=no
+    with_selinux=no
+    with_modules=no
+    with_tree_sitter=no
+    with_imagemagick=no
+    with_lcms2=no
+    with_mailutils=no
+    with_pop=no
+    with_harfbuzz=no
+  fi
+
+  with_rsvg=no
+  with_libsystemd=no
+  with_cairo=no
+  with_xft=no
+  with_libotf=no
+  with_gpm=no
+  with_dbus=no
+  with_gsettings=no
+  with_threads=no
+  with_ns=no
+
+  # zlib is available in android.
+fi
+
 dnl This used to use changequote, but, apart from 'changequote is evil'
 dnl per the autoconf manual, we can speed up autoconf somewhat by quoting
 dnl the great gob of text.  Thus it's not processed for possible expansion.
@@ -706,6 +1357,11 @@ dnl quotation begins
 opsys='' unported=no
 case "${canonical}" in
 
+  ## Android
+  *linux-android* )
+    opsys=android
+  ;;
+
   ## GNU/Linux and similar ports
   *-*-linux* )
     opsys=gnu-linux
@@ -872,6 +1528,7 @@ AC_DEFUN([_AC_PROG_CC_C89], [$2])
 
 dnl Sets GCC=yes if using gcc.
 AC_PROG_CC([gcc cc cl clang "$XCRUN gcc" "$XCRUN clang"])
+
 if test -n "$XCRUN"; then
   AC_CHECK_PROGS([AR], [ar "$XCRUN ar"])
   test -n "$AR" && export AR
@@ -909,6 +1566,7 @@ AC_DEFUN_ONCE([gl_STDLIB_H],
 # Initialize gnulib right after choosing the compiler.
 dnl Amongst other things, this sets AR and ARFLAGS.
 gl_EARLY
+ndk_LATE
 
 if test "$ac_test_CFLAGS" != set; then
   # It's helpful to have C macros available to GDB, so prefer -g3 to -g
@@ -1128,6 +1786,13 @@ AS_IF([test $gl_gcc_warnings = no],
     nw="$nw -Wsuggest-attribute=format"
   fi
 
+  # If Emacs is being built for Android and many functions are
+  # currently stubbed out for operation on the build machine, disable
+  # -Wsuggest-attribute=noreturn.
+
+  AS_IF([test "$ANDROID" = "yes"],
+    [nw="$nw -Wsuggest-attribute=noreturn"])
+
   gl_MANYWARN_ALL_GCC([ws])
   gl_MANYWARN_COMPLEMENT([ws], [$ws], [$nw])
   for w in $ws; do
@@ -1151,6 +1816,7 @@ AS_IF([test $gl_gcc_warnings = no],
     gl_WARN_ADD([-Wno-null-pointer-arithmetic])
     gl_WARN_ADD([-Wno-implicit-const-int-float-conversion])
     gl_WARN_ADD([-Wno-int-in-bool-context])
+    gl_WARN_ADD([-Wno-shift-overflow])
   fi
 
   # This causes too much noise in the MinGW build
@@ -1273,7 +1939,7 @@ else
   AM_DEFAULT_VERBOSITY=0
 fi
 AC_SUBST([AM_DEFAULT_VERBOSITY])
-AC_CONFIG_FILES([src/verbose.mk])
+ARCH_INDEPENDENT_CONFIG_FILES([src/verbose.mk])
 
 dnl Some other nice autoconf tests.
 AC_PROG_INSTALL
@@ -1698,7 +2364,6 @@ AC_CACHE_CHECK([for math library],
          d = frexp (d, &i);
          d = ldexp (d, i);
          d = log (d);
-         d = log2 (d);
          d = log10 (d);
          d = pow (d, d);
          d = rint (d);
@@ -1767,11 +2432,18 @@ AC_DEFINE_UNQUOTED([SYSTEM_TYPE], ["$SYSTEM_TYPE"],
   [The type of system you are compiling for; sets 'system-type'.])
 AC_SUBST([SYSTEM_TYPE])
 
+# Check for pw_gecos in struct passwd; this is known to be missing on
+# Android.
+
+AC_CHECK_MEMBERS([struct passwd.pw_gecos], [], [], [#include <pwd.h>])
 
 pre_PKG_CONFIG_CFLAGS=$CFLAGS
 pre_PKG_CONFIG_LIBS=$LIBS
 
-PKG_PROG_PKG_CONFIG([0.9.0])
+dnl pkg-config does not work when cross-compiling for Android.
+if test "${ANDROID}" != "yes"; then
+  PKG_PROG_PKG_CONFIG([0.9.0])
+fi
 
 dnl EMACS_CHECK_MODULES([GSTUFF], [gtk+-2.0 >= 1.3 glib = 1.3.4])
 dnl acts like PKG_CHECK_MODULES([GSTUFF], [gtk+-2.0 >= 1.3 glib = 1.3.4],
@@ -1781,10 +2453,25 @@ dnl EMACS_CHECK_MODULES accepts optional 3rd and 4th 
arguments that
 dnl can take the place of the default HAVE_GSTUFF=yes and HAVE_GSTUFF=no
 dnl actions.
 AC_DEFUN([EMACS_CHECK_MODULES],
-  [PKG_CHECK_MODULES([$1], [$2],
-     [$1_CFLAGS=`AS_ECHO(["$$1_CFLAGS"]) | sed -e "$edit_cflags"`
-      m4_default([$3], [HAVE_$1=yes])],
-     [m4_default([$4], [HAVE_$1=no])])])
+  [AS_IF([test -n "$ndk_INITIALIZED"],
+    [ndk_CHECK_MODULES([$1], [$2], m4_default([$3], [HAVE_$1=yes]),
+      m4_default([$4],[HAVE_$1=no]))],
+    [PKG_CHECK_MODULES([$1], [$2],
+       [$1_CFLAGS=`AS_ECHO(["$$1_CFLAGS"]) | sed -e "$edit_cflags"`
+        m4_default([$3], [HAVE_$1=yes])],
+       [m4_default([$4], [HAVE_$1=no])])])])
+
+dnl EMACS_CHECK_LIB(NAME, FUNCTION, ACTION-IF-FOUND, ACTION-IF-NOT-FOUND,
+dnl OTHER-LIBRARIES, INCLUDES)
+dnl ---------------------------------------------------------------------
+dnl This is like AC_CHECK_LIB; however, there is no default action, and
+dnl when cross-configuring for Android, AC_CHECK_DECLS is called with NAME
+dnl and INCLUDES instead, as the library being checked against will likely
+dnl be built together with Emacs.
+AC_DEFUN([EMACS_CHECK_LIB],
+  [AS_IF([test -n "$ndk_INITIALIZED"],
+    [AC_CHECK_DECL([$2], [$3], [$4], [$6])],
+    [AC_CHECK_LIB([$1], [$2], [$3], [$4], [$5])])])
 
 HAVE_SOUND=no
 if test "${with_sound}" != "no"; then
@@ -1800,12 +2487,15 @@ if test "${with_sound}" != "no"; then
     AC_MSG_ERROR([OSS sound support requested but not found.])
 
   if test "${with_sound}" = "bsd-ossaudio" || test "${with_sound}" = "yes"; 
then
-    # Emulation library used on NetBSD.
+    # OSS emulation library used on NetBSD and OpenBSD.
     AC_CHECK_LIB([ossaudio], [_oss_ioctl], [LIBSOUND=-lossaudio], [LIBSOUND=])
     test "${with_sound}" = "bsd-ossaudio" && test -z "$LIBSOUND" && \
       AC_MSG_ERROR([bsd-ossaudio sound support requested but not found.])
-    dnl FIXME?  If we did find ossaudio, should we set with_sound=bsd-ossaudio?
-    dnl Traditionally, we go on to check for alsa too.  Does that make sense?
+    # On {Net,Open}BSD use the system audio library instead of
+    # potentially switching to ALSA below, as ALSA on these appears to
+    # just wrap system libraries.
+    test "${with_sound}" = "yes" && test "$LIBSOUND" = "-lossaudio" && \
+      with_sound="bsd-ossaudio"
   fi
   AC_SUBST([LIBSOUND])
 
@@ -1849,7 +2539,7 @@ AC_CHECK_HEADERS_ONCE(
   sys/sysinfo.h
   coff.h pty.h
   sys/resource.h
-  sys/utsname.h pwd.h utmp.h util.h
+  sys/utsname.h pwd.h util.h
   sanitizer/lsan_interface.h
   sanitizer/asan_interface.h
   sanitizer/common_interface_defs.h])
@@ -1954,14 +2644,93 @@ AC_SUBST([AUTO_DEPEND])
 
 window_system=none
 
+ANDROID_OBJ=
+ANDROID_LIBS=
+# ANDROID_CFLAGS is a precious variable used to pass information to
+# the cross-compiler.
+ANDROID_BUILD_CFLAGS=
+REALLY_ANDROID=
+CM_OBJ="cm.o"
+
+AS_IF([test "$ANDROID" = "yes"],[
+  window_system=android
+  no_x=yes
+  ANDROID_OBJ="androidterm.o androidfns.o androidfont.o androidmenu.o"
+  ANDROID_OBJ="$ANDROID_OBJ android.o"
+  ANDROID_LIBS=
+  CM_OBJ=
+
+  AC_DEFINE([HAVE_ANDROID], [1], [Define to 1 if Emacs is being built
+with Android support])
+
+  AS_IF([test "$XCONFIGURE" != "android"], [
+    AC_DEFINE([ANDROID_STUBIFY], [1], [Define to 1 if Emacs is being built
+for Android, but all API calls need to be stubbed out])
+
+    # Now set any shared user ID that was specified.
+    AS_IF([test -n "$with_shared_user_id"],
+      [emacs_val=$with_shared_user_id
+       emacs_val=`AS_ECHO(["$with_shared_user_id"]) \
+                  | sed -e 's/"/\\"/'`
+       emacs_val="\"$emacs_val\""
+       ANDROID_SHARED_USER_ID="android:sharedUserId=$emacs_val"
+       # `android:sharedUserName' is required for sharedUserID to work
+       # on recent Android releases.  It does not otherwise affect the
+       # behavior of any code.
+       emacs_val="\"@string/shared_user_name\""
+       ANDROID_SHARED_USER_NAME="android:sharedUserLabel=$emacs_val"])],[
+    # Emacs will be built as a shared library, and a wrapper around it
+    # will also be built for the benefit of applications.  This
+    # requires Emacs be built as a position independent executable.
+    ANDROID_BUILD_CFLAGS="-fPIC -fvisibility=hidden"
+
+    # Graphics code in sfntfont-android.c benefits heavily from
+    # vectorization.
+    ANDROID_BUILD_CFLAGS="$ANDROID_BUILD_CFLAGS -ftree-vectorize"
+
+    # Link with libraries required for Android support.
+    # API 9 and later require `-landroid' for the asset manager.
+    # API 8 uses an emulation via the JNI.
+    AS_IF([test "$ANDROID_SDK" -lt "9"],
+      [ANDROID_LIBS="-llog -ljnigraphics"],
+      [ANDROID_LIBS="-landroid -llog -ljnigraphics"])
+
+    # This is required to make the system load emacs.apk's libpng
+    # (among others) instead of the system's own.  But it doesn't work
+    # on all Android versions yet, so for now just suffix shared
+    # libraries with _emacs.
+    # ANDROID_LDFLAGS="-Wl,-rpath,'\$\$ORIGIN'"
+
+    # Link with the sfnt font library and sfntfont.o, along with
+    # sfntfont-android.o.
+    ANDROID_OBJ="$ANDROID_OBJ sfnt.o sfntfont.o sfntfont-android.o"
+
+    # Build androidselect.o and androidvfs.o.
+    ANDROID_OBJ="$ANDROID_OBJ androidselect.o androidvfs.o"
+
+    # Check for some functions not always present in the NDK.
+    AC_CHECK_DECLS([android_get_device_api_level])
+
+    # Mention this build is really for Android.
+    REALLY_ANDROID=yes])])
+
+AC_SUBST([ANDROID])
+AC_SUBST([ANDROID_OBJ])
+AC_SUBST([ANDROID_LIBS])
+AC_SUBST([ANDROID_LDFLAGS])
+AC_SUBST([ANDROID_BUILD_CFLAGS])
+AC_SUBST([ANDROID_SHARED_USER_ID])
+AC_SUBST([ANDROID_SHARED_USER_NAME])
+
 if test "${with_pgtk}" = "yes"; then
   window_system=pgtk
 fi
 
-
-AC_PATH_X
-if test "$no_x" != yes && test "${with_pgtk}" != "yes"; then
-  window_system=x11
+if test "${ANDROID}" != "yes"; then
+  AC_PATH_X
+  if test "$no_x" != yes && test "${with_pgtk}" != "yes"; then
+    window_system=x11
+  fi
 fi
 
 LD_SWITCH_X_SITE_RPATH=
@@ -2003,16 +2772,16 @@ if test x"${x_includes}" = x; then
   bitmapdir=/usr/include/X11/bitmaps
 else
   # accumulate include directories that have X11 bitmap subdirectories
-  bmd_acc=
+  AS_UNSET([bmd_acc])
   for bmd in `AS_ECHO(["$x_includes"]) | sed -e 's/:/ /g'`; do
     if test -d "${bmd}/X11/bitmaps"; then
-      bmd_acc="${bmd_acc}:${bmd}/X11/bitmaps"
+      bmd_acc="${bmd_acc+$bmd_acc:}${bmd}/X11/bitmaps"
     fi
     if test -d "${bmd}/bitmaps"; then
-      bmd_acc="${bmd_acc}:${bmd}/bitmaps"
+      bmd_acc="${bmd_acc+$bmd_acc:}${bmd}/bitmaps"
     fi
   done
-  bitmapdir=${bmd_acc#:}
+  bitmapdir=$bmd_acc
 fi
 
 NATIVE_IMAGE_API=no
@@ -2277,7 +3046,6 @@ NTDIR=
 LIBS_ECLIENT=
 LIB_WSOCK32=
 NTLIB=
-CM_OBJ="cm.o"
 XARGS_LIMIT=
 if test "${HAVE_W32}" = "yes"; then
   AC_DEFINE([HAVE_NTGUI], [1], [Define to use native MS Windows GUI.])
@@ -2440,7 +3208,11 @@ dnl use the toolkit if we have gtk, or X11R5 or newer.
   haiku )
     term_header=haikuterm.h
   ;;
+  android )
+    term_header=androidterm.h
+  ;;
 esac
+
 AC_SUBST([HAVE_PGTK])
 
 if test "$window_system" = none && test "X$with_x" != "Xno"; then
@@ -2741,7 +3513,6 @@ fail;
   fi
 fi
 
-
 ### Use -lrsvg-2 if available, unless '--with-rsvg=no' is specified.
 HAVE_RSVG=no
 if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" \
@@ -2771,7 +3542,8 @@ HAVE_WEBP=no
 if test "${with_webp}" != "no"; then
    if test "${HAVE_X11}" = "yes" || test "${opsys}" = "mingw32" \
    || test "${HAVE_W32}" = "yes" || test "${HAVE_NS}" = "yes" \
-   || test "${HAVE_BE_APP}" = "yes" || test "${HAVE_PGTK}" = "yes"; then
+   || test "${HAVE_BE_APP}" = "yes" || test "${HAVE_PGTK}" = "yes" \
+   || test "${REALLY_ANDROID}" = "yes"; then
       WEBP_REQUIRED=0.6.0
       WEBP_MODULE="libwebpdemux >= $WEBP_REQUIRED"
 
@@ -2785,12 +3557,13 @@ if test "${with_webp}" != "no"; then
       CFLAGS="$CFLAGS $WEBP_CFLAGS"
       LIBS="$LIBS $WEBP_LIBS"
 
-      AC_CHECK_FUNC([WebPGetInfo], [],
-        [WEBP_MODULE="$WEBP_MODULE libwebpdecoder >= $WEBP_REQUIRED"
-        HAVE_WEBP=no
-        AS_UNSET([WEBP_LIBS])
-        AS_UNSET([WEBP_CFLAGS])
-        EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE])])
+      AS_IF([test "$REALLY_ANDROID" != "yes"], [
+       AC_CHECK_FUNC([WebPGetInfo], [],
+         [WEBP_MODULE="$WEBP_MODULE libwebpdecoder >= $WEBP_REQUIRED"
+          HAVE_WEBP=no
+          AS_UNSET([WEBP_LIBS])
+          AS_UNSET([WEBP_CFLAGS])
+          EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE])])])
 
       CFLAGS=$OLD_CFLAGS
       LIBS=$OLD_LIBS
@@ -2800,7 +3573,6 @@ if test "${with_webp}" != "no"; then
    fi
    if test $HAVE_WEBP = yes; then
       AC_DEFINE([HAVE_WEBP], [1], [Define to 1 if using libwebp.])
-      CFLAGS="$CFLAGS $WEBP_CFLAGS"
       # Windows loads libwebp dynamically
       if test "${opsys}" = "mingw32"; then
        WEBP_LIBS=
@@ -2810,33 +3582,53 @@ fi
 
 ### Use -lsqlite3 if available, unless '--with-sqlite3=no'
 HAVE_SQLITE3=no
+SQLITE3_LIBS=
+SQLITE3_CFLAGS=
 if test "${with_sqlite3}" != "no"; then
-   AC_CHECK_LIB([sqlite3], [sqlite3_open_v2],
-     [HAVE_SQLITE3=yes],
-     [HAVE_SQLITE3=no])
-   if test "$HAVE_SQLITE3" = "yes"; then
-     SQLITE3_LIBS=-lsqlite3
-     AC_SUBST([SQLITE3_LIBS])
-     LIBS="$SQLITE3_LIBS $LIBS"
-     AC_DEFINE([HAVE_SQLITE3], [1],
-       [Define to 1 if you have the libsqlite3 library (-lsqlite).])
-     # Windows loads libsqlite dynamically
-     if test "${opsys}" = "mingw32"; then
-        SQLITE3_LIBS=
+   if test "${REALLY_ANDROID}" = "yes"; then
+     ndk_SEARCH_MODULE([sqlite3], [SQLITE3], [HAVE_SQLITE3=yes])
+
+     if test "$HAVE_SQLITE3" = "yes"; then
+       SAVE_CFLAGS="$CFLAGS"
+       CFLAGS="$CFLAGS $SQLITE3_CFLAGS"
+       AC_CHECK_DECL([sqlite3_open_v2], [HAVE_SQLITE=yes],
+         [HAVE_SQLITE3=no], [#include <sqlite3.h>])
+       CFLAGS="$SAVE_CFLAGS"
      fi
-     AC_CHECK_LIB([sqlite3], [sqlite3_load_extension],
-       [HAVE_SQLITE3_LOAD_EXTENSION=yes],
-       [HAVE_SQLITE3_LOAD_EXTENSION=no])
-     if test "$HAVE_SQLITE3_LOAD_EXTENSION" = "yes"; then
-       AC_DEFINE([HAVE_SQLITE3_LOAD_EXTENSION], [1],
-        [Define to 1 if sqlite3 supports loading extensions.])
+   else
+     AC_CHECK_LIB([sqlite3], [sqlite3_open_v2],
+       [HAVE_SQLITE3=yes],
+       [HAVE_SQLITE3=no])
+     if test "$HAVE_SQLITE3" = "yes"; then
+       SQLITE3_LIBS=-lsqlite3
+       LIBS="$SQLITE3_LIBS $LIBS"
+       # Windows loads libsqlite dynamically
+       if test "${opsys}" = "mingw32"; then
+         SQLITE3_LIBS=
+       fi
+       AC_CHECK_LIB([sqlite3], [sqlite3_load_extension],
+        [HAVE_SQLITE3_LOAD_EXTENSION=yes],
+        [HAVE_SQLITE3_LOAD_EXTENSION=no])
+       if test "$HAVE_SQLITE3_LOAD_EXTENSION" = "yes"; then
+        AC_DEFINE([HAVE_SQLITE3_LOAD_EXTENSION], [1],
+          [Define to 1 if sqlite3 supports loading extensions.])
+       fi
      fi
-   fi
+  fi
+
+  if test "$HAVE_SQLITE3" = "yes"; then
+    AC_DEFINE([HAVE_SQLITE3], [1],
+      [Define to 1 if you have the libsqlite3 library (-lsqlite).])
+  fi
 fi
 
+AC_SUBST([SQLITE3_LIBS])
+AC_SUBST([SQLITE3_CFLAGS])
+
 HAVE_IMAGEMAGICK=no
 if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test 
"${HAVE_W32}" = "yes" || \
-   test "${HAVE_BE_APP}" = "yes" || test "${window_system}" = "pgtk"; then
+   test "${HAVE_BE_APP}" = "yes" || test "${window_system}" = "pgtk" || \
+   test "${REALLY_ANDROID}" = "yes"; then
   if test "${with_imagemagick}" != "no"; then
     if test -n "$BREW"; then
       # Homebrew doesn't link ImageMagick 6 by default, so make sure
@@ -2859,14 +3651,23 @@ if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = 
"yes" || test "${HAVE_W32}"
       OLD_LIBS=$LIBS
       CFLAGS="$CFLAGS $IMAGEMAGICK_CFLAGS"
       LIBS="$IMAGEMAGICK_LIBS $LIBS"
-      AC_CHECK_FUNCS([MagickRelinquishMemory MagickExportImagePixels \
-                     MagickMergeImageLayers MagickAutoOrientImage])
+      if test "$REALLY_ANDROID" != "yes"; then
+       AC_CHECK_FUNCS([MagickRelinquishMemory MagickExportImagePixels \
+                       MagickMergeImageLayers MagickAutoOrientImage])
+      else
+        # AC_CHECK_FUNCS doesn't work for Android dependencies because
+       # they are built alongside Emacs.
+       AC_CHECK_DECLS([MagickRelinquishMemory,MagickExportImagePixels,
+MagickMergeImageLayers,MagickAutoOrientImage],
+                      [], [], [#include <MagickWand/MagickWand.h>])
+      fi
       CFLAGS=$OLD_CFLAGS
       LIBS=$OLD_LIBS
       # Check that ImageMagick links.  It does not link on Fedora 25
       # with './configure CC=clang', as pkg-config outputs flags like
       # -lomp that work for GCC but not Clang.
-      if test "$ac_cv_func_MagickRelinquishMemory" != yes; then
+      if test "$ac_cv_func_MagickRelinquishMemory" != yes \
+         && test "$REALLY_ANDROID" != "yes"; then
        HAVE_IMAGEMAGICK=no
       fi
     fi
@@ -3140,19 +3941,29 @@ if test "${HAVE_GTK}" = "yes"; then
 fi
 AC_SUBST([USE_STARTUP_NOTIFICATION])
 
-dnl SELinux is available for GNU/Linux only.
+dnl SELinux is available for Linux kernel based systems only.
+dnl These include GNU/Linux and Android.
 HAVE_LIBSELINUX=no
 LIBSELINUX_LIBS=
+LIBSELINUX_CFLAGS=
 if test "${with_selinux}" = "yes"; then
-   AC_CHECK_LIB([selinux], [lgetfilecon],
-     [HAVE_LIBSELINUX=yes],
-     [HAVE_LIBSELINUX=no])
+   if test "$REALLY_ANDROID" = "yes"; then
+     ndk_SEARCH_MODULE([libselinux], [LIBSELINUX],
+       [HAVE_LIBSELINUX=yes])
+   else
+     AC_CHECK_LIB([selinux], [lgetfilecon],
+       [HAVE_LIBSELINUX=yes],
+       [HAVE_LIBSELINUX=no])
+   fi
    if test "$HAVE_LIBSELINUX" = yes; then
       AC_DEFINE([HAVE_LIBSELINUX], [1], [Define to 1 if using SELinux.])
-      LIBSELINUX_LIBS=-lselinux
+      if test "$REALLY_ANDROID" != "yes"; then
+        LIBSELINUX_LIBS=-lselinux
+      fi
    fi
 fi
 AC_SUBST([LIBSELINUX_LIBS])
+AC_SUBST([LIBSELINUX_CFLAGS])
 
 HAVE_GNUTLS=no
 if test "${with_gnutls}" != "no" ; then
@@ -3222,6 +4033,7 @@ if test "${with_tree_sitter}" != "no"; then
     [HAVE_TREE_SITTER=yes], [HAVE_TREE_SITTER=no])
   if test "${HAVE_TREE_SITTER}" = yes; then
     AC_DEFINE(HAVE_TREE_SITTER, 1, [Define if using tree-sitter.])
+    NEED_DYNLIB=yes
   else
     EMACS_CHECK_MODULES([TREE_SITTER], [tree-sitter >= 0.6.3],
       [HAVE_TREE_SITTER=yes], [HAVE_TREE_SITTER=no])
@@ -3840,17 +4652,23 @@ else
 fi
 if test "${HAVE_X11}" = "yes" && test "${HAVE_FREETYPE}" = "yes" \
         || test "$window_system" = "pgtk" \
-        || test "${HAVE_W32}" = "yes"; then
+        || test "${HAVE_W32}" = "yes" \
+       || test "$REALLY_ANDROID" = "yes"; then
   if test "${with_harfbuzz}" != "no"; then
     EMACS_CHECK_MODULES([HARFBUZZ], [harfbuzz >= $harfbuzz_required_ver])
-    if test "$HAVE_HARFBUZZ" = "yes"; then
+    AS_IF([test "$HAVE_HARFBUZZ" = "yes"],[
       AC_DEFINE([HAVE_HARFBUZZ], [1], [Define to 1 if using HarfBuzz.])
       ### mingw32 and Cygwin-w32 don't use -lharfbuzz, since they load
       ### the library dynamically.
-      if test "${HAVE_W32}" = "yes"; then
-        HARFBUZZ_LIBS=
-      fi
-    fi
+      AS_IF([test "${HAVE_W32}" = "yes"], [HARFBUZZ_LIBS=])
+      ## Now check for `hb_font_set_var_named_instance'.
+      OLD_CFLAGS=$CFLAGS
+      CFLAGS="$HARFBUZZ_CFLAGS $CFLAGS"
+      EMACS_CHECK_LIB([harfbuzz], [hb_font_set_var_named_instance],
+        [AC_DEFINE([HAVE_HB_FONT_SET_VAR_NAMED_INSTANCE], [1],
+         [Define to 1 if `hb_font_set_var_named_instance' is present.])],
+       [], [$HARFBUZZ_LIBS], [#include <hb.h>])
+      CFLAGS=$OLD_CFLAGS])
   fi
 fi
 
@@ -4033,51 +4851,66 @@ AC_SUBST([LIBXPM])
 ### Use -ljpeg if available, unless '--with-jpeg=no'.
 HAVE_JPEG=no
 LIBJPEG=
+JPEG_CFLAGS=
 if test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \
    || test "${HAVE_NS}" = "yes" || test "${HAVE_BE_APP}" = "yes" \
-   || test "$window_system" = "pgtk"; then
+   || test "$window_system" = "pgtk" \
+   || test "${REALLY_ANDROID}" = "yes"; then
   if test "${with_jpeg}" != "no"; then
-    AC_CACHE_CHECK([for jpeglib 6b or later],
-      [emacs_cv_jpeglib],
-      [OLD_LIBS=$LIBS
-       for emacs_cv_jpeglib in yes -ljpeg no; do
-        case $emacs_cv_jpeglib in
-          yes) ;;
-          no) break;;
-          *) LIBS="$LIBS $emacs_cv_jpeglib";;
-        esac
-        AC_LINK_IFELSE(
-          [AC_LANG_PROGRAM(
-             [[#undef HAVE_STDLIB_H /* Avoid config.h/jpeglib.h collision.  */
-               #include <stdio.h> /* jpeglib.h needs FILE and size_t.  */
-               #include <jpeglib.h>
-               #include <jerror.h>
-               char verify[JPEG_LIB_VERSION < 62 ? -1 : 1];
-               struct jpeg_decompress_struct cinfo;
-             ]],
-             [[
-               jpeg_create_decompress (&cinfo);
-               WARNMS (&cinfo, JWRN_JPEG_EOF);
-               jpeg_destroy_decompress (&cinfo);
-             ]])],
-          [emacs_link_ok=yes],
-          [emacs_link_ok=no])
-        LIBS=$OLD_LIBS
-        test $emacs_link_ok = yes && break
-       done])
-    if test "$emacs_cv_jpeglib" != no; then
-      HAVE_JPEG=yes
-      AC_DEFINE([HAVE_JPEG], [1],
-       [Define to 1 if you have the jpeg library (typically -ljpeg).])
-      ### mingw32 doesn't use -ljpeg, since it loads the library
-      ### dynamically when needed, and doesn't want a run-time
-      ### dependency on the jpeglib DLL.
-      test "$emacs_cv_jpeglib" != yes && test "${opsys}" != "mingw32" \
-      && LIBJPEG=$emacs_cv_jpeglib
+    if test "${REALLY_ANDROID}" = "yes"; then
+      # Look for libjpeg using the NDK.
+      ndk_SEARCH_MODULE([libjpeg], [JPEG], [HAVE_JPEG=yes])
+
+      if test "$HAVE_JPEG" = "yes"; then
+        LIBJPEG="$JPEG_LIBS"
+
+       AC_DEFINE([HAVE_JPEG], [1],
+         [Define to 1 if you have the jpeg library (typically -ljpeg).])
+      fi
+    else
+      AC_CACHE_CHECK([for jpeglib 6b or later],
+       [emacs_cv_jpeglib],
+       [OLD_LIBS=$LIBS
+        for emacs_cv_jpeglib in yes -ljpeg no; do
+          case $emacs_cv_jpeglib in
+            yes) ;;
+            no) break;;
+            *) LIBS="$LIBS $emacs_cv_jpeglib";;
+          esac
+          AC_LINK_IFELSE(
+            [AC_LANG_PROGRAM(
+               [[#undef HAVE_STDLIB_H /* Avoid config.h/jpeglib.h collision.  
*/
+                 #include <stdio.h> /* jpeglib.h needs FILE and size_t.  */
+                 #include <jpeglib.h>
+                 #include <jerror.h>
+                 char verify[JPEG_LIB_VERSION < 62 ? -1 : 1];
+                 struct jpeg_decompress_struct cinfo;
+               ]],
+               [[
+                 jpeg_create_decompress (&cinfo);
+                 WARNMS (&cinfo, JWRN_JPEG_EOF);
+                 jpeg_destroy_decompress (&cinfo);
+               ]])],
+            [emacs_link_ok=yes],
+            [emacs_link_ok=no])
+          LIBS=$OLD_LIBS
+          test $emacs_link_ok = yes && break
+        done])
+      if test "$emacs_cv_jpeglib" != no; then
+       HAVE_JPEG=yes
+       AC_DEFINE([HAVE_JPEG], [1],
+         [Define to 1 if you have the jpeg library (typically -ljpeg).])
+       ### mingw32 doesn't use -ljpeg, since it loads the library
+       ### dynamically when needed, and doesn't want a run-time
+       ### dependency on the jpeglib DLL.
+       test "$emacs_cv_jpeglib" != yes && test "${opsys}" != "mingw32" \
+       && LIBJPEG=$emacs_cv_jpeglib
+      fi
     fi
   fi
 fi
 AC_SUBST([LIBJPEG])
+AC_SUBST([JPEG_CFLAGS])
 
 HAVE_LCMS2=no
 LCMS2_CFLAGS=
@@ -4150,50 +4983,43 @@ if test $window_system = pgtk; then
   esac
 fi
 
-if test "${with_modules}" != "no"; then
-  case $opsys in
-    gnu|gnu-linux)
-      LIBMODULES="-ldl"
-      HAVE_MODULES=yes
-      ;;
-    cygwin|mingw32|darwin)
-      HAVE_MODULES=yes
-      ;;
-    *)
-      # BSD systems have dlopen in libc.
-      AC_CHECK_FUNC([dlopen], [HAVE_MODULES=yes])
-      ;;
-  esac
-
-  if test "${HAVE_MODULES}" = no; then
-    AC_MSG_ERROR([Dynamic modules are not supported on your system])
-  else
-    SAVE_LIBS=$LIBS
-    LIBS="$LIBS $LIBMODULES"
-    AC_CHECK_FUNCS([dladdr dlfunc])
-    LIBS=$SAVE_LIBS
-  fi
-fi
+AS_IF([test "x$with_modules" != "xno"],
+  [AS_CASE(["$opsys"],
+    [gnu|gnu-linux],
+      [LIBMODULES="-ldl"
+       HAVE_MODULES=yes],
+    [cygwin|mingw32|darwin],
+      [HAVE_MODULES=yes],
+    # BSD systems have dlopen in libc.
+    [AC_CHECK_FUNC([dlopen], [HAVE_MODULES=yes])])
+
+  AS_IF([test "x$HAVE_MODULES" = "xno"],
+    [AS_IF([test "$with_modules" = "ifavailable"],
+      [AC_MSG_WARN([Dynamic modules are not supported on your system])],
+      [AC_MSG_ERROR([Dynamic modules are not supported on your system])])],
+    [SAVE_LIBS=$LIBS
+     LIBS="$LIBS $LIBMODULES"
+     AC_CHECK_FUNCS([dladdr dlfunc])
+     LIBS=$SAVE_LIBS])])
+
+AS_IF([test "x$HAVE_MODULES" = xyes],
+   [MODULES_OBJ="emacs-module.o"
+    NEED_DYNLIB=yes
+    AC_DEFINE([HAVE_MODULES], [1], [Define to 1 if dynamic modules are 
enabled])
+    AC_DEFINE_UNQUOTED([MODULES_SUFFIX], ["$MODULES_SUFFIX"],
+      [System extension for dynamic libraries])
+    AS_IF([test -n "$MODULES_SECONDARY_SUFFIX"],
+      [AC_DEFINE_UNQUOTED([MODULES_SECONDARY_SUFFIX],
+       ["$MODULES_SECONDARY_SUFFIX"],
+       [Alternative system extension for dynamic libraries.])])])
 
-if test "${HAVE_MODULES}" = yes; then
-   MODULES_OBJ="emacs-module.o"
-   NEED_DYNLIB=yes
-   AC_DEFINE([HAVE_MODULES], [1], [Define to 1 if dynamic modules are enabled])
-   AC_DEFINE_UNQUOTED([MODULES_SUFFIX], ["$MODULES_SUFFIX"],
-     [System extension for dynamic libraries])
-   if test -n "${MODULES_SECONDARY_SUFFIX}"; then
-     AC_DEFINE_UNQUOTED([MODULES_SECONDARY_SUFFIX],
-       ["$MODULES_SECONDARY_SUFFIX"],
-       [Alternative system extension for dynamic libraries.])
-   fi
-fi
 AC_SUBST([MODULES_OBJ])
 AC_SUBST([LIBMODULES])
 AC_SUBST([HAVE_MODULES])
 AC_SUBST([MODULES_SUFFIX])
 AC_SUBST([MODULES_SECONDARY_SUFFIX])
 
-AC_CONFIG_FILES([src/emacs-module.h])
+ARCH_INDEPENDENT_CONFIG_FILES([src/emacs-module.h])
 AC_SUBST_FILE([module_env_snippet_25])
 AC_SUBST_FILE([module_env_snippet_26])
 AC_SUBST_FILE([module_env_snippet_27])
@@ -4383,7 +5209,8 @@ if test "${with_png}" != no; then
     AC_CHECK_HEADER([png.h], [HAVE_PNG=yes])
   elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \
        || test "${HAVE_NS}" = "yes" || test "${HAVE_BE_APP}" = "yes" \
-       || test "$window_system" = "pgtk"; then
+       || test "$window_system" = "pgtk" \
+       || test "${REALLY_ANDROID}" = "yes"; then
     EMACS_CHECK_MODULES([PNG], [libpng >= 1.0.0])
     if test $HAVE_PNG = yes; then
       LIBPNG=$PNG_LIBS
@@ -4450,6 +5277,7 @@ AC_SUBST([PNG_CFLAGS])
 ### mingw32 doesn't use -ltiff, since it loads the library dynamically.
 HAVE_TIFF=no
 LIBTIFF=
+TIFF_CFLAGS=
 if test "${opsys}" = "mingw32"; then
   if test "${with_tiff}" != "no"; then
     AC_CHECK_HEADER([tiffio.h], [HAVE_TIFF=yes], [HAVE_TIFF=no])
@@ -4460,28 +5288,42 @@ if test "${opsys}" = "mingw32"; then
   fi
 elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \
      || test "${HAVE_NS}" = "yes" || test "${HAVE_BE_APP}" = "yes" \
-     || test "$window_system" = "pgtk"; then
+     || test "$window_system" = "pgtk" \
+     || test "${REALLY_ANDROID}" = "yes"; then
   if test "${with_tiff}" != "no"; then
-    AC_CHECK_HEADER([tiffio.h],
-      [tifflibs="-lz -lm"
-      # At least one tiff package requires the jpeg library.
-      if test "${HAVE_JPEG}" = yes; then tifflibs="-ljpeg $tifflibs"; fi
-      AC_CHECK_LIB([tiff], [TIFFGetVersion], [HAVE_TIFF=yes], [],
-       [$tifflibs])])
+    if test "${REALLY_ANDROID}" != "yes"; then
+      AC_CHECK_HEADER([tiffio.h],
+       [tifflibs="-lz -lm"
+       # At least one tiff package requires the jpeg library.
+       if test "${HAVE_JPEG}" = yes; then tifflibs="-ljpeg $tifflibs"; fi
+       AC_CHECK_LIB([tiff], [TIFFGetVersion], [HAVE_TIFF=yes], [],
+         [$tifflibs])])
+    else
+      ndk_SEARCH_MODULE([libtiff], [TIFF], [HAVE_TIFF=yes])
+
+      if test "$HAVE_TIFF" = "yes"; then
+        LIBTIFF="$TIFF_LIBS"
+      fi
+    fi
   fi
 
   if test "${HAVE_TIFF}" = "yes"; then
     AC_DEFINE([HAVE_TIFF], [1],
       [Define to 1 if you have the tiff library (-ltiff).])
-    dnl FIXME -lz -lm, as per libpng?
-    LIBTIFF=-ltiff
+
+    if test "$REALLY_ANDROID" != "yes"; then
+      dnl FIXME -lz -lm, as per libpng?
+      LIBTIFF=-ltiff
+    fi
   fi
 fi
 AC_SUBST([LIBTIFF])
+AC_SUBST([TIFF_CFLAGS])
 
 ### Use -lgif or -lungif if available, unless '--with-gif=no'.
 ### mingw32 doesn't use -lgif/-lungif, since it loads the library dynamically.
 HAVE_GIF=no
+GIF_CFLAGS=
 LIBGIF=
 if test "${opsys}" = "mingw32"; then
   if test "${with_gif}" != "no"; then
@@ -4494,6 +5336,7 @@ if test "${opsys}" = "mingw32"; then
 elif test "${HAVE_X11}" = "yes" && test "${with_gif}" != "no" \
         || test "${HAVE_W32}" = "yes" || test "${HAVE_NS}" = "yes" \
        || test "${HAVE_BE_APP}" = "yes" || test "$window_system" = "pgtk" \
+       || test "${REALLY_ANDROID}" = "yes" \
        && test "${with_gif}" != "no"; then
   AC_CHECK_HEADER([gif_lib.h],
 # EGifPutExtensionLast only exists from version libungif-4.1.0b1.
@@ -4513,12 +5356,20 @@ elif test "${HAVE_X11}" = "yes" && test "${with_gif}" 
!= "no" \
     test "$HAVE_GIF" = yes && LIBGIF=-lungif
   fi
 
+# Finally, try ndk-build on Android.
+  if test "$REALLY_ANDROID" = "yes"; then
+    ndk_SEARCH_MODULE([libgif], [GIF], [HAVE_GIF=yes],
+      [HAVE_GIF=no])
+    test "$HAVE_GIF" = yes && LIBGIF="$GIF_LIBS"
+  fi
+
   if test "${HAVE_GIF}" = "yes"; then
     AC_DEFINE([HAVE_GIF], [1],
       [Define to 1 if you have a gif (or ungif) library.])
   fi
 fi
 AC_SUBST([LIBGIF])
+AC_SUBST([GIF_CFLAGS])
 
 dnl Check for required libraries.
 MISSING=
@@ -4857,10 +5708,13 @@ if test "${with_xml2}" != "no"; then
   fi
   if test "${HAVE_LIBXML2}" = "yes"; then
     if test "${opsys}" != "mingw32"; then
-      AC_CHECK_LIB([xml2], [htmlReadMemory],
+      SAVE_CFLAGS=$CFLAGS
+      CFLAGS="$CFLAGS $LIBXML2_CFLAGS"
+      EMACS_CHECK_LIB([xml2], [htmlReadMemory],
        [HAVE_LIBXML2=yes],
        [HAVE_LIBXML2=no],
-        [$LIBXML2_LIBS])
+        [$LIBXML2_LIBS], [#include <libxml/HTMLparser.h>])
+      CFLAGS="$SAVE_CFLAGS"
     else
       LIBXML2_LIBS=""
     fi
@@ -4986,16 +5840,53 @@ OLD_LIBS=$LIBS
 LIBS="$LIB_PTHREAD $LIB_MATH $LIBS"
 AC_CHECK_FUNCS([accept4 fchdir gethostname \
 getrusage get_current_dir_name \
-lrand48 random rint trunc \
+lrand48 random rint tcdrain trunc \
 select getpagesize setlocale newlocale \
 getrlimit setrlimit shutdown \
 pthread_sigmask strsignal setitimer \
 sendto recvfrom getsockname getifaddrs freeifaddrs \
 gai_strerror sync \
-getpwent endpwent getgrent endgrent \
+endpwent getgrent endgrent \
 renameat2 \
 cfmakeraw cfsetspeed __executable_start log2 pthread_setname_np \
 pthread_set_name_np])
+
+# getpwent is not present in older versions of Android.  (bug#65319)
+gl_CHECK_FUNCS_ANDROID([getpwent], [[#include <pwd.h>]])
+
+if test "$ac_cv_func_cfmakeraw" != "yes"; then
+  # On some systems (Android), cfmakeraw is inline, so AC_CHECK_FUNCS
+  # cannot find it.  Check if some code including termios.h and using
+  # cfmakeraw builds.
+  AC_CACHE_CHECK([whether cfmakeraw is inline],
+   [emacs_cv_func_cfmakeraw_inline],
+   [AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+       [[#include <termios.h>]],
+       [[&cfmakeraw;]])],
+     [emacs_cv_func_cfmakeraw_inline=yes],
+     [emacs_cv_func_cfmakeraw_inline=no])])
+
+  if test "$emacs_cv_func_cfmakeraw_inline" = "yes"; then
+     # Define HAVE_CFMAKERAW again.
+     AC_DEFINE([HAVE_CFMAKERAW], [1])
+  fi
+fi
+
+if test "$ac_cv_func_cfsetspeed" != "yes"; then
+  AC_CACHE_CHECK([whether cfsetspeed is inline],
+   [emacs_cv_func_cfsetspeed_inline],
+   [AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+       [[#include <termios.h>]],
+       [[&cfsetspeed;]])],
+     [emacs_cv_func_cfsetspeed_inline=yes],
+     [emacs_cv_func_cfsetspeed_inline=no])])
+
+  if test "$emacs_cv_func_cfsetspeed_inline" = "yes"; then
+     # Define HAVE_CFSETSPEED again.
+     AC_DEFINE([HAVE_CFSETSPEED], [1])
+  fi
+fi
+
 LIBS=$OLD_LIBS
 
 if test "$ac_cv_func_pthread_setname_np" = "yes"; then
@@ -5047,8 +5938,8 @@ if test "$with_unexec" = yes && test "$opsys" = "haiku"; 
then
 Please use the portable dumper instead.])
 fi
 
-# Dump loading
-AC_CHECK_FUNCS([posix_madvise])
+# Dump loading.  Android lacks posix_madvise.
+AC_CHECK_FUNCS([posix_madvise madvise])
 
 dnl Cannot use AC_CHECK_FUNCS
 AC_CACHE_CHECK([for __builtin_frame_address],
@@ -5101,7 +5992,7 @@ AC_DEFUN([tputs_link_source], [
 # than to expect to find it in ncurses.
 # Also we need tputs and friends to be able to build at all.
 AC_CACHE_CHECK([for library containing tputs], [emacs_cv_tputs_lib],
-[if test "${opsys}" = "mingw32"; then
+[if test "${opsys}" = "mingw32" || test x"$REALLY_ANDROID" = "xyes"; then
   emacs_cv_tputs_lib='none required'
 else
   # curses precedes termcap because of AIX (Bug#9736#35) and OpenIndiana.
@@ -5139,7 +6030,7 @@ TERMINFO=yes
 ## LIBS_TERMCAP="-lncurses", this overrides LIBS_TERMCAP = -ltinfo,
 ## if that was found above to have tputs.
 ## Should we use the gnu* logic everywhere?
-case "$opsys" in
+case "$opsys$REALLY_ANDROID" in
   ## darwin: Prevents crashes when running Emacs in Terminal.app under 10.2.
   ##  The ncurses library has been moved out of the System framework in
   ##  Mac OS X 10.2.  So if configure detects it, set the command-line
@@ -5168,7 +6059,10 @@ fail;
     fi
     ;;
 
-  mingw32)
+  # The case condition is a concatenation of both $opsys and
+  # $REALLY_ANDROID.  Only disable termcap if building a GUI program.
+  # (bug#65340)
+  mingw32 | androidyes)
     TERMINFO=no
     LIBS_TERMCAP=
     ;;
@@ -5719,6 +6613,25 @@ case $opsys in
     AC_DEFINE([FIRST_PTY_LETTER], ['p'])
     ;;
 
+  android )
+    AC_DEFINE([PTY_ITERATION], [int i; for (i = 0; i < 1; ++i)])
+    dnl grantpt may be defined in libc but not declared.  The same
+    dnl goes for posix_openpt.  When that is the case, it means that
+    dnl grantpt or posix_openpt cannot actually be used.
+    AC_CHECK_DECLS([grantpt, posix_openpt])
+    AS_IF([test "x$ac_cv_have_decl_grantpt" = xyes],
+      [AC_DEFINE([PTY_TTY_NAME_SPRINTF],
+        [{ char *ptyname = 0; sigset_t blocked; sigemptyset (&blocked); 
sigaddset (&blocked, SIGCHLD); pthread_sigmask (SIG_BLOCK, &blocked, 0); if 
(grantpt (fd) != -1 && unlockpt (fd) != -1) ptyname = ptsname(fd); 
pthread_sigmask (SIG_UNBLOCK, &blocked, 0); if (!ptyname) { emacs_close (fd); 
return -1; } snprintf (pty_name, PTY_NAME_SIZE, "%s", ptyname); }])],
+      [AC_DEFINE([PTY_TTY_NAME_SPRINTF],
+        [{ char *ptyname = 0; sigset_t blocked; sigemptyset (&blocked); 
sigaddset (&blocked, SIGCHLD); pthread_sigmask (SIG_BLOCK, &blocked, 0); if 
(unlockpt (fd) != -1) ptyname = ptsname(fd); pthread_sigmask (SIG_UNBLOCK, 
&blocked, 0); if (!ptyname) { emacs_close (fd); return -1; } snprintf 
(pty_name, PTY_NAME_SIZE, "%s", ptyname); }])])
+    AS_IF([test "x$ac_cv_have_decl_posix_openpt" = xyes],
+      [AC_DEFINE([PTY_OPEN],
+        [do { fd = posix_openpt (O_RDWR | O_CLOEXEC | O_NOCTTY); if (fd < 0 && 
errno == EINVAL) fd = posix_openpt (O_RDWR | O_NOCTTY); } while (false)])
+       AC_DEFINE([PTY_NAME_SPRINTF], [])],
+      [AC_DEFINE([PTY_NAME_SPRINTF], [])
+       AC_DEFINE([PTY_OPEN], [fd = getpt ()])])
+    ;;
+
   gnu-linux | gnu-kfreebsd | dragonfly | freebsd | openbsd | netbsd | darwin | 
nacl )
     dnl if HAVE_GRANTPT
     if test "x$ac_cv_func_grantpt" = xyes; then
@@ -6126,6 +7039,9 @@ AC_DEFINE_UNQUOTED([COPYRIGHT], ["$copyright"],
   [Short copyright string for this version of Emacs.])
 AC_SUBST([copyright])
 
+# This is needed for gnulib's printf modules.
+CFLAGS="$CFLAGS -DHAVE_CONFIG_H"
+
 ### Specify what sort of things we'll be editing into Makefile and config.h.
 ### Use configuration here uncanonicalized to avoid exceeding size limits.
 AC_SUBST([version])
@@ -6399,6 +7315,38 @@ gl_INIT
 CFLAGS=$SAVE_CFLAGS
 LIBS=$SAVE_LIBS
 
+# Set up libgmp on Android.  Make sure to override what gnulib has
+# found.
+LIBGMP_CFLAGS=
+if test "$REALLY_ANDROID" = "yes" && test "$with_libgmp" != "no"; then
+  HAVE_LIBGMP=no
+  ndk_SEARCH_MODULE([libgmp], [LIBGMP], [HAVE_LIBGMP=yes])
+
+  if test "$HAVE_LIBGMP" = "yes"; then
+    SAVE_CFLAGS="$CFLAGS"
+    CFLAGS="$CFLAGS $LIBGMP_CFLAGS"
+    unset ac_cv_header_gmp_h
+    unset ac_cv_header_gmp_gmp_h
+    AC_CHECK_HEADERS([gmp.h gmp/gmp.h], [break])
+    CFLAGS="$SAVE_CFLAGS"
+    GL_GENERATE_GMP_H=
+    GL_GENERATE_GMP_H_CONDITION=
+    GL_GENERATE_GMP_GMP_H=
+    GL_GENERATE_GMP_GMP_H_CONDITION=
+    GL_GENERATE_MINI_GMP_H=
+    GL_GENERATE_MINI_GMP_H_CONDITION=
+
+    if test "$ac_cv_header_gmp_h" != "no" \
+       || test "$ac_cv_header_gmp_gmp_h" != "no"; then
+       HAVE_LIBGMP=yes
+       GL_GENERATE_GMP_H=false
+       LIBGMP="$LIBGMP_LIBS"
+       GMP_H=
+    fi
+  fi
+fi
+AC_SUBST([LIBGMP_CFLAGS])
+
 # timer_getoverrun needs the same library as timer_settime
 OLD_LIBS=$LIBS
 LIBS="$LIB_TIMER_TIME $LIBS"
@@ -6525,7 +7473,7 @@ if test "$window_system" != "none"; then
     [Define if you poll periodically to detect C-g.])
   WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o"
 
-  if test "$window_system" = "x11"; then
+  if test "$window_system" = "x11" || test "$REALLY_ANDROID" = "yes"; then
     AC_DEFINE([HAVE_TEXT_CONVERSION], [1],
       [Define if the window system has text conversion support.])
     WINDOW_SYSTEM_OBJ="$WINDOW_SYSTEM_OBJ textconv.o"
@@ -6534,6 +7482,35 @@ fi
 
 AC_SUBST([WINDOW_SYSTEM_OBJ])
 
+# Some systems have MB_CUR_MAX defined to a call to
+# __ctype_get_mb_cur_max, but do not have __ctype_get_mb_cur_max in
+# libc.  Check for that situation and define MB_CUR_MAX to something
+# sane.
+
+AC_CHECK_FUNC([__ctype_get_mb_cur_max])
+
+AC_CACHE_CHECK([whether MB_CUR_MAX is defined to function that won't link],
+  [emacs_cv_broken_mb_cur_max],
+  [AC_EGREP_CPP(__ctype_get_mb_cur_max, [
+#include <stdlib.h>
+#ifndef MB_CUR_MAX
+#define MB_CUR_MAX -1
+#endif
+static int foo = MB_CUR_MAX;
+], [AS_IF([test "$ac_cv_func___ctype_get_mb_cur_max" = "yes"],
+     [emacs_cv_broken_mb_cur_max=no],
+     [emacs_cv_broken_mb_cur_max=yes])],
+  [emacs_cv_broken_mb_cur_max=no])])
+
+AS_IF([test "$emacs_cv_broken_mb_cur_max" = "yes"],
+ # Define this to 4, which is right for Android.
+ [AS_CASE([$opsys], [android],
+   [AC_DEFINE([REPLACEMENT_MB_CUR_MAX], [4],
+      [Define to MB_CUR_MAX if stdlib.h is broken.])],
+   [AC_MSG_ERROR([MB_CUR_MAX does not work on your system.
+Please modify configure.ac to set an appropriate value, then
+send your change to bug-gnu-emacs@gnu.org])])])
+
 AH_TOP([/* GNU Emacs site configuration template file.
 
 Copyright (C) 1988, 1993-1994, 1999-2002, 2004-2021
@@ -6654,7 +7631,12 @@ done
 AC_DEFINE_UNQUOTED([EMACS_CONFIG_FEATURES], ["${emacs_config_features}"],
   [Summary of some of the main features enabled by configure.])
 
+# This is just a printable representation of the shared user ID.
+android_shared_user=
+AS_IF([test -n 
"$with_shared_user_id"],[android_shared_user="($with_shared_user_id)"])
+
 AS_ECHO(["  Does Emacs use -lXaw3d?                                 
${HAVE_XAW3D}
+  Is Emacs being built for Android?                       ${ANDROID} 
${android_shared_user}
   Does Emacs use the X Double Buffer Extension?           ${HAVE_XDBE}
   Does Emacs use -lXpm?                                   ${HAVE_XPM}
   Does Emacs use -ljpeg?                                  ${HAVE_JPEG}
@@ -6759,12 +7741,15 @@ fi
 AC_CONFIG_FILES([Makefile lib/gnulib.mk])
 
 dnl config.status treats $srcdir specially, so I think this is ok...
-AC_CONFIG_FILES([$srcdir/doc/man/emacs.1])
+ARCH_INDEPENDENT_CONFIG_FILES([$srcdir/doc/man/emacs.1])
+
+AC_CONFIG_FILES([lib/Makefile lib-src/Makefile oldXMenu/Makefile src/Makefile
+                 lwlib/Makefile nextstep/Makefile nt/Makefile])
+ARCH_INDEPENDENT_CONFIG_FILES([doc/emacs/Makefile doc/misc/Makefile
+                               doc/lispintro/Makefile doc/lispref/Makefile
+                              lisp/Makefile leim/Makefile])
 
-m4_define([subdir_makefiles],
-  [lib/Makefile lib-src/Makefile oldXMenu/Makefile doc/emacs/Makefile 
doc/misc/Makefile doc/lispintro/Makefile doc/lispref/Makefile src/Makefile 
lwlib/Makefile lisp/Makefile leim/Makefile nextstep/Makefile nt/Makefile])
-SUBDIR_MAKEFILES="subdir_makefiles"
-AC_CONFIG_FILES(subdir_makefiles)
+SUBDIR_MAKEFILES="lib/Makefile lib-src/Makefile oldXMenu/Makefile src/Makefile 
lwlib/Makefile nextstep/Makefile nt/Makefile doc/emacs/Makefile 
doc/misc/Makefile doc/lispintro/Makefile doc/lispref/Makefile lisp/Makefile 
leim/Makefile"
 
 dnl The test/ directory is missing if './make-dist --no-tests' was used.
 opt_makefile=test/Makefile
@@ -6772,24 +7757,27 @@ if test -f "$srcdir/$opt_makefile.in"; then
   SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES $opt_makefile"
   dnl Again, it's best not to use a variable.  Though you can add
   dnl ", [], [opt_makefile='$opt_makefile']" and it should work.
-  AC_CONFIG_FILES([test/Makefile])
-  AC_CONFIG_FILES([test/manual/noverlay/Makefile])
+  ARCH_INDEPENDENT_CONFIG_FILES([test/Makefile])
+  ARCH_INDEPENDENT_CONFIG_FILES([test/manual/noverlay/Makefile])
 fi
 opt_makefile=test/infra/Makefile
 if test -f "$srcdir/$opt_makefile.in"; then
   SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES $opt_makefile"
   dnl Again, it's best not to use a variable.  Though you can add
   dnl ", [], [opt_makefile='$opt_makefile']" and it should work.
-  AC_CONFIG_FILES([test/infra/Makefile])
+  ARCH_INDEPENDENT_CONFIG_FILES([test/infra/Makefile])
 fi
 
+if test "$ANDROID" = "yes"; then
+  SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES java/Makefile cross/Makefile"
+fi
 
 dnl The admin/ directory used to be excluded from tarfiles.
 if test -d $srcdir/admin; then
   SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES admin/charsets/Makefile 
admin/unidata/Makefile admin/grammars/Makefile"
-  AC_CONFIG_FILES([admin/charsets/Makefile])
-  AC_CONFIG_FILES([admin/unidata/Makefile])
-  AC_CONFIG_FILES([admin/grammars/Makefile])
+  ARCH_INDEPENDENT_CONFIG_FILES([admin/charsets/Makefile])
+  ARCH_INDEPENDENT_CONFIG_FILES([admin/unidata/Makefile])
+  ARCH_INDEPENDENT_CONFIG_FILES([admin/grammars/Makefile])
 fi                              dnl -d admin
 
 
@@ -6800,64 +7788,112 @@ AC_SUBST([SUBDIR_MAKEFILES_IN])
 SMALL_JA_DIC=$with_small_ja_dic
 AC_SUBST([SMALL_JA_DIC])
 
-dnl You might wonder (I did) why epaths.h is generated by running make,
-dnl rather than just letting configure generate it from epaths.in.
-dnl One reason is that the various paths are not fully expanded (see above);
-dnl e.g., gamedir='${localstatedir}/games/emacs'.
-dnl Secondly, the GNU Coding standards require that one should be able
-dnl to run 'make prefix=/some/where/else' and override the values set
-dnl by configure.  This also explains the 'move-if-change' test and
-dnl the use of force in the 'epaths-force' rule in Makefile.in.
-AC_CONFIG_COMMANDS([src/epaths.h], [
-if test "${opsys}" = "mingw32"; then
-  ${MAKE-make} MAKEFILE_NAME=do-not-make-Makefile epaths-force-w32
-elif test "$HAVE_NS" = "yes" && test "$EN_NS_SELF_CONTAINED" = "yes"; then
-  ${MAKE-make} MAKEFILE_NAME=do-not-make-Makefile 
epaths-force-ns-self-contained
-else
-  ${MAKE-make} MAKEFILE_NAME=do-not-make-Makefile epaths-force
-fi || AC_MSG_ERROR(['src/epaths.h' could not be made.])
-], [GCC="$GCC" CPPFLAGS="$CPPFLAGS" opsys="$opsys" HAVE_NS="$HAVE_NS"
-    EN_NS_SELF_CONTAINED="$EN_NS_SELF_CONTAINED"])
-
-dnl NB we have to cheat and use the ac_... version because abs_top_srcdir
-dnl is not yet set, sigh.  Or we could use ../$srcdir/src/.gdbinit,
-dnl or a symlink?
-AC_CONFIG_COMMANDS([src/.gdbinit], [
-if test ! -f src/.gdbinit && test -f "$srcdir/src/.gdbinit"; then
-  AS_ECHO(["source $ac_abs_top_srcdir/src/.gdbinit"]) > src/.gdbinit
-fi
-])
+dnl The following commands are run on the build system when building
+dnl Emacs.
+
+if test "$XCONFIGURE" != "android"; then
+  dnl You might wonder (I did) why epaths.h is generated by running
+  dnl make, rather than just letting configure generate it from
+  dnl epaths.in.  One reason is that the various paths are not fully
+  dnl expanded (see above); e.g.,
+  dnl gamedir='${localstatedir}/games/emacs'.  Secondly, the GNU
+  dnl Coding standards require that one should be able to run 'make
+  dnl prefix=/some/where/else' and override the values set by
+  dnl configure.  This also explains the 'move-if-change' test and the
+  dnl use of force in the 'epaths-force' rule in Makefile.in.
+  AC_CONFIG_COMMANDS([src/epaths.h], [
+  if test "${opsys}" = "mingw32"; then
+    ${MAKE-make} MAKEFILE_NAME=do-not-make-Makefile epaths-force-w32
+  elif test "$HAVE_NS" = "yes" && test "$EN_NS_SELF_CONTAINED" = "yes"; then
+    ${MAKE-make} MAKEFILE_NAME=do-not-make-Makefile 
epaths-force-ns-self-contained
+  else
+    ${MAKE-make} MAKEFILE_NAME=do-not-make-Makefile epaths-force
+  fi || AC_MSG_ERROR(['src/epaths.h' could not be made.])
+  ], [GCC="$GCC" CPPFLAGS="$CPPFLAGS" opsys="$opsys" HAVE_NS="$HAVE_NS"
+      EN_NS_SELF_CONTAINED="$EN_NS_SELF_CONTAINED"])
+
+  dnl NB we have to cheat and use the ac_... version because abs_top_srcdir
+  dnl is not yet set, sigh.  Or we could use ../$srcdir/src/.gdbinit,
+  dnl or a symlink?
+  AC_CONFIG_COMMANDS([src/.gdbinit], [
+  if test ! -f src/.gdbinit && test -f "$srcdir/src/.gdbinit"; then
+    AS_ECHO(["source $ac_abs_top_srcdir/src/.gdbinit"]) > src/.gdbinit
+  fi
+  ])
 
-dnl Perhaps this would be better named doc-emacs-emacsver.texi?
-dnl See comments for etc-refcards-emacsver.tex.
-dnl Since we get a doc/emacs directory generated anyway, for the Makefile,
-dnl it is not quite the same.  But we are generating in $srcdir.
-AC_CONFIG_COMMANDS([doc/emacs/emacsver.texi], [
-${MAKE-make} -s --no-print-directory -C doc/emacs doc-emacsver || \
-AC_MSG_ERROR(['doc/emacs/emacsver.texi' could not be made.])
-])
+  dnl Perhaps this would be better named doc-emacs-emacsver.texi?
+  dnl See comments for etc-refcards-emacsver.tex.
+  dnl Since we get a doc/emacs directory generated anyway, for the Makefile,
+  dnl it is not quite the same.  But we are generating in $srcdir.
+  AC_CONFIG_COMMANDS([doc/emacs/emacsver.texi], [
+  ${MAKE-make} -s --no-print-directory -C doc/emacs doc-emacsver || \
+  AC_MSG_ERROR(['doc/emacs/emacsver.texi' could not be made.])
+  ])
 
-dnl If we give this the more natural name, etc/refcards/emacsver.texi,
-dnl then a directory etc/refcards is created in the build directory,
-dnl which is probably harmless, but confusing (in out-of-tree builds).
-dnl (If we were to generate etc/refcards/Makefile, this might change.)
-dnl It is really $srcdir/etc/refcards/emacsver.tex that we generate.
-AC_CONFIG_COMMANDS([etc-refcards-emacsver.tex], [
-${MAKE-make} -s MAKEFILE_NAME=do-not-make-Makefile etc-emacsver || \
-AC_MSG_ERROR(['etc/refcards/emacsver.tex' could not be made.])
-])
+  dnl If we give this the more natural name, etc/refcards/emacsver.texi,
+  dnl then a directory etc/refcards is created in the build directory,
+  dnl which is probably harmless, but confusing (in out-of-tree builds).
+  dnl (If we were to generate etc/refcards/Makefile, this might change.)
+  dnl It is really $srcdir/etc/refcards/emacsver.tex that we generate.
+  AC_CONFIG_COMMANDS([etc-refcards-emacsver.tex], [
+  ${MAKE-make} -s MAKEFILE_NAME=do-not-make-Makefile etc-emacsver || \
+  AC_MSG_ERROR(['etc/refcards/emacsver.tex' could not be made.])
+  ])
 
-if test $AUTO_DEPEND = yes; then
-   for dir in $AUTODEPEND_PARENTS; do
-     AS_MKDIR_P([$dir/deps])
-   done
-fi
-if $gl_gnulib_enabled_dynarray || $gl_gnulib_enabled_scratch_buffer; then
-  AS_MKDIR_P([lib/malloc])
   if test $AUTO_DEPEND = yes; then
-    AS_MKDIR_P([lib/deps/malloc])
+     for dir in $AUTODEPEND_PARENTS; do
+       AS_MKDIR_P([$dir/deps])
+       AS_MKDIR_P([cross/$dir/deps])
+     done
   fi
-fi
+  if $gl_gnulib_enabled_dynarray || $gl_gnulib_enabled_scratch_buffer; then
+    AS_MKDIR_P([lib/malloc])
+    AS_MKDIR_P([cross/lib/malloc])
+    if test $AUTO_DEPEND = yes; then
+      AS_MKDIR_P([lib/deps/malloc])
+      AS_MKDIR_P([cross/lib/deps/malloc])
+    fi
+  fi
+
+  dnl Make cross/lib, which various Makefiles in cross expect to
+  dnl always exist.
+  AS_MKDIR_P([cross/lib])
+  AS_MKDIR_P([cross/lib/malloc])
+  AS_MKDIR_P([cross/lib/sys])
+  AS_MKDIR_P([cross/lib-src])
+
+  dnl Make cross/etc; this directory will hold the documentation file
+  dnl holding doc strings for Android specific C files that aren't
+  dnl built during the initial compilation of Emacs for the build
+  dnl machine.
+  AS_MKDIR_P([cross/etc])
+
+  AS_IF([test "x$with_android" != "xno"], [
+    dnl Link gnulib files to cross/lib as well.  af_alg.h and
+    dnl lib/save-cwd.h are copied manually from gnulib, and as such
+    dnl aren't specified in gl_FILE_LIST.
+    emacs_files='gl_FILE_LIST lib/af_alg.h lib/save-cwd.h'
+    dnl These files are specific to Emacs.
+    emacs_files="$emacs_files lib/fingerprint.c lib/fingerprint.h \
+                  lib/save-cwd.c lib/openat-die.c lib/save-cwd.c \
+                  lib/min-max.h"
+    for file in $emacs_files; do
+      AS_IF([expr "X${file}J" : "Xlib/.*[[ch]]J" >/dev/null],
+       [AS_IF([test -f "$srcdir/$file"],
+         [AC_CONFIG_LINKS([cross/$file:$file])])])
+    done])
+fi
+
+# Make java/Makefile
+ARCH_INDEPENDENT_CONFIG_FILES([java/Makefile])
+ARCH_INDEPENDENT_CONFIG_FILES([cross/Makefile])
+
+# Make java/AndroidManifest.xml
+ARCH_INDEPENDENT_CONFIG_FILES([java/AndroidManifest.xml])
+
+# Make ndk-build Makefiles.  This is only done inside the recursive
+# configure.
+ndk_CONFIG_FILES
 
 AC_OUTPUT
 
diff --git a/cross/Makefile.in b/cross/Makefile.in
new file mode 100644
index 00000000000..b66025283aa
--- /dev/null
+++ b/cross/Makefile.in
@@ -0,0 +1,194 @@
+### @configure_input@
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+top_builddir = @top_builddir@
+builddir = @builddir@
+
+FIND_DELETE = @FIND_DELETE@
+
+-include $(top_builddir)/src/verbose.mk
+
+# Cross-compiling Emacs for Android.
+
+# The cross compiled binaries are built by having ``variant''
+# Makefiles generated at configure-time.  First,
+# $(top_builddir)/src/Makefile.android,
+# $(top_builddir)/lib/Makefile.android,
+# $(top_builddir)/lib/gnulib.mk.android and
+# $(top_builddir)/lib-src/Makefile.android are copied to their usual
+# locations in this directory.
+
+# N.B. that LIB_SRCDIR is actually relative to builddir, because that
+# is where the gnulib files get linked.
+
+LIB_SRCDIR = $(realpath $(builddir)/lib)
+LIB_TOP_SRCDIR = $(realpath $(top_srcdir))
+
+SRC_SRCDIR = $(realpath $(top_srcdir)/src)
+SRC_TOP_SRCDIR = $(realpath $(top_srcdir))
+
+LIB_SRC_SRCDIR = $(realpath $(top_srcdir)/lib-src)
+LIB_SRC_TOP_SRCDIR = $(realpath $(top_src))
+
+# This is a list of binaries to build and install in lib-src.
+
+LIBSRC_BINARIES = lib-src/etags lib-src/ctags lib-src/emacsclient \
+                 lib-src/ebrowse lib-src/hexl lib-src/movemail
+
+CLEAN_SUBDIRS = $(wildcard src lib-src lib etc)
+
+.PHONY: all
+all: lib/libgnu.a src/libemacs.so src/android-emacs $(LIBSRC_BINARIES)
+
+# This Makefile relies on builddir and top_builddir being relative
+# paths in *.android.
+
+# This file is used to tell lib/gnulib.mk when
+# $(top_builddir)/config.status changes.
+config.status: $(top_builddir)/config.status
+       $(AM_V_GEN) touch config.status
+
+src/verbose.mk: $(srcdir)/verbose.mk.android
+       $(AM_V_SILENT) cp -f $(srcdir)/verbose.mk.android \
+         src/verbose.mk
+
+# Gnulib, make-fingerprint and make-docfile must be built before
+# entering any of the rules below, or they will get the Android
+# versions of many headers.
+
+.PHONY: $(top_builddir)/lib/libgnu.a
+$(top_builddir)/lib/libgnu.a:
+       $(MAKE) -C $(top_builddir)/lib libgnu.a
+
+.PHONY: $(top_builddir)/lib-src/make-fingerprint
+$(top_builddir)/lib-src/make-fingerprint: $(top_builddir)/lib/libgnu.a
+       $(MAKE) -C $(top_builddir)/lib-src make-fingerprint
+
+.PHONY: $(top_builddir)/lib-src/make-docfile
+$(top_builddir)/lib-src/make-docfile: $(top_builddir)/lib/libgnu.a
+       $(MAKE) -C $(top_builddir)/lib-src make-docfile
+
+PRE_BUILD_DEPS=$(top_builddir)/lib/libgnu.a            \
+              $(top_builddir)/lib-src/make-fingerprint \
+              $(top_builddir)/lib-src/make-docfile
+
+lib/config.h: $(top_builddir)/src/config.h.android
+       $(AM_V_GEN) cp -f -p $(top_builddir)/src/config.h.android       \
+         lib/config.h
+
+lib-src/config.h: $(top_builddir)/src/config.h.android
+       $(AM_V_GEN) cp -f -p $(top_builddir)/src/config.h.android       \
+         lib-src/config.h
+
+# Figure out where build-aux is.
+# Then, replace the build-aux directory with its actual location,
+# in case MKDIR_P points there.
+
+relative_buildaux_dir := $(subst /,\/,$(top_srcdir)/build-aux)
+
+lib/gnulib.mk: $(top_builddir)/lib/gnulib.mk.android
+       $(AM_V_GEN) \
+         sed -e 's/^srcdir =.*$$/srcdir = $(subst /,\/,$(LIB_SRCDIR))/g' \
+             -e 's/$(relative_buildaux_dir)/$(subst 
/,\/,../$(top_builddir))\/build-aux/g' \
+           < $(top_builddir)/lib/gnulib.mk.android > $@
+
+lib/Makefile: $(top_builddir)/lib/Makefile.android
+       $(AM_V_GEN) \
+         sed -e 's/^top_srcdir =.*$$/top_srcdir = $(subst 
/,\/,$(LIB_TOP_SRCDIR))/g' \
+             -e 's/^srcdir =.*$$/srcdir = $(subst /,\/,$(LIB_SRCDIR))/g' \
+             -e 's/^VPATH =.*$$/VPATH = $(subst /,\/,$(LIB_SRCDIR))/g' \
+               < $(top_builddir)/lib/Makefile.android > $@
+
+# What is needed to build gnulib.
+LIB_DEPS = lib/config.h lib/gnulib.mk lib/Makefile
+
+.PHONY: lib/libgnu.a
+lib/libgnu.a: src/verbose.mk config.status $(LIB_DEPS) $(PRE_BUILD_DEPS)
+       $(MAKE) -C lib libgnu.a
+
+# Edit srcdir and top_srcdir to the right locations.
+# Edit references to ../admin/unidata to read ../../admin/unidata.
+# Next, edit libsrc to the location at top_srcdir! It is important
+# that src/Makefile uses the binaries there, instead of any
+# cross-compiled binaries at ./lib-src.
+# Edit out anything saying -I($(top_srcdir)/lib) into
+# -I$../(srcdir)/lib; that should be covered by -I$(lib)
+
+src/Makefile: $(top_builddir)/src/Makefile.android
+       $(AM_V_GEN) \
+       sed -e 's/^srcdir =.*$$/srcdir = $(subst /,\/,$(SRC_SRCDIR))/g' \
+           -e 's/^top_srcdir =.*$$/top_srcdir = $(subst 
/,\/,$(LIB_TOP_SRCDIR))/g' \
+           -e 's/\.\.\/admin\/unidata/..\/..\/admin\/unidata/g' \
+           -e 's/\.\.\/admin\/charsets/..\/..\/admin\/charsets/g' \
+           -e 's/^libsrc =.*$$/libsrc = \.\.\/\.\.\/lib-src/g' \
+           -e 's/libsrc =.*$$/libsrc = \.\.\/\.\.\/lib-src/g' \
+           -e 's/-I\$$(top_srcdir)\/lib/-I..\/$(subst /,\/,$(srcdir))\/lib/g' \
+           < $(top_builddir)/src/Makefile.android > $@
+
+src/config.h: $(top_builddir)/src/config.h.android
+       $(AM_V_GEN) cp -f -p $< $@
+
+.PHONY: src/android-emacs src/libemacs.so
+
+src/libemacs.so: src/Makefile src/config.h src/verbose.mk \
+  lib/libgnu.a $(PRE_BUILD_DEPS)
+       $(MAKE) -C src libemacs.so
+
+src/android-emacs: src/Makefile src/config.h lib/libgnu.a \
+  $(PRE_BUILD_DEPS)
+       $(MAKE) -C src android-emacs
+
+# Edit out SCRIPTS, it interferes with the build.
+# Make BASE_CFLAGS also include cross/lib as well as ../lib.
+
+lib-src/Makefile: $(top_builddir)/lib-src/Makefile.android
+       $(AM_V_GEN) \
+       sed -e 's/-I\$${srcdir}\/\.\.\/lib//g' \
+           -e 's/^srcdir=.*$$/srcdir = $(subst /,\/,$(LIB_SRC_SRCDIR))/g' \
+           -e 's/^top_srcdir=.*$$/top_srcdir = $(subst 
/,\/,$(LIB_SRC_TOP_SRCDIR))/g' \
+           -e 's/^SCRIPTS=.*$$/SCRIPTS=/g' \
+           -e 's/-I\.\.\/lib/-I..\/lib -I..\/$(subst /,\/,$(srcdir))\/lib/g' \
+         < $(top_builddir)/lib-src/Makefile.android > $@
+
+.PHONY: $(LIBSRC_BINARIES)
+$(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \
+  lib-src/config.h lib-src/Makefile $(PRE_BUILD_DEPS)
+# Finally, go into lib-src and make everything being built
+       $(MAKE) -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin)))
+
+.PHONY: clean maintainer-clean distclean
+clean:
+       for dir in $(CLEAN_SUBDIRS); do         \
+         find $$dir -type f $(FIND_DELETE);    \
+       done
+       rm -rf lib/config.h lib-src/config.h
+# ndk-build won't have been generated in a non-Android build.
+       if test -f ndk-build/Makefile; then     \
+          $(MAKE) -C ndk-build clean;          \
+       fi
+
+maintainer-clean distclean bootstrap-clean: clean
+# Remove links created by configure.
+       for dir in $(CLEAN_SUBDIRS); do         \
+         find $$dir -type l $(FIND_DELETE);    \
+       done
+       rm -rf lib/Makefile lib/gnulib.mk ndk-build/Makefile
+       rm -rf ndk-build/ndk-build.mk Makefile
diff --git a/cross/README b/cross/README
new file mode 100644
index 00000000000..3ec6f2c0b3c
--- /dev/null
+++ b/cross/README
@@ -0,0 +1,5 @@
+This directory holds Makefiles and other required assets to build an
+Emacs binary independently for another toolchain.
+
+The directory ndk-build also contains an implementation of the Android
+`ndk-build' build system.
diff --git a/cross/langinfo.h b/cross/langinfo.h
new file mode 100644
index 00000000000..b296ba8db80
--- /dev/null
+++ b/cross/langinfo.h
@@ -0,0 +1,20 @@
+/* Replacement langinfo.h file for building GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#define nl_langinfo(ignore)  "ASCII"
diff --git a/cross/ndk-build/Makefile.in b/cross/ndk-build/Makefile.in
new file mode 100644
index 00000000000..cdf18471ff3
--- /dev/null
+++ b/cross/ndk-build/Makefile.in
@@ -0,0 +1,144 @@
+### @configure_input@
+
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
+
+              srcdir = @srcdir@
+
+# This is a list of Android.mk files which provide targets.
+NDK_BUILD_ANDROID_MK = @NDK_BUILD_ANDROID_MK@
+      NDK_BUILD_ARCH = @NDK_BUILD_ARCH@
+       NDK_BUILD_ABI = @NDK_BUILD_ABI@
+       NDK_BUILD_SDK = @NDK_BUILD_SDK@
+        NDK_BUILD_CC = @NDK_BUILD_CC@
+       NDK_BUILD_CXX = @NDK_BUILD_CXX@
+        NDK_BUILD_AR = @NDK_BUILD_AR@
+      NDK_BUILD_NASM = @NDK_BUILD_NASM@
+    NDK_BUILD_CFLAGS = @NDK_BUILD_CFLAGS@
+
+# This is a list of targets to build.
+   NDK_BUILD_MODULES = @NDK_BUILD_MODULES@
+
+# This is set by the Android in tree build system and is used by some
+# libraries to look for the NDK.  Its value is unimportant.
+           NDK_ROOT = /tmp/
+
+# Finally, here are rules common to Emacs.
+.PHONY: all
+all: $(NDK_BUILD_MODULES)
+
+define uniqify
+$(if $1,$(firstword $1) $(call uniqify,$(filter-out $(firstword $1),$1)))
+endef
+
+# Remove duplicate files.
+NDK_BUILD_ANDROID_MK := $(call uniqify,$(NDK_BUILD_ANDROID_MK))
+
+# Remove duplicate modules as well.  These can occur when a single
+# module imports a module and also declares it in
+# LOCAL_SHARED_LIBRARIES.
+NDK_BUILD_MODULES := $(call uniqify,$(NDK_BUILD_MODULES))
+
+# Define CFLAGS for compiling C++ code; this involves removing all
+# -std=NNN options.
+NDK_BUILD_CFLAGS_CXX := $(filter-out -std=%,$(NDK_BUILD_CFLAGS))
+
+define subr-1
+
+# Define ndk-build functions.  Many of these are identical to those in
+# build-aux/ndk-build-helper.mk.
+
+# NDK_LAST_MAKEFILE is the last Makefile that was included.
+NDK_LAST_MAKEFILE = $$(lastword $$(filter %Android.mk,$$(MAKEFILE_LIST)))
+
+# local-makefile is the current Makefile being loaded.
+local-makefile = $$(NDK_LAST_MAKEFILE)
+
+# my-dir is a function that returns the Android module directory.  If
+# no Android.mk has been loaded, use the directory of the Makefile
+# being included.
+my-dir = $$(or $$(and $$(local-makefile),$$(dir $$(local-makefile))),$(dir 
$(1)))
+
+# Return all Android.mk files under the first arg.
+all-makefiles-under = $$(wildcard $$(1)/*/Android.mk)
+
+# Return all Android.mk files in subdirectories of this Makefile's
+# location.
+all-subdir-makefiles = $$(call all-makefiles-under,$$(call my-dir))
+
+# NDK-defined include variables.
+
+CLEAR_VARS = $(srcdir)/ndk-clear-vars.mk
+BUILD_EXECUTABLE = $(srcdir)/ndk-build-executable.mk
+BUILD_SHARED_LIBRARY = $(srcdir)/ndk-build-shared-library.mk
+BUILD_STATIC_LIBRARY = $(srcdir)/ndk-build-static-library.mk
+PREBUILT_SHARED_LIBRARY = $(srcdir)/ndk-prebuilt-shared-library.mk
+PREBUILT_STATIC_LIBRARY = $(srcdir)/ndk-prebuilt-static-library.mk
+
+# Target information variables.
+
+TARGET_ARCH = $(NDK_BUILD_ARCH)
+TARGET_PLATFORM = android-$(NDK_BUILD_SDK)
+TARGET_ARCH_ABI = $(NDK_BUILD_ABI)
+TARGET_ABI = $(TARGET_PLATFORM)-$(TARGET_ABI)
+
+# Module description variables.  These are defined by Android.mk.
+LOCAL_PATH :=
+LOCAL_MODULE :=
+LOCAL_MODULE_FILENAME :=
+LOCAL_SRC_FILES :=
+LOCAL_CPP_EXTENSION :=
+LOCAL_CPP_FEATURES :=
+LOCAL_C_INCLUDES :=
+LOCAL_CFLAGS :=
+LOCAL_CPPFLAGS :=
+LOCAL_STATIC_LIBRARIES :=
+LOCAL_SHARED_LIBRARIES :=
+LOCAL_WHOLE_STATIC_LIBRARIES :=
+LOCAL_LDLIBS :=
+LOCAL_LDFLAGS :=
+LOCAL_ALLOW_UNDEFINED_SYMBOLS :=
+LOCAL_ARM_MODE :=
+LOCAL_ARM_NEON :=
+LOCAL_DISABLE_FORMAT_STRING_CHECKS :=
+LOCAL_EXPORT_CFLAGS :=
+LOCAL_EXPORT_CPPFLAGS :=
+LOCAL_EXPORT_C_INCLUDES :=
+LOCAL_EXPORT_LDFLAGS :=
+LOCAL_EXPORT_LDLIBS :=
+LOCAL_ASM_RULE_DEFINED :=
+LOCAL_ASM_RULE :=
+
+# Now load Android.mk.
+include $(1)
+
+endef
+
+# Now define rules for each Android.mk file.
+$(foreach android_mk,$(NDK_BUILD_ANDROID_MK),$(eval $(call 
subr-1,$(android_mk))))
+
+.PHONY: clean mostlyclean
+clean mostlyclean:
+       rm -rf *.o *.so *.a
+
+.PHONY: extraclean dist-clean maintainer-clean
+extraclean dist-clean maintainer-clean:
+       rm -rf Makefile
diff --git a/cross/ndk-build/README b/cross/ndk-build/README
new file mode 100644
index 00000000000..aca2e7230bf
--- /dev/null
+++ b/cross/ndk-build/README
@@ -0,0 +1,353 @@
+NDK BUILD SYSTEM IMPLEMENTATION
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+See the end of the file for license conditions.
+
+Emacs implements ndk-build itself, because the version that comes with
+the Android NDK is not easy to use from another Makefile, and keeps
+accumulating incompatible changes.
+
+The Emacs implementation of ndk-build consists of one m4 file:
+
+  m4/ndk-build.m4
+
+four Makefiles in build-aux, run during configure:
+
+  build-aux/ndk-build-helper-1.mk
+  build-aux/ndk-build-helper-2.mk
+  build-aux/ndk-build-helper-3.mk
+  build-aux/ndk-build-helper.mk
+
+one awk script in build-awx, run during configure:
+
+  build-aux/ndk-module-extract.awk
+
+seven Makefiles in cross/ndk-build,
+
+  cross/ndk-build/ndk-build-shared-library.mk
+  cross/ndk-build/ndk-build-static-library.mk
+  cross/ndk-build/ndk-build-executable.mk
+  cross/ndk-build/ndk-clear-vars.mk
+  cross/ndk-build/ndk-prebuilt-shared-library.mk
+  cross/ndk-build/ndk-prebuilt-static-library.mk
+  cross/ndk-build/ndk-resolve.mk
+
+and finally, two more Makefiles in cross/ndk-build, generated by
+configure:
+
+  cross/ndk-build/Makefile     (generated from cross/ndk-build/Makefile.in)
+  cross/ndk-build/ndk-build.mk (generated from cross/ndk-build/ndk-build.mk.in)
+
+m4/ndk-build.m4 is a collection of macros which are used by the
+configure script to set up the ndk-build system, look for modules, add
+the appropriate options to LIBS and CFLAGS, and generate the Makefiles
+necessary to build the rest of Emacs.
+
+Immediately after determining the list of directories in which to look
+for ``Android.mk'' files, the version and type of Android system being
+built for, configure calls:
+
+  ndk_INIT([$android_abi], [$ANDROID_SDK], [cross/ndk-build])
+
+This expands to a sequence of shell script that enumerates all of the
+Android.mk files specified in "$with_ndk_path", sets up some shell
+functions used by the rest of the ndk-build code run by the configure
+script, and teaches the ndk-build system that the Makefiles to be
+generated are found in the directory "cross/ndk-build/Makefile".
+
+When configure is cross-compiling for Android, the macro
+EMACS_CHECK_MODULES will expand to the macro ndk_CHECK_MODULES,
+instead of pkg-config.m4's PKG_CHECK_MODULES.  Thus, the following
+code:
+
+    EMACS_CHECK_MODULES([PNG], [libpng >= 1.0.0])
+
+will actually expand to:
+
+    ndk_CHECK_MODULES([PNG], [libpng >= 1.0.0], [HAVE_PNG=yes],
+                     [HAVE_PNG=no])
+
+which in turn expands to a sequence shell script that first invokes:
+
+    make -f build-aux/ndk-build-helper.mk
+
+for each ``Android.mk'' file found by ndk_INIT, with the following
+variables given to Make:
+
+    EMACS_SRCDIR=.  # the source directory (in which configure is running)
+    BUILD_AUXDIR=$ndk_AUX_DIR # the build-aux directory
+    EMACS_ABI=$ndk_ABI # this is the $android_abi given to ndk_INIT
+    ANDROID_MAKEFILE="/opt/android/libpng/Android.mk"
+    ANDROID_MODULE_DIRECTORY="/opt/android/libpng"
+    NDK_BUILD_DIR="$ndk_DIR" # this is the directory given as to ndk_INIT
+
+build-aux/ndk-build-helper.mk will then evaluate the contents
+$(ANDROID_MAKEFILE), the ``Android.mk'' file, for the first time.  The
+purpose of this evaluation is to establish a list of packages (or
+modules) provided by the ``Android.mk'' file, and the corresponding
+Makefile targets and compiler and linker flags required to build and
+link to those tagets.
+
+Before doing so, build-aux/ndk-build-helper.mk will define several
+variables and functions required by all ``Android.mk'' files.  The
+most important of these are:
+
+  my-dir # the directory containing the Android.mk file.
+  BUILD_SHARED_LIBRARY # build-aux/ndk-build-helper-1.mk
+  BUILD_STATIC_LIBRARY # build-aux/ndk-build-helper-2.mk
+  BUILD_EXECUTABLE # build-aux/ndk-build-helper-3.mk
+  CLEAR_VARS # build-aux/ndk-build-helper-4.mk
+
+Then, ``Android.mk'' will include $(CLEAN_VARS), possibly other
+``Android.mk'' files, (to clear variables previously set), set several
+variables describing each module to the ndk-build system, and include
+one of $(BUILD_SHARED_LIBRARY), $(BUILD_STATIC_LIBRARY) and
+$(BUILD_EXECUTABLE).
+
+Each one of those three scripts will then read from the variables set
+by ``Android.mk'', resolve dependencies, and print out some text
+describing the module to Emacs.         For example, the shared library
+module "libpng" results in the following text being printed:
+
+Building shared
+libpng
+/opt/android/libpng/png.c /opt/android/libpng/pngerror.c 
/opt/android/libpng/pngget.c /opt/android/libpng/pngmem.c 
/opt/android/libpng/pngpread.c /opt/android/libpng/pngread.c 
/opt/android/libpng/pngrio.c /opt/android/libpng/pngrtran.c 
/opt/android/libpng/pngrutil.c /opt/android/libpng/pngset.c 
/opt/android/libpng/pngtrans.c /opt/android/libpng/pngwio.c 
/opt/android/libpng/pngwrite.c /opt/android/libpng/pngwtran.c 
/opt/android/libpng/pngwutil.c
+-I/opt/android/libpng
+
+  -L/opt/emacs/cross/ndk-build -l:libpng_emacs.so
+libpng_emacs.so
+End
+
+The output is arranged as follows:
+
+  - The first line consists of the word ``Building'', followed by
+    either ``shared'', ``static'', or ``executable'', depending on
+    what type of module being built.
+
+  - The second line consists of the name of the module currently being
+    built.
+
+  - The third line consists of all of the source code files comprising
+    the module.
+
+  - The fourth line consists of the text that has to be added to
+    CFLAGS in order to find the includes associated with the module.
+
+  - The fifth line consists of the text that has to be added to LIBS
+    in order to link with this module and all of its dependencies.
+
+  - The sixth line consists of the Make targets (more on this later)
+    that will build the final shared object or library archive of this
+    module, along with all of its dependencies.
+
+  - The seventh line is either empty, or the name of a dependency on
+    the C++ standard library.  This is used to determine whether or
+    not Emacs will include the C++ standard library in the application
+    package.
+
+The output from Make is given to an awk script,
+build-aux/ndk-module-extract.awk.  This is responsible for parsing the
+that output and filtering out modules other than what is being built:
+
+  awk -f build-aux/ndk-module-extract.awk MODULE=libpng
+
+eventually generating this section of shell script:
+
+module_name=libpng
+module_kind=shared
+module_src="/opt/android/libpng/png.c /opt/android/libpng/pngerror.c 
/opt/android/libpng/pngget.c /opt/android/libpng/pngmem.c 
/opt/android/libpng/pngpread.c /opt/android/libpng/pngread.c 
/opt/android/libpng/pngrio.c /opt/android/libpng/pngrtran.c 
/opt/android/libpng/pngrutil.c /opt/android/libpng/pngset.c 
/opt/android/libpng/pngtrans.c /opt/android/libpng/pngwio.c 
/opt/android/libpng/pngwrite.c /opt/android/libpng/pngwtran.c 
/opt/android/libpng/pngwutil.c"
+module_includes="-I/opt/android/libpng"
+module_cflags=""
+module_ldflags="  -L/opt/emacs/cross/ndk-build -l:libpng_emacs.so"
+module_target="libpng_emacs.so"
+module_cxx_deps=""
+module_imports=""
+
+which is then evaluated by `configure'.         Once the variable
+`module_name' is set, configure apends the remaining
+$(module_includes), $(module_cflags) and $(module_ldflags) to the
+module's CFLAGS and LIBS variables, and appends the list of Makefile
+targets specified to the variable NDK_BUILD_MODULES.
+
+In some cases, an ``Android.mk'' file may chose to import a module
+defined in ``--with-ndk-path'', but not defined inside its own
+``Android.mk'' file.  build-aux/ndk-build-helper.mk defines the
+`import-module' function to add the modules being imported to a
+variable, which is then printed out after ``ndk-build-helper.mk''
+completes.  For example, libxml2 imports the ``libicucc'' module,
+which results in the following text being printed:
+
+Building shared
+libxml2
+/home/oldosfan/libxml2/SAX.c /home/oldosfan/libxml2/entities.c 
/home/oldosfan/libxml2/encoding.c /home/oldosfan/libxml2/error.c 
/home/oldosfan/libxml2/parserInternals.c /home/oldosfan/libxml2/parser.c 
/home/oldosfan/libxml2/tree.c /home/oldosfan/libxml2/hash.c 
/home/oldosfan/libxml2/list.c /home/oldosfan/libxml2/xmlIO.c 
/home/oldosfan/libxml2/xmlmemory.c /home/oldosfan/libxml2/uri.c 
/home/oldosfan/libxml2/valid.c /home/oldosfan/libxml2/xlink.c 
/home/oldosfan/libxml2/debugXML.c /home/oldo [...]
+
+
+  -L/home/oldosfan/emacs-dev/emacs-android/cross/ndk-build -l:libxml2_emacs.so 
-l:libicuuc_emacs.so
+libxml2_emacs.so libicuuc_emacs.so
+End
+Start Imports
+libicuuc
+End Imports
+
+Upon encountering the ``Start Imports'' section,
+build-aux/ndk-module-extract.awk collects all imports until it
+encounters the line ``End Imports'', at which point it prints:
+
+module_imports="libicuuc"
+
+Then, if the list of imports is not empty, ndk_CHECK_MODULES
+additionally calls itself for each import before appending the
+module's own ``Android.mk'', ensuring that the module's imported
+dependencies are included by $ndk_DIR/Makefile before itself.
+
+Finally, immediately before generating src/Makefile.android, configure
+expands:
+
+  ndk_CONFIG_FILES
+
+to generate $ndk_DIR/Makefile and $ndk_DIR/ndk-build.mk.
+
+Now, the $ndk_DIR directory is set up to build all modules upon which
+depends, and $ndk_DIR/ndk-build.mk includes a list of files required
+to link Emacs, along with the rules to chdir into $ndk_DIR in order to
+build them.
+
+$ndk_DIR/ndk-build.mk is included by cross/src/Makefile
+(Makefile.android) and java/Makefile.  It defines three different
+variables:
+
+  NDK_BUILD_MODULES    the file names of all modules to be built.
+  NDK_BUILD_STATIC     absolute names of all library archives
+                       to be built.
+  NDK_BUILD_SHARED     absolute names of all shared libraries to
+                       be built.
+
+and then proceeds to define rules to build each of the modules in
+$(NDK_BUILD_MODULES).
+
+cross/src/Makefile arranges to have all dependencies of Emacs not
+already built built before linking ``libemacs.so'' with them.
+
+java/Makefile additionally arranges to have all shared object
+dependencies built before the application package is built, which is
+normally redundant because they should have already been built before
+linking ``libemacs.so''.
+
+Building the modules is performed through $ndk_DIR/Makefile, which
+contains the actual implementation of the ``ndk-build'' build system.
+First, it defines certain variables constant within the ``ndk-build''
+build system, such as the files included by ``Android.mk'' to build
+shared or static libraries, and CLEAR_VARS.  The most important of
+these are:
+
+  CLEAR_VARS                   cross/ndk-build/ndk-clear-vars.mk
+  BUILD_EXECUTABLE             cross/ndk-build/ndk-build-executable.mk
+  BUILD_SHARED_LIBRARY         cross/ndk-build/ndk-build-shared-library.mk
+  BUILD_STATIC_LIBRARY         cross/ndk-build/ndk-build-static-library.mk
+  PREBUILT_SHARED_LIBRARY      cross/ndk-build/ndk-prebuilt-shared-library.mk
+  PREBUILT_STATIC_LIBRARY      cross/ndk-build/ndk-prebuilt-static-library.mk
+
+Then, it loads each Emacs dependency's ``Android.mk'' file.  For each
+module defined there, ``Android.mk'' includes $(CLEAR_VARS) to unset
+all variables specific to each module, and then includes
+$(BUILD_SHARED_LIBRARY) or $(BUILD_STATIC_LIBRARY) for each shared or
+static library module.
+
+This results in cross/ndk-build/ndk-build-shared-library.mk or
+cross/ndk-build/ndk-build-static-library being included, just like the
+Makefiles in build-aux were inside the configure script.
+
+Each one of those two scripts then defines rules to build all of the
+object files associated with the module, and then link or archive
+them.  The name under which the module is linked is the same as the
+Make target found on the sixth line of output from
+build-aux/ndk-build-helper.mk.
+
+In doing so, they both include the file ndk-resolve.mk.
+ndk-resolve.mk is expected to recursively add all of the exported
+CFLAGS and includes of any dependencies to the compiler and linker
+command lines for the module being built.
+
+When building a shared library module, ndk-resolve.mk is also expected
+to define the variables NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) and
+NDK_WHOLE_A_NAMES_$(LOCAL_MODULE), containing all static library
+dependencies' archive files.  They are to be linked in to the
+resulting shared object file.
+
+This is done by including cross/ndk-build/ndk-resolve.mk each time a
+shared or static library module is going to be built.  How is this
+done?
+
+First, ndk-resolve.mk saves the LOCAL_PATH, LOCAL_STATIC_LIBRARIES,
+LOCAL_SHARED_LIBRARIES, LOCAL_EXPORT_CFLAGS and
+LOCAL_EXPORT_C_INCLUDES from the module.
+
+Next, ndk-resolve loops through the dependencies the module has
+specified, appending its CFLAGS and includes to the command line for
+the current module.
+
+Then, that process is repeated for each such dependency which has not
+already been resolved, until all dependencies have been resolved.
+
+libpng is a very simple module, providing only a single shared object
+module.         This module is named libpng_emacs.so and is eventually built
+and packaged into the library directory of the Emacs application
+package.  Now, let us look at a more complex module, libwebp:
+
+
+
+When built with libwebp, Emacs depends on a single library,
+libwebpdemux.  This library is named ``libwebpdemux'' on Unix systems,
+and that is the name by which it is found with pkg-config.
+
+However, the library's module is only named ``webpdemux'' on Android.
+When ndk_CHECK_MODULES begins to look for a module, it first tries to
+see if its name is found in the variable `ndk_package_map', which was
+set inside ndk_INIT.  In this case, it finds the following word:
+
+  libwebpdemux:webpdemux
+
+and immediately replaces ``libwebpdemux'' with ``webpdemux''.
+
+Then, it locates the ``Android.mk'' file containing a static library
+module named webpdemux and gives the output from
+build-aux/ndk-build-helper.mk to the awk script, resulting in:
+
+module_name=webpdemux
+module_kind=static
+module_src="/opt/android/webp/src/demux/anim_decode.c 
/opt/android/webp/src/demux/demux.c"
+module_includes="-I/opt/android/webp/src"
+module_cflags=""
+module_ldflags=" cross/ndk-build/libwebpdemux.a cross/ndk-build/libwebp.a 
cross/ndk-build/libwebpdecoder_static.a "
+module_target="libwebpdemux.a libwebp.a libwebpdecoder_static.a"
+
+The attentive reader will notice that in addition to the
+``libwebpdemux.a'' archive associated with the ``webpdemux'' library,
+Emacs has been made to link with two additional libraries.  This is
+because the ``webpdemux'' module specifies a dependency on the
+``webp'' module (defined in the same Android.mk).
+build-aux/ndk-build-helper.mk resolved that dependency, noticing that
+it in turn specified another dependency on ``webpdecoder_static'',
+which in turn was added to the linker command line and list of targets
+to build.
+
+As a result, all three dependencies will be built and linked to Emacs,
+instead of just the single ``webpdemux'' dependency that was
+specified.
+
+
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
diff --git a/build-aux/git-hooks/prepare-commit-msg 
b/cross/ndk-build/ndk-build-executable.mk
old mode 100755
new mode 100644
similarity index 50%
copy from build-aux/git-hooks/prepare-commit-msg
copy to cross/ndk-build/ndk-build-executable.mk
index 7802dffda43..9591c862b18
--- a/build-aux/git-hooks/prepare-commit-msg
+++ b/cross/ndk-build/ndk-build-executable.mk
@@ -1,7 +1,4 @@
-#!/bin/sh
-# Check the format of GNU Emacs change log entries.
-
-# Copyright 2019-2023 Free Software Foundation, Inc.
+# Copyright 2023 Free Software Foundation, Inc.
 
 # This file is part of GNU Emacs.
 
@@ -18,28 +15,8 @@
 # You should have received a copy of the GNU General Public License
 # along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-COMMIT_MSG_FILE=$1
-COMMIT_SOURCE=$2
-SHA1=$3
-
-# Prefer gawk if available, as it handles NUL bytes properly.
-if type gawk >/dev/null 2>&1; then
-  awk="gawk"
-else
-  awk="awk"
-fi
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
 
-exec $awk '
-  # Catch the case when someone ran git-commit with -s option,
-  # which automatically adds Signed-off-by.
-  /^Signed-off-by: / {
-    print "'\''Signed-off-by:'\'' in commit message"
-    status = 1
-  }
-  END {
-    if (status != 0) {
-      print "Commit aborted; please see the file 'CONTRIBUTE'"
-    }
-    exit status
-  }
-' <"$COMMIT_MSG_FILE"
+# Building executables is not supported
diff --git a/cross/ndk-build/ndk-build-shared-library.mk 
b/cross/ndk-build/ndk-build-shared-library.mk
new file mode 100644
index 00000000000..d60802da1d0
--- /dev/null
+++ b/cross/ndk-build/ndk-build-shared-library.mk
@@ -0,0 +1,171 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
+
+eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
+
+# Objects for shared libraries are prefixed with `-shared-' in
+# addition to the name of the module, because a common practice in
+# Android.mk files written by Google is to define two modules with the
+# same name but of different types.
+objname = $(1)-shared-$(subst /,_,$(2).o)
+
+# LOCAL_SRC_FILES sometimes contains absolute file names.  Filter them
+# out with this function.  If $(2), this is a file relative to the
+# build directory.
+maybe-absolute = $(or $(and $(2),$(1)),$(and $(wildcard 
$(1)),$(1)),$(LOCAL_PATH)/$(1))
+
+# Here are the default flags to link shared libraries with.
+NDK_SO_DEFAULT_LDFLAGS := -lc -lm
+
+define single-object-target
+
+ifeq (x$(suffix $(1)),x.c)
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) 
$(NDK_BUILD_CFLAGS) $(call LOCAL_C_ADDITIONAL_FLAGS,$(1))
+
+else
+ifeq (x$(suffix $(1)),x.$(or $(LOCAL_CPP_EXTENSION),cpp))
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call maybe-absolute,$(1))
+       $(NDK_BUILD_CXX) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) 
$(NDK_BUILD_CFLAGS_CXX) $(NDK_CXXFLAGS_$(LOCAL_MODULE))
+
+else
+ifneq ($(or $(call eq,x$(suffix $(1)),x.s),$(call eq,x$(suffix $(1)),x.S)),)
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_ASFLAGS_$(LOCAL_MODULE))
+
+else
+ifneq (x$(suffix $(1)),x.asm)
+ifeq (x$(suffix $(1)),x.cc)
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CXX) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) 
$(NDK_BUILD_CFLAGS_CXX) $(NDK_CXXFLAGS_$(LOCAL_MODULE))
+
+else
+$$(error Unsupported suffix: $(suffix $(1)))
+endif
+else
+ifneq (x$(LOCAL_ASM_RULE_DEFINED),x)
+# Call this function to define a rule that will generate $(1) from
+# $(2), a ``.asm'' file.  This is an Emacs extension.
+
+$(call LOCAL_ASM_RULE,$(call objname,$(LOCAL_MODULE),$(basename 
$(1))),$(LOCAL_PATH)/$(strip $(1)))
+
+else
+ifeq ($(findstring x86,$(NDK_BUILD_ARCH)),)
+$$(error Trying to build nasm file on non-Intel platform!)
+else
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1)
+       $(NDK_BUILD_NASM) -felf$(findstring 64,$(NDK_BUILD_ARCH)) -o $$@ -i 
$(LOCAL_PATH) -i $$(dir $$<) $(NDK_ASFLAGS_$(LOCAL_MODULE)) $$<
+
+endif
+endif
+endif
+endif
+endif
+endif
+
+ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename 
$(1)))
+
+endef
+
+define single-neon-target
+
+# Define rules for the target.
+$$(eval $$(call single-object-target,$(patsubst %.neon,%,$(1)),))
+
+endef
+
+# Make sure to not add a prefix to local includes that already specify
+# $(LOCAL_PATH).
+NDK_CFLAGS_$(LOCAL_MODULE)      := $(addprefix -I,$(LOCAL_C_INCLUDES))
+NDK_CFLAGS_$(LOCAL_MODULE)      += -fPIC -iquote $(LOCAL_PATH) 
$(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH))
+NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) 
$(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) $(and $(findstring 
clang,$(NDK_BUILD_CC)),$(LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH)))
+NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS)
+NDK_CXXFLAGS_$(LOCAL_MODULE) := $(LOCAL_CPPFLAGS) $(LOCAL_RTTI_FLAG)
+
+# Now look for features in LOCAL_CPP_FEATURES and enable them.
+
+ifneq ($(findstring exceptions,$(LOCAL_CPPFLAGS)),)
+NDK_CXXFLAGS_$(LOCAL_MODULE) += -fexceptions
+endif
+
+ifneq ($(findstring rtti,$(LOCAL_CPPFLAGS)),)
+NDK_CXXFLAGS_$(LOCAL_MODULE) += -frtti
+endif
+
+ALL_OBJECT_FILES$(LOCAL_MODULE) :=
+
+ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm)
+NDK_CFLAGS ::= -marm
+else
+ifeq ($(NDK_BUILD_ARCH),arm)
+NDK_CFLAGS ::= -mthumb
+endif
+endif
+
+ifeq ($(findstring lib,$(LOCAL_MODULE)),lib)
+LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)_emacs
+else
+LOCAL_MODULE_FILENAME := lib$(LOCAL_MODULE)_emacs
+endif
+
+# Since a shared library is being built, suffix the library with
+# _emacs.  Otherwise, libraries already on the system will be found
+# first, with potentially nasty consequences.
+
+LOCAL_MODULE_FILENAME := $(LOCAL_MODULE_FILENAME).so
+
+# Record this module's dependencies and exported includes and CFLAGS,
+# and then add that of its dependencies.
+
+include $(srcdir)/ndk-resolve.mk
+
+# Then define rules to build all objects.
+ALL_SOURCE_FILES := $(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES_$(NDK_BUILD_ARCH))
+
+# This defines all dependencies.
+ALL_OBJECT_FILES$(LOCAL_MODULE) :=
+
+# Now filter out code that is built with neon.  Define rules to build
+# those separately.
+NEON_SOURCE_FILES := $(filter %.neon,$(ALL_SOURCE_FILES))
+ALL_SOURCE_FILES  := $(filter-out %.neon,$(ALL_SOURCE_FILES))
+
+$(foreach source,$(ALL_SOURCE_FILES),$(eval $(call 
single-object-target,$(source),)))
+$(foreach source,$(NEON_SOURCE_FILES),$(eval $(call 
single-neon-target,$(source))))
+
+# Now define the rule to build the shared library.  Shared libraries
+# link with all of the archive files from the static libraries on
+# which they depend, and also any shared libraries they depend on.
+
+define define-module-rule
+$(LOCAL_MODULE_FILENAME): $(ALL_OBJECT_FILES$(LOCAL_MODULE)) 
$(NDK_LOCAL_A_NAMES_$(LOCAL_MODULE)) $(NDK_WHOLE_A_NAMES_$(LOCAL_MODULE)) 
$(NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE))
+       $(NDK_BUILD_CC) $(1) $(2) -o $$@ -shared $(NDK_LDFLAGS_$(LOCAL_MODULE)) 
$(NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)) $(NDK_SO_DEFAULT_LDFLAGS) $(foreach 
so,$(NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE)),-L $(abspath $(CURDIR)) -l:$(so))
+endef
+
+NDK_WHOLE_ARCHIVE_PREFIX = -Wl,--whole-archive
+NDK_WHOLE_ARCHIVE_SUFFIX = -Wl,--no-whole-archive
+
+$(eval $(call define-module-rule,$(ALL_OBJECT_FILES$(LOCAL_MODULE)) 
$(NDK_LOCAL_A_NAMES_$(LOCAL_MODULE)),$(and $(strip 
$(NDK_WHOLE_A_NAMES_$(LOCAL_MODULE))),$(NDK_WHOLE_ARCHIVE_PREFIX) 
$(NDK_WHOLE_A_NAMES_$(LOCAL_MODULE)) $(NDK_WHOLE_ARCHIVE_SUFFIX))))
diff --git a/cross/ndk-build/ndk-build-static-library.mk 
b/cross/ndk-build/ndk-build-static-library.mk
new file mode 100644
index 00000000000..98afd864ed6
--- /dev/null
+++ b/cross/ndk-build/ndk-build-static-library.mk
@@ -0,0 +1,142 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
+
+eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
+objname = $(1)-static-$(subst /,_,$(2).o)
+maybe-absolute = $(or $(and $(2),$(1)),$(and $(wildcard 
$(1)),$(1)),$(LOCAL_PATH)/$(1))
+
+define single-object-target
+
+ifeq (x$(suffix $(1)),x.c)
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_BUILD_CFLAGS) 
$(NDK_CFLAGS_$(LOCAL_MODULE)) $(call LOCAL_C_ADDITIONAL_FLAGS,$(1))
+
+else
+ifeq (x$(suffix $(1)),x.$(or $(LOCAL_CPP_EXTENSION),cpp))
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CXX) -c $$< -o $$@ $(NDK_BUILD_CFLAGS_CXX) 
$(NDK_CFLAGS_$(LOCAL_MODULE)) $(NDK_CXXFLAGS_$(LOCAL_MODULE))
+
+else
+ifneq ($(or $(call eq,x$(suffix $(1)),x.s),$(call eq,x$(suffix $(1)),x.S)),)
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_ASFLAGS_$(LOCAL_MODULE))
+
+else
+ifneq (x$(suffix $(1)),x.asm)
+ifeq (x$(suffix $(1)),x.cc)
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_CXX) -c $$< -o $$@ $(NDK_BUILD_CFLAGS_CXX) 
$(NDK_CFLAGS_$(LOCAL_MODULE)) $(NDK_CXXFLAGS_$(LOCAL_MODULE))
+
+else
+$$(error Unsupported suffix: $(suffix $(1)))
+endif
+else
+ifneq (x$(LOCAL_ASM_RULE_DEFINED),x)
+# Call this function to define a rule that will generate $(1) from
+# $(2), a ``.asm'' file.  This is an Emacs extension.
+
+$(call LOCAL_ASM_RULE,$(call objname,$(LOCAL_MODULE),$(basename 
$(1))),$(LOCAL_PATH)/$(strip $(1)))
+
+else
+ifeq ($(findstring x86,$(NDK_BUILD_ARCH)),)
+$$(error Trying to build nasm file on non-Intel platform!)
+else
+
+$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(call 
maybe-absolute,$(1),$(2))
+       $(NDK_BUILD_NASM) -felf$(findstring 64,$(NDK_BUILD_ARCH)) -o $$@ -i 
$(LOCAL_PATH) -i $$(dir $$<) $(NDK_ASFLAGS_$(LOCAL_MODULE)) $$<
+
+endif
+endif
+endif
+endif
+endif
+endif
+
+ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename 
$(1)))
+endef
+
+define single-neon-target
+
+# Define rules for the target.
+$$(eval $$(call single-object-target,$(patsubst %.neon,%,$(1)),))
+
+endef
+
+NDK_CFLAGS_$(LOCAL_MODULE)  := $(addprefix -I,$(LOCAL_C_INCLUDES))
+NDK_CFLAGS_$(LOCAL_MODULE)  += -fPIC -iquote $(LOCAL_PATH) 
$(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH))
+NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) 
$(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) $(and $(findstring 
clang,$(NDK_BUILD_CC)),$(LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH)))
+NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS)
+NDK_CXXFLAGS_$(LOCAL_MODULE) := $(LOCAL_CPPFLAGS) $(LOCAL_RTTI_FLAG)
+ALL_OBJECT_FILES$(LOCAL_MODULE) :=
+
+# Now look for features in LOCAL_CPP_FEATURES and enable them.
+
+ifneq ($(findstring exceptions,$(LOCAL_CPPFLAGS)),)
+NDK_CXXFLAGS_$(LOCAL_MODULE) += -fexceptions
+endif
+
+ifneq ($(findstring rtti,$(LOCAL_CPPFLAGS)),)
+NDK_CXXFLAGS_$(LOCAL_MODULE) += -frtti
+endif
+
+
+ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm)
+NDK_CFLAGS ::= -marm
+else
+ifeq ($(NDK_BUILD_ARCH),arm)
+NDK_CFLAGS ::= -mthumb
+endif
+endif
+
+ifeq ($(findstring lib,$(LOCAL_MODULE)),lib)
+LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)
+else
+LOCAL_MODULE_FILENAME := lib$(LOCAL_MODULE)
+endif
+
+LOCAL_MODULE_FILENAME := $(LOCAL_MODULE_FILENAME).a
+
+# Record this module's dependencies and exported includes and CFLAGS,
+# and then add that of its dependencies.
+
+include $(srcdir)/ndk-resolve.mk
+
+# Then define rules to build all objects.
+ALL_SOURCE_FILES := $(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES_$(NDK_BUILD_ARCH))
+
+# Now filter out code that is built with neon.  Define rules to build
+# those separately.
+NEON_SOURCE_FILES := $(filter %.neon,$(ALL_SOURCE_FILES))
+ALL_SOURCE_FILES  := $(filter-out %.neon,$(ALL_SOURCE_FILES))
+
+# This defines all dependencies.
+ALL_OBJECT_FILES$(LOCAL_MODULE) =
+
+$(foreach source,$(ALL_SOURCE_FILES),$(eval $(call 
single-object-target,$(source),)))
+$(foreach source,$(NEON_SOURCE_FILES),$(eval $(call 
single-neon-target,$(source),)))
+
+# Now define the rule to build the library.
+$(LOCAL_MODULE_FILENAME): $(ALL_OBJECT_FILES$(LOCAL_MODULE))
+       $(NDK_BUILD_AR) r $@ $^
diff --git a/cross/ndk-build/ndk-build.mk.in b/cross/ndk-build/ndk-build.mk.in
new file mode 100644
index 00000000000..57006901721
--- /dev/null
+++ b/cross/ndk-build/ndk-build.mk.in
@@ -0,0 +1,68 @@
+### @configure_input@
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# This file is included all over the place to get and build
+# prerequisites.
+
+NDK_BUILD_MODULES = @NDK_BUILD_MODULES@
+NDK_BUILD_CXX_SHARED = @NDK_BUILD_CXX_SHARED@
+NDK_BUILD_ANY_CXX_MODULE = @NDK_BUILD_ANY_CXX_MODULE@
+NDK_BUILD_SHARED =
+NDK_BUILD_STATIC =
+
+define uniqify
+$(if $1,$(firstword $1) $(call uniqify,$(filter-out $(firstword $1),$1)))
+endef
+
+# Remove duplicate modules.  These can occur when a single module
+# imports a module and also declares it in LOCAL_SHARED_LIBRARIES.
+NDK_BUILD_MODULES := $(call uniqify,$(NDK_BUILD_MODULES))
+
+# Here are all of the files to build.
+NDK_BUILD_ALL_FILES := $(foreach file,$(NDK_BUILD_MODULES), \
+                        $(top_builddir)/cross/ndk-build/$(file))
+
+# The C++ standard library must be extracted from the Android NDK
+# directories and included in the application package, if any module
+# requires the C++ standard library.
+
+ifneq ($(NDK_BUILD_ANY_CXX_MODULE),)
+NDK_BUILD_SHARED += $(NDK_BUILD_CXX_SHARED)
+endif
+
+define subr-1
+ifeq ($(suffix $(1)),.so)
+NDK_BUILD_SHARED += $(top_builddir)/cross/ndk-build/$(1)
+else
+ifeq ($(suffix $(1)),.a)
+NDK_BUILD_STATIC += $(top_builddir)/cross/ndk-build/$(1)
+endif
+endif
+endef
+
+# Generate rules for each module.
+
+$(foreach module,$(NDK_BUILD_MODULES),$(eval $(call subr-1,$(module))))
+
+# Generate rules to build everything now.
+# Make sure to use the top_builddir currently defined.
+
+NDK_TOP_BUILDDIR := $(top_builddir)
+$(NDK_BUILD_ALL_FILES) &:
+       $(MAKE) -C $(NDK_TOP_BUILDDIR)/cross/ndk-build $(NDK_BUILD_MODULES)
diff --git a/cross/ndk-build/ndk-clear-vars.mk 
b/cross/ndk-build/ndk-clear-vars.mk
new file mode 100644
index 00000000000..7309b7bb513
--- /dev/null
+++ b/cross/ndk-build/ndk-clear-vars.mk
@@ -0,0 +1,57 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
+
+LOCAL_MODULE :=
+LOCAL_MODULE_FILENAME :=
+LOCAL_SRC_FILES :=
+LOCAL_CPP_EXTENSION :=
+LOCAL_CPP_FEATURES :=
+LOCAL_C_INCLUDES :=
+LOCAL_CFLAGS :=
+LOCAL_CPPFLAGS :=
+LOCAL_STATIC_LIBRARIES :=
+LOCAL_SHARED_LIBRARIES :=
+LOCAL_WHOLE_STATIC_LIBRARIES :=
+LOCAL_LDLIBS :=
+LOCAL_LDFLAGS :=
+LOCAL_ALLOW_UNDEFINED_SYMBOLS :=
+LOCAL_ARM_MODE :=
+LOCAL_ARM_NEON :=
+LOCAL_DISABLE_FORMAT_STRING_CHECKS :=
+LOCAL_EXPORT_CFLAGS :=
+LOCAL_EXPORT_CPPFLAGS :=
+LOCAL_EXPORT_C_INCLUDES :=
+LOCAL_EXPORT_C_INCLUDE_DIRS :=
+LOCAL_EXPORT_LDFLAGS :=
+LOCAL_EXPORT_LDLIBS :=
+
+# AOSP extensions.
+LOCAL_SRC_FILES_$(NDK_BUILD_ARCH) :=
+LOCAL_ASFLAGS_$(NDK_BUILD_ARCH) :=
+LOCAL_CFLAGS_$(NDK_BUILD_ARCH) :=
+LOCAL_ADDITIONAL_DEPENDENCIES :=
+LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH) :=
+LOCAL_IS_HOST_MODULE :=
+
+# Emacs extensions!
+LOCAL_ASM_RULE_DEFINED :=
+LOCAL_ASM_RULE :=
+LOCAL_C_ADDITIONAL_FLAGS :=
diff --git a/build-aux/git-hooks/prepare-commit-msg 
b/cross/ndk-build/ndk-prebuilt-shared-library.mk
old mode 100755
new mode 100644
similarity index 50%
copy from build-aux/git-hooks/prepare-commit-msg
copy to cross/ndk-build/ndk-prebuilt-shared-library.mk
index 7802dffda43..2a8260f9851
--- a/build-aux/git-hooks/prepare-commit-msg
+++ b/cross/ndk-build/ndk-prebuilt-shared-library.mk
@@ -1,7 +1,6 @@
-#!/bin/sh
-# Check the format of GNU Emacs change log entries.
+### @configure_input@
 
-# Copyright 2019-2023 Free Software Foundation, Inc.
+# Copyright 2023 Free Software Foundation, Inc.
 
 # This file is part of GNU Emacs.
 
@@ -18,28 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-COMMIT_MSG_FILE=$1
-COMMIT_SOURCE=$2
-SHA1=$3
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
 
-# Prefer gawk if available, as it handles NUL bytes properly.
-if type gawk >/dev/null 2>&1; then
-  awk="gawk"
-else
-  awk="awk"
-fi
-
-exec $awk '
-  # Catch the case when someone ran git-commit with -s option,
-  # which automatically adds Signed-off-by.
-  /^Signed-off-by: / {
-    print "'\''Signed-off-by:'\'' in commit message"
-    status = 1
-  }
-  END {
-    if (status != 0) {
-      print "Commit aborted; please see the file 'CONTRIBUTE'"
-    }
-    exit status
-  }
-' <"$COMMIT_MSG_FILE"
+$(warn Prebuilt shared libraries are not supported)
diff --git a/build-aux/git-hooks/prepare-commit-msg 
b/cross/ndk-build/ndk-prebuilt-static-library.mk
old mode 100755
new mode 100644
similarity index 50%
copy from build-aux/git-hooks/prepare-commit-msg
copy to cross/ndk-build/ndk-prebuilt-static-library.mk
index 7802dffda43..9230f690bb1
--- a/build-aux/git-hooks/prepare-commit-msg
+++ b/cross/ndk-build/ndk-prebuilt-static-library.mk
@@ -1,7 +1,6 @@
-#!/bin/sh
-# Check the format of GNU Emacs change log entries.
+### @configure_input@
 
-# Copyright 2019-2023 Free Software Foundation, Inc.
+# Copyright 2023 Free Software Foundation, Inc.
 
 # This file is part of GNU Emacs.
 
@@ -18,28 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-COMMIT_MSG_FILE=$1
-COMMIT_SOURCE=$2
-SHA1=$3
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
 
-# Prefer gawk if available, as it handles NUL bytes properly.
-if type gawk >/dev/null 2>&1; then
-  awk="gawk"
-else
-  awk="awk"
-fi
-
-exec $awk '
-  # Catch the case when someone ran git-commit with -s option,
-  # which automatically adds Signed-off-by.
-  /^Signed-off-by: / {
-    print "'\''Signed-off-by:'\'' in commit message"
-    status = 1
-  }
-  END {
-    if (status != 0) {
-      print "Commit aborted; please see the file 'CONTRIBUTE'"
-    }
-    exit status
-  }
-' <"$COMMIT_MSG_FILE"
+$(warn Prebuilt static libraries are not supported)
diff --git a/cross/ndk-build/ndk-resolve.mk b/cross/ndk-build/ndk-resolve.mk
new file mode 100644
index 00000000000..b29a2c6dc39
--- /dev/null
+++ b/cross/ndk-build/ndk-resolve.mk
@@ -0,0 +1,162 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# ndk-build works by including a bunch of Makefiles which set
+# variables, and then having those Makefiles include another makefile
+# which actually builds targets.
+
+# List of system libraries to ignore.
+NDK_SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ log liblog 
android libandroid
+
+# Save information.
+NDK_LOCAL_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH)
+NDK_LOCAL_STATIC_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_STATIC_LIBRARIES) 
$(LOCAL_WHOLE_STATIC_LIBRARIES)
+NDK_LOCAL_WHOLE_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_WHOLE_STATIC_LIBRARIES)
+NDK_LOCAL_SHARED_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_SHARED_LIBRARIES)
+NDK_LOCAL_EXPORT_CFLAGS_$(LOCAL_MODULE) := $(LOCAL_EXPORT_CFLAGS)
+NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE) := $(LOCAL_EXPORT_C_INCLUDES) 
$(LOCAL_EXPORT_C_INCLUDE_DIRS)
+NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) :=
+NDK_WHOLE_A_NAMES_$(LOCAL_MODULE) :=
+NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE) :=
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) :=
+
+# List of all dependencies resolved for this module thus far.
+# Used to avoid infinite recursion.
+# Separate the variable which lists modules for which CFLAGS
+# have been resolved from the variable which lists modules
+# for which library dependencies have been resolved, in order
+# to catch the case where a library dependency is skipped
+# despite its CFLAGS being added.
+NDK_RESOLVED_$(LOCAL_MODULE) :=
+NDK_RESOLVED_CFLAGS_$(LOCAL_MODULE) :=
+
+define ndk-resolve
+
+ifeq ($$(filter $(1)$(and 
$(3),whole),$$(NDK_RESOLVED_CFLAGS_$(LOCAL_MODULE))),)
+# Always mark this module's cflags as having been resolved, even if
+# this is a whole library.
+NDK_RESOLVED_CFLAGS_$(LOCAL_MODULE) += $(1)
+
+NDK_CFLAGS_$(LOCAL_MODULE) += $(NDK_LOCAL_EXPORT_CFLAGS_$(1))
+NDK_CFLAGS_$(LOCAL_MODULE) += $(addprefix 
-I,$(NDK_LOCAL_EXPORT_C_INCLUDES_$(1)))
+endif
+
+ifeq ($$(filter $(1)$(and $(3),whole),$$(NDK_RESOLVED_$(LOCAL_MODULE))),)
+# Now append local libraries, as long as this library isn't a shared
+# library itself.
+ifeq ($(4),)
+
+# Mark this module's library dependencies as having been resolved.
+NDK_RESOLVED_$(LOCAL_MODULE) += $(1)
+
+# If this is a whole library, then mark this as resolved too, and
+# remove the library from the normal static library list.
+ifneq ($(3),)
+NDK_RESOLVED_$(LOCAL_MODULE) += $(1)whole
+endif
+
+# If the module happens to be zlib, then add -lz to the shared library
+# flags.
+ifeq ($(strip $(1)),libz)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lz
+endif
+
+ifeq ($(strip $(1)),z)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lz
+endif
+
+# Likewise for libdl.
+ifeq ($(strip $(1)),libdl)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -ldl
+endif
+
+ifeq ($(strip $(1)),dl)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -ldl
+endif
+
+# Likewise for libstdc++.
+ifeq ($(strip $(1)),libstdc++)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lstdc++
+endif
+
+ifeq ($(strip $(1)),dl)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lstdc++
+endif
+
+# Likewise for liblog.
+ifeq ($(strip $(1)),liblog)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -llog
+endif
+
+ifeq ($(strip $(1)),log)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -llog
+endif
+
+# Likewise for libandroid.
+ifeq ($(strip $(1)),libandroid)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -landroid
+endif
+
+ifeq ($(strip $(1)),android)
+NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -landroid
+endif
+
+ifeq ($(findstring $(1),$(NDK_SYSTEM_LIBRARIES))$(2)$(3),)
+ifneq ($(findstring lib,$(1)),)
+NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE) += $(1)_emacs.so
+else
+NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE) += lib$(1)_emacs.so
+endif
+endif
+
+ifneq ($(2),)
+ifneq ($(findstring lib,$(1)),)
+NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) += $(1).a
+else
+NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) += lib$(1).a
+endif
+endif
+
+ifneq ($(3),)
+ifneq ($(findstring lib,$(1)),)
+NDK_WHOLE_A_NAMES_$(LOCAL_MODULE) += $(1).a
+else
+NDK_WHOLE_A_NAMES_$(LOCAL_MODULE) += lib$(1).a
+endif
+
+# Remove this archive from the regular archive list, should it already
+# exists.  Any given archive should only appear once, and if an
+# archive has been specified as whole it should always be whole.
+NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) := $$(filter-out 
lib$(1).a,$$(NDK_LOCAL_A_NAMES_$(LOCAL_MODULE)))
+NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) := $$(filter-out 
$(1).a,$$(NDK_LOCAL_A_NAMES_$(LOCAL_MODULE)))
+endif
+endif
+
+$$(foreach module,$$(NDK_LOCAL_STATIC_LIBRARIES_$(1)),$$(eval $$(call 
ndk-resolve,$$(module),1,,$(or $(4),$(if $(2)$(3),,1)))))
+$$(foreach module,$$(NDK_LOCAL_SHARED_LIBRARIES_$(1)),$$(eval $$(call 
ndk-resolve,$$(module),,,$(or $(4),$(if $(2)$(3),,1)))))
+$$(foreach module,$$(NDK_LOCAL_WHOLE_LIBRARIES_$(1)),$$(eval $$(call 
ndk-resolve,$$(module),,1,$(or $(4),$(if $(2)$(3),,1)))))
+endif
+
+endef
+
+# Add shared libraries to the shared object names when they appear as
+# a top level dependency.  However, do not recursively add the names
+# of this module's shared library dependencies, if it is just a shared
+# library, since it will link to those shared libraries itself.
+$(foreach module,$(LOCAL_SHARED_LIBRARIES),$(eval $(call 
ndk-resolve,$(module),,,)))
+$(foreach module,$(LOCAL_STATIC_LIBRARIES),$(eval $(call 
ndk-resolve,$(module),1,,)))
+$(foreach module,$(LOCAL_WHOLE_STATIC_LIBRARIES), $(eval $(call 
ndk-resolve,$(module),,1,)))
diff --git a/src/verbose.mk.in b/cross/verbose.mk.android
similarity index 71%
copy from src/verbose.mk.in
copy to cross/verbose.mk.android
index a4e2aad9325..d41d6b6aac0 100644
--- a/src/verbose.mk.in
+++ b/cross/verbose.mk.android
@@ -1,6 +1,7 @@
-### verbose.mk --- Makefile fragment for GNU Emacs
+### verbose.mk --- Makefile fragment for GNU Emacs during
+### cross-compilation.
 
-## Copyright (C) 2021-2023 Free Software Foundation, Inc.
+## Copyright (C) 2023 Free Software Foundation, Inc.
 
 ## This file is part of GNU Emacs.
 
@@ -18,7 +19,7 @@
 ## along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
 # 'make' verbosity.
-V = @AM_DEFAULT_VERBOSITY@
+V = 0
 ifeq (${V},1)
 AM_V_AR =
 AM_V_at =
@@ -26,12 +27,7 @@ AM_V_CC =
 AM_V_CXX =
 AM_V_CCLD =
 AM_V_CXXLD =
-AM_V_ELC =
-AM_V_ELN =
 AM_V_GEN =
-AM_V_GLOBALS =
-AM_V_NO_PD =
-AM_V_RC =
 else
 
 # Whether $(info ...) works.  This is to work around a bug in GNU Make
@@ -49,31 +45,11 @@ have_working_info = $(filter notintermediate,$(value 
.FEATURES))
 # since the bug is not annoying elsewhere.
 
 AM_V_AR      = @$(info $   AR       $@)
-AM_V_at = @
+AM_V_at             = @
 AM_V_CC      = @$(info $   CC       $@)
 AM_V_CXX     = @$(info $   CXX      $@)
 AM_V_CCLD    = @$(info $   CCLD     $@)
 AM_V_CXXLD   = @$(info $   CXXLD    $@)
-
-ifeq ($(HAVE_NATIVE_COMP)-$(NATIVE_DISABLED)-$(ANCIENT),yes--)
-ifneq (,$(have_working_info))
-AM_V_ELC     = @$(info $   ELC+ELN  $@)
-AM_V_ELN     = @$(info $   ELN      $@)
-else
-AM_V_ELC     = @echo "  ELC+ELN " $@;
-AM_V_ELN     = @echo "  ELN     " $@;
-endif
-else
-ifneq (,$(have_working_info))
-AM_V_ELC     = @$(info $   ELC      $@)
-else
-AM_V_ELC     = @echo "  ELC     " $@;
-endif
-AM_V_ELN =
-endif
-
 AM_V_GEN     = @$(info $   GEN      $@)
-AM_V_GLOBALS = @$(info $   GEN      globals.h)
 AM_V_NO_PD = --no-print-directory
-AM_V_RC      = @$(info $   RC       $@)
 endif
diff --git a/doc/emacs/ChangeLog.1 b/doc/emacs/ChangeLog.1
index 16afa073169..1cf26aeff06 100644
--- a/doc/emacs/ChangeLog.1
+++ b/doc/emacs/ChangeLog.1
@@ -81,7 +81,7 @@
        * misc.texi (Network Security): Mention the new protocol-level
        `high' NSM checks.
 
-2014-12-08  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-08  Eric S. Raymond  <esr@thyrsus.com>
 
        * maintaining.texi: Support for Arch has been moved to obsolete,
        remove references that imply otherwise.
@@ -128,7 +128,7 @@
 
        * maintaining.texi (Version Control Systems): Fix a typo.
 
-2014-11-20  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-11-20  Eric S. Raymond  <esr@thyrsus.com>
 
        * maintaining.texi: Document SRC support.
 
@@ -5616,11 +5616,11 @@
        * custom.texi (Variables): Add Directory Variables to menu.
        (Directory Variables): New node.
 
-2008-05-16  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-16  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc2-xtra.texi: Modify an example so it reflects what vc.el now does.
 
-2008-05-15  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-15  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc2-xtra.texi, emacs.texi, files.texi: Snapshots node renamed to
        Revision Tags and rewritten.  Section now uses modern terminology,
@@ -5632,7 +5632,7 @@
        * msdog.texi (Windows Files): Update documentation of
        w32-get-true-file-attributes.
 
-2008-05-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi, vc-xtra.texi, vc1-xtra.texi: Document the new VC
        directory mode.
@@ -5642,11 +5642,11 @@
        * killing.texi (Appending Kills): Remove a strangely off-topic index
        entry "television".
 
-2008-05-07  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-07  Eric S. Raymond  <esr@thyrsus.com>
 
        * ack.texi, files.texi, vc2-xtra.texi: Meta-CVS is no longer supported.
 
-2008-05-02  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-02  Eric S. Raymond  <esr@thyrsus.com>
 
        * buffers.texi, files.texi (Version-control):
        vc-toggle-read-only is no longer a good idea...
@@ -5923,7 +5923,7 @@
        * mini.texi (Minibuffer History): Add text about a list of minibuffer
        default values.
 
-2007-10-20  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-20  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi: Disambiguate two slightly different uses of the term
        'filesets'.
@@ -5963,7 +5963,7 @@
 
        * calendar.texi (Diary): Clarify text about diary file example.
 
-2007-10-13  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-13  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi: Capitalize node names according to convention.
 
@@ -5971,13 +5971,13 @@
 
        * misc.texi (Interactive Shell): Correct INSIDE_EMACS reference.
 
-2007-10-11  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-11  Eric S. Raymond  <esr@thyrsus.com>
 
        * emacs.texi:
        * files.texi (Version Systems): Minor fixes to version-control material
        suggested by RMS and Robert J. Chassell.
 
-2007-10-10  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-10  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi (Version Systems):
        * vc-xtra.texi:
@@ -5987,7 +5987,7 @@
        Revise text for adequate description of VCSes with monotonic IDs.
        * emacs.texi: Change of terminology from `version' to `revision'.
 
-2007-10-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi (Version Systems): Describe newer VCses.
        Reorder the descriptions to be chronological.
@@ -6015,7 +6015,7 @@
        * basic.texi (Arguments): Replace fill-paragraph with
        fill-paragraph-or-region.
 
-2007-10-06  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-06  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi: Update the section on version control for 2007
        conditions.  None of these changes are new-VC-specific; that
@@ -6128,7 +6128,7 @@
 
        * files.texi (Why Version Control?): Improve previous change.
 
-2007-07-18  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-07-18  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.texi (Why Version Control?): New node.
 
@@ -10860,7 +10860,7 @@
        * emacs.texi: Add a sentence to the top menu mentioning the
        specific version of Emacs this manual applies to.
 
-1993-04-25  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-25  Eric S. Raymond  (esr@thyrsus.com)
 
        * basic.texi: Document next-line-add-lines variable used to
        implement down-arrow.
@@ -10871,17 +10871,17 @@
 
        * text.texi: Update unix TeX ordering information.
 
-1993-03-26  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * news.texi: Mention fill-rectangle in keybinding list.
 
        * killing.texi: Document fill-rectangle.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.texi: Bring the docs up to date with VC 5.2.
 
-1992-01-10  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-01-10  Eric S. Raymond  (esr@thyrsus.com)
 
        * emacs.tex: Mention blackbox and gomoku under Amusements.
        Assembler mode is now mentioned and appropriately indexed
diff --git a/doc/emacs/Makefile.in b/doc/emacs/Makefile.in
index 161bdcb1c59..c7415312753 100644
--- a/doc/emacs/Makefile.in
+++ b/doc/emacs/Makefile.in
@@ -146,6 +146,8 @@ EMACSSOURCES= \
        ${srcdir}/glossary.texi \
        ${srcdir}/ack.texi \
        ${srcdir}/kmacro.texi \
+       ${srcdir}/android.texi \
+       ${srcdir}/input.texi \
        $(EMACS_XTRA)
 
 ## Disable implicit rules.
diff --git a/doc/emacs/ack.texi b/doc/emacs/ack.texi
index d61809fa58f..4a8a2a24377 100644
--- a/doc/emacs/ack.texi
+++ b/doc/emacs/ack.texi
@@ -899,7 +899,7 @@ Takahashi Naoto co-wrote @file{quail.el} (q.v.), and wrote
 @file{robin.el}, another input method.
 
 @item
-Thomas Neumann and Eric Raymond wrote @file{make-mode.el},
+Thomas Neumann and Eric S. Raymond wrote @file{make-mode.el},
 a mode for editing makefiles.
 
 @item
diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
new file mode 100644
index 00000000000..5e7ff0e4bb3
--- /dev/null
+++ b/doc/emacs/android.texi
@@ -0,0 +1,793 @@
+@c This is part of the Emacs manual.
+@c Copyright (C) 2023 Free Software Foundation, Inc.
+@c See file emacs.texi for copying conditions.
+@node Android
+@appendix Emacs and Android
+@cindex Android
+
+  Android is a mobile operating system developed by the Open Handset
+Alliance.  This section describes the peculiarities of using Emacs on
+an Android device running Android 2.2 or later.
+
+  Android devices commonly rely on user input through a touch screen
+or digitizer device and on-screen keyboard.  For more information
+about using such devices with Emacs, @pxref{Other Input Devices}.
+
+@menu
+* What is Android?::            Preamble.
+* Android Startup::             Starting up Emacs on Android.
+* Android File System::         The Android file system.
+* Android Document Providers::  Accessing files from other programs.
+* Android Environment::         Running Emacs under Android.
+* Android Windowing::           The Android window system.
+* Android Fonts::               Font selection under Android.
+* Android Troubleshooting::     Dealing with problems.
+* Android Software::            Getting extra software.
+@end menu
+
+@node What is Android?
+@section Android History
+
+  Android is an operating system for mobile devices developed by the
+Open Handset Alliance, a group of companies interested in developing
+handsets that can run a common set of software.  It is supposedly free
+software.
+
+  Like the X Consortium of times past, the Open Handset Alliance
+believes that ``openness'' (namely, the regular release of the Android
+source code) is simply a tool to increase the popularity of the
+Android platform.  Computer companies normally produce proprietary
+software.  The companies in the Open Handset Alliance are no different
+-- most versions of Android installed on devices are proprietary, by
+virtue of containing proprietary components, that often cannot even be
+replaced by the user.
+
+  Android is not designed to respect users' freedom.  Almost all
+versions of Android (including some which are supposedly free
+software) include support for Digital Restrictions Management,
+technology that is designed to limit users' ability to copy media to
+and from their own devices.  Most Android devices also come with
+proprietary Google applications which are required to run the system,
+and many other Android applications.
+
+  Thus, it must be necessary to consider Android proprietary software
+from a practical standpoint.  That is an injustice.  If you use
+Android, we urge you to switch to a free operating system, if only for
+your freedom's sake.
+
+  We support GNU Emacs on proprietary operating systems because we
+hope this taste of freedom will inspire users to escape from them.
+
+@node Android Startup
+@section Starting Emacs on Android
+
+  Emacs is not installed on Android devices from source code or
+through a package manager.  Instead, Emacs is compiled for Android on
+a different operating system, with the resulting binaries packaged
+into an archive, that is then transferred to the system and installed.
+
+  After being installed, the system places an application icon on the
+desktop (a.k.a@: ``home screen''.)  Emacs then starts up once the
+application icon is clicked.
+
+@cindex ``adb logcat''
+
+  During startup, Emacs will display messages in the system log
+buffer; reading that buffer during start-up requires the Android Debug
+Bridge (@command{adb}) utility to be installed on another computer.
+
+  After enabling the ``USB Debugging'' feature on the Android system,
+and connecting it via USB to another system with the @command{adb}
+utility installed, the log can be viewed by running the following
+command on that other system:
+
+@example
+$ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)"
+@end example
+
+  Assuming that the @command{adb} utility is installed on a GNU/Linux
+or Unix system, follow the steps below to connect to your device.
+
+@enumerate
+@item
+Enable ``developer options'' on your device, by going to the ``About''
+page in the system settings application and clicking on the ``build
+version'' or ``kernel version'' items five to seven times.
+
+@item
+Open the ``developer options'' settings page, which should be under
+the ``system'' page in the settings application.
+
+@item
+Turn on the switch ``USB debugging''.
+
+@item
+Connect one end of a USB cable to your device, and the other end to
+your computer's USB port.
+
+@item
+Run the command @command{adb shell} on your computer.  This will fail
+or hang because you have not yet granted your computer permission to
+access the connected device.
+
+@item
+Confirm the pop-up displayed on your device asking whether or not it
+should allow access from your computer.
+@end enumerate
+
+  Depending on the versions of Android and @command{adb} installed,
+there may be other ways to establish a connection.  See the official
+documentation at
+@url{https://developer.android.com/studio/command-line/adb} for more
+details.
+
+  Once Emacs starts up, simply running the command @command{logcat} as
+an asynchronous shell command (@pxref{Shell}) will display the log
+buffer.
+
+@cindex emacsclient wrapper, android
+  Since there is no other way to start the @command{emacsclient}
+program (@pxref{Emacs Server}) from another Android program, Emacs
+provides a wrapper around the @command{emacsclient} program, which is
+registered with the system as an application that can open all text
+files.
+
+  When that wrapper is selected as the program with which to open a
+file, it invokes @command{emacsclient} with the options
+@command{--reuse-frame}, @command{--timeout=10}, @command{--no-wait},
+and the name of the file being opened.  Then, upon success, the focus
+is transferred to any open Emacs frame.
+
+  However, if Emacs is not running at the time the wrapper is opened,
+it starts Emacs and gives it the file to open as an argument.  Note
+that if that Emacs in turn does not start the Emacs server, subsequent
+attempts to open the file with the wrapper will fail.
+
+@cindex /content/by-authority directory, android
+  Some files are given to Emacs as ``content identifiers'' that the
+system provides access to outside the normal filesystem APIs.  Emacs
+uses a pseudo-directory named @file{/content/by-authority} to access
+those files.  Do not make any assumptions about the contents of this
+directory, or try to open files in it yourself.
+
+  This feature is not provided on Android 4.3 and earlier, in which
+case such files are copied to a temporary directory before being
+opened.
+
+@node Android File System
+@section What Files Emacs Can Access on Android
+@cindex /assets directory, android
+
+  Emacs exposes a special directory on Android systems: the name of
+the directory is @file{/assets}, and it contains the @file{etc},
+@file{lisp} and @file{info} directories which are normally installed
+in @file{/usr/share/emacs} directory on GNU and Unix systems.  On
+Android systems, the Lisp emulation of @command{ls} (@pxref{ls in
+Lisp}) is also enabled by default, as the @command{ls} binary which
+comes with the system varies by manufacturer and usually does not
+support all of the features required by Emacs.  One copy of
+@command{ls} distributed with some Android systems is even known to
+lack support for the @code{-l} flag.
+
+@cindex limitations of the /assets directory
+
+  This directory exists because Android does not extract the contents
+of application packages on to the file system while unpacking them,
+but instead requires programs like Emacs to access its contents using
+a special ``asset manager'' interface.  Here are the peculiarities
+that result from such an implementation:
+
+@itemize @bullet
+@item
+Subprocesses (such as @command{ls}) can not run from the
+@file{/assets} directory; if you try to run a subprocess with
+@code{current-directory} set to @file{/assets},
+@file{/content/storage} or a subdirectory thereof, it will run from
+the home directory instead.
+
+@item
+There are no @file{.} and @file{..} directories inside the
+@file{/assets} or @file{/content} directory.
+
+@item
+Files in the @file{/assets} directory are always read only, and may be
+read in to memory more than once each time they are opened.
+@end itemize
+
+  Aside from the @file{/assets} directory, Android programs normally
+have access to four other directories.  They are:
+
+@itemize @bullet
+@item
+The @dfn{app data} directory.  This also serves as the home directory
+for Emacs, and is always accessible read-write.
+
+@item
+The @dfn{app library} directory.  This is automatically appended to
+@code{exec-path} and made @code{exec-directory} upon startup, and
+contains utility executables alongside Emacs itself.
+
+@item
+The @dfn{external storage} directory.  This is accessible to Emacs
+when the user grants the ``Files and Media'' permission to Emacs via
+system settings.
+
+@item
+Directories provided by @dfn{document providers} on Android 5.0 and
+later.  These directories exist outside the normal Unix filesystem,
+containing files provided by external programs (@pxref{Android
+Document Providers}.)
+@end itemize
+
+  Despite ordinary installations of Android not having files within
+the (normally read-only) root directory named @file{content} or
+@file{assets}, you may want to access real files by these names if the
+Android installation in use has been customized.  These files will
+conflict with the aformentioned special directories, but can
+nevertheless be accessed by writing their names relative to the
+``parent'' directory of the root directory, as so illustrated:
+@file{/../content}, @file{/../assets}.
+
+  The external storage directory is found at @file{/sdcard}.  The
+other directories are not found at any fixed location (but see below),
+although the app data directory is typically symlinked to
+@file{/data/data/org.gnu.emacs/files}.
+
+@cindex app library directory, android
+@cindex where is emacsclient under android
+  Older versions of Android used to place the app library directory
+under the name @file{lib} in the parent of the app data directory.
+Today, this directory is often placed in a directory with a randomly
+generated name under @file{/data/app}.
+
+  For the convenience of scripts running within applications sharing
+the same user ID as Emacs (which have no access to the
+@code{exec-directory} variable), a fairly considerable effort is made
+at startup to symlink the application library directory to its
+traditional location within the parent of the app data directory.
+
+  If Emacs is reinstalled and the location of the app library
+directory consequentially changes, that symlink will also be updated
+to point to its new location the next time Emacs is started by the
+system.
+
+@cindex temp~unlinked.NNNN files, Android
+  On Android devices running very old (2.6.29) versions of the Linux
+kernel, Emacs needs to create files named starting with
+@file{temp~unlinked} in the the temporary file directory in order to
+read from asset files.  Do not create files with such names yourself,
+or they may be overwritten or removed.
+
+@cindex file system limitations, Android 11
+  On Android 11 and later, the Android system restricts applications
+from accessing files in the @file{/sdcard} directory using
+file-related system calls such as @code{open} and @code{readdir}.
+
+  This restriction is known as ``Scoped Storage'', and supposedly
+makes the system more secure.  Unfortunately, it also means that Emacs
+cannot access files in those directories, despite holding the
+necessary permissions.  Thankfully, the Open Handset Alliance's
+version of Android allows this restriction to be disabled on a
+per-program basis; the corresponding option in the system settings
+panel is:
+
+@example
+System -> Apps -> Special App Access -> All files access -> Emacs
+@end example
+
+  After you disable or enable this setting as appropriate and grant
+Emacs the ``Files and Media'' permission, it will be able to access
+files under @file{/sdcard} as usual.  These settings are not present
+on some proprietary versions of Android.
+
+@node Android Document Providers
+@section Accessing Files from Other Programs on Android
+@cindex document providers, Android
+@cindex /content/storage directory, Android
+
+  Android 5.0 introduces a new sort of program, the ``document
+provider'': these programs are small programs that provide access to
+their own files outside both the asset manager and the Unix
+filesystem.  Emacs supports accessing files and directories they
+provide, placing their files within the directory
+@file{/content/storage}.
+
+@findex android-request-directory-access
+  Before Emacs is granted access to any of these directories, it must
+first request the right to access it.  This is done by running the
+command (@pxref{M-x}) @code{android-request-directory-access}, which
+displays a file selection dialog.
+
+  If a directory is selected within this dialog, its contents are
+subsequently made available within a new directory named
+@file{/content/storage/@var{authority}/@var{id}}, where
+@var{authority} is the name of the document provider, and @var{id} is
+a unique identifier assigned to the directory by the document
+provider.
+
+  The same limitations applied to the @file{/assets} directory
+(@pxref{Android File System}) are applied when creating sub-processes
+within those directories, because they do not exist within the Unix
+file-system.  In addition, although Emacs can normally write and
+create files inside these directories, it cannot create symlinks or
+hard links.
+
+  Since document providers are allowed to perform expensive network
+operations to obtain file contents, a file access operation within one
+of these directories has the potential to take a significant amount of
+time.
+
+@node Android Environment
+@section Running Emacs under Android
+
+  From the perspective of users, Android is mostly a single user
+operating system; however, from the perspective of applications and
+Emacs, the system has an overwhelming number of users.
+
+  Each application runs in its own user, with its home directory set
+to its app data directory (@pxref{Android File
+System}.)@footnote{Except in cases where a ``shared user ID'' is
+specified and other applications signed using the same ``package
+signing key'' are installed, in which case Emacs runs as the same user
+and has access to the same files as each of the aformentioned
+applications.}
+
+  Each application is also prohibited from accessing many system
+directories and the app data directories of other applications.
+
+  The Emacs distribution also incorporates several binaries.  While
+being executable files, they are packaged as libraries in the library
+directory, because otherwise the system will not unpack them while
+Emacs is being installed.  This means that instead of @code{ctags} or
+@code{emacsclient}, Lisp code must specify @code{libctags.so} or
+@code{libemacsclient.so} on the command line when starting either of
+those programs in a subprocess; to determine which names to use,
+consult the values of the variables @code{ctags-program-name},
+@code{etags-program-name}, @code{hexl-program-name},
+@code{emacsclient-program-name}, @code{movemail-program-name},
+@code{ebrowse-program-name}, and @code{rcs2log-program-name}.
+@xref{Subprocess Creation,,, elisp, the Emacs Lisp Reference Manual}.
+
+  The @file{/assets} directory containing Emacs start-up files is
+supposed to be inaccessible to processes not directly created by
+@code{zygote}, the system service responsible for starting
+applications.  Since required Lisp is found in the @file{/assets}
+directory, it would thus follow that it is not possible for Emacs to
+start itself as a subprocess.  A special binary named
+@command{libandroid-emacs.so} is provided with Emacs, and does its
+best to start Emacs for the purpose of running Lisp in batch mode.
+However, the approach it takes was devised by reading Android source
+code, and is not sanctioned by the Android compatibility definition
+documents, so your mileage may vary.
+
+@cindex call-process, Android
+@vindex android-use-exec-loader
+  Android 10 and later also prohibit Emacs itself from running
+executables inside the app data directory, obstensibly for security
+reasons.  On these systems, Emacs normally applies a workaround;
+however, this workaround requires running all sub-processes through
+another subprocess which implements an executable loader and applies
+process tracing to all its children, which may prove to be problematic
+for various different reasons.  In that case, the workaround can be
+disabled by changing the variable @code{android-use-exec-loader} to
+@code{nil}.
+
+  When this workaround is in effect, process IDs retrieved through the
+@code{process-id} function will be that of the executable loader
+process; its child will belong to the same process group as the
+loader.  As a result, @code{interrupt-process}, and other related
+functions will work correctly, but using the process ID returned by
+@code{process-id} for other purposes will not.
+
+  One side effect of the mechanism by which process tracing is carried
+out is that job control facilities inside inferior shells
+(@pxref{Interactive Shell}) will not be able to stop processes, and
+sending the @code{SIGSTOP} signal to a subprocess created by Emacs
+will appear to have no effect.
+
+  In addition, Android 12 also terminates subprocesses which are
+consuming CPU while Emacs itself is in the background.  The system
+determines which processes are consuming too much CPU in intervals of
+five minutes, and terminates the process that has consumed the most
+CPU time.
+
+  Android 12.1 and Android 13 provide an option to disable this
+behavior; to use it, enable ``USB debugging'' (@pxref{Android
+Startup}) connect the Android system to another computer, and run:
+
+@example
+$ adb shell "settings put global settings_enable_monitor_phantom_procs false"
+@end example
+
+@cindex running emacs in the background, android
+@cindex emacs killed, android
+@cindex emacs in the background, android
+
+  Application processes are treated as disposable entities by the
+system.  When all Emacs frames move to the background, Emacs might be
+terminated by the system at any time, for the purpose of saving system
+resources.
+
+  On Android 7.1 and earlier, Emacs tells the system to treat it as a
+``background service''.  The system will try to avoid killing Emacs
+unless the system is stressed for memory.
+
+  Android 8.0 removed the ability for background services to receive
+such special treatment.  However, Emacs applies a workaround: the
+system considers applications that create a permanent notification to
+be performing active work, and will avoid killing such applications.
+Thus, on those systems, Emacs displays a permanent notification for as
+long as it is running.  Once the notification is displayed, it can be
+safely hidden through the system settings without resulting in Emacs
+being killed.
+
+  However, it is not guaranteed that the system will not kill Emacs
+even if a notification is being displayed.  While the Open Handset
+Alliance's sample implementation of Android behaves correctly, many
+manufacturers place additional restrictions on program execution in
+the background in their proprietary versions of Android.  There is a
+list of such troublesome manufacturers and sometimes workarounds at
+@url{https://dontkillmyapp.com/}.
+
+@cindex permissions under android
+@cindex external storage, android
+
+  Android also defines a permissions system that determines what
+system services Emacs is allowed to access.  Programs must specify
+what permissions they want; what then happens depends on the version
+of Android being used:
+
+@itemize @bullet
+@item
+On Android 5.1 and earlier, Emacs automatically receives the following
+permissions it has requested upon being installed:
+
+@itemize @minus
+@item
+@code{android.permission.READ_CONTACTS}
+@item
+@code{android.permission.WRITE_CONTACTS}
+@item
+@code{android.permission.VIBRATE}
+@item
+@code{android.permission.ACCESS_COARSE_LOCATION}
+@item
+@code{android.permission.ACCESS_NETWORK_STATE}
+@item
+@code{android.permission.INTERNET}
+@item
+@code{android.permission.SET_WALLPAPER}
+@item
+@code{android.permission.READ_EXTERNAL_STORAGE}
+@item
+@code{android.permission.WRITE_EXTERNAL_STORAGE}
+@item
+@code{android.permission.SEND_SMS}
+@item
+@code{android.permission.RECEIVE_SMS}
+@item
+@code{android.permission.RECEIVE_MMS}
+@item
+@code{android.permission.WRITE_SMS}
+@item
+@code{android.permission.READ_SMS}
+@item
+@code{android.permission.NFC}
+@item
+@code{android.permission.TRANSMIT_IR}
+@item
+@code{android.permission.READ_PHONE_STATE}
+@item
+@code{android.permission.WAKE_LOCK}
+@item
+@code{android.permission.FOREGROUND_SEVICE}
+@item
+@code{android.permission.REQUEST_INSTALL_PACKAGES}
+@item
+@code{android.permission.REQUEST_DELETE_PACKAGES}
+@item
+@code{android.permission.SYSTEM_ALERT_WINDOW}
+@item
+@code{android.permission.RECORD_AUDIO}
+@item
+@code{android.permission.CAMERA}
+@item
+@code{android.permission.POST_NOTIFICATIONS}
+@end itemize
+
+While most of these permissions are left unused by Emacs itself, they
+are declared by Emacs as they could be useful for other programs; for
+example, the permission to access contacts may be useful for EUDC.
+
+@item
+On Android 6.0 and later, Emacs only receives the following
+permissions upon installation:
+
+@itemize @minus
+@item
+@code{android.permission.VIBRATE}
+@item
+@code{android.permission.ACCESS_NETWORK_STATE}
+@item
+@code{android.permission.INTERNET}
+@item
+@code{android.permission.SET_WALLPAPER}
+@item
+@code{android.permission.NFC}
+@item
+@code{android.permission.TRANSMIT_IR}
+@item
+@code{android.permission.WAKE_LOCK}
+@end itemize
+
+Other permissions must be granted by the user through the system
+settings application.  Consult the manufacturer of your device for
+more details, as how to do this varies by device.
+@end itemize
+
+@node Android Windowing
+@section The Android Window System
+
+  Android has an unusual window system; there, all windows are
+maximized or full-screen, and only one window can be displayed at a
+time.  On larger devices, the system allows up to four windows to be
+tiled on the screen at any time.
+
+  Windows on Android do not continue to exist indefinitely after they
+are created.  Instead, the system may choose to close windows that are
+not on screen in order to save memory, with the assumption that the
+program will save its contents to disk and restore them later, when
+the user asks for it to be opened again.  As this is obviously not
+possible with Emacs, Emacs separates the resources associated with a
+frame from its system window.
+
+  Each system window created (including the initial window created
+during Emacs startup) is appended to a list of windows that do not
+have associated frames.  When a frame is created, Emacs looks up any
+window within that list, and displays the contents of the frame
+within; if there is no window at all, then one is created.  Likewise,
+when a new window is created by the system, Emacs places the contents
+of any frame that is not already displayed within a window inside.
+When a frame is closed, the corresponding system window is also
+closed.  Upon startup, the system creates a window itself (within
+which Emacs displays the first window system frame shortly
+thereafter.)  Emacs differentiates between that window and windows
+created on behalf of other frames to determine what to do when the
+system window associated with a frame is closed:
+
+@itemize @bullet
+@item
+When the system closes the window created during application startup
+in order to save memory, Emacs retains the frame for when that window
+is created later.
+
+@item
+When the user closes the window created during application startup,
+and the window was not previously closed by the system in order to
+save resources, Emacs deletes any frame displayed within that window.
+
+@item
+When the user or the system closes any window created by Emacs on
+behalf of a specific frame, Emacs deletes the frame displayed within
+that window.
+@end itemize
+
+@cindex windowing limitations, android
+@cindex frame parameters, android
+Emacs only supports a limited subset of GUI features on Android; the
+limitations are as follows:
+
+@itemize @bullet
+@item
+Scroll bars are not supported, as they are close to useless on Android
+devices.
+
+@item
+The @code{alpha}, @code{alpha-background}, @code{z-group},
+@code{override-redirect}, @code{mouse-color}, @code{title},
+@code{wait-for-wm}, @code{sticky}, @code{undecorated} and
+@code{tool-bar-position} frame parameters (@pxref{Frame Parameters,,,
+elisp, the Emacs Lisp Reference Manual}) are unsupported.
+
+@item
+On Android 4.0 and earlier, the @code{fullscreen} frame parameter is
+always @code{maximized} for top-level frames; on later versions of
+Android, it can also be @code{fullscreen}.
+@end itemize
+
+@cindex selections, android
+@cindex android clipboard
+  Emacs does not implement all selection related features supported
+under the X Window System on Android.  For example, only the
+@code{CLIPBOARD} and @code{PRIMARY} selections (@pxref{Cut and Paste})
+are supported, and Emacs is only able to set selections to plain text.
+
+  In addition, the Android system itself places certain restrictions
+on what selection data Emacs can access:
+
+@itemize @bullet
+@item
+On Android 2.3 and earlier, the function @code{gui-selection-owner-p}
+always returns @code{nil} for the clipboard selection.
+
+@item
+Between Android 3.0 and Android 9.0, Emacs is able to access the
+clipboard whenever it wants, and @code{gui-selection-owner-p} always
+returns accurate results.
+
+@item
+Under Android 10.0 and later, Emacs can only access clipboard data
+when one of its frames has the input focus, and
+@code{gui-selection-owner-p} always returns @code{nil} for the
+clipboard selection.
+@end itemize
+
+  Since the Android system itself has no concept of a primary
+selection, Emacs provides an emulation instead.  This means there is
+no way to transfer the contents of the primary selection to another
+application via cut-and-paste.
+
+@vindex android-pass-multimedia-buttons-to-system
+@cindex volume/multimedia buttons, Android
+  The volume keys are normally reserved by Emacs and used to provide
+the ability to quit Emacs without a physical keyboard
+(@pxref{On-Screen Keyboards}.)  However, if you want them to adjust
+the volume instead, you can set the variable
+@code{android-pass-multimedia-buttons-to-system} to a non-@code{nil}
+value; note that you will no longer be able to quit Emacs using the
+volume buttons in that case.
+
+@cindex dialog boxes, android
+  Emacs is unable to display dialog boxes (@pxref{Dialog Boxes}) while
+it does not have the input focus on Android 6.0 or later.  If this is
+important to you, this ability can be restored by granting Emacs
+permission to display over other programs.  Normally, this can be done
+from the:
+
+@example
+System -> Apps -> Emacs -> More -> Display over other apps
+@end example
+
+menu in the system settings, but this procedure may vary by device.
+
+@cindex keyboard modifiers, android
+  There is a direct relation between physical modifier keys and Emacs
+modifiers (@pxref{Modifier Keys}) reported within key events, subject
+to a single exception: if @key{Alt} on your keyboard is depressed,
+then the @key{Meta} modifier will be reported by Emacs in its place,
+and vice versa.  This irregularity is since most keyboards posses no
+special @key{Meta} key, and the @key{Alt} modifier is seldom employed
+in Emacs.
+
+  Bear in mind that Android uses a different name for the @key{Super}
+modifier: it is referred to as @key{SYM} on Android keyboards and
+within the Settings keymap menu.
+
+@node Android Fonts
+@section Font Backends and Selection under Android
+@cindex fonts, android
+
+  Emacs supports two font backends under Android: they are respectively
+named @code{sfnt-android} and @code{android}.
+
+  Upon startup, Emacs enumerates all the TrueType format fonts in the
+directories @file{/system/fonts} and @file{/product/fonts}, and the
+@file{fonts} directory (@dfn{user fonts directory}) inside the Emacs
+home directory.  Emacs assumes there will always be a font named
+``Droid Sans Mono'', and then defaults to using this font.  These
+fonts are then displayed by the @code{sfnt-android} font driver.
+
+  When running on Android, Emacs currently lacks support for OpenType
+fonts.  This means that only a subset of the fonts installed on the
+system are currently available to Emacs.  If you are interested in
+lifting this limitation, please contact @email{emacs-devel@@gnu.org}.
+
+  If the @code{sfnt-android} font driver fails to find any fonts at
+all, Emacs falls back to the @code{android} font driver.  This is a
+very lousy font driver, because of limitations and inaccuracies in the
+font metrics provided by the Android platform.  In that case, Emacs
+uses the ``Monospace'' typeface configured on your system; this should
+always be Droid Sans Mono.
+
+@cindex TrueType GX fonts, android
+@cindex distortable fonts, android
+
+  As on X systems, Emacs supports distortable fonts under Android.
+These fonts (also termed ``TrueType GX fonts'', ``variable fonts'',
+and ``multiple master fonts'') provide multiple different styles
+(``Bold'', ``Italic'', etc) using a single font file.
+
+  When a user-installed distortable font is found, each style that a
+previously discovered font provided will no longer be used.  In
+addition, any previously installed distortable fonts with the same
+family name are also disregarded, provided that the new distortable
+font supplies a superset of the styles furnished by the previously
+installed font.  When a conventional font is found, any previous
+conventional font with the same style and family will be removed;
+distortable fonts with the same family will no longer be used to
+provide that style.
+
+@node Android Troubleshooting
+@section Troubleshooting Startup Problems on Android
+@cindex troubleshooting, android
+
+@cindex emacs -Q, android
+@cindex emacs --debug-init, android
+  Since Android has no command line, there is normally no way to
+specify command-line arguments when starting Emacs.  This is very
+nasty when you make a mistake in your Emacs initialization files that
+prevents Emacs from starting up at all, as the system normally
+prevents other programs from accessing Emacs's home directory.
+@xref{Initial Options}.
+
+  However, Emacs can be started with the equivalent of either the
+option @code{--quick}, or @code{--debug-init} through a special
+preferences screen.  Under Android 7.0 and later, this can be accessed
+through the Emacs ``app info'' page in the system settings program; on
+older systems, this is displayed as a separate icon on the desktop
+labeled ``Emacs options''.
+
+  Consult the manufacturer of your device for more details, as how to
+do this varies by device.
+
+@cindex dumping, android
+  The first time any given copy of Emacs starts on a device, it spends
+a while loading the preloaded Lisp files which normally come with
+Emacs.  This produces a ``dump file'' (@pxref{Initial Options}) in the
+files directory, containing an identifier unique to that copy of
+Emacs.
+
+  The next time that same copy of Emacs starts up, it simply loads the
+data contained in that dump file, greatly reducing start up time.
+
+  If by some unforeseen circumstance the dump file is corrupted, Emacs
+can crash.  If that happens, the dump file stored in the Emacs files
+directory can be erased through the preferences screen described
+above.
+
+@cindex accessing Emacs directories, Android
+  Emacs supports an alternative method of rescuing broken Emacs
+installations on Android 4.4 and later: Emacs exports a ``documents
+provider'' which accesses the contents of Emacs's home directory, that
+can then be accessed by any file manager program.
+
+  If you can find out how to open that documents provider in the file
+manager that comes with your device, you can rename, delete, or edit
+your initialization or dump files from there instead.
+
+@node Android Software
+@section Installing Extra Software on Android
+@cindex installing extra software on Android
+@cindex installing Unix software on Android
+
+  Android includes an extremely limited set of Unix-like command line
+tools in a default installation.  Several projects exist to argument
+this selection, providing options that range from improved
+reproductions of Unix command-line utilities to package repositories
+containing extensive collections of free GNU and Unix software.
+
+  @uref{http://busybox.net, Busybox} provides Unix utilities and
+limited replicas of certain popular GNU programs such as
+@command{wget} in a single statically-linked Linux binary, which is
+capable of running under Android.
+
+  @uref{https://termux.dev, Termux} provides a package manager based
+on the Debian project's @command{dpkg} system and a set of package
+repositories containing substantial amounts of free software for Unix
+systems, including compilers, debuggers, and runtimes for languages
+such as C, C++, Java, Python and Common Lisp.  These packages are
+normally installed from within a purpose-built terminal emulator
+application, but Emacs can access them if it is built with the same
+application signing key as the Termux terminal emulator, and with its
+``shared user ID'' set to the package name of the terminal emulator
+program.  The file @file{java/INSTALL} within the Emacs distribution
+explains how to build Emacs in this fashion.
+
+  @uref{https://github.com/termux/termux-packages, termux-packages}
+provides the package definitions that are used by Termux to generate
+their package repositories, which may also be independently compiled
+for installation within Emacs's home directory.
+
+  In addition to the projects mentioned above, statically linked
+binaries for most Linux kernel-based systems can also be run on
+Android.
diff --git a/doc/emacs/custom.texi b/doc/emacs/custom.texi
index d2d51e344dd..456e2087217 100644
--- a/doc/emacs/custom.texi
+++ b/doc/emacs/custom.texi
@@ -2823,7 +2823,12 @@ library.  @xref{Hooks}.
 @subsection How Emacs Finds Your Init File
 
   Emacs normally finds your init file in a location under your home
-directory.  @xref{Init File}.
+directory@footnote{
+On MS-Windows, there's no single directory considered by all programs
+as ``the home directory'' of the user.  Emacs uses one of the
+pertinent directories as the equivalent of your home directory; see
+@ref{Windows HOME}, for the details.
+}.  @xref{Init File}.
 
   Emacs looks for your init file using the filenames @file{~/.emacs.el},
 @file{~/.emacs}, or @file{~/.emacs.d/init.el} in that order; you can
diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi
index 77c4e09c826..87124e962ca 100644
--- a/doc/emacs/dired.texi
+++ b/doc/emacs/dired.texi
@@ -684,6 +684,19 @@ cause trouble.  For example, after renaming one or more 
files,
 @code{dired-undo} restores the original names in the Dired buffer,
 which gets the Dired buffer out of sync with the actual contents of
 the directory.
+
+@item touchscreen-hold
+@kindex touchscreen-hold @r{(Dired)}
+@findex dired-click-to-select-mode
+@findex dired-enable-click-to-select-mode
+Enter a ``click to select'' mode, where using the mouse button
+@kbd{mouse-2} on a file name will cause its mark to be toggled.  This
+mode is useful when performing file management using a touch screen
+device.
+
+It is enabled when a ``hold'' gesture (@pxref{Touchscreens}) is
+detected over a file name, and is automatically disabled once a Dired
+command operates on the marked files.
 @end table
 
 @node Operating on Files
@@ -1726,12 +1739,17 @@ rotation is lossless, and uses an external utility 
called
 @section Other Dired Features
 
 @vindex dired-free-space
-  By default, Dired will display the available space on the disk in
-the first line.  This is the @code{first} value of the
-@code{dired-free-space} variable.  If you set this to
-@code{separate} instead, Dired will display this on a separate line
-(including the space the files in the current directory takes).  If
-you set this to @code{nil}, the free space isn't displayed at all.
+  By default, Dired displays the available space on the directory's
+disk on the first line of that directory's listing, following the
+directory name.  You can control this display by customizing the
+variable @code{dired-free-space}.  Its default value is @code{first},
+which produces the available space after the directory name.  If you
+customize it to the value @code{separate} instead, Dired will display
+the disk space information on a separate line, following the line with
+the directory name, and will include in that line the space used by
+the files in the current directory as well as the available disk
+space.  If you set this to @code{nil}, the available disk space
+information will not be displayed at all.
 
 @kindex + @r{(Dired)}
 @findex dired-create-directory
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index 0efd99261ac..7a21eb49e24 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -223,7 +223,9 @@ Appendices
 * Antinews::            Information about Emacs version 28.
 * Mac OS / GNUstep::     Using Emacs under macOS and GNUstep.
 * Haiku::               Using Emacs on Haiku.
+* Android::             Using Emacs on Android.
 * Microsoft Windows::   Using Emacs on Microsoft Windows and MS-DOS.
+* Other Input Devices:: Using Emacs with other input devices.
 * Manifesto::           What's GNU?  Gnu's Not Unix!
 
 * Glossary::            Terms used in this manual.
@@ -1260,6 +1262,23 @@ Emacs and Haiku
 * Haiku Basics::        Basic Emacs usage and installation under Haiku.
 * Haiku Fonts::         The various options for displaying fonts on Haiku.
 
+Emacs and Android
+
+* What is Android?::            Preamble.
+* Android Startup::             Starting up Emacs on Android.
+* Android File System::         The Android file system.
+* Android Document Providers::  Accessing files from other programs.
+* Android Environment::         Running Emacs under Android.
+* Android Windowing::           The Android window system.
+* Android Fonts::               Font selection under Android.
+* Android Troubleshooting::     Dealing with problems.
+* Android Software::            Getting extra software.
+
+Emacs and Unconventional Input Devices
+
+* Touchscreens::        Using Emacs on touchscreens.
+* On-Screen Keyboards:: Using Emacs with virtual keyboards.
+
 Emacs and Microsoft Windows/MS-DOS
 
 * Windows Startup::     How to start Emacs on Windows.
@@ -1630,8 +1649,10 @@ Lisp programming.
 @include anti.texi
 @include macos.texi
 @include haiku.texi
+@include android.texi
 @c Includes msdos-xtra.
 @include msdos.texi
+@include input.texi
 @include gnu.texi
 @include glossary.texi
 @ifnottex
diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi
index 9734223b95e..7efb4516d15 100644
--- a/doc/emacs/files.texi
+++ b/doc/emacs/files.texi
@@ -372,12 +372,12 @@ you could say:
       '(("src/emacs/[^/]+/\\(.*\\)\\'" "src/emacs/.*/\\1")))
 @end lisp
 
-As you can see, this is a list of @var{(MATCH EXPANSION...)} elements.
-The @var{match} is a regular expression that matches the visited file
-name, and each @var{expansion} may refer to match groups by using
-@samp{\\1} and so on.  The resulting expansion string is then applied
-to the file system to see if any files match this expansion
-(interpreted as a regexp).
+As you can see, this is a list of @w{@code{(@var{MATCH}
+@var{EXPANSION}...)}} elements.  The @var{match} is a regular
+expression that matches the visited file name, and each
+@var{expansion} may refer to match groups by using @samp{\\1} and so
+on.  The resulting expansion string is then applied to the file system
+to see if any files match this expansion (interpreted as a regexp).
 
 @vindex find-file-hook
 @vindex find-file-not-found-functions
@@ -2093,10 +2093,11 @@ Otherwise, Emacs uses @command{ssh}.
 @end enumerate
 
 @cindex disabling remote files
+@cindex inhibit-remote-files
 @noindent
-You can entirely turn off the remote file name feature by setting the
-variable @code{tramp-mode} to @code{nil}.  You can turn off the
-feature in individual cases by quoting the file name with @samp{/:}
+You can entirely turn off the remote file name feature by running
+@kbd{M-x inhibit-remote-files}.  You can turn off the feature in
+individual cases by quoting the file name with @samp{/:}
 (@pxref{Quoted File Names}).
 
 @cindex @code{ange-ftp}
diff --git a/doc/emacs/frames.texi b/doc/emacs/frames.texi
index ce631561be7..a968c2a97c5 100644
--- a/doc/emacs/frames.texi
+++ b/doc/emacs/frames.texi
@@ -1333,6 +1333,21 @@ Parameters,,, elisp, The Emacs Lisp Reference Manual}.  
On macOS the
 tool bar is hidden when the frame is put into fullscreen, but can be
 displayed by moving the mouse pointer to the top of the screen.
 
+@vindex modifier-bar-mode
+@findex modifier-bar-mode
+@cindex displaying modifier keys in the tool bar
+@cindex mode, Modifier Bar
+@cindex Modifier Bar
+  Keyboards often lack one or more of the modifier keys that Emacs
+might want to use, making it difficult or impossible to input key
+sequences that contain them.  Emacs can optionally display a list of
+buttons that act as substitutes for modifier keys within the tool bar;
+these buttons are also referred to as the ``modifier bar''.  Clicking
+an icon within the modifier bar will cause a modifier key to be
+applied to the next keyboard event that is read.  The modifier bar is
+displayed when the global minor mode @code{modifier-bar-mode} is
+enabled; to do so, type @kbd{M-x modifier-bar-mode}.
+
 @node Tab Bars
 @section Tab Bars
 @cindex tab bar mode
@@ -1571,6 +1586,15 @@ the items that operate on the clicked tab.  Dragging the 
tab with
 wheel scrolling switches to the next or previous tab.  Holding down
 the @key{SHIFT} key during scrolling moves the tab to the left or right.
 
+  Touch screen input (@pxref{Other Input Devices}) can also be used to
+operate on tabs.  Long-pressing (@pxref{Touchscreens}) a tab will
+display a context menu with items that operate on the tab that was
+pressed, and long-pressing the tab bar itself will display a context
+menu which lets you create and remove tabs; tapping a tab itself will
+result in that tab's window configuration being selected, and tapping
+a button on the tab bar will behave as if it was clicked with
+@kbd{mouse-1}.
+
 @findex tab-bar-history-mode
   You can enable @code{tab-bar-history-mode} to remember window
 configurations used in every tab, and later restore them.
diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi
new file mode 100644
index 00000000000..4f49ca3cece
--- /dev/null
+++ b/doc/emacs/input.texi
@@ -0,0 +1,179 @@
+@c This is part of the Emacs manual.
+@c Copyright (C) 2023 Free Software Foundation, Inc.
+@c See file emacs.texi for copying conditions.
+@node Other Input Devices
+@appendix Emacs and Unconventional Input Devices
+@cindex other input devices
+
+  Emacs was originally developed with the assumption that its users
+have access to a desktop computer or computer terminal, with a
+keyboard and perhaps a suitable pointing device such as a mouse.
+
+  However, recent developments in the X Window System and operating
+systems such as Android mean that this assumption no longer holds
+true.  Emacs supports input from various other kinds of input devices,
+which is detailed here.
+
+@menu
+* Touchscreens::                Using Emacs on touchscreens.
+* On-Screen Keyboards::         Using Emacs with virtual keyboards.
+@end menu
+
+@node Touchscreens
+@section Using Emacs on Touchscreens
+@cindex touchscreen input
+
+  Touchscreen input works by pressing and moving tools (which include
+fingers and some pointing devices--styluses, for example) onto a frame
+in order to manipulate its contents.
+
+  When running under the X Window System or Android, Emacs
+automatically detects and maps the following sequences of movements
+(``gestures'') to common actions:
+
+@itemize @bullet
+@item
+@cindex tapping, touchscreens
+  ``Tapping'', briefly placing and lifting a tool from the display,
+will result in Emacs selecting the window that was tapped, and
+executing any command bound to @code{mouse-1} at that location in the
+window.  If the tap happened on top of a link (@pxref{Mouse
+References}), then Emacs will follow the link instead.
+
+  If a command bound to @code{down-mouse-1} is bound to the location
+where the tap took place, Emacs will execute that command as well.
+
+@item
+@cindex scrolling, touchscreens
+  ``Scrolling'', meaning to place a tool on the display and move it up
+or down, will result in Emacs scrolling the window contents in the
+direction where the tool moves.
+
+  If the tool is moved left or right, Emacs additionally scrolls the
+window horizontally to follow (@pxref{Horizontal Scrolling}.)
+
+@item
+@cindex dragging, touchscreens
+@cindex long-press, touchscreens
+  ``Dragging'', which is performing a @dfn{long-press} by placing a
+tool on the display and leaving it there for a while prior to moving
+the tool around will make Emacs set the point to where the tool was
+and begin selecting text under the tool as it moves around, as if
+@code{mouse-1} were to be held down.  @xref{Mouse Commands}.
+
+@vindex touch-screen-word-select
+@cindex word selection mode, touchscreens
+  Some people find it difficult to position a tool accurately on a
+touch screen display, to the detriment of text selection.  The user
+option @code{touch-screen-word-select} enables ``word selection
+mode'', causing dragging to select the complete word, not only the
+character containing the position of the tool.
+
+@vindex touch-screen-extend-selection
+@cindex extending the selection, touchscreens
+  Similarly, it may be difficult to select all of the text intended
+within a single gesture.  If the user option
+@code{touch-screen-extend-selection} is enabled, taps on the locations
+of the point or the mark within a window will begin a new ``drag''
+gesture, where the region will be extended in the direction of any
+subsequent movement.
+
+@vindex touch-screen-preview-select
+@cindex previewing the region during selection, touchscreens
+  Difficulties in making accurate adjustments to the region can also
+be alleviated by indicating the position of the point relative to its
+containing line within the echo area, since the window cursor may be
+physically obscured by the tool.  If
+@code{touch-screen-preview-select} is non-@code{nil}, the line
+containing point is displayed in the echo area (@pxref{Echo Area})
+during the motion of the tool, followed by another line indicating the
+position of point within the first line.
+@end itemize
+
+@vindex touch-screen-delay
+  By default, Emacs considers a tool as having been left on the
+display long enough to trigger a ``long-press'' after 0.7 seconds, but
+this can be changed by customizing the variable
+@code{touch-screen-delay}.
+
+@node On-Screen Keyboards
+@section Using Emacs with Virtual Keyboards
+@cindex virtual keyboards
+@cindex on-screen keyboards
+
+  When there is no physical keyboard attached to a system, the
+windowing system typically provides an on-screen keyboard, more often
+known as a ``virtual keyboard'', containing rows of clickable buttons
+that send keyboard input to the application, much like a real keyboard
+would.  This virtual keyboard is hidden by default, as it uses up
+valuable on-screen real estate, and must be opened once the program
+being used is ready to accept keyboard input.
+
+  Under the X Window System, the client that provides the on-screen
+keyboard typically detects when the application is ready to accept
+keyboard input through a set of complex heuristics, and automatically
+displays the keyboard when necessary.
+
+  On other systems such as Android, Emacs must tell the system when it
+is ready to accept keyboard input.  Typically, this is done in
+response to a touchscreen ``tap'' gesture (@pxref{Touchscreens}), or
+once to the minibuffer becomes in use (@pxref{Minibuffer}.)
+
+@vindex touch-screen-set-point-commands
+  When a ``tap'' gesture results in a command being executed, Emacs
+checks to see whether or not the command is supposed to set the point
+by looking for it in the list @code{touch-screen-set-point-commands}.
+If it is, then Emacs looks up whether or not the text under the point
+is read-only; if not, it activates the on-screen keyboard, assuming
+that the user is about to enter text in to the current buffer.
+
+@vindex touch-screen-display-keyboard
+  The user option @code{touch-screen-display-keyboard} forces Emacs to
+always display the on screen keyboard; it may also be set buffer
+locally, which means that Emacs should always display the keyboard
+when the buffer is selected.
+
+  Emacs also provides a set of functions to show or hide the on-screen
+keyboard.  For more details, @pxref{On-Screen Keyboards,,, elisp, The
+Emacs Lisp Reference Manual}.
+
+@cindex quitting, without a keyboard
+  Since it may not be possible for Emacs to display the on screen
+keyboard while it is executing a command, Emacs implements a feature
+on devices with only an on-screen keyboard, by which two rapid clicks
+of a hardware button that is always present on the device results in
+Emacs quitting.  @xref{Quitting}.
+
+@vindex x-quit-keysym
+  Which button is used to do this depends on the window system in use:
+on X, it is defined in the variable @code{x-quit-keysym}, and on
+Android, it is always the volume down button.
+
+@cindex text conversion, keyboards
+  Most input methods designed to work with on-screen keyboards perform
+buffer edits differently from desktop input methods.
+
+  On a conventional desktop windowing system, an input method will
+simply display the contents of any on going character compositions on
+screen, and send the appropriate key events to Emacs after completion.
+
+  However, on screen keyboard input methods directly perform edits to
+the selected window of each frame; this is known as ``text
+conversion'', or ``string conversion'' under the X Window System.
+Emacs enables these input methods whenever the buffer local value of
+@code{text-conversion-style} is non-@code{nil}, normally inside
+derivatives of @code{text-mode} and @code{prog-mode}.
+
+  Text conversion is performed asynchronously whenever Emacs receives
+a request to perform the conversion from the input method, and Emacs
+is not currently reading a key sequence for which one prefix key has
+already been read (@pxref{Keys}.)  After the conversion completes, a
+@code{text-conversion} event is sent.  @xref{Misc Events,,, elisp, the
+Emacs Reference Manual}.
+
+@vindex text-conversion-face
+  If the input method needs to work on a region of the buffer, then
+the region becomes known as the ``composing region'' (or
+``preconversion region''.)  The variable @code{text-conversion-face}
+describes whether or not to display the composing region in a specific
+face.
diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi
index 21e2d38e96f..a104cd2bfa1 100644
--- a/doc/emacs/mini.texi
+++ b/doc/emacs/mini.texi
@@ -411,8 +411,8 @@ the minibuffer.
 @itemx @key{prior}
 Typing @kbd{M-v}, while in the minibuffer, selects the window showing
 the completion list (@code{switch-to-completions}).  This paves the
-way for also using the commands below.  @key{PageUp}, @key{prior} and
-@kbd{M-g M-c} does the same.  You can also select the window in other
+way for using the commands below.  @key{PageUp}, @key{prior} and
+@kbd{M-g M-c} do the same.  You can also select the window in other
 ways (@pxref{Windows}).
 
 @findex choose-completion
diff --git a/doc/emacs/package.texi b/doc/emacs/package.texi
index b294e3d58bd..96ebd35f547 100644
--- a/doc/emacs/package.texi
+++ b/doc/emacs/package.texi
@@ -690,13 +690,13 @@ an Org file.
 @item :make
 A string or list of strings providing the target or targets defined in
 the repository Makefile which should run before building the Info
-file.  Only takes effect when @code{package-vc-allow-side-effects} is
-non-nil.
+file.  Only takes effect when @code{package-vc-allow-build-commands}
+is non-nil.
 
 @item :shell-command
 A string providing the shell command to run before building the Info
-file.  Only takes effect when @code{package-vc-allow-side-effects} is
-non-@code{nil}.
+file.  Only takes effect when @code{package-vc-allow-build-commands}
+is non-@code{nil}.
 
 @item :vc-backend
 A symbol naming the VC backend to use for downloading a copy of the
diff --git a/doc/emacs/search.texi b/doc/emacs/search.texi
index e8e79888ed8..6d8b3a2727a 100644
--- a/doc/emacs/search.texi
+++ b/doc/emacs/search.texi
@@ -1659,6 +1659,7 @@ command's first argument.  If @code{case-fold-search} is 
set to
 @code{nil}, case is always significant in all searches.
 
 @vindex case-replace
+@cindex case preservation in replace commands
   In addition, when the second argument of a replace command is all or
 partly lower case, replacement commands try to preserve the case
 pattern of each occurrence.  Thus, the command
@@ -1672,7 +1673,27 @@ replaces a lower case @samp{foo} with a lower case 
@samp{bar}, an
 all-caps @samp{FOO} with @samp{BAR}, and a capitalized @samp{Foo} with
 @samp{Bar}.  (These three alternatives---lower case, all caps, and
 capitalized, are the only ones that @code{replace-string} can
-distinguish.)
+distinguish.)  Note that Emacs decides whether to up-case or capitalize
+the replacement text by analyzing each word in the text being
+replaced, and will preserve the letter-case of the replaced text only
+if @emph{all} of its words use the same letter-case.  Thus, the
+command
+
+@example
+M-x replace-string @key{RET} foo bar @key{RET} baz quux @key{RET}
+@end example
+
+@noindent
+replaces @samp{Foo Bar} with @samp{Baz Quux} because both words in
+@samp{Foo Bar} are capitalized.  By contrast, the same command
+replaces @samp{Foo bar} with @samp{baz quux}, i.e.@: it leaves the
+letter-case of the replacement text unchanged, since the two words in
+@samp{Foo bar} use different capitalization.  What exactly is
+considered a ``word'' depends on the syntax tables that are in effect
+in the current buffer (@pxref{Syntax Tables,,, elisp, The Emacs Lisp
+Reference Manual}); thus, @samp{Foo_Bar} is two words in Text mode,
+but could be a single word in some major mode that supports a
+programming language.
 
   If upper-case letters are used in the replacement string, they remain
 upper case every time that text is inserted.  If upper-case letters are
@@ -1709,7 +1730,7 @@ This command finds occurrences of @samp{foo} one by one, 
displays each
 occurrence and asks you whether to replace it.  Aside from querying,
 @code{query-replace} works just like @code{replace-string}
 (@pxref{Unconditional Replace}).  In particular, it preserves case
-provided @code{case-replace} is non-@code{nil}, as it normally is
+provided that @code{case-replace} is non-@code{nil}, as it normally is
 (@pxref{Replacement and Lax Matches}).  A numeric argument means to
 consider only occurrences that are bounded by word-delimiter
 characters.  A negative prefix argument replaces backward.
diff --git a/doc/emacs/sending.texi b/doc/emacs/sending.texi
index 6b94cd0b737..fb566a7f7f4 100644
--- a/doc/emacs/sending.texi
+++ b/doc/emacs/sending.texi
@@ -257,6 +257,24 @@ of the address, such as the person's full name.  Emacs 
puts them in if
 they are needed.  For instance, it inserts the above address as
 @samp{"John Q. Smith" <none@@example.com>}.
 
+@findex rebuild-mail-abbrevs
+@findex merge-mail-abbrevs
+  After editing the @file{~/.mailrc} file, or if the file was modified
+outside of Emacs, you can update the mail aliases used by a running
+Emacs session with @w{@kbd{M-x rebuild-mail-abbrevs @key{RET}}}.  This
+prompts for the name of the file to use, the default being the value
+of @code{mail-personal-alias-file}.  A similar command
+@code{merge-mail-abbrevs} prompts for a file with mail aliases, then
+merges the aliases in that file with the existing ones.
+
+@findex define-mail-abbrev
+  Alternatively, you can use Emacs commands to define mail aliases.
+The command @code{define-mail-abbrev} prompts for the alias and the
+full address, and defines the alias to expand to the full address.
+Emacs will save the added aliases whenever it offers to save all
+files (for @kbd{C-x s} or @kbd{C-x C-c}), like it does with other
+abbrevs (@pxref{Saving Abbrevs}).
+
   Emacs also recognizes include commands in @file{~/.mailrc}.  They
 look like this:
 
@@ -282,6 +300,10 @@ fields, such as @samp{Subject}.
 @kbd{M-x mail-abbrev-insert-alias}.  This reads an alias name, with
 completion, and inserts its definition at point.
 
+@findex mail-abbrev-complete-alias
+  The command @code{mail-abbrev-complete-alias} completes on the mail
+alias preceding point.
+
 @node Mail Commands
 @section Mail Commands
 @cindex Message mode
diff --git a/doc/emacs/trouble.texi b/doc/emacs/trouble.texi
index bccdea72b19..3a43203619b 100644
--- a/doc/emacs/trouble.texi
+++ b/doc/emacs/trouble.texi
@@ -706,7 +706,7 @@ produced by typing those commands.
 for the detailed raw data.  Reporting the facts is straightforward,
 but many people strain to posit explanations and report them instead
 of the facts.  If the explanations are based on guesses about how
-Emacs is implemented, they night not be useful; meanwhile, lacking the
+Emacs is implemented, they might not be useful; meanwhile, lacking the
 facts, we will have no real information about the bug.  If you want to
 actually @emph{debug} the problem, and report explanations that are
 more than guesses, that is useful---but please include the raw facts
@@ -1514,7 +1514,7 @@ Appendix, elisp, Emacs Lisp Reference}.
 @end ifclear
 
 @item
-Avoid using @code{defadvice} or @code{with-eval-after-load} for Lisp code
+Avoid using @code{advice-add} or @code{with-eval-after-load} for Lisp code
 to be included in Emacs.
 
 @item
diff --git a/doc/emacs/windows.texi b/doc/emacs/windows.texi
index e4abdef76be..665fd80e53b 100644
--- a/doc/emacs/windows.texi
+++ b/doc/emacs/windows.texi
@@ -642,6 +642,13 @@ to the window-local tab line of buffers, and clicking on 
the @kbd{x}
 icon of a tab deletes it.  The mouse wheel on the tab line scrolls
 the tabs horizontally.
 
+  Touch screen input (@pxref{Other Input Devices}) can also be used to
+interact with the ``tab line''.  Long-pressing (@pxref{Touchscreens})
+a tab will display a context menu with items that operate on the tab
+that was pressed; tapping a tab itself will result in switching to
+that tab's buffer, and tapping a button on the tab line will behave as
+if it was clicked with @kbd{mouse-1}.
+
 Selecting the previous window-local tab is the same as typing @kbd{C-x
 @key{LEFT}} (@code{previous-buffer}), selecting the next tab is the
 same as @kbd{C-x @key{RIGHT}} (@code{next-buffer}).  Both commands
diff --git a/doc/lispref/ChangeLog.1 b/doc/lispref/ChangeLog.1
index dd6a220398f..c96ba40dbe5 100644
--- a/doc/lispref/ChangeLog.1
+++ b/doc/lispref/ChangeLog.1
@@ -7394,7 +7394,7 @@
        * tips.texi (Coding Conventions): Do not encourage the use of "-flag"
        variable names.
 
-2008-05-03  Eric S. Raymond  <esr@golux>
+2008-05-03  Eric S. Raymond  <esr@thyrsus.com>
 
        * keymaps.texi: Clarify that (current-local-map) and
        (current-global-map) return references, not copies.
@@ -13889,17 +13889,17 @@
 
        * Makefile (dist): Change to use Gzip instead of compress.
 
-1993-04-23  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * loading.texi (Unloading): define-function changed back to
        defalias.  It may not stay this way, but at least it's
        consistent with the known-good version of the code patch.
 
-1993-03-26  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * modes.texi (Hooks): Document new optional arg of add-hook.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * variables.texi: Document nil initial value of buffer-local variables.
 
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index cd1745614eb..78fc43d0daf 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -710,29 +710,77 @@ context.
 @node Generic Commands
 @subsection Select among Command Alternatives
 @cindex generic commands
-@cindex alternatives, defining
-
-The macro @code{define-alternatives} can be used to define
-@dfn{generic commands}.  These are interactive functions whose
-implementation can be selected from several alternatives, as a matter
-of user preference.
+@cindex alternative commands, defining
+
+Sometimes it is useful to define a command that serves as a ``generic
+dispatcher'' capable of invoking one of a set of commands according to
+the user's needs.  For example, imagine that you want to define a
+command named @samp{open} that can ``open'' and display several
+different types of objects.  Or you could have a command named
+@samp{mua} (which stands for Mail User Agent) that can read and send
+email using one of several email backends, such as Rmail, Gnus, or
+MH-E.  The macro @code{define-alternatives} can be used to define such
+@dfn{generic commands}.  A generic command is an interactive function
+whose implementation can be selected from several alternatives, as a
+matter of user preference.
 
 @defmac define-alternatives command &rest customizations
-Define the new command @var{command}, a symbol.
+This macro defines the new generic @var{command}, which can have
+several alternative implementations.  The argument @var{command}
+should be an unquoted symbol.
+
+When invoked, the macro creates an interactive Lisp closure
+(@pxref{Closures}).  When the user runs @w{@kbd{M-x @var{command}
+@key{RET}}} for the first time, Emacs asks to select one of the
+alternative implementations of @var{command}, offering completion for
+the names of these alternatives.  These names come from the user
+option whose name is @code{@var{command}-alternatives}, which the
+macro creates (if it didn't exist before).  To be useful, this
+variable's value should be an alist whose elements have the form
+@w{@code{(@var{alt-name} . @var{alt-func})}}, where @var{alt-name} is
+the name of the alternative and @var{alt-func} is the interactive
+function to be called if this alternative is selected.  When the user
+selects an alternative, Emacs remembers the selection, and will
+thereafter automatically call that selected alternative without
+prompting when the user invokes @kbd{M-x @var{command}} again.  To
+choose a different alternative, type @w{@kbd{C-u M-x @var{command}
+@key{RET}}}--then Emacs will again prompt for one of the alternatives,
+and the selection will override the previous one.
+
+The variable @code{@var{command}-alternatives} can be created before
+calling @code{define-alternatives}, with the appropriate values;
+otherwise the macro creates the variable with a @code{nil} value, and
+it should then be populated with the associations describing the
+alternatives.  Packages that wish to provide their own implementation
+of an existing generic command can use @code{autoload} cookies
+(@pxref{Autoload}) to add to the alist, for example:
+
+@lisp
+;;;###autoload (push '("My name" . my-foo-symbol) foo-alternatives
+@end lisp
+
+If the optional argument @var{customizations} is non-@code{nil}, it
+should consist of alternating @code{defcustom} keywords (typically
+@code{:group} and @code{:version}) and values to add to the definition
+of the @code{defcustom} @code{@var{command}-alternatives}.
 
-When a user runs @kbd{M-x @var{command} @key{RET}} for the first time,
-Emacs prompts for which real form of the command to use, and records
-the selection by way of a custom variable.  Using a prefix argument
-repeats this process of choosing an alternative.
+Here is an example of a simple generic dispatcher command named
+@code{open} with 3 alternative implementations:
 
-The variable @code{@var{command}-alternatives} should contain an alist
-with alternative implementations of @var{command}.
-Until this variable is set, @code{define-alternatives} has no effect.
+@example
+@group
+(define-alternatives open
+  :group 'files
+  :version "42.1")
+@end group
+@group
+(setq open-alternatives
+      '(("file" . find-file)
+       ("directory" . dired)
+       ("hexl" . hexl-find-file)))
+@end group
+@end example
 
-If @var{customizations} is non-@code{nil}, it should consist of
-alternating @code{defcustom} keywords (typically @code{:group} and
-@code{:version}) and values to add to the declaration of
-@code{@var{command}-alternatives}.
 @end defmac
 
 @node Interactive Call
@@ -1132,7 +1180,9 @@ up a menu.  It is also used internally by @code{y-or-n-p}
 This variable is set to the last input event that was read by the
 command loop as part of a command.  The principal use of this variable
 is in @code{self-insert-command}, which uses it to decide which
-character to insert.
+character to insert, and in @code{post-self-insert-hook}
+(@pxref{Commands for Insertion}), which uses it to access the
+character that was just inserted.
 
 @example
 @group
@@ -1253,12 +1303,19 @@ A device used by the XTEST extension to report input.
 @cindex @code{display} property, and point display
 @cindex @code{composition} property, and point display
 
-  Emacs cannot display the cursor when point is in the middle of a
-sequence of text that has the @code{display} or @code{composition}
-property, or is invisible.  Therefore, after a command finishes and
-returns to the command loop, if point is within such a sequence, the
-command loop normally moves point to the edge of the sequence, making this
-sequence effectively intangible.
+  When a sequence of text has the @code{display} or @code{composition}
+property, or is invisible, there can be several buffer positions that
+result in the cursor being displayed at same place on the screen.
+Therefore, after a command finishes and returns to the command loop,
+if point is in such a sequence, the command loop normally moves point
+to try and make this sequence effectively intangible.
+
+This @emph{point adjustment} follows the following general rules: first, the
+adjustment should not change the overall direction of the command;
+second if the command moved point, the adjustment tries to ensure the
+cursor is also moved; third, Emacs prefers the edges of an intangible
+sequence and among those edges it prefers the non sticky ones, such
+that newly inserted text is visible.
 
   A command can inhibit this feature by setting the variable
 @code{disable-point-adjustment}:
@@ -2011,6 +2068,10 @@ the position of the finger when the event occurred.
 This event is sent when @var{point} is created by the user pressing a
 finger against the touchscreen.
 
+Imaginary prefix keys are also affixed to these events
+@code{read-key-sequence} when they originate on top of a special part
+of a frame or window.  @xref{Key Sequence Input}.
+
 @cindex @code{touchscreen-update} event
 @item (touchscreen-update @var{points})
 This event is sent when a point on the touchscreen has changed
@@ -2018,12 +2079,181 @@ position.  @var{points} is a list of touch points 
containing the
 up-to-date positions of each touch point currently on the touchscreen.
 
 @cindex @code{touchscreen-end} event
-@item (touchscreen-end @var{point})
+@item (touchscreen-end @var{point} @var{canceled})
 This event is sent when @var{point} is no longer present on the
 display, because another program took the grab, or because the user
 raised the finger from the touchscreen.
+
+@var{canceled} is non-@code{nil} if the touch sequence has been
+intercepted by another program (such as the window manager), and Emacs
+should undo or avoid any editing commands that would otherwise result
+from the touch sequence.
+
+Imaginary prefix keys are also affixed to these events
+@code{read-key-sequence} when they originate on top of a special part
+of a frame or window.
 @end table
 
+If a touchpoint is pressed against the menu bar, then Emacs will not
+generate any corresponding @code{touchscreen-begin} or
+@code{touchscreen-end} events; instead, the menu bar may be displayed
+after @code{touchscreen-end} would have been delivered under other
+circumstances.
+
+@cindex mouse emulation from touch screen events
+When no command is bound to @code{touchscreen-begin},
+@code{touchscreen-end} or @code{touchscreen-update}, Emacs calls a
+``key translation function'' (@pxref{Translation Keymaps}) to
+translate key sequences containing touch screen events into ordinary
+mouse events (@pxref{Mouse Events}.)  Since Emacs doesn't support
+distinguishing events originating from separate mouse devices, it
+assumes that only one touchpoint is active while translation takes
+place; breaking this assumption may lead to unexpected behavior.
+
+Emacs applies two different strategies for translating touch events
+into mouse events, contingent on factors such as the commands bound to
+keymaps that are active at the location of the
+@code{touchscreen-begin} event.  If a command is bound to
+@code{down-mouse-1} at that location, the initial translation consists
+of a single @code{down-mouse-1} event, with subsequent
+@code{touchscreen-update} events translated to mouse motion events
+(@pxref{Motion Events}), and a final @code{touchscreen-end} event
+translated to a @code{mouse-1} or @code{drag-mouse-1} event (unless
+the @code{touchscreen-end} event indicates that the touch sequence has
+been intercepted by another program.)  This is dubbed ``simple
+translation'', and produces a simple correspondence between touchpoint
+motion and mouse motion.
+
+@cindex @code{ignored-mouse-command}, a symbol property
+However, some commands bound to
+@code{down-mouse-1}--@code{mouse-drag-region}, for example--either
+conflict with defined touch screen gestures (such as ``long-press to
+drag''), or with user expectations for touch input, and shouldn't
+subject the touch sequence to simple translation.  If a command whose
+name contains the property (@pxref{Symbol Properties})
+@code{ignored-mouse-command} is encountered or there is no command
+bound to @code{down-mouse-1}, a more irregular form of translation
+takes place: here, Emacs processes touch screen gestures
+(@pxref{Touchscreens,,, emacs, The GNU Emacs Manual}) first, and
+finally attempts to translate touch screen events into mouse events if
+no gesture was detected prior to a closing @code{touchscreen-end}
+event (with its @var{canceled} parameter @code{nil}, as with simple
+translation) and a command is bound to @code{mouse-1} at the location
+of that event.  Before generating the @code{mouse-1} event, point is
+also set to the location of the @code{touchscreen-end} event, and the
+window containing the position of that event is selected, as a
+compromise for packages which assume @code{mouse-drag-region} has
+already set point to the location of any mouse click and selected the
+window where it took place.
+
+To prevent unwanted @code{mouse-1} events arriving after a mouse menu
+is dismissed (@pxref{Mouse Menus}), Emacs also avoids simple
+translation if @code{down-mouse-1} is bound to a keymap, making it a
+prefix key.  In lieu of simple translation, it translates the closing
+@code{touchscreen-end} to a @code{down-mouse-1} event with the
+starting position of the touch sequence, consequentially displaying
+the mouse menu.
+
+@cindex @code{mouse-1-menu-command}, a symbol property
+Since certain commands are also bound to @code{down-mouse-1} for the
+purpose of displaying pop-up menus, Emacs additionally behaves as
+illustrated in the last paragraph if @code{down-mouse-1} is bound to a
+command whose name has the property @code{mouse-1-menu-command}.
+
+@cindex touchscreen gesture events
+If touch gestures are detected during translation, one of the
+following input events may be generated:
+
+@table @code
+@cindex @code{touchscreen-scroll} event
+@item (touchscreen-scroll @var{window} @var{dx} @var{dy})
+If a ``scrolling'' gesture is detected during the translation process,
+each subsequent @code{touchscreen-update} event is translated to a
+@code{touchscreen-scroll} event, where @var{dx} and @var{dy} specify,
+in pixels, the relative motion of the touchpoint from the position of
+the @code{touchscreen-begin} event that started the sequence or the
+last @code{touchscreen-scroll} event, whichever came later.
+
+@cindex @code{touchscreen-hold} event
+@item (touchscreen-hold @var{posn})
+If the single active touchpoint remains stationary for more than
+@code{touch-screen-delay} seconds after a @code{touchscreen-begin} is
+generated, a ``long-press'' gesture is detected during the translation
+process, and a @code{touchscreen-hold} event is sent, with @var{posn}
+set to a mouse position list containing the position of the
+@code{touchscreen-begin} event.
+
+@cindex @code{touchscreen-drag} event
+@item (touchscreen-drag @var{posn})
+If a ``long-press'' gesture is detected while translating the current
+touch sequence or ``drag-to-select'' is being resumed as a result of
+the @code{touch-screen-extend-selection} user option, a
+@code{touchscreen-drag} event is sent upon each subsequent
+@code{touchscreen-update} event with @var{posn} set to the new
+position of the touchpoint.
+
+@cindex @code{touchscreen-restart-drag} event
+@item (touchscreen-restart-drag @var{posn})
+This event is sent upon the start of a touch sequence resulting in the
+continuation of a ``drag-to-select'' gesture (subject to the
+aformentioned user option) with @var{posn} set to the position list of
+the initial @code{touchscreen-begin} event within that touch sequence.
+@end table
+
+@cindex handling touch screen events
+@cindex tap and drag, touch screen gestures
+Several functions are provided for Lisp programs that handle touch
+screen events.  The intended use of the first two functions described
+below is from commands bound directly to @code{touchscreen-begin}
+events; they allow responding to commonly used touch screen gestures
+separately from mouse event translation.
+
+@defun touch-screen-track-tap event &optional update data
+This function is used to track a single ``tap'' gesture originating
+from the @code{touchscreen-begin} event @var{event}, often used to
+set the point or to activate a button.  It waits for a
+@code{touchscreen-end} event with the same touch identifier to arrive,
+at which point it returns @code{t}, signifying the end of the gesture.
+
+If a @code{touchscreen-update} event arrives in the mean time and
+contains at least one touchpoint with the same identifier as in
+@var{event}, the function @var{update} is called with two arguments,
+the list of touchpoints in that @code{touchscreen-update} event, and
+@var{data}.
+
+If any other event arrives in the mean time, @code{nil} is returned.
+The caller should not perform any action in that case.
+@end defun
+
+@defun touch-screen-track-drag event update &optional data
+This function is used to track a single ``drag'' gesture originating
+from the @code{touchscreen-begin} event @code{event}.
+
+It behaves like @code{touch-screen-track-tap}, except that it returns
+@code{no-drag} and refrains from calling @var{update} if the
+touchpoint in @code{event} did not move far enough (by default, 5
+pixels from its position in @code{event}) to qualify as an actual
+drag.
+@end defun
+
+In addition to those two functions, a function is provided for
+commands bound to some types of events generated through mouse event
+translation to prevent unwanted events from being generated after it
+is called.
+
+@defun touch-screen-inhibit-drag
+This function inhibits the generation of @code{touchscreen-drag}
+events during mouse event translation for the duration of the touch
+sequence being translated after it is called.  It must be called from
+a command which is bound to a @code{touchscreen-hold} or
+@code{touchscreen-drag} event, and signals an error otherwise.
+
+Since this function can only be called after a gesture is already
+recognized during mouse event translation, no mouse events will be
+generated from touch events constituting the previously mentioned
+touch sequence after it is called.
+@end defun
+
 @node Focus Events
 @subsection Focus Events
 @cindex focus event
@@ -2160,6 +2390,65 @@ the buffer in which the xwidget will be displayed, using
 A few other event types represent occurrences within the system.
 
 @table @code
+@cindex @code{text-conversion} event
+@item text-conversion
+This kind of event is sent @strong{after} a system-wide input method
+performs an edit to one or more buffers.
+
+@vindex text-conversion-edits
+Once the event is sent, the input method may already have made changes
+to multiple buffers inside many different frames.  To determine which
+buffers have been changed, and what edits have been made to them, use
+the variable @code{text-conversion-edits}, which is set prior to each
+@code{text-conversion} event being sent; it is a list of the form:
+
+@example
+@w{@code{((@var{buffer} @var{beg} @var{end} @var{ephemeral}) ...)}}
+@end example
+
+Where @var{ephemeral} is the buffer which was modified, @var{beg} and
+@var{end} are markers set to the positions of the edit at the time it
+was completed, and @var{ephemeral} is either a string, containing any
+text which was inserted (or any text before point which was deleted),
+@code{t}, meaning that the edit is a temporary edit made by the input
+method, or @code{nil}, meaning that some text was deleted after point.
+
+@vindex text-conversion-style
+Whether or not this event is sent depends on the value of the
+buffer-local variable @code{text-conversion-style}, which determines
+how an input method that wishes to make edits to buffer contents will
+behave.
+
+This variable can have one of three values:
+
+@table @code
+@item nil
+This means that the input method will be disabled entirely, and key
+events will be sent instead of text conversion events.
+
+@item action
+This means that the input method will be enabled, but @key{RET} will
+be sent whenever the input method wants to insert a new line.
+
+@item t
+This, or any other value, means that the input method will be enabled
+and make edits followed by @code{text-conversion} events.
+@end table
+
+@findex set-text-conversion-style
+Changes to the value of this variable will only take effect upon the
+next redisplay after the buffer becomes the selected buffer of a
+frame.  If you need to disable text conversion in a way that takes
+immediate effect, call the function @code{set-text-conversion-style}
+instead.  This has the potential to lock up the input method for a
+significant amount of time, and should be used with care.
+
+@vindex disable-inhibit-text-conversion
+In addition, text conversion is automatically disabled after a prefix
+key is read by the command loop or @code{read-key-sequence}.  This can
+be disabled by setting or binding the variable
+@code{disable-inhibit-text-conversion} to a non-@code{nil} value.
+
 @cindex @code{delete-frame} event
 @item (delete-frame (@var{frame}))
 This kind of event indicates that the user gave the window manager
@@ -2192,35 +2481,48 @@ mouse cursor when the finger moved off the mouse wheel.
 @cindex @code{wheel-down} event
 @item (wheel-up @var{position} @var{clicks} @var{lines} @var{pixel-delta})
 @itemx (wheel-down @var{position} @var{clicks} @var{lines} @var{pixel-delta})
-These kinds of event are generated by moving a mouse wheel.  The
+These events are generated by moving a mouse wheel.  The
 @var{position} element is a mouse position list (@pxref{Click
 Events}), specifying the position of the mouse cursor when the event
 occurred.
 
+@vindex mwheel-coalesce-scroll-events
 @var{clicks}, if present, is the number of times that the wheel was
 moved in quick succession.  @xref{Repeat Events}.  @var{lines}, if
-present and not @code{nil}, is the number of screen lines that should
-be scrolled.  @var{pixel-delta}, if present, is a cons cell of the
-form @w{@code{(@var{x} . @var{y})}}, where @var{x} and @var{y} are the
-numbers of pixels by which to scroll in each axis, a.k.a.@:
-@dfn{pixelwise deltas}.
+present and not @code{nil}, is the positive number of screen lines
+that should be scrolled (either up, when the event is @code{wheel-up},
+or down when the event is @code{wheel-down}).  @var{pixel-delta}, if
+present, is a cons cell of the form @w{@code{(@var{x} . @var{y})}},
+where @var{x} and @var{y} are the numbers of pixels by which to scroll
+in each axis, a.k.a.@: @dfn{pixelwise deltas}.  Usually, only one of
+the two will be non-zero, the other will be either zero or very close
+to zero; the larger number indicates the axis to scroll the window.
+When the variable @code{mwheel-coalesce-scroll-events} is @code{nil},
+the scroll commands ignore the @var{lines} element, even if it's
+non-@code{nil}, and use the @var{pixel-delta} data instead; in that
+case, the direction of scrolling is determined by the sign of the
+pixelwise deltas, and the direction (up or down) implied by the event
+kind is ignored.
 
 @cindex pixel-resolution wheel events
 You can use these @var{x} and @var{y} pixelwise deltas to determine
 how much the mouse wheel has actually moved at pixel resolution.  For
 example, the pixelwise deltas could be used to scroll the display at
 pixel resolution, exactly according to the user's turning the mouse
-wheel.
+wheel.  This pixelwise scrolling is possible only when
+@code{mwheel-coalesce-scroll-events} is @code{nil}, and in general the
+@var{pixel-delta} data is not generated when that variable is
+non-@code{nil}.
 
 @vindex mouse-wheel-up-event
 @vindex mouse-wheel-down-event
-This kind of event is generated only on some kinds of systems.  On
-some systems, @code{mouse-4} and @code{mouse-5} are used instead.  For
-portable code, use the variables @code{mouse-wheel-up-event},
-@code{mouse-wheel-up-alternate-event}, @code{mouse-wheel-down-event}
-and @code{mouse-wheel-down-alternate-event} defined in
-@file{mwheel.el} to determine what event types to expect for the mouse
-wheel.
+The @code{wheel-up} and @code{wheel-down} events are generated only on
+some kinds of systems.  On other systems, @code{mouse-4} and
+@code{mouse-5} are used instead.  For portable code, use the variables
+@code{mouse-wheel-up-event}, @code{mouse-wheel-up-alternate-event},
+@code{mouse-wheel-down-event} and
+@code{mouse-wheel-down-alternate-event} defined in @file{mwheel.el} to
+determine what event types to expect from the mouse wheel.
 
 @vindex mouse-wheel-left-event
 @vindex mouse-wheel-right-event
@@ -2751,6 +3053,17 @@ If @var{whole} is non-@code{nil}, the @var{x} coordinate 
is relative
 to the entire window area including scroll bars, margins and fringes.
 @end defun
 
+@defopt mouse-prefer-closest-glyph
+If this variable is non-@code{nil}, the @code{posn-point} of a mouse
+position list will be set to the position of the glyph whose leftmost
+edge is the closest to the mouse click, as opposed to the position of
+the glyph underneath the mouse pointer itself.  For example, if
+@code{posn-at-x-y} is called with @var{x} set to @code{9}, which is
+contained within a character of width 10 displayed at column 0, the
+point saved within the mouse position list will be @emph{after} that
+character, not @emph{before} it.
+@end defopt
+
 @node Accessing Scroll
 @subsection Accessing Scroll Bar Events
 @cindex scroll bar events, data in
@@ -2928,7 +3241,7 @@ debugging terminal input.
 @code{read-key-sequence}.  Lisp programs can also call this function;
 for example, @code{describe-key} uses it to read the key to describe.
 
-@defun read-key-sequence prompt &optional continue-echo dont-downcase-last 
switch-frame-ok command-loop
+@defun read-key-sequence prompt &optional continue-echo dont-downcase-last 
switch-frame-ok command-loop disable-text-conversion
 This function reads a key sequence and returns it as a string or
 vector.  It keeps reading events until it has accumulated a complete key
 sequence; that is, enough to specify a non-prefix command using the
@@ -2968,6 +3281,12 @@ key sequence is being read by something that will read 
commands one
 after another.  It should be @code{nil} if the caller will read just
 one key sequence.
 
+The argument @var{disable-text-conversion}, if non-@code{nil}, means
+that system input methods will not directly perform edits to buffer
+text while this key sequence is being read; user input will always
+generated individual key events instead.  @xref{Misc Events}, for more
+about text conversion.
+
 In the following example, Emacs displays the prompt @samp{?} in the
 echo area, and then the user types @kbd{C-x C-f}.
 
@@ -2988,7 +3307,7 @@ typed while reading with this function works like any 
other character,
 and does not set @code{quit-flag}.  @xref{Quitting}.
 @end defun
 
-@defun read-key-sequence-vector prompt &optional continue-echo 
dont-downcase-last switch-frame-ok command-loop
+@defun read-key-sequence-vector prompt &optional continue-echo 
dont-downcase-last switch-frame-ok command-loop disable-text-conversion
 This is like @code{read-key-sequence} except that it always
 returns the key sequence as a vector, never as a string.
 @xref{Strings of Events}.
@@ -3036,19 +3355,22 @@ with any other events.
 @cindex @code{right-divider}, prefix key
 @cindex @code{bottom-divider}, prefix key
 @cindex mouse events, in special parts of window or frame
-When mouse events occur in special parts of a window or frame, such as a mode
+@cindex touch screen events, in special parts of window or frame
+When mouse or @code{touchscreen-begin} and @code{touchscreen-end}
+events occur in special parts of a window or frame, such as a mode
 line or a scroll bar, the event type shows nothing special---it is the
 same symbol that would normally represent that combination of mouse
-button and modifier keys.  The information about the window part is kept
-elsewhere in the event---in the coordinates.  But
+button and modifier keys.  The information about the window part is
+kept elsewhere in the event---in the coordinates.  But
 @code{read-key-sequence} translates this information into imaginary
-prefix keys, all of which are symbols: @code{tab-line}, @code{header-line},
-@code{horizontal-scroll-bar}, @code{menu-bar}, @code{tab-bar}, 
@code{mode-line},
-@code{vertical-line}, @code{vertical-scroll-bar}, @code{left-margin},
-@code{right-margin}, @code{left-fringe}, @code{right-fringe},
-@code{right-divider}, and @code{bottom-divider}.  You can define meanings for
-mouse clicks in special window parts by defining key sequences using these
-imaginary prefix keys.
+prefix keys, all of which are symbols: @code{tab-line},
+@code{header-line}, @code{horizontal-scroll-bar}, @code{menu-bar},
+@code{tab-bar}, @code{mode-line}, @code{vertical-line},
+@code{vertical-scroll-bar}, @code{left-margin}, @code{right-margin},
+@code{left-fringe}, @code{right-fringe}, @code{right-divider}, and
+@code{bottom-divider}.  You can define meanings for mouse clicks in
+special window parts by defining key sequences using these imaginary
+prefix keys.
 
 For example, if you call @code{read-key-sequence} and then click the
 mouse on the window's mode line, you get two events, like this:
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index e229935170f..0331c48fa6b 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -2942,8 +2942,9 @@ apply to.  Here are the possible values of 
@var{characteristic}:
 The kind of window system the terminal uses---either @code{graphic}
 (any graphics-capable display), @code{x}, @code{pc} (for the MS-DOS
 console), @code{w32} (for MS Windows 9X/NT/2K/XP), @code{haiku} (for
-Haiku), @code{pgtk} (for pure GTK), or @code{tty} (a non-graphics-capable
-display).  @xref{Window Systems, window-system}.
+Haiku), @code{pgtk} (for pure GTK), @code{android} (for Android), or
+@code{tty} (a non-graphics-capable display).  @xref{Window Systems,
+window-system}.
 
 @item class
 What kinds of colors the terminal supports---either @code{color},
@@ -5287,10 +5288,18 @@ the ``pixel width'' of a character is usually 1, but it 
could be more
 for TABs and double-width CJK characters.)
 
 @item :align-to @var{hpos}
-Specifies that the space should be wide enough to reach @var{hpos}.
-If @var{hpos} is a number, it is measured in units of the normal
-character width.  @var{hpos} can also be a @dfn{pixel width}
-specification (@pxref{Pixel Specification}).
+Specifies that the space should be wide enough to reach the column
+@var{hpos}.  If @var{hpos} is a number, it is a column number, and is
+measured in units of the canonical character width (@pxref{Frame
+Font}).  @var{hpos} can also be a @dfn{pixel width} specification
+(@pxref{Pixel Specification}).  When the current line is wider than
+the window, and is either displayed by one or more continuation lines,
+or is truncated and possibly scrolled horizontally (@pxref{Horizontal
+Scrolling}), @var{hpos} is measured from the beginning of the logical
+line, not from the visual beginning of the screen line.  This way,
+alignment produced by @code{:align-to} is consistent with functions
+that count columns, such as @code{current-column} and
+@code{move-to-column} (@pxref{Columns}).
 @end table
 
   You should use one and only one of the above properties.  You can
@@ -8866,6 +8875,8 @@ Emacs is displaying the frame using MS-DOS direct screen 
writes.
 Emacs is displaying the frame using the Application Kit on Haiku.
 @item pgtk
 Emacs is displaying the frame using pure GTK facilities.
+@item android
+Emacs is displaying the frame on Android.
 @item nil
 Emacs is displaying the frame on a character-based terminal.
 @end table
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index a1d7b51b609..72441c8d442 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -1139,6 +1139,7 @@ Frames
 * Dialog Boxes::            Displaying a box to ask yes or no.
 * Pointer Shape::           Specifying the shape of the mouse pointer.
 * Window System Selections::Transferring text to and from other X clients.
+* Accessing Selections::    The multiple different kinds of selections.
 * Yanking Media::           Yanking things that aren't plain text.
 * Drag and Drop::           Internals of Drag-and-Drop implementation.
 * Color Names::             Getting the definitions of color names.
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index 31d4aaca507..afedf776c86 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -582,11 +582,12 @@ contents and inserting the whole file, because (1) it 
preserves some
 marker positions and (2) it puts less data in the undo list.
 
 It is possible to read a special file (such as a FIFO or an I/O
-device) with @code{insert-file-contents}, as long as @var{replace},
-and @var{visit} and @var{beg} are @code{nil}.  However, you should
-normally use an @var{end} argument for these files to avoid inserting
-(potentially) unlimited data into the buffer (for instance, when
-inserting data from @file{/dev/urandom}).
+device) with @code{insert-file-contents}, as long as @var{replace} is
+@code{nil} or @code{if-regular}, and @var{visit} and @var{beg} are
+@code{nil}.  However, you should normally use an @var{end} argument
+for these files to avoid inserting (potentially) unlimited data into
+the buffer (for instance, when inserting data from
+@file{/dev/urandom}).
 @end defun
 
 @defun insert-file-contents-literally filename &optional visit beg end replace
@@ -3698,6 +3699,17 @@ between consecutive checks.  For example:
 @end example
 @end defopt
 
+@defmac without-remote-files body@dots{}
+The @code{without-remote-files} macro evaluates the @var{body} forms
+with deactivated file name handlers for remote files.  Those file
+names would be handled literally.
+
+The macro should be used only in forms where it is obvious, that
+remote files cannot appear or where it is intended not to handle
+remote file names.  It also reduces checks with
+@code{file-name-handler-alist}, resulting in more performant code.
+@end defmac
+
 @node Format Conversion
 @section File Format Conversion
 
diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi
index 5a2d9f29295..853ca5ae69e 100644
--- a/doc/lispref/frames.texi
+++ b/doc/lispref/frames.texi
@@ -104,9 +104,11 @@ window of another Emacs frame.  @xref{Child Frames}.
 * Mouse Tracking::              Getting events that say when the mouse moves.
 * Mouse Position::              Asking where the mouse is, or moving it.
 * Pop-Up Menus::                Displaying a menu for the user to select from.
+* On-Screen Keyboards::                Displaying the virtual keyboard.
 * Dialog Boxes::                Displaying a box to ask yes or no.
 * Pointer Shape::               Specifying the shape of the mouse pointer.
 * Window System Selections::    Transferring text to and from other X clients.
+* Accessing Selections::        The multiple different kinds of selections.
 * Yanking Media::               Yanking things that aren't plain text.
 * Drag and Drop::               Internals of Drag-and-Drop implementation.
 * Color Names::                 Getting the definitions of color names.
@@ -187,9 +189,13 @@ selected frame.
 @end defvar
 
 @defopt server-after-make-frame-hook
-A normal hook run when the Emacs server creates a client frame.  When
-this hook is called, the created frame is the selected one.
-@xref{Emacs Server,,, emacs, The GNU Emacs Manual}.
+A normal hook run when the Emacs server starts using a client frame.
+When this hook is called, the client frame is the selected one.  Note
+that, depending on how @command{emacsclient} was invoked
+(@pxref{Invoking emacsclient,,, emacs, The GNU Emacs Manual}), this
+client frame could be a new frame created for the client, or it could
+be an existing frame that the server reused for handling the client
+commands.  @xref{Emacs Server,,, emacs, The GNU Emacs Manual}.
 @end defopt
 
 
@@ -696,7 +702,7 @@ The position of the top left corner of the native frame 
specifies the
 indicate that position for the various builds:
 
 @itemize @w{}
-@item (1) non-toolkit, Haiku, and terminal frames
+@item (1) non-toolkit, Android, Haiku, and terminal frames
 
 @item (2) Lucid, Motif, and MS-Windows frames
 
@@ -2404,6 +2410,7 @@ engine), and @code{harfbuzz} (font driver for OTF and TTF 
fonts with
 HarfBuzz text shaping) (@pxref{Windows Fonts,,, emacs, The GNU Emacs
 Manual}).  The @code{harfbuzz} driver is similarly recommended.  On
 Haiku, there can be several font drivers (@pxref{Haiku Fonts,,, emacs,
+The GNU Emacs Manual}), as on Android (@pxref{Android Fonts,,, emacs,
 The GNU Emacs Manual}).
 
 On other systems, there is only one available font backend, so it does
@@ -3749,9 +3756,9 @@ This function displays a pop-up menu and returns an 
indication of
 what selection the user makes.
 
 The argument @var{position} specifies where on the screen to put the
-top left corner of the menu.  It can be either a mouse button event
-(which says to put the menu where the user actuated the button) or a
-list of this form:
+top left corner of the menu.  It can be either a mouse button or
+@code{touchscreen-begin} event (which says to put the menu where the
+user actuated the button) or a list of this form:
 
 @example
 ((@var{xoffset} @var{yoffset}) @var{window})
@@ -3836,6 +3843,30 @@ keymap.  It won't be called if @code{x-popup-menu} 
returns for some
 other reason without displaying a pop-up menu.
 @end defvar
 
+@node On-Screen Keyboards
+@section On-Screen Keyboards
+
+  An on-screen keyboard is a special kind of pop up provided by the
+system, with rows of clickable buttons that act as a real keyboard.
+
+  On certain systems (@pxref{On-Screen Keyboards,,,emacs, The Emacs
+Manual}), Emacs is supposed to display and hide the on screen keyboard
+depending on whether or not the user is about to type something.
+
+@defun frame-toggle-on-screen-keyboard frame hide
+This function displays or hides the on-screen keyboard on behalf of
+the frame @var{frame}.  If @var{hide} is non-@code{nil}, then the
+on-screen keyboard is hidden; otherwise, it is displayed.
+
+It returns whether or not the on screen keyboard @strong{may} have
+been displayed, which should be used to determine whether or not to
+hide the on-screen keyboard later.
+
+This has no effect if the system automatically detects when to display
+the on-screen keyboard, or when it does not provide any on-screen
+keyboard.
+@end defun
+
 @node Dialog Boxes
 @section Dialog Boxes
 @cindex dialog boxes
@@ -4043,6 +4074,542 @@ For backward compatibility, there are obsolete aliases
 names of @code{gui-get-selection} and @code{gui-set-selection} before
 Emacs 25.1.
 
+@node Accessing Selections
+@section Accessing Selections
+
+  @code{gui-get-selection} is able to retrieve multiple different
+kinds of selection data from any number of selections.  However, the
+data types and selections that Emacs understands is not precisely
+specified and differs depending on the window system on which Emacs is
+running.
+
+  At the same time, @code{gui-set-selection} hides a great deal of
+complexity behind its back, at least on some systems: its @var{data}
+argument need not be a string, but is actually given verbatim to
+system specific code.
+
+  Emacs's implementation of selections is most complete on the X
+Window System.  This is both an artifact of history (X was the first
+window system supported by Emacs) and one of technical reasons:
+instead of using selections only to transfer text and multimedia
+content between clients, X uses selections as a general inter-client
+communication system, leading to a great proliferation of selection
+data types.
+
+  Even more confusingly, X also supports another inter-client
+communication mechanism: the Inter-Client Exchange.  However, ICE is
+only used by Emacs to communicate with session managers, and is a
+separate topic.
+
+@menu
+* X Selections::        Selection data types (and more) on X.
+* Other Selections::    How they work on other window systems.
+@end menu
+
+@node X Selections
+@subsection X Selections
+
+  X refrains from defining fixed data types for selection data, or a
+fixed number of selections.  Selections are instead identified by X
+``atoms'', which are unique 29-bit identifiers issued by the X server
+for a corresponding name.  In Emacs, you can simply write a symbol
+with the name of the atom, and Emacs will transparently request these
+identifiers where necessary.
+
+  When a program ``sets'' a selection under X, it actually makes
+itself the ``owner'' of the selection---the X server will then deliver
+selection requests to the program, which is obliged to respond to the
+requesting client with the selection data.
+
+  Similarly, a program does not ``get'' selection data from the X
+server.  Instead, its selection requests are sent to the client with
+the window which last took ownership over the selection, which then
+replies with the requested data.
+
+  Each selection request contains three parameters:
+
+@itemize @bullet
+@item
+The window which requested the selection; this is used to identify the
+@c Not a typo: X spells ``requestor'' with an o.
+requesting program, otherwise known as the @dfn{requestor}.
+
+@item
+An atom identifying the ``target'' to which the owner should convert
+the selection.  It is easiest to think of the conversion target as the
+kind of data that the requestor wants: in selection requests made by
+Emacs, the target is determined by the @dfn{type} argument to
+@code{gui-get-selection}.
+
+@item
+A 32-bit timestamp containing the X server time at which the requestor
+last obtained input.
+@end itemize
+
+  The selection owner responds by tranferring to the requestor a
+series of bytes, 16 bit words, or 32 bit words, along with another
+atom identifying the type of those words.  After requesting a
+selection, Emacs then applies its own interpretation of the data
+format and data type to convert the data transferred by the selection
+owner to a Lisp representation, which @code{gui-get-selection}
+returns.
+
+  By default, Emacs converts selection data consisting of any series
+of bytes to a unibyte string containing those bytes, selection data
+consisting of a single 16-bit or 32-bit word as an unsigned number,
+and selection data consisting of multiple such words as a vector of
+unsigned numbers.  However, Emacs applies special treatment for
+several selection data types:
+
+@table @code
+@item INTEGER
+16-bit or 32-bit words of this type are treated as signed integers,
+instead of unsigned ones.  If there are multiple words in the
+selection data, a vector is returned; otherwise, the integer is
+returned by itself.
+
+@item ATOM
+32-bit words of this type are treated as X atoms, and returned (either
+alone or as vectors) as Lisp symbols containing the names they
+identify.  Invalid atoms are returned as @code{nil}.
+
+@item COMPOUND_TEXT
+@item UTF8_STRING
+@item STRING
+Unibyte strings returned for these data types will have a single
+@code{foreign-selection} text property set to a symbol with the type
+of the selection data.
+@end table
+
+  Each selection owner must return at least two selection targets:
+@code{TARGETS}, which returns a number of atoms describing the
+selection targets that the owner supports, and @code{MULTIPLE}, used
+for internal purposes by X clients.  A selection owner may support any
+number of other targets, some of which may be standardized by the X
+Consortium's
+@url{http://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html,
+Inter-Client Communication Conventions Manual}, while others, such as
+@code{UTF8_STRING}, were supposed to be standardized by the XFree86
+Project, which unfortunately did not happen.
+
+  Requests for a given selection target may, by convention, return
+data in a specific type, or it may return data in one of several
+types, whichever is most convenient for the selection owner; the
+latter type of selection target is dubbed a @dfn{polymorphic target}.
+A selection target may also return no data at all: by convention, the
+selection owner performs some action a side effect upon responding to
+a selection request with that target, and as such these targets are
+referred to as @dfn{side-effect targets}.
+
+  Here are some selection targets which behave in a reasonably
+standard manner when used with the @code{CLIPBOARD}, @code{PRIMARY},
+or @code{SECONDARY} selections.
+
+@table @code
+@item ADOBE_PORTABLE_DOCUMENT_FORMAT
+This target returns data in Adobe System's ``Portable Document
+Format'' format, as a string.
+
+@item APPLE_PICT
+This target returns data in the ``PICT'' image format used on
+Macintosh computers, as a string.
+
+@item BACKGROUND
+@item BITMAP
+@item COLORMAP
+@item FOREGROUND
+Together, these four targets return integer data necessary to make use
+of a bitmap image stored on the X server: the pixel value of the
+bitmap's background color, the X identifier of the bitmap, the
+colormap inside which the background and foreground are allocated, and
+the pixel value of the bitmap's foreground color.
+
+@item CHARACTER_POSITION
+This target returns two unsigned 32-bit integers of type @code{SPAN}
+describing the start and end positions of the selection data in the
+text field containing it, in bytes.
+
+@item COMPOUND_TEXT
+This target returns a string of type @code{COMPOUND_TEXT} in the X
+Consortium's multi-byte text encoding system.
+
+@item DELETE
+This target returns nothing, but as a side-effect deletes the
+selection contents from any text field containing them.
+
+@item DRAWABLE
+@item PIXMAP
+This target returns a list of unsigned 32-bit integers, each of which
+corresponds to an X server drawable or pixmap.
+
+@item ENCAPSULATED_POSTSCRIPT
+@item _ADOBE_EPS
+This target returns a string containing encapsulated Postscript code.
+
+@item FILE_NAME
+This target returns a string containing one or more file names,
+separated by NULL characters.
+
+@item HOST_NAME
+This target returns a string containing the fully-qualified domain
+name of the machine on which the selection owner is running.
+
+@item USER
+This target returns a string containing the user name of the machine
+on which the selection owner is running.
+
+@item LENGTH
+This target returns an unsigned 32-bit or 16-bit integer containing
+the length of the selection data.
+
+@item LINE_NUMBER
+This target returns two unsigned 32-bit integers of type @code{SPAN}
+describing the line numbers corresponding to the start and end
+positions of the selection data in the text field containing it.
+
+@item MODULE
+This target returns the name of any function containing the selection
+data.  It is mainly used by text editors.
+
+@item STRING
+This target returns the selection data as a string of type
+@code{STRING}, encoded in ISO Latin-1 format, with Unix newline
+characters.
+
+@item C_STRING
+This target returns the selection data as a ``C string''.  This has
+been interpreted as meaning the raw selection data in whatever
+encoding used by the owner, either terminated with a NULL byte or not
+at all, or an ASCII string which may or may not be terminated.
+
+@item UTF8_STRING
+This returns the selection data as a string of type
+@code{UTF8_STRING}, encoded in UTF-8, with unspecified EOL format.
+
+@item TIMESTAMP
+This target returns the X server time at which the selection owner
+took ownership over the selection as a 16-bit or 32-bit word of type
+@code{CARDINAL}.
+
+@item TEXT
+This polymorphic target returns selection data as a string, either
+@code{COMPOUND_TEXT}, @code{STRING}, @code{C_STRING}, or
+@code{UTF8_STRING}, whichever data type is convenient for the
+selection owner.
+@end table
+
+  When a request for the targets @code{STRING}, @code{COMPOUND_TEXT},
+or @code{UTF8_STRING} is made using the function
+@code{gui-get-selection}, and neither @code{selection-coding-system}
+nor @code{next-selection-coding-system} are set, the returned strings
+are additionally decoded using the appropriate coding system for those
+data types: @code{iso-8859-1}, @code{compound-text-with-extensions}
+and @code{utf-8} respectively.
+
+  In addition to the targets specified above (and the many targets
+used by various programs for their own purposes), several popular
+programs and toolkits have decided to define selection data types of
+their own, without consulting the appropriate X standards bodies.
+These targets are usually named after MIME types, such as
+@code{text/html} or @code{image/jpeg}, and have been known to contain:
+
+@itemize @bullet
+@item
+Unterminated, newline terminated, or NULL character terminated file
+names of an image or text file.
+
+@item
+Image or text data in the appropriate format.
+
+@item
+@code{file://} URIs (or possibly newline or NUL terminated lists of
+URIs) leading to files in the appropriate format.
+@end itemize
+
+  These selection targets were first used by Netscape, but are now
+found in all kinds of programs, especially those based on recent
+versions of the GTK+ or Qt toolkits.
+
+  Emacs is also capable of acting as a selection owner.  When
+@code{gui-set-selection} is called, the selection data specified is
+not transferred to the X server; instead, Emacs records it internally
+and obtains ownership of the selection.
+
+@defvar selection-converter-alist
+  Alist of selection targets to ``selection converter'' functions.
+When a selection request is received, Emacs looks up the selection
+converter associated with the requested selection target.
+
+  The selection converter is called with three arguments: the symbol
+corresponding to the atom identifying the selection being requested,
+the selection target that is being requested, and the value set with
+@code{gui-set-selection}.  The value which it returns is either a cons
+of a symbol specifying the data type and a number, symbol, or a vector
+of numbers or symbols, or its cdr by itself.
+
+  If the value is the special symbol @code{NULL}, the data type is set
+to @code{NULL}, and no data is returned to the requestor.
+
+  If the value is a string, it must be a unibyte string; should no
+data type be explicitly specified, the data is transferred to the
+requestor with the type @code{STRING}.
+
+  If the value is a symbol, its ``atom'' is retrieved, and it is
+transferred to the requestor as a 32-bit value---if no data type was
+specified, its type is @code{ATOM}.
+
+  If the value is a number between @code{-32769} and @code{32768}, it
+is transferred to the requestor as a 16 bit value---if no data type
+was specified, its type is @code{INTEGER}.
+
+  If the value is any other number, it is returned as a 32 bit value.
+Even if the number returned is unsigned, the requestor will treat
+words of type @code{INTEGER} as signed.  To return an unsigned value,
+specify the type @code{CARDINAL} instead.
+
+  If the value is a vector of symbols or numbers, it is returned as a
+list of multiple atoms or numbers.  The data type returned by default
+is determined by that of its first element.
+@end defvar
+
+  By default, Emacs is configured with selection converters for the
+following selection targets:
+
+@table @code
+@item TEXT
+This selection converter returns selection data as:
+
+@itemize @bullet
+@item
+A string of type @code{C_STRING}, if the selection contents contain no
+multibyte characters, or contains 8-bit characters with all 8 bits
+set.
+
+@item
+A string of type @code{STRING}, if the selection contents can be
+represented as ISO-Latin-1 text.
+
+@item
+A string of type @code{COMPOUND_TEXT}, if the selection contents can
+be encoded in the X Consortium's Compound Text Encoding, and
+@code{selection-coding-system} or @code{next-selection-coding-system}
+is set to a coding system whose @code{:mime-charset} property is
+@code{x-ctext}.
+
+@item
+A string of type @code{UTF8_STRING} otherwise.
+@end itemize
+
+@item COMPOUND_TEXT
+This selection converter returns selection data as a string of type
+@code{COMPOUND_TEXT}.
+
+@item STRING
+This selection converter returns selection data as a string of type
+@code{STRING}, encoded in ISO-Latin-1 format.
+
+@item UTF8_STRING
+This selection converter returns selection data in UTF-8 format.
+
+@item text/plain
+@item text/plain;charset=utf-8
+@item text/uri-list
+@item text/x-xdnd-username
+@item XmTRANSFER_SUCCESS
+@item XmTRANSFER_FAILURE
+@item FILE
+@item _DT_NETFILE
+These selection converters are used for internal purposes during
+drag-and-drop operations and are not available for selections other
+than @code{XdndSelection}.
+
+@item TARGETS
+This selection converter returns a list of atoms, one for each
+selection target understood by Emacs.
+
+@item MULTIPLE
+This selection converter is implemented in C code and is used to
+implement efficient transfer of selection requests which specify
+multiple selection targets at the same time.
+
+@item LENGTH
+This selection converter returns the length of the selection data, in
+bytes.
+
+@item DELETE
+This selection converter is used for internal purposes during
+drag-and-drop operations.
+
+@item FILE_NAME
+This selection converter returns the file name of the buffer
+containing the selection data.
+
+@item CHARACTER_POSITION
+This selection converter returns the character positions of each end
+of the selection in the buffer containing the selection data.
+
+@item LINE_NUMBER
+@item COLUMN_NUMBER
+This selection converter returns the line and column numbers of each
+end of the selection in the buffer containing the selection data.
+
+@item OWNER_OS
+This selection converter returns the name of the operating system on
+which Emacs is running.
+
+@item HOST_NAME
+This selection converter returns the fully-qualified domain name of
+the machine on which Emacs is running.
+
+@item USER
+This selection converter returns the username of the user account
+under which Emacs is running.
+
+@item CLASS
+@item NAME
+These selection converters return the resource class and name used by
+Emacs.
+
+@item INTEGER
+This selection converter returns an integer value verbatim.
+
+@item SAVE_TARGETS
+@item _EMACS_INTERNAL
+These selection converters are used for internal purposes.
+@end table
+
+  With the exception of @code{INTEGER}, all selection converters
+expect the value given to @code{gui-set-selection} to be one of the
+following:
+
+@itemize @bullet
+@item
+A string.
+
+@item
+A list of the form @w{@code{(@var{beg} @var{end} @var{buf})}}, where
+@var{beg} and @var{end} are two markers or overlays describing the
+bounds of the selection data in the buffer @var{buf}.
+@end itemize
+
+@node Other Selections
+@subsection Other Selections
+
+  Window systems such as MS-Windows, Nextstep, Haiku and Android do
+not provide selections corresponding to the X semantics.  Each window
+system provides its own ad-hoc emulation of selections, none of which
+make use of the ``selection converter'' mechanism described above.  In
+addition, only the @code{PRIMARY}, @code{CLIPBOARD}, and
+@code{SECONDARY} selections are typically supported, alongside the
+@code{XdndSelection} used for drag-and-drop operations.
+
+  GTK itself exposes emulations of X selections to applications, but
+those emulations are of varying completeness.  While Emacs built with
+PGTK will use the same selection interface as Emacs built with X, many
+selection targets will not be useful.
+
+  On MS-Windows, @code{gui-get-selection} accepts a single target,
+@code{STRING}.  The value returned is the selection data decoded
+using @code{selection-coding-system}.
+
+  @code{gui-set-selection} also only accepts strings, encodes them
+in the selection coding system, and saves them to the clipboard.
+
+  On Nextstep, Emacs only supports saving strings to selections.
+However, requests for the following targets are accepted:
+
+@c FIXME: how is the text coding system determined, and do image/* or
+@c application/* return image data or file names?
+@itemize @bullet
+@item text/plain
+@item image/png
+@item text/html
+@item application/pdf
+@item application/rtf
+@item application/rtfd
+@item STRING
+@item text/plain
+@item image/tiff
+@end itemize
+
+  On Haiku, Emacs supports the same selection values as on X.  In
+addition, Emacs fully implements the primary and secondary selections.
+However, instead of taking ownership over the selection data, Emacs
+transfers the selection data to the window server when
+@code{gui-set-selection} is called.  The Haiku window server expects
+selection data to be provided in the form of a ``message'', containing
+associations between data types and selection data.
+
+@defvar haiku-normal-selection-encoders
+List of functions which act as selection encoders.  When
+@code{gui-set-selection} is called, each function in this list is
+successively called with its @var{selection} and @var{value}
+arguments.  If the function returns non-@code{nil}, it should return a
+list of the form @w{@code{(@var{key} @var{type} @var{value})}}, where
+@var{key} is the name of the data type being transferred, @var{type}
+is either a number identifying a data type (in which case @var{value}
+should be a unibyte string that is directly transferred to the window
+server), or a symbol identifying both a data type and how @var{value}
+should be interpreted.
+@end defvar
+
+  Here are the meaningful values of @var{type}, and what they will
+cause Emacs to interpret @var{value} as:
+
+@table @code
+@item string
+A unibyte string.  The string is NULL-terminated after being placed in
+the message.
+
+@item ref
+A file name.  The file is looked up and file system information
+identifying the file is placed in the message.
+
+@item short
+A 16-bit integer value.
+
+@item long
+A 32-bit integer value.
+
+@item llong
+A 64-bit integer value.
+
+@item byte
+@item char
+An unsigned byte between 0 and 255.
+
+@item size_t
+A number between 0 and 1 minus two to the power of the word size of
+the computer Emacs is running on.
+
+@item ssize_t
+A number which fits in the C type @code{ssize_t}.
+
+@item point
+A cons of two floats, specifying a coordinate on-screen.
+
+@item float
+@item double
+A single or double-precision floating point number in an unspecified
+format.
+
+@item (haiku-numeric-enum MIME)
+A unibyte string containing data in a certain MIME type.
+@end table
+
+  Under Haiku, @code{gui-get-selection} accepts either the targets
+@code{TARGETS} and @code{TIMESTAMP}, where the former returns a vector
+containing supported data types (much like on X), and the latter
+returns the number of times the selection has been set, the targets
+@code{STRING} and @code{UTF8_STRING}, which return text in ISO-Latin-1
+and UTF-8 format, or a MIME type, in which the data is returned
+undecoded as a unibyte string.
+
+  Under Android, @code{gui-get-selection} is restricted to returning
+UTF-8 string data of the type @code{STRING}, or image and application
+data associated with a MIME type.  @code{gui-set-selection} will only
+set string data, as on MS-Windows.
+
 @node Yanking Media
 @section Yanking Media
 
@@ -4457,20 +5024,12 @@ really supports that color.  When using X, you can ask 
for any defined
 color on any kind of display, and you will get some result---typically,
 the closest it can do.  To determine whether a frame can really display
 a certain color, use @code{color-supported-p} (see below).
-
-@findex x-color-defined-p
-This function used to be called @code{x-color-defined-p},
-and that name is still supported as an alias.
 @end defun
 
 @defun defined-colors &optional frame
 This function returns a list of the color names that are defined
 and supported on frame @var{frame} (default, the selected frame).
 If @var{frame} does not support colors, the value is @code{nil}.
-
-@findex x-defined-colors
-This function used to be called @code{x-defined-colors},
-and that name is still supported as an alias.
 @end defun
 
 @defun color-supported-p color &optional frame background-p
@@ -4522,10 +5081,6 @@ The color values are returned for @var{frame}'s display. 
 If
 @var{frame} is omitted or @code{nil}, the information is returned for
 the selected frame's display.  If the frame cannot display colors, the
 value is @code{nil}.
-
-@findex x-color-values
-This function used to be called @code{x-color-values},
-and that name is still supported as an alias.
 @end defun
 
 @defun color-name-to-rgb color &optional frame
@@ -4710,10 +5265,7 @@ This function returns @code{t} if @var{display} has a 
mouse available,
 @end defun
 
 @defun display-color-p &optional display
-@findex x-display-color-p
 This function returns @code{t} if the screen is a color screen.
-It used to be called @code{x-display-color-p}, and that name
-is still supported as an alias.
 @end defun
 
 @defun display-grayscale-p &optional display
diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi
index af116f62973..53525e6b386 100644
--- a/doc/lispref/functions.texi
+++ b/doc/lispref/functions.texi
@@ -2177,7 +2177,7 @@ More specifically, the composition of the two functions 
behaves like:
 @findex defadvice
 @findex ad-activate
 
-A lot of code uses the old @code{defadvice} mechanism, which is largely made
+A lot of code uses the old @code{defadvice} mechanism, which has been made
 obsolete by the new @code{advice-add}, whose implementation and semantics is
 significantly simpler.
 
diff --git a/doc/lispref/keymaps.texi b/doc/lispref/keymaps.texi
index 9e36d6716b9..99bc10677cd 100644
--- a/doc/lispref/keymaps.texi
+++ b/doc/lispref/keymaps.texi
@@ -2044,6 +2044,15 @@ to turn the character that follows into a Hyper 
character:
 @end group
 @end example
 
+@cindex accessing events within a key translation function
+@vindex current-key-remap-sequence
+A key translation function might want to adjust its behavior based on
+parameters to events within a key sequence containing non-key events
+(@pxref{Input Events}.)  This information is available from the
+variable @code{current-key-remap-sequence}, which is bound to the key
+sub-sequence being translated around calls to key translation
+functions.
+
 @subsection Interaction with normal keymaps
 
 The end of a key sequence is detected when that key sequence either is bound
@@ -2506,7 +2515,8 @@ get a non-selectable menu item.  This is mostly useful 
when creating
 separator lines and the like.
 
 The tail of the list, @var{item-property-list}, has the form of a
-property list which contains other information.
+property list (@pxref{Property Lists}) which contains other
+information.
 
   Here is a table of the properties that are supported:
 
@@ -2587,6 +2597,12 @@ function should return the binding to use instead.
 Emacs can call this function at any time that it does redisplay or
 operates on menu data structures, so you should write it so it can
 safely be called at any time.
+
+@item :wrap @var{wrap-p}
+If @var{wrap-p} is non-nil inside a tool bar, the menu item is not
+displayed, but instead causes subsequent items to be displayed on a
+new line.  This is not supported when Emacs uses the GTK+ or Nextstep
+toolkits.
 @end table
 
 @node Menu Separators
@@ -3093,6 +3109,16 @@ specifies the local map to make the definition in.  The 
argument
 @code{tool-bar-add-item-from-menu}.
 @end defun
 
+@vindex secondary-tool-bar-map
+In addition to the tool bar items defined in @code{tool-bar-map},
+Emacs also supports displaying an additional row of ``secondary'' tool
+bar items specified in the keymap @code{secondary-tool-bar-map}.
+These items are normally displayed below those defined within
+@code{tool-bar-map} if the tool bar is positioned at the top of its
+frame, but are displayed above them if the tool bar is positioned at
+the bottom (@pxref{Layout Parameters}.)  They are not displayed if the
+tool bar is positioned at the left or right of a frame.
+
 @defvar auto-resize-tool-bars
 If this variable is non-@code{nil}, the tool bar automatically resizes to
 show all defined tool bar items---but not larger than a quarter of the
@@ -3171,14 +3197,15 @@ the menu.  To put it elsewhere in the menu, use 
@code{keymap-set-after}:
 
 @defun keymap-set-after map key binding &optional after
 Define a binding in @var{map} for @var{key}, with value @var{binding},
-just like @code{define-key}, but position the binding in @var{map} after
-the binding for the event @var{after}.  The argument @var{key} should
-represent a single menu item or key, and @var{after} should be a
-single event type---a symbol or a character, not a sequence.  The new
-binding goes after the binding for @var{after}.  If @var{after} is
-@code{t} or is omitted, then the new binding goes last, at the end of
-the keymap.  However, new bindings are added before any inherited
-keymap.
+just like @code{keymap-set} (@pxref{Changing Key Bindings}), but
+position the binding in @var{map} after the binding for the event
+@var{after}.  The argument @var{key} should represent a single menu
+item or key, and should satisfy @code{key-valid-p} (@pxref{Key
+Sequences}).  @var{after} should be a single event type---a symbol or
+a character, not a sequence.  The new binding goes after the binding
+for @var{after}.  If @var{after} is @code{t} or is omitted, then the
+new binding goes last, at the end of the keymap.  However, new
+bindings are added before any inherited keymap.
 
 Here is an example:
 
diff --git a/doc/lispref/loading.texi b/doc/lispref/loading.texi
index d6fc4e8d636..125011c780f 100644
--- a/doc/lispref/loading.texi
+++ b/doc/lispref/loading.texi
@@ -660,9 +660,7 @@ and @code{define-overloadable-function} (see the commentary 
in
 @item Definitions for major or minor modes:
 @code{define-minor-mode}, @code{define-globalized-minor-mode},
 @code{define-generic-mode}, @code{define-derived-mode},
-@code{easy-mmode-define-minor-mode},
-@code{easy-mmode-define-global-mode}, @code{define-compilation-mode},
-and @code{define-global-minor-mode}.
+@code{define-compilation-mode}, and @code{define-global-minor-mode}.
 
 @item Other definition types:
 @code{defcustom}, @code{defgroup}, @code{deftheme}, @code{defclass}
diff --git a/doc/lispref/minibuf.texi b/doc/lispref/minibuf.texi
index a861b8e910b..8ff5c14055e 100644
--- a/doc/lispref/minibuf.texi
+++ b/doc/lispref/minibuf.texi
@@ -156,7 +156,11 @@ reads the text and returns the resulting Lisp object, 
unevaluated.
 The argument @var{default} specifies default values to make available
 through the history commands.  It should be a string, a list of
 strings, or @code{nil}.  The string or strings become the minibuffer's
-``future history'', available to the user with @kbd{M-n}.
+``future history'', available to the user with @kbd{M-n}.  In
+addition, if the call provides completion (e.g., via the @var{keymap}
+argument), the completion candidates are added to the ``future
+history'' when the values in @var{default} are exhausted by @kbd{M-n};
+see @ref{Minibuffer History,, minibuffer-default-add-function}.
 
 If @var{read} is non-@code{nil}, then @var{default} is also used
 as the input to @code{read}, if the user enters empty input.
@@ -648,10 +652,25 @@ buffer local, then each buffer will have its own input 
history list.
 
   Both @code{read-from-minibuffer} and @code{completing-read} add new
 elements to the history list automatically, and provide commands to
-allow the user to reuse items on the list.  The only thing your program
-needs to do to use a history list is to initialize it and to pass its
-name to the input functions when you wish.  But it is safe to modify the
-list by hand when the minibuffer input functions are not using it.
+allow the user to reuse items on the list (@pxref{Minibuffer
+Commands}).  The only thing your program needs to do to use a history
+list is to initialize it and to pass its name to the input functions
+when you wish.  But it is safe to modify the list by hand when the
+minibuffer input functions are not using it.
+
+@vindex minibuffer-default-add-function
+  By default, when @kbd{M-n} (@code{next-history-element},
+@pxref{Minibuffer Commands,,next-history-element}) reaches the end of
+the list of default values provided by the command which initiated
+reading input from the minibuffer, @kbd{M-n} adds all of the
+completion candidates, as specified by
+@code{minibuffer-completion-table} (@pxref{Completion Commands}), to
+the list of defaults, so that all those candidates are available as
+``future history''.  Your program can control that via the variable
+@code{minibuffer-default-add-function}: if its value is not a
+function, this automatic addition is disabled, and you can also set
+this variable to your own function which adds only some candidates, or
+some other values, to the ``future history''.
 
   Emacs functions that add a new element to a history list can also
 delete old elements if the list gets too long.  The variable
@@ -1161,7 +1180,10 @@ However, empty input is always permitted, regardless of 
the value of
 first element of @var{default}, if it is a list; @code{""}, if
 @var{default} is @code{nil}; or @var{default}.  The string or strings
 in @var{default} are also available to the user through the history
-commands.
+commands (@pxref{Minibuffer Commands}).  In addition, the completion
+candidates are added to the ``future history'' when the values in
+@var{default} are exhausted by @kbd{M-n}; see @ref{Minibuffer
+History,, minibuffer-default-add-function}.
 
 The function @code{completing-read} uses
 @code{minibuffer-local-completion-map} as the keymap if
@@ -1515,7 +1537,8 @@ that it uses the predicate @code{custom-variable-p} 
instead of
 @code{commandp}.
 @end defun
 
-@deffn Command read-color &optional prompt convert allow-empty display
+@deffn Command read-color &optional prompt convert allow-empty @
+  display foreground face
 This function reads a string that is a color specification, either the
 color's name or an RGB hex value such as @code{#RRRGGGBBB}.  It
 prompts with @var{prompt} (default: @code{"Color (name or #RGB triplet):"})
@@ -1535,6 +1558,13 @@ non-@code{nil} and the user enters null input.
 
 Interactively, or when @var{display} is non-@code{nil}, the return
 value is also displayed in the echo area.
+
+The optional arguments @var{foreground} and @var{face} control the
+appearence of the completion candidates in the @file{*Completions*}
+buffer.  The candidates are displayed in the specified @var{face} but
+with different colors: if @var{foreground} is non-@code{nil}, the
+foreground color is changed to be the color of the candidate,
+otherwise the background is changed to the candidate's color.
 @end deffn
 
   See also the functions @code{read-coding-system} and
@@ -2551,7 +2581,11 @@ minibuffer.  The argument @var{nabs} specifies the 
absolute history
 position in descending order, where 0 means the current element and a
 positive number @var{n} means the @var{n}th previous element.  NABS
 being a negative number -@var{n} means the @var{n}th entry of ``future
-history.''
+history''.  When this function reaches the end of the default values
+provided by @code{read-from-minibuffer} (@pxref{Text from Minibuffer})
+and @code{completing-read} (@pxref{Minibuffer Completion}), it adds
+the completion candidates to ``future history'', see @ref{Minibuffer
+History,, minibuffer-default-add-function}.
 @end deffn
 
 @node Minibuffer Windows
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index b4f69e79155..00148420893 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -1794,10 +1794,6 @@ and will always be loaded by that time, enabling it by 
default is
 harmless.  But these are unusual circumstances.  Normally, the
 initial value must be @code{nil}.
 
-@findex easy-mmode-define-minor-mode
-  The name @code{easy-mmode-define-minor-mode} is an alias
-for this macro.
-
   Here is an example of using @code{define-minor-mode}:
 
 @smallexample
diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 4bcc9d5fea6..3c704970cda 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -972,6 +972,9 @@ Hewlett-Packard HPUX operating system.
 @item nacl
 Google Native Client (@acronym{NaCl}) sandboxing system.
 
+@item android
+The Open Handset Alliance's Android operating system.
+
 @item ms-dos
 Microsoft's DOS@.  Emacs compiled with DJGPP for MS-DOS binds
 @code{system-type} to @code{ms-dos} even when you run it on MS-Windows.
@@ -2847,7 +2850,9 @@ Emacs is restarted by the session manager.
 @cindex notifications, on desktop
 
 Emacs is able to send @dfn{notifications} on systems that support the
-freedesktop.org Desktop Notifications Specification and on MS-Windows.
+freedesktop.org Desktop Notifications Specification, MS-Windows,
+Haiku, and Android.
+
 In order to use this functionality on POSIX hosts, Emacs must have
 been compiled with D-Bus support, and the @code{notifications} library
 must be loaded.  @xref{Top, , D-Bus,dbus,D-Bus integration in Emacs}.
@@ -2885,6 +2890,13 @@ must be the result of a previous 
@code{notifications-notify} call.
 @item :app-icon @var{icon-file}
 The file name of the notification icon.  If set to @code{nil}, no icon
 is displayed.  The default is @code{notifications-application-icon}.
+If the value is a string, the function interprets it as a file name
+and converts to absolute by using @code{expand-file-name}; if it is a
+symbol, the function will use its name (which is useful when using the
+Icon Naming Specification @footnote{For more information about icon
+naming convention see
+@uref{https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html,
+Icon Naming Specification}}).
 
 @item :actions (@var{key} @var{title} @var{key} @var{title} ...)
 A list of actions to be applied.  @var{key} and @var{title} are both
@@ -3157,6 +3169,87 @@ This function removes the tray notification given by its 
unique
 @var{id}.
 @end defun
 
+@cindex desktop notifications, Haiku
+When Emacs runs under Haiku as a GUI program, it is also provides a
+restricted pastiche of the D-Bus desktop notifications interface
+previously addressed.  The principle capabilities absent from the
+function detailed below are call-back functions such as
+@code{:actions}, @code{:on-action} and @code{:on-close}.
+
+@defun haiku-notifications-notify &rest params
+This function sends a notification to the desktop notification server,
+incorporating a number of parameters that are akin to some of those
+accepted by @code{notifications-notify}.  The parameters are:
+
+@table @code
+@item :title @var{title}
+@item :body @var{body}
+@item :replaces-id @var{replaces-id}
+@item :urgency @var{urgency}
+These have the same meaning as they do when used in calls to
+@code{notifications-notify}.
+
+@item :app-icon @var{app-icon}
+This should be the file name designating an image file to use as the
+icon for the notification displayed.  If @code{nil}, the icon
+presented will instead be Emacs's app icon.
+@end table
+
+Its return value is a number identifying the notification, which can
+be exploited as the @code{:replaces-id} parameter to a subsequent call
+to this function.
+@end defun
+
+@cindex desktop notifications, Android
+When Emacs is built as an Android application package, displaying
+notifications is facilitated by the function
+@code{android-notifications-notify}.  This function does not feature
+call-backs, and has several idiosyncrasies, when compared to
+@code{notifications-notify}.
+
+@defun android-notifications-notify &rest params
+This function displays a desktop notification.  @var{params} is a list
+of parameters analogous to its namesake in
+@code{notifications-notify}.  The parameters are:
+
+@table @code
+@item :title @var{title}
+@item :body @var{body}
+@item :replaces-id @var{replaces-id}
+These have the same meaning as they do when used in calls to
+@code{notifications-notify}.
+
+@item :urgency @var{urgency}
+@item :group @var{group}
+These two parameters are ignored under Android 7.1 and earlier
+versions of the system.  The set of values for @var{urgency} is the
+same as with @code{notifications-notify}, but the urgency applies to
+all notifications displayed with the defined @var{group}.
+
+If @var{group} is nil or not present within @var{params}, it is
+replaced by the string @samp{"Desktop Notifications"}.
+
+@item :icon @var{icon}
+This parameter controls the symbolic icon the notification will be
+displayed with.  Its value is a string designating an icon within the
+@code{android.R.drawable} system package.  See
+@uref{https://developer.android.com/reference/android/R.drawable,R.drawable
+| Android Developers} for a list of such icons.
+
+If it is not provided within @var{params} or @var{icon} does not
+exist, it defaults to @samp{"ic_dialog_alert"}.
+@end table
+
+It returns a number identifying the notification, which may be
+supplied as the @code{:replaces-id} parameter to a later call to this
+function.
+
+If Emacs is not afforded the permission to display notifications
+(@pxref{Android Environment,,, emacs, The GNU Emacs Manual}) under
+Android 13 and later, any notifications sent will be silently
+disregarded.
+@end defun
+
 @node File Notifications
 @section Notifications on File Changes
 @cindex file notifications
diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 43c794104b8..e055d043c91 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -185,6 +185,25 @@ respective remote host.  In case of a local 
@code{default-directory},
 the function returns just the value of the variable @code{exec-path}.
 @end defun
 
+@cindex programs distributed with Emacs, starting
+@vindex ctags-program-name
+@vindex etags-program-name
+@vindex hexl-program-name
+@vindex emacsclient-program-name
+@vindex movemail-program-name
+@vindex ebrowse-program-name
+@vindex rcs2log-program-name
+  When starting a program that is part of the Emacs distribution, you
+must take into account that the program may have been renamed in order
+to comply with executable naming restrictions present on the system.
+
+  Instead of starting @command{ctags}, for example, you should specify
+the value of @code{ctags-program-name} instead.  Likewise, instead of
+starting @command{movemail}, you must start
+@code{movemail-program-name}, and the same goes for @command{etags},
+@command{hexl}, @command{emacsclient}, @code{rcs2log}, and
+@command{ebrowse}.
+
 @node Shell Arguments
 @section Shell Arguments
 @cindex arguments for shell commands
@@ -1757,7 +1776,7 @@ program was running when the filter function was started. 
 However, if
 This makes it possible to use the Lisp debugger to debug filter
 functions.  @xref{Debugger}.  If an error is caught, Emacs pauses for
 @code{process-error-pause-time} seconds so that the user sees the
-error.  @xref{Asynchronous Processes}
+error.  @xref{Asynchronous Processes}.
 
   Many filter functions sometimes (or always) insert the output in the
 process's buffer, mimicking the actions of the default filter.
@@ -2163,7 +2182,7 @@ programs was running when the sentinel was started.  
However, if
 This makes it possible to use the Lisp debugger to debug the
 sentinel.  @xref{Debugger}.  If an error is caught, Emacs pauses for
 @code{process-error-pause-time} seconds so that the user sees the
-error.  @xref{Asynchronous Processes}
+error.  @xref{Asynchronous Processes}.
 
   While a sentinel is running, the process sentinel is temporarily
 set to @code{nil} so that the sentinel won't run recursively.
diff --git a/doc/lispref/searching.texi b/doc/lispref/searching.texi
index a0289d1f3cd..66b33316faa 100644
--- a/doc/lispref/searching.texi
+++ b/doc/lispref/searching.texi
@@ -669,6 +669,10 @@ This matches the hexadecimal digits: @samp{0} through 
@samp{9}, @samp{a}
 through @samp{f} and @samp{A} through @samp{F}.
 @end table
 
+The classes @samp{[:space:]}, @samp{[:word:]} and @samp{[:punct:]} use
+the syntax-table of the current buffer but not any overriding syntax
+text properties (@pxref{Syntax Properties}).
+
 @node Regexp Backslash
 @subsubsection Backslash Constructs in Regular Expressions
 @cindex backslash in regular expressions
@@ -1341,6 +1345,9 @@ Match any @acronym{ASCII} character (codes 0--127).
 Match any non-@acronym{ASCII} character (but not raw bytes).
 @end table
 
+The classes @code{space}, @code{word} and @code{punct} use the
+syntax-table of the current buffer but not any overriding syntax text
+properties (@pxref{Syntax Properties}).@*
 Corresponding string regexp: @samp{[[:@var{class}:]]}
 
 @item @code{(syntax @var{syntax})}
@@ -1920,7 +1927,8 @@ causing a match to fail early.
 @item
 Avoid or-patterns in favor of bracket expressions: write
 @samp{[ab]} instead of @samp{a\|b}.  Recall that @samp{\s-} and @samp{\sw}
-are equivalent to @samp{[[:space:]]} and @samp{[[:word:]]}, respectively.
+are equivalent to @samp{[[:space:]]} and @samp{[[:word:]]}, respectively,
+most of the time.
 
 @item
 Since the last branch of an or-pattern does not add a backtrack point
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index bfd81662292..4ffd5126e7e 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -551,14 +551,17 @@ character has close parenthesis syntax (@pxref{Blinking}).
 @vindex self-insert-uses-region-functions
 The final thing this command does is to run the hook
 @code{post-self-insert-hook}.  You could use this to automatically
-reindent text as it is typed, for example.  If any function on this
-hook needs to act on the region (@pxref{The Region}), it should make
-sure Delete Selection mode (@pxref{Using Region, Delete Selection, ,
-emacs, The GNU Emacs Manual}) doesn't delete the region before
-@code{post-self-insert-hook} functions are invoked.  The way to do so
-is to add a function that returns @code{nil} to
-@code{self-insert-uses-region-functions}, a special hook that tells
-Delete Selection mode it should not delete the region.
+reindent text as it is typed, for example.  The functions on this hook
+can use @code{last-command-event} (@pxref{Command Loop Info}) to
+access the character just inserted.
+
+If any function on this hook needs to act on the region (@pxref{The
+Region}), it should make sure Delete Selection mode (@pxref{Using
+Region, Delete Selection, , emacs, The GNU Emacs Manual}) doesn't
+delete the region before @code{post-self-insert-hook} functions are
+invoked.  The way to do so is to add a function that returns
+@code{nil} to @code{self-insert-uses-region-functions}, a special hook
+that tells Delete Selection mode it should not delete the region.
 
 Do not try substituting your own definition of
 @code{self-insert-command} for the standard one.  The editor command
diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi
index 55761ff75e2..93930d17587 100644
--- a/doc/lispref/variables.texi
+++ b/doc/lispref/variables.texi
@@ -351,8 +351,8 @@ A function call is in the tail position if it's the very 
last thing
 done so that the value returned by the call is the value of @var{body}
 itself, as is the case in the recursive call to @code{sum} above.
 
-@strong{Warning:} @code{named-let} works as expected only when
-lexical-binding is enabled.  @xref{Lexical Binding}.
+@code{named-let} can only be used when lexical-binding is enabled.
+@xref{Lexical Binding}.
 @end defspec
 
   Here is a complete list of the other facilities that create local
diff --git a/doc/misc/calc.texi b/doc/misc/calc.texi
index 00c0fa6001a..5064f76e7b8 100644
--- a/doc/misc/calc.texi
+++ b/doc/misc/calc.texi
@@ -28476,12 +28476,12 @@ B and
 the octave numbered 0 was chosen to correspond to the lowest
 audible frequency.  Using this system, middle C (about 261.625 Hz)
 corresponds to the note @slanted{C} in octave 4 and is denoted
-@slanted{C@sub{4}}.  Any frequency can be described by giving a note plus an
+@slanted{C4}.  Any frequency can be described by giving a note plus an
 offset in cents (where a cent is a ratio of frequencies so that a
 semitone consists of 100 cents).
 
 The midi note number system assigns numbers to notes so that
-@slanted{C@sub{-1}} corresponds to the midi note number 0 and 
@slanted{G@sub{9}}
+@slanted{C-1} corresponds to the midi note number 0 and @slanted{G9}
 corresponds to the midi note number 127.   A midi controller can have
 up to 128 keys and each midi note number from  0 to 127 corresponds to
 a possible key.
diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi
index 0284554ed9e..5de33350f4f 100644
--- a/doc/misc/cl.texi
+++ b/doc/misc/cl.texi
@@ -1238,10 +1238,15 @@ of variables.
 
 @defmac cl-flet (bindings@dots{}) forms@dots{}
 This form establishes @code{let}-style bindings for functions rather
-than values.  Each @var{binding} must be a list of the form
-@samp{(@var{name} @var{arglist} @var{body}@dots{})}.  Within
-@var{forms}, any reference to the function @var{name} uses the local
-definition instead of the global one.
+than values.  Each @var{binding} must be a list of one of two forms:
+either @w{@code{(@var{name} @var{expr})}} or @w{@code{(@var{name}
+@var{arglist} @var{body}@dots{})}}.  The @var{name} is the name of the
+function, @var{expr} is an expression which returns the function value
+to which the corresponding @var{name} should be bound, and
+@var{arglist} and @var{body} are the argument list and the body of the
+function to bind to @var{name}.  Within @var{forms}, any reference to
+the function @var{name} uses the local definition provided by
+@var{bindings} instead of the global one.
 
 A ``reference'' to a function name is either a call to that function,
 or a use of its name quoted by @code{function} to be passed on to,
diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi
index 962e6c914ce..6eb212ca841 100644
--- a/doc/misc/eglot.texi
+++ b/doc/misc/eglot.texi
@@ -406,9 +406,10 @@ provides:
 At-point documentation: when point is at or near a symbol or an
 identifier, the information about the symbol/identifier, such as the
 signature of a function or class method and server-generated
-diagnostics, is made available via the ElDoc package (@pxref{Lisp
-Doc,,, emacs, GNU Emacs Manual}).  This allows major modes to provide
-extensive help and documentation about the program identifiers.
+diagnostics, is made available via the ElDoc package
+(@pxref{Programming Language Doc,,, emacs, GNU Emacs Manual}).  This
+allows major modes to provide extensive help and documentation about
+the program identifiers.
 
 @item
 On-the-fly diagnostic annotations with server-suggested fixes, via the
diff --git a/doc/misc/erc.texi b/doc/misc/erc.texi
index 63ea94d9b2e..6d7785a9b54 100644
--- a/doc/misc/erc.texi
+++ b/doc/misc/erc.texi
@@ -1218,15 +1218,14 @@ you aren't familiar with @samp{use-package} or have no 
interest in
 learning it.  For our purposes, it's just a means of presenting
 configuration details in a tidy, standardized format.  If it helps,
 just pretend it's some make-believe, pseudo configuration language.
-Although the syntax below is easy enough to intuit and adapt to your
-setup, you may wish to keep the following in mind (or @pxref{Top,,,
-use-package,}):
+And while the syntax below is easy enough to intuit and adapt to your
+setup, you may wish to keep the following in mind:
 
 @itemize @bullet
 @item
 Each @code{use-package} ``declaration'' focuses on a library
 ``feature'', which is just a symbol you'd normally @code{require} in
-your config @pxref{Named Features,,, elisp,}).
+your config.
 
 @item
 Emacs loads anything in a @code{:config} section @emph{after} loading
@@ -1235,6 +1234,10 @@ whatever library @code{provide}s the declaration's 
feature.
 @item
 Everything in a @code{:custom} or @code{:custom-face} section is
 basically something you'd find in your @code{custom-file}.
+
+@item
+For more info, @pxref{Named Features,,, elisp,}, or @pxref{Top,,,
+use-package,}.
 @end itemize
 
 @noindent
@@ -1265,6 +1268,9 @@ settings (@pxref{Sample configuration via Customize}).
   (erc-server-reconnect-function #'erc-server-delayed-check-reconnect)
   (erc-server-reconnect-timeout 30)
 
+  ;; Show new buffers in the current window instead of a split.
+  (erc-interactive-display 'buffer)
+
   ;; Insert a newline when I hit <RET> at the prompt, and prefer
   ;; something more deliberate for actually sending messages.
   :bind (:map erc-mode-map
@@ -1300,12 +1306,12 @@ settings (@pxref{Sample configuration via Customize}).
 
 (use-package erc-goodies
   ;; Turn on read indicators when joining channels.
-  :hook (erc-join . my-erc-enable-read-indicator-on-join))
+  :hook (erc-join . my-erc-enable-keep-place-indicator-on-join))
 
 (defvar my-erc-read-indicator-channels '("#emacs")
   "Channels in which to show a `keep-place-indicator'.")
 
-(defun my-erc-enable-read-indicator-on-join ()
+(defun my-erc-enable-keep-place-indicator-on-join ()
   "Enable read indicators for certain queries or channels."
   (when (member (erc-default-target) my-erc-read-indicator-channels)
     (erc-keep-place-indicator-mode +1)))
@@ -1313,14 +1319,16 @@ settings (@pxref{Sample configuration via Customize}).
 ;; Handy commands from the Emacs Wiki.
 (defun erc-cmd-TRACK (&optional target)
   "Start tracking TARGET or that of current buffer."
-  (setq erc-track-exclude (delete (or target (erc-default-target))
-                                  erc-track-exclude)))
+  (setq erc-track-exclude
+        (delete (or target (erc-default-target) (current-buffer))
+                erc-track-exclude)))
 
 (defun erc-cmd-UNTRACK (&optional target)
   "Stop tracking TARGET or that of current buffer."
-  (setq erc-track-exclude (cl-pushnew (or target (erc-default-target))
-                                      erc-track-exclude
-                                      :test #'equal)))
+  (setq erc-track-exclude
+        (cl-pushnew (or target (erc-default-target) (current-buffer))
+                    erc-track-exclude
+                    :test #'equal)))
 
 @end lisp
 
@@ -1386,6 +1394,16 @@ Indeed, you can always get back here by running @kbd{M-x
 customize-group @key{RET} erc-server @key{RET}} from almost anywhere
 in Emacs.
 
+To make sure you've got this, try quickly customizing the option
+@code{erc-interactive-display}, which lives in the @samp{Erc Buffers}
+group (@kbd{M-x customize-group @key{RET} erc-buffers @key{RET}}).  As
+its doc string explains, the option controls where new buffers show up
+when you do @kbd{M-x erc-tls @key{RET}} or issue certain ``slash''
+commands, like @kbd{/JOIN #emacs-beginners @key{RET}}, at ERC's
+prompt.  Change its value to the symbol @code{buffer} by choosing
+@samp{Use current window} (item @kbd{5}) from the option's
+@samp{[Value Menu]}.  Don't forget to save.
+
 Now it's time to set some key bindings for @code{erc-mode-map}, a
 major-mode keymap active in all ERC buffers.  In general, it's best to
 do this part either entirely or in conjunction with some lisp code in
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 099bf3e9809..f8f60bae13a 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -4,7 +4,7 @@
 @settitle Eshell: The Emacs Shell
 @include docstyle.texi
 @defindex cm
-@synindex vr fn
+@syncodeindex vr fn
 @c %**end of header
 
 @copying
@@ -141,7 +141,7 @@ computer frequently enough, it is more than worthwhile in 
the long run.
 Any tool you use often deserves the time spent learning to master it.
 @footnote{For the understandably curious, here is what that command
 looks like: But don't let it fool you; once you know what's going on,
-it's easier than it looks: @code{ls -lt **/*.doc(Lk+50aM+5)}.}
+it's easier than it looks: @code{ls -lt **/*.doc(Lk+100aM+6)}.}
 
 @menu
 * Contributors to Eshell::      People who have helped out!
@@ -317,9 +317,10 @@ specify an argument of some other data type, you can use a 
Lisp form
 (1 2 3)
 @end example
 
-Additionally, many built-in Eshell commands (@pxref{Built-ins}) will
-flatten the arguments they receive, so passing a list as an argument
-will ``spread'' the elements into multiple arguments:
+When calling external commands (and many built-in Eshell commands,
+too) Eshell will flatten the arguments the command receives, so
+passing a list as an argument will ``spread'' the elements into
+multiple arguments:
 
 @example
 ~ $ printnl (list 1 2) 3
@@ -409,8 +410,18 @@ implementing common command-line utilities, but enhanced 
for Eshell.
 (These built-in commands are just ordinary Lisp functions whose names
 begin with @code{eshell/}.)  In order to call the external variant of
 a built-in command @code{foo}, you could call @code{*foo}.  Usually,
-this should not be necessary.  You can check what will be applied by
-the @code{which} command:
+this should not be necessary; if the Eshell version of a command
+doesn't support a particular option, it will automatically invoke the
+external command for you.
+
+Some built-in Eshell commands provide enhanced versions of regular
+Emacs Lisp functions.  If you want to call the regular Emacs Lisp
+version, you can write your command in Lisp form (@pxref{Invocation}).
+To call the regular version in command form, you can use
+@code{funcall} or @code{apply}, e.g.@: @samp{funcall #'compile "make all"}
+(@pxref{Calling Functions,,, elisp, GNU Emacs Lisp Reference Manual}).
+
+You can check what will be applied by the @code{which} command:
 
 @example
 ~ $ which ls
@@ -420,14 +431,19 @@ eshell/ls is a compiled Lisp function in `em-ls.el'
 @end example
 
 If you want to discard a given built-in command, you could declare an
-alias (@pxref{Aliases}).  Example:
+alias (@pxref{Aliases}).  For example:
 
 @example
-~ $ which sudo
-eshell/sudo is a compiled Lisp function in `em-tramp.el'.
-~ $ alias sudo '*sudo $@@*'
-~ $ which sudo
-sudo is an alias, defined as "*sudo $@@*"
+@group
+~ $ alias ls '*ls $@@*'
+~ $ which ls
+ls is an alias, defined as "*ls $@@*"
+@end group
+@group
+~ $ alias compile 'apply #''compile $*'
+~ $ which compile
+ls is an alias, defined as "apply #'compile $*"
+@end group
 @end example
 
 Some of the built-in commands have different behavior from their
@@ -442,6 +458,7 @@ overwriting files.  If both settings are non-@code{nil}, 
the commands
 always prompt.  If both settings are @code{nil} (the default), the
 commands signal an error.
 
+@vindex eshell-default-target-is-dot
 Several commands observe the value of
 @code{eshell-default-target-is-dot}.  If non-@code{nil}, then the
 default target for the commands @command{cp}, @command{mv}, and
@@ -505,6 +522,8 @@ directory.
 With @kbd{cd -42}, you can access the directory stack slots by number.
 
 @item
+@vindex eshell-cd-shows-directory
+@vindex eshell-list-files-after-cd
 If @code{eshell-cd-shows-directory} is non-@code{nil}, @command{cd}
 will report the directory it changes to.  If
 @code{eshell-list-files-after-cd} is non-@code{nil}, then @command{ls}
@@ -523,11 +542,24 @@ Clear the scrollback contents of the Eshell window.  
Unlike the
 command @command{clear}, this command deletes content in the Eshell
 buffer.
 
+@item compile
+@cmindex compile
+Run an external command, sending its output to a compilation buffer if
+the command would output to the screen and is not part of a pipeline
+or subcommand.  This is particularly useful when defining aliases, so
+that interactively, the output shows up in a compilation buffer, but
+you can still pipe the output elsewhere if desired.  For example, if
+you have a grep-like command on your system, you might define an alias
+for it like so: @samp{alias mygrep 'compile --mode=grep-mode -- mygrep
+$*'}.
+
 @item cp
 @cmindex cp
 Copy a file to a new location or copy multiple files to the same
 directory.
 
+@vindex eshell-cp-overwrite-files
+@vindex eshell-cp-interactive-query
 If @code{eshell-cp-overwrite-files} is non-@code{nil}, then
 @command{cp} will overwrite files without warning.  If
 @code{eshell-cp-interactive-query} is non-@code{nil}, then
@@ -545,6 +577,7 @@ Compare files using Emacs's internal @code{diff} (not to be 
confused
 with @code{ediff}).  @xref{Comparing Files, , , emacs, The GNU Emacs
 Manual}.
 
+@vindex eshell-plain-diff-behavior
 If @code{eshell-plain-diff-behavior} is non-@code{nil}, then this
 command does not use Emacs's internal @code{diff}.  This is the same
 as using @samp{alias diff '*diff $@@*'}.
@@ -572,6 +605,7 @@ Echoes its input.  By default, this prints in a 
Lisp-friendly fashion
 prints a list of all the arguments; otherwise, it prints the empty
 string.
 
+@vindex eshell-plain-echo-behavior
 If @code{eshell-plain-echo-behavior} is non-@code{nil}, @command{echo}
 will try to behave more like a plain shell's @command{echo}, printing
 each argument as a string, separated by a space.
@@ -592,6 +626,7 @@ cmd*}.
 
 @item exit
 @cmindex exit
+@vindex eshell-kill-on-exit
 Exit Eshell and save the history.  By default, this command kills the
 Eshell buffer, but if @code{eshell-kill-on-exit} is @code{nil}, then
 the buffer is merely buried instead.
@@ -617,6 +652,7 @@ The @command{grep} commands are compatible with GNU 
@command{grep},
 but use Emacs's internal @code{grep} instead.
 @xref{Grep Searching, , , emacs, The GNU Emacs Manual}.
 
+@vindex eshell-plain-grep-behavior
 If @code{eshell-plain-grep-behavior} is non-@code{nil}, then these
 commands do not use Emacs's internal @code{grep}.  This is the same as
 using @samp{alias grep '*grep $@@*'}, though this setting applies to
@@ -655,6 +691,8 @@ and @code{("foo" "bar")} both evaluate to @code{("foo" 
"bar")}.
 @cmindex ln
 Create links to files.
 
+@vindex eshell-ln-overwrite-files
+@vindex eshell-ln-interactive-query
 If @code{eshell-ln-overwrite-files} is non-@code{nil}, @command{ln}
 will overwrite files without warning.  If
 @code{eshell-ln-interactive-query} is non-@code{nil}, then
@@ -666,6 +704,7 @@ Alias to Emacs's @code{locate} function, which simply runs 
the external
 @command{locate} command and parses the results.
 @xref{Dired and Find, , , emacs, The GNU Emacs Manual}.
 
+@vindex eshell-plain-locate-behavior
 If @code{eshell-plain-locate-behavior} is non-@code{nil}, then Emacs's
 internal @code{locate} is not used.  This is the same as using
 @samp{alias locate '*locate $@@*'}.
@@ -674,21 +713,25 @@ internal @code{locate} is not used.  This is the same as 
using
 @cmindex ls
 Lists the contents of directories.
 
+@vindex eshell-ls-use-colors
 If @code{eshell-ls-use-colors} is non-@code{nil}, the contents of a
 directory is color-coded according to file type and status.  These
 colors and the regexps used to identify their corresponding files can
 be customized via @w{@kbd{M-x customize-group @key{RET} eshell-ls @key{RET}}}.
 
+@vindex eshell-ls-date-format
 The user option @code{eshell-ls-date-format} determines how the date
 is displayed when using the @option{-l} option.  The date is produced
 using the function @code{format-time-string} (@pxref{Time Parsing,,,
 elisp, GNU Emacs Lisp Reference Manual}).
 
+@vindex eshell-ls-initial-args
 The user option @code{eshell-ls-initial-args} contains a list of
 arguments to include with any call to @command{ls}.  For example, you
 can include the option @option{-h} to always use a more human-readable
 format.
 
+@vindex eshell-ls-default-blocksize
 The user option @code{eshell-ls-default-blocksize} determines the
 default blocksize used when displaying file sizes with the option
 @option{-s}.
@@ -712,6 +755,8 @@ Make new directories.
 @cmindex mv
 Move or rename files.
 
+@vindex eshell-mv-overwrite-files
+@vindex eshell-mv-interactive-query
 If @code{eshell-mv-overwrite-files} is non-@code{nil}, @command{mv}
 will overwrite files without warning.  If
 @code{eshell-mv-interactive-query} is non-@code{nil}, @command{mv}
@@ -736,6 +781,8 @@ Print the arguments separated by newlines.
 Push the current directory onto the directory stack, then change to
 another directory.
 
+@vindex eshell-pushd-dunique
+@vindex eshell-pushd-dextract
 If @code{eshell-pushd-dunique} is non-@code{nil}, then only unique
 directories will be added to the stack.  If
 @code{eshell-pushd-dextract} is non-@code{nil}, then @samp{pushd
@@ -750,6 +797,8 @@ Prints the current working directory.
 Removes files, buffers, processes, or Emacs Lisp symbols, depending on
 the argument.
 
+@vindex eshell-rm-interactive-query
+@vindex eshell-rm-removes-directories
 If @code{eshell-rm-interactive-query} is non-@code{nil}, @command{rm}
 will prompt before removing anything.  If
 @code{eshell-rm-removes-directories} is non-@code{nil}, then
@@ -1045,6 +1094,13 @@ necessary.  By default, its value is
 @code{@var{emacs-version},eshell}.  Other parts of Emacs, such as
 Tramp, may add extra information to this value.
 
+@vindex $PAGER
+@item $PAGER
+This variable indicates the pager that commands should use when they
+wish to paginate long output.  Its value is that of
+@code{comint-pager} if non-@code{nil}; otherwise, it uses the value of
+@code{$PAGER} from the @code{process-environment}.
+
 @end table
 
 @xref{Aliases}, for the built-in variables @samp{$*}, @samp{$1},
@@ -1054,6 +1110,7 @@ Tramp, may add extra information to this value.
 @section Aliases
 
 @findex eshell-read-aliases-list
+@vindex eshell-aliases-file
 Aliases are commands that expand to a longer input line.  For example,
 @command{ll} is a common alias for @code{ls -l}.  To define this alias
 in Eshell, you can use the command invocation @kbd{alias ll 'ls -l
@@ -1092,6 +1149,8 @@ the alias.  This lets you selectively use an alias's 
arguments, so
 @kbd{alias mcd 'mkdir $1 && cd $1'} would cause @kbd{mcd foo} to
 create and switch to a directory called @samp{foo}.
 
+@end table
+
 @node Remote Access
 @section Remote Access
 @cmindex remote access
@@ -1132,6 +1191,7 @@ the option @code{eshell-explicit-remote-commands} to 
@code{nil}.
 @node History
 @section History
 @cmindex history
+@vindex eshell-history-size
 The @samp{history} command shows all commands kept in the history ring
 as numbered list.  If the history ring contains
 @code{eshell-history-size} commands, those numbers change after every
@@ -1151,6 +1211,7 @@ command beginning with @code{foo}, and @samp{!?foo} to 
the last
 command containing @code{foo}.  The n-th argument of the last command
 beginning with @code{foo} is accessible by @code{!foo:n}.
 
+@vindex eshell-history-file-name
 The history ring is loaded from a file at the start of every session,
 and written back to the file at the end of every session.  The file path
 is specified in @code{eshell-history-file-name}.  Unlike other shells,
@@ -1336,6 +1397,7 @@ to @code{$(@var{lisp})}, this is identical to 
@code{@{@var{command}@}}
 when on its own, but the @code{$} allows it to be used inside double
 quotes or as part of a string.
 
+@vindex eshell-convert-numeric-arguments
 Normally, the output is split line-by-line, returning a list (or the
 first element if there's only one line of output); if
 @code{eshell-convert-numeric-arguments} is non-@code{nil} and every
@@ -1438,18 +1500,28 @@ other arguments around it.  For example, if 
@var{numbers} is the list
 
 @node Globbing
 @section Globbing
-@vindex eshell-glob-case-insensitive
 Eshell's globbing syntax is very similar to that of Zsh
 (@pxref{Filename Generation, , , zsh, The Z Shell Manual}).  Users
 coming from Bash can still use Bash-style globbing, as there are no
 incompatibilities.
 
-By default, globs are case sensitive, except on MS-DOS/MS-Windows
+@vindex eshell-glob-case-insensitive
+Globs are case sensitive by default, except on MS-DOS/MS-Windows
 systems.  You can control this behavior via the
-@code{eshell-glob-case-insensitive} option.  You can further customize
-the syntax and behavior of globbing in Eshell via the Customize group
-@code{eshell-glob} (@pxref{Easy Customization, , , emacs, The GNU
-Emacs Manual}).
+@code{eshell-glob-case-insensitive} option.
+
+@vindex eshell-glob-splice-results
+By default, Eshell expands the results of a glob as a sublist into the
+list of arguments.  You can change this to splice the results in-place
+by setting @code{eshell-glob-splice-results} to a non-@code{nil}
+value.  If you want to splice a glob in-place for just one use, you
+can use a subcommand form like @samp{$@@@{listify @var{my-glob}@}}.
+(Conversely, you can explicitly expand a glob as a sublist via
+@samp{$@{listify @var{my-glob}@}}.)
+
+You can further customize the syntax and behavior of globbing in
+Eshell via the Customize group @code{eshell-glob} (@pxref{Easy
+Customization, , , emacs, The GNU Emacs Manual}).
 
 @table @samp
 
@@ -1713,7 +1785,7 @@ Treating the value as a file name, gets the directory 
name (the
 
 @item t
 Treating the value as a file name, gets the base name (the ``tail'').
-For example, @samp{foo/bar/baz.el(:h)} expands to @samp{baz.el}.
+For example, @samp{foo/bar/baz.el(:t)} expands to @samp{baz.el}.
 
 @item e
 Treating the value as a file name, gets the final extension of the
@@ -1797,6 +1869,9 @@ garbage output, since the Eshell buffer is not a terminal 
emulator.
 Eshell solves this problem by running such programs in Emacs's
 terminal emulator.
 
+@vindex eshell-visual-commands
+@vindex eshell-visual-subcommands
+@vindex eshell-visual-options
 Programs that need a terminal to display output properly are referred
 to in this manual as ``visual commands'', because they are not simply
 line-oriented.  You must tell Eshell which commands are visual, by
@@ -2014,6 +2089,7 @@ modules.@footnote{ERC provides a similar module facility.}
 @node Optional modules
 @section Optional modules
 
+@vindex eshell-modules-list
 In addition to the various modules enabled by default (documented
 above), Eshell provides several other modules which are @emph{not}
 enabled by default.  If you want to enable these, you can add them to
@@ -2041,6 +2117,7 @@ For example, it binds @kbd{C-u} to kill the current input 
text and
 enabled, it also binds @kbd{C-p} and @kbd{C-n} to move through the
 input history.
 
+@vindex eshell-confine-point-to-input
 If @code{eshell-confine-point-to-input} is non-@code{nil}, this module
 prevents certain commands from causing the point to leave the input
 area, such as @code{backward-word}, @code{previous-line}, etc.
@@ -2306,11 +2383,6 @@ This happens because the @code{grep} Lisp function 
returns immediately,
 and then the asynchronous @command{grep} process expects to examine the
 temporary file, which has since been deleted.
 
-@item Problem with C-r repeating text
-
-If the text @emph{before point} reads "./run", and you type @kbd{C-r r u
-n}, it will repeat the line for every character typed.
-
 @item Backspace doesn't scroll back after continuing (in smart mode)
 
 Hitting space during a process invocation, such as @command{make}, will
@@ -2363,10 +2435,6 @@ be Eshell's job?
 This would be so that if a Lisp function calls @code{print}, everything
 will happen as it should (albeit slowly).
 
-@item When an extension module fails to load, @samp{cd /} gives a Lisp error
-
-@item If a globbing pattern returns one match, should it be a list?
-
 @item Make sure syntax table is correct in Eshell mode
 
 So that @kbd{M-@key{DEL}} acts in a predictable manner, etc.
@@ -2668,12 +2736,6 @@ Everywhere in Emacs where @code{shell-mode} is specially 
noticed, add
 
 @item Permit the umask to be selectively set on a @command{cp} target
 
-@item Problem using @kbd{M-x eshell} after using @code{eshell-command}
-
-If the first thing that I do after entering Emacs is to run
-@code{eshell-command} and invoke @command{ls}, and then use @kbd{M-x
-eshell}, it doesn't display anything.
-
 @item @kbd{M-@key{RET}} during a long command (using smart display) doesn't 
work
 
 Since it keeps the cursor up where the command was invoked.
diff --git a/doc/misc/eww.texi b/doc/misc/eww.texi
index b67624af9f8..abd498824c5 100644
--- a/doc/misc/eww.texi
+++ b/doc/misc/eww.texi
@@ -132,12 +132,13 @@ visible in its rendered content.
 
 @findex eww-open-in-new-buffer
 @kindex M-RET
-  The @kbd{M-@key{RET}} command (@code{eww-open-in-new-buffer}) opens the
-URL at point in a new EWW buffer, akin to opening a link in a new
-``tab'' in other browsers.  When @code{global-tab-line-mode} is
-enabled, this buffer is displayed in the tab on the window tab line.
-When @code{tab-bar-mode} is enabled, a new tab is created on the frame
-tab bar.
+  The @kbd{M-@key{RET}} command (@code{eww-open-in-new-buffer}) opens
+the URL at point in a new EWW buffer, akin to opening a link in a new
+``tab'' in other browsers.  If invoked with prefix argument, the
+command will not make the new buffer the current one.  When
+@code{global-tab-line-mode} is enabled, this buffer is displayed in
+the tab on the window tab line.  When @code{tab-bar-mode} is enabled,
+a new tab is created on the frame tab bar.
 
 @findex eww-readable
 @kindex R
diff --git a/doc/misc/gnus.texi b/doc/misc/gnus.texi
index 8d25e868c8a..f017b011d71 100644
--- a/doc/misc/gnus.texi
+++ b/doc/misc/gnus.texi
@@ -25476,7 +25476,7 @@ There is no specific spam or ham processor for regular 
expressions.
 
 @defvar spam-use-bogofilter
 
-Set this variable if you want @code{spam-split} to use Eric Raymond's
+Set this variable if you want @code{spam-split} to use Eric S. Raymond's
 speedy Bogofilter.
 
 With a minimum of care for associating the @samp{$} mark for spam
@@ -25508,7 +25508,7 @@ Get the Bogofilter spamicity score 
(@code{spam-bogofilter-score}).
 
 @defvar spam-use-bogofilter-headers
 
-Set this variable if you want @code{spam-split} to use Eric Raymond's
+Set this variable if you want @code{spam-split} to use Eric S. Raymond's
 speedy Bogofilter, looking only at the message headers.  It works
 similarly to @code{spam-use-bogofilter}, but the @code{X-Bogosity} header
 must be in the message already.  Normally you would do this with a
diff --git a/doc/misc/org.org b/doc/misc/org.org
index ae3fae0623e..a4ce53cc6cb 100644
--- a/doc/misc/org.org
+++ b/doc/misc/org.org
@@ -3619,7 +3619,7 @@ replacement text.  Here is an example:
 
 #+begin_src emacs-lisp
 (setq org-link-abbrev-alist
-      '(("bugzilla"        . "http://10.1.2.9/bugzilla/show_bug.cgi?id=";)
+      '(("bugzilla"        . "https://10.1.2.9/bugzilla/show_bug.cgi?id=";)
         ("Nu Html Checker" . "https://validator.w3.org/nu/?doc=%h";)
         ("duckduckgo"      . "https://duckduckgo.com/?q=%s";)
         ("omap"            . 
"https://nominatim.openstreetmap.org/search?q=%s&polygon=1";)
@@ -4562,7 +4562,7 @@ all children are done, you can use the following setup:
 #+begin_src emacs-lisp
 (defun org-summary-todo (n-done n-not-done)
   "Switch entry to DONE when all subentries are done, to TODO otherwise."
-  (let (org-log-done org-log-states)   ; turn off logging
+  (let (org-log-done org-todo-log-states)   ; turn off logging
     (org-todo (if (= n-not-done 0) "DONE" "TODO"))))
 
 (add-hook 'org-after-todo-statistics-hook #'org-summary-todo)
@@ -16034,7 +16034,12 @@ can remove every headline in the buffer during export 
like this:
   "Remove all headlines in the current buffer.
 BACKEND is the export back-end being used, as a symbol."
   (org-map-entries
-   (lambda () (delete-region (point) (line-beginning-position 2)))))
+   (lambda ()
+     (delete-region (point) (line-beginning-position 2))
+     ;; We need to tell `org-map-entries' to not skip over heading at
+     ;; point. Otherwise, it would continue from _next_ heading. See
+     ;; the docstring of `org-map-entries' for details.
+     (setq org-map-continue-from (point)))))
 
 (add-hook 'org-export-before-parsing-hook #'my-headline-removal)
 #+end_src
@@ -20688,8 +20693,8 @@ adding ~:rewrites~ rules like this:
 
 #+texinfo: @noindent
 Since =example.com/$= is used as a regular expression, it maps
-=http://example.com/=, =https://example.com=,
-=http://www.example.com/= and similar to
+=https://example.com/=, =https://example.com=,
+=https://www.example.com/= and similar to
 =/home/user/example/index.php=.
 
 The ~:rewrites~ rules are searched as a last resort if and only if no
diff --git a/doc/misc/ses.texi b/doc/misc/ses.texi
index 15722056f33..f63bffe2a9f 100644
--- a/doc/misc/ses.texi
+++ b/doc/misc/ses.texi
@@ -1166,7 +1166,7 @@ details-and-summary spreadsheet.
 * Nonrelocatable references::
 * The data area::
 * Buffer-local variables in spreadsheets::
-* Uses of defadvice in @acronym{SES}::
+* Uses of advice-add in @acronym{SES}::
 @end menu
 
 @node Deferred updates
@@ -1308,20 +1308,13 @@ avoid virus warnings, each function used in a formula 
needs
 (put 'your-function-name 'safe-function t)
 @end lisp
 
-@node Uses of defadvice in @acronym{SES}
-@section Uses of defadvice in @acronym{SES}
-@findex defadvice
-@findex undo-more
+@node Uses of advice-add in @acronym{SES}
+@section Uses of advice-add in @acronym{SES}
+@findex advice-add
 @findex copy-region-as-kill
 @findex yank
 
 @table @code
-@item undo-more
-Defines a new undo element format (@var{fun} . @var{args}), which
-means ``undo by applying @var{fun} to @var{args}''.  For spreadsheet
-buffers, it allows undos in the data area even though that's outside
-the narrowing.
-
 @item copy-region-as-kill
 When copying from the print area of a spreadsheet, treat the region as
 a rectangle and attach each cell's formula and printer as 'ses
diff --git a/doc/misc/smtpmail.texi b/doc/misc/smtpmail.texi
index 99363483827..cc2f4f85fb3 100644
--- a/doc/misc/smtpmail.texi
+++ b/doc/misc/smtpmail.texi
@@ -264,12 +264,14 @@ file, @pxref{Top,,auth-source, auth, Emacs auth-source 
Library}.
 @cindex CRAM-MD5
 @cindex PLAIN
 @cindex LOGIN
+@cindex OAuth2
+@cindex OAuth 2.0
 The process by which the @acronym{SMTP} library authenticates you to
 the server is known as ``Simple Authentication and Security Layer''
 (@acronym{SASL}).  There are various @acronym{SASL} mechanisms, and
-this library supports three of them: @code{cram-md5}, @code{plain},
+this library supports four of them: @code{cram-md5}, @code{plain},
 @code{login} and @code{xoauth2}, where the first uses a form of
-encryption to obscure your password, while the other two do not.  It
+encryption to obscure your password, while the others do not.  It
 tries each of them, in that order, until one succeeds.
 (@code{xoauth2} requires using the @file{oauth2.el} library.  You can
 override this by assigning a specific authentication mechanism to a
diff --git a/doc/misc/texinfo.tex b/doc/misc/texinfo.tex
index b1d2999e5d7..6e521944b22 100644
--- a/doc/misc/texinfo.tex
+++ b/doc/misc/texinfo.tex
@@ -3,7 +3,7 @@
 % Load plain if necessary, i.e., if running under initex.
 \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi
 %
-\def\texinfoversion{2023-07-02.10}
+\def\texinfoversion{2023-07-27.21}
 %
 % Copyright 1985, 1986, 1988, 1990-2023 Free Software Foundation, Inc.
 %
@@ -426,42 +426,21 @@
 }
 
 % First remove any @comment, then any @c comment.  Pass the result on to
-% \argcheckspaces.
+% \argremovespace.
 \def\argremovecomment#1\comment#2\ArgTerm{\argremovec #1\c\ArgTerm}
-\def\argremovec#1\c#2\ArgTerm{\argcheckspaces#1\^^M\ArgTerm}
-
-% Each occurrence of `\^^M' or `<space>\^^M' is replaced by a single space.
-%
-% \argremovec might leave us with trailing space, e.g.,
+\def\argremovec#1\c#2\ArgTerm{\argremovespace#1$ $\ArgTerm}
+% \argremovec might leave us with trailing space, though; e.g.,
 %    @end itemize  @c foo
-% This space token undergoes the same procedure and is eventually removed
-% by \finishparsearg.
-%
-\def\argcheckspaces#1\^^M{\argcheckspacesX#1\^^M \^^M}
-\def\argcheckspacesX#1 \^^M{\argcheckspacesY#1\^^M}
-\def\argcheckspacesY#1\^^M#2\^^M#3\ArgTerm{%
-  \def\temp{#3}%
-  \ifx\temp\empty
-    % Do not use \next, perhaps the caller of \parsearg uses it; reuse \temp:
-    \let\temp\finishparsearg
-  \else
-    \let\temp\argcheckspaces
-  \fi
-  % Put the space token in:
-  \temp#1 #3\ArgTerm
-}
+% Note that the argument cannot contain the TeX $, as its catcode is
+% changed to \other when Texinfo source is read.
+\def\argremovespace#1 $#2\ArgTerm{\finishparsearg#1$\ArgTerm}
 
 % If a _delimited_ argument is enclosed in braces, they get stripped; so
 % to get _exactly_ the rest of the line, we had to prevent such situation.
-% We prepended an \empty token at the very beginning and we expand it now,
-% just before passing the control to \argtorun.
-% (Similarly, we have to think about #3 of \argcheckspacesY above: it is
-% either the null string, or it ends with \^^M---thus there is no danger
-% that a pair of braces would be stripped.
-%
-% But first, we have to remove the trailing space token.
-%
-\def\finishparsearg#1 \ArgTerm{\expandafter\argtorun\expandafter{#1}}
+% We prepended an \empty token at the very beginning and we expand it
+% just before passing the control to \next.
+% (But first, we have to remove the remaining $ or two.)
+\def\finishparsearg#1$#2\ArgTerm{\expandafter\argtorun\expandafter{#1}}
 
 
 % \parseargdef - define a command taking an argument on the line
@@ -5575,6 +5554,11 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 \newdimen\entryrightmargin
 \entryrightmargin=0pt
 
+% amount to indent subsequent lines in an entry when it spans more than
+% one line.
+\newdimen\entrycontskip
+\entrycontskip=1em
+
 % for PDF output, whether to make the text of the entry a link to the page
 % number.  set for @contents and @shortcontents where there is only one
 % page number.
@@ -5684,25 +5668,30 @@ might help (with 'rm \jobname.?? \jobname.??s')%
       \advance\dimen@ii by 1\dimen@i
       \ifdim\wd\boxA > \dimen@ii % If the entry doesn't fit in one line
       \ifdim\dimen@ > 0.8\dimen@ii   % due to long index text
+        % Undo changes above
+        \advance \parfillskip by 0pt minus -1\dimen@i
+        \advance\dimen@ii by -1\dimen@i
+        %
         % Try to split the text roughly evenly.  \dimen@ will be the length of
         % the first line.
         \dimen@ = 0.7\dimen@
         \dimen@ii = \hsize
         \ifnum\dimen@>\dimen@ii
           % If the entry is too long (for example, if it needs more than
-          % two lines), use all the space in the first line.
+          % two lines), use the same line length for all lines.
           \dimen@ = \dimen@ii
+        \else
+          \advance \dimen@ by 1\rightskip
         \fi
         \advance\leftskip by 0pt plus 1fill % ragged right
-        \advance \dimen@ by 1\rightskip
         \parshape = 2 0pt \dimen@ 0em \dimen@ii
         % Ideally we'd add a finite glue at the end of the first line only,
         % instead of using \parshape with explicit line lengths, but TeX
         % doesn't seem to provide a way to do such a thing.
         %
         % Indent all lines but the first one.
-        \advance\leftskip by 1em
-        \advance\parindent by -1em
+        \advance\leftskip by \entrycontskip
+        \advance\parindent by -\entrycontskip
       \fi\fi
       \indent % start paragraph
       \unhbox\boxA
@@ -6721,6 +6710,82 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \input \tocreadfilename
 }
 
+% process toc file to find the maximum width of the section numbers for
+% each chapter
+\def\findsecnowidths{%
+  \begingroup
+  \setupdatafile
+  \activecatcodes
+  \secentryfonts
+  % Redefinitions
+  \def\numchapentry##1##2##3##4{%
+    \def\curchapname{secnowidth-##2}%
+    \curchapmax=0pt
+  }%
+  \let\appentry\numchapentry
+  %
+  \def\numsecentry##1##2##3##4{%
+    \def\cursecname{secnowidth-##2}%
+    \cursecmax=0pt
+    %
+    \setbox0=\hbox{##2}%
+    \ifdim\wd0>\curchapmax
+      \curchapmax=\wd0
+      \expandafter\xdef\csname\curchapname\endcsname{\the\wd0}%
+    \fi
+  }%
+  \let\appsecentry\numsecentry
+  %
+  \def\numsubsecentry##1##2##3##4{%
+    \def\curssecname{secnowidth-##2}%
+    \curssecmax=0pt
+    %
+    \setbox0=\hbox{##2}%
+    \ifdim\wd0>\cursecmax
+      \cursecmax=\wd0
+      \expandafter\xdef\csname\cursecname\endcsname{\the\wd0}%
+    \fi
+  }%
+  \let\appsubsecentry\numsubsecentry
+  %
+  \def\numsubsubsecentry##1##2##3##4{%
+    \setbox0=\hbox{##2}%
+    \ifdim\wd0>\curssecmax
+      \curssecmax=\wd0
+      \expandafter\xdef\csname\curssecname\endcsname{\the\wd0}%
+    \fi
+  }%
+  \let\appsubsubsecentry\numsubsubsecentry
+  %
+  % Discard any output by outputting to dummy vbox, in case the toc file
+  % contains macros that we have not redefined above.
+  \setbox\dummybox\vbox\bgroup
+    \input \tocreadfilename\relax
+  \egroup
+  \endgroup
+}
+\newdimen\curchapmax
+\newdimen\cursecmax
+\newdimen\curssecmax
+
+
+% set #1 to the maximum section width for #2
+\def\retrievesecnowidth#1#2{%
+  \expandafter\let\expandafter\savedsecnowidth \csname secnowidth-#2\endcsname
+  \ifx\savedsecnowidth\relax
+    #1=0pt
+  \else
+    #1=\savedsecnowidth
+  \fi
+}
+\newdimen\secnowidthchap
+\secnowidthchap=0pt
+\newdimen\secnowidthsec
+\secnowidthsec=0pt
+\newdimen\secnowidthssec
+\secnowidthssec=0pt
+
+
 \newskip\contentsrightmargin \contentsrightmargin=1in
 \newcount\savepageno
 \newcount\lastnegativepageno \lastnegativepageno = -1
@@ -6766,6 +6831,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \startcontents{\putwordTOC}%
     \openin 1 \tocreadfilename\space
     \ifeof 1 \else
+      \findsecnowidths
       \readtocfile
     \fi
     \vfill \eject
@@ -6793,6 +6859,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
     \rm
     \hyphenpenalty = 10000
     \advance\baselineskip by 1pt % Open it up a little.
+    \extrasecnoskip=0.4pt
     \def\numsecentry##1##2##3##4{}
     \let\appsecentry = \numsecentry
     \let\unnsecentry = \numsecentry
@@ -6828,8 +6895,6 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   % This space should be enough, since a single number is .5em, and the
   % widest letter (M) is 1em, at least in the Computer Modern fonts.
   % But use \hss just in case.
-  % (This space doesn't include the extra space that gets added after
-  % the label; that gets put in by \shortchapentry above.)
   %
   % We'd like to right-justify chapter numbers, but that looks strange
   % with appendix letters.  And right-justifying numbers and
@@ -6839,10 +6904,15 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \hbox to 1em{#1\hss}%
 }
 
-% These macros generate individual entries in the table of contents.
-% The first argument is the chapter or section name.
-% The last argument is the page number.
-% The arguments in between are the chapter number, section number, ...
+% These macros generate individual entries in the table of contents,
+% and are read in from the *.toc file.
+%
+% The arguments are like:
+% \def\numchapentry#1#2#3#4
+%   #1 - the chapter or section name.
+%   #2 - section number
+%   #3 - level of section (e.g "chap", "sec")
+%   #4 - page number
 
 % Parts, in the main contents.  Replace the part number, which doesn't
 % exist, with an empty box.  Let's hope all the numbers have the same width.
@@ -6855,7 +6925,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \vskip 0pt plus 5\baselineskip
   \penalty-300
   \vskip 0pt plus -5\baselineskip
-  \dochapentry{\numeralbox\labelspace#1}{}%
+  \dochapentry{#1}{\numeralbox}{}%
 }
 %
 % Parts, in the short toc.
@@ -6866,12 +6936,14 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 }
 
 % Chapters, in the main contents.
-\def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}}
+\def\numchapentry#1#2#3#4{%
+  \retrievesecnowidth\secnowidthchap{#2}%
+  \dochapentry{#1}{#2}{#4}%
+}
 
 % Chapters, in the short toc.
-% See comments in \dochapentry re vbox and related settings.
 \def\shortchapentry#1#2#3#4{%
-  \tocentry{\shortchaplabel{#2}\labelspace #1}{#4}%
+  \tocentry{#1}{\shortchaplabel{#2}}{#4}%
 }
 
 % Appendices, in the main contents.
@@ -6882,67 +6954,111 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \setbox0 = \hbox{\putwordAppendix{} M}%
   \hbox to \wd0{\putwordAppendix{} #1\hss}}
 %
-\def\appentry#1#2#3#4{\dochapentry{\appendixbox{#2}\hskip.7em#1}{#4}}
+\def\appentry#1#2#3#4{%
+  \retrievesecnowidth\secnowidthchap{#2}%
+  \dochapentry{\appendixbox{#2}\hskip.7em#1}{}{#4}%
+}
 
 % Unnumbered chapters.
-\def\unnchapentry#1#2#3#4{\dochapentry{#1}{#4}}
-\def\shortunnchapentry#1#2#3#4{\tocentry{#1}{#4}}
+\def\unnchapentry#1#2#3#4{\dochapentry{#1}{}{#4}}
+\def\shortunnchapentry#1#2#3#4{\tocentry{#1}{}{#4}}
 
 % Sections.
-\def\numsecentry#1#2#3#4{\dosecentry{#2\labelspace#1}{#4}}
+\def\numsecentry#1#2#3#4{\dosecentry{#1}{#2}{#4}}
+
+\def\numsecentry#1#2#3#4{%
+  \retrievesecnowidth\secnowidthsec{#2}%
+  \dosecentry{#1}{#2}{#4}%
+}
 \let\appsecentry=\numsecentry
-\def\unnsecentry#1#2#3#4{\dosecentry{#1}{#4}}
+\def\unnsecentry#1#2#3#4{%
+  \retrievesecnowidth\secnowidthsec{#2}%
+  \dosecentry{#1}{}{#4}%
+}
 
 % Subsections.
-\def\numsubsecentry#1#2#3#4{\dosubsecentry{#2\labelspace#1}{#4}}
+\def\numsubsecentry#1#2#3#4{%
+  \retrievesecnowidth\secnowidthssec{#2}%
+  \dosubsecentry{#1}{#2}{#4}%
+}
 \let\appsubsecentry=\numsubsecentry
-\def\unnsubsecentry#1#2#3#4{\dosubsecentry{#1}{#4}}
+\def\unnsubsecentry#1#2#3#4{%
+  \retrievesecnowidth\secnowidthssec{#2}%
+  \dosubsecentry{#1}{}{#4}%
+}
 
 % And subsubsections.
-\def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#2\labelspace#1}{#4}}
+\def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#2}{#4}}
 \let\appsubsubsecentry=\numsubsubsecentry
-\def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#4}}
+\def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{}{#4}}
 
 % This parameter controls the indentation of the various levels.
 % Same as \defaultparindent.
 \newdimen\tocindent \tocindent = 15pt
 
-% Now for the actual typesetting. In all these, #1 is the text and #2 is the
-% page number.
+% Now for the actual typesetting. In all these, #1 is the text, #2 is
+% a section number if present, and #3 is the page number.
 %
 % If the toc has to be broken over pages, we want it to be at chapters
 % if at all possible; hence the \penalty.
-\def\dochapentry#1#2{%
+\def\dochapentry#1#2#3{%
    \penalty-300 \vskip1\baselineskip plus.33\baselineskip minus.25\baselineskip
    \begingroup
      % Move the page numbers slightly to the right
      \advance\entryrightmargin by -0.05em
      \chapentryfonts
-     \tocentry{#1}{#2}%
+     \extrasecnoskip=0.4em % separate chapter number more
+     \tocentry{#1}{#2}{#3}%
    \endgroup
    \nobreak\vskip .25\baselineskip plus.1\baselineskip
 }
 
-\def\dosecentry#1#2{\begingroup
+\def\dosecentry#1#2#3{\begingroup
+  \secnowidth=\secnowidthchap
   \secentryfonts \leftskip=\tocindent
-  \tocentry{#1}{#2}%
+  \tocentry{#1}{#2}{#3}%
 \endgroup}
 
-\def\dosubsecentry#1#2{\begingroup
+\def\dosubsecentry#1#2#3{\begingroup
+  \secnowidth=\secnowidthsec
   \subsecentryfonts \leftskip=2\tocindent
-  \tocentry{#1}{#2}%
+  \tocentry{#1}{#2}{#3}%
 \endgroup}
 
-\def\dosubsubsecentry#1#2{\begingroup
+\def\dosubsubsecentry#1#2#3{\begingroup
+  \secnowidth=\secnowidthssec
   \subsubsecentryfonts \leftskip=3\tocindent
-  \tocentry{#1}{#2}%
+  \tocentry{#1}{#2}{#3}%
 \endgroup}
 
-% We use the same \entry macro as for the index entries.
-\let\tocentry = \entry
+% Used for the maximum width of a section number so we can align
+% section titles.
+\newdimen\secnowidth
+\secnowidth=0pt
+\newdimen\extrasecnoskip
+\extrasecnoskip=0pt
 
-% Space between chapter (or whatever) number and the title.
-\def\labelspace{\hskip1em \relax}
+% \tocentry{TITLE}{SEC NO}{PAGE}
+%
+\def\tocentry#1#2#3{%
+  \def\secno{#2}%
+  \ifx\empty\secno
+    \entry{#1}{#3}%
+  \else
+    \ifdim 0pt=\secnowidth
+      \setbox0=\hbox{#2\hskip\labelspace\hskip\extrasecnoskip}%
+    \else
+      \advance\secnowidth by \labelspace
+      \advance\secnowidth by \extrasecnoskip
+      \setbox0=\hbox to \secnowidth{%
+        #2\hskip\labelspace\hskip\extrasecnoskip\hfill}%
+    \fi
+    \entrycontskip=\wd0
+    \entry{\box0 #1}{#3}%
+  \fi
+}
+\newdimen\labelspace
+\labelspace=0.6em
 
 \def\chapentryfonts{\secfonts \rm}
 \def\secentryfonts{\textfonts}
@@ -7787,6 +7903,8 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \tolerance=10000 \hbadness=10000
   \exdentamount=\defbodyindent
   {%
+    \def\^^M{}% for line continuation
+    %
     % defun fonts. We use typewriter by default (used to be bold) because:
     % . we're printing identifiers, they should be in tt in principle.
     % . in languages with many accents, such as Czech or French, it's
@@ -7819,6 +7937,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 % Print arguments.  Use slanted for @def*, typewriter for @deftype*.
 \def\defunargs#1{%
   \bgroup
+    \def\^^M{}% for line continuation
     \df \ifdoingtypefn \tt \else \sl \fi
     \ifflagclear{txicodevaristt}{}%
        {\def\var##1{{\setregularquotes \ttsl ##1}}}%
diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi
index ff2a66ae720..6189ef2d41d 100644
--- a/doc/misc/tramp.texi
+++ b/doc/misc/tramp.texi
@@ -375,7 +375,7 @@ From behind a proxy:
 
 @example
 @group
-$ git config --global http.proxy http://user:pwd@@proxy.server.com:8080
+$ git config --global http.proxy https://user:pwd@@proxy.server.com:8080
 $ git clone https://git.savannah.gnu.org/r/tramp.git
 @end group
 @end example
@@ -5851,16 +5851,6 @@ If you want to enable Ange FTP's syntax, add the 
following form:
 (tramp-change-syntax 'simplified)
 @end lisp
 
-@item
-@vindex tramp-mode
-To disable both @value{tramp} (and Ange FTP), set @code{tramp-mode} to
-@code{nil} in @file{.emacs}.  @strong{Note}, that we don't use
-@code{customize-set-variable}, in order to avoid loading @value{tramp}.
-
-@lisp
-(setq tramp-mode nil)
-@end lisp
-
 @item
 @vindex tramp-ignored-file-name-regexp
 To deactivate @value{tramp} for some look-alike remote file names, set
@@ -5877,6 +5867,29 @@ This is needed, if you mount for example a virtual file 
system on your
 local host's root directory as @file{/ssh:example.com:}.
 
 @item
+@findex inhibit-remote-files
+To disable both @value{tramp} (and Ange FTP), type @kbd{M-x
+inhibit-remote-files @key{RET}}.  You can also add this to your
+@file{.emacs}.
+
+@lisp
+(inhibit-remote-files)
+@end lisp
+
+@item
+@findex without-remote-files
+If you write code, which is intended to run only for local files, you
+can use the @code{without-remote-files} macro.
+
+@lisp
+(without-remote-files @dots{})
+@end lisp
+
+This improves performance, because many primitive file name operations
+don't check any longer for Tramp file name regexps then.
+
+@item
+@findex tramp-unload-tramp
 To unload @value{tramp}, type @kbd{M-x tramp-unload-tramp @key{RET}}.
 Unloading @value{tramp} resets Ange FTP plugins also.
 @end itemize
@@ -6041,6 +6054,7 @@ wrapping the timer function body as follows:
 @chapter How to Customize Traces
 @vindex tramp-verbose
 @vindex tramp-debug-to-file
+@vindex tramp-debug-command-messages
 
 @value{tramp} messages are raised with verbosity levels ranging from 0
 to 10.  @value{tramp} does not display all messages; only those with a
@@ -6062,9 +6076,10 @@ The verbosity levels are
 @*@indent @w{11}  call traces (maintainer only)
 
 With @code{tramp-verbose} greater than or equal to 4, messages are
-also written to a @value{tramp} debug buffer.  Such debug buffers are
-essential to bug and problem analyzes.  For @value{tramp} bug reports,
-set the @code{tramp-verbose} level to 6 (@pxref{Bug Reports}).
+also written to the @value{tramp} debug buffer @file{*debug
+tramp/foo*}.  Such debug buffers are essential to bug and problem
+analyzes.  For @value{tramp} bug reports, set the @code{tramp-verbose}
+level to 6 (@pxref{Bug Reports}).
 
 The debug buffer is in
 @ifinfo
@@ -6108,7 +6123,13 @@ directory}.  Use this option with care, because it could 
decrease the
 performance of @value{tramp} actions.
 
 If @code{tramp-verbose} is greater than or equal to 11, @value{tramp}
-function call traces are written to the buffer @file{*trace-output*}.
+function call traces are written to the buffer @file{*trace tramp/foo*}.
+
+When @code{tramp-debug-command-messages} is non-@code{nil}, the debug
+buffer contains all messages with verbosity level 6 (sent and received
+strings), and the entry and exit messages for the function
+@code{tramp-file-name-handler}.  This is intended for @value{tramp}
+maintainers, analyzing the remote commands for performance analysis.
 
 
 @node GNU Free Documentation License
diff --git a/doc/misc/transient.texi b/doc/misc/transient.texi
index be9e8698ab4..e06f7759d1b 100644
--- a/doc/misc/transient.texi
+++ b/doc/misc/transient.texi
@@ -31,7 +31,7 @@ General Public License for more details.
 @finalout
 @titlepage
 @title Transient User and Developer Manual
-@subtitle for version 0.4.1
+@subtitle for version 0.4.3
 @author Jonas Bernoulli
 @page
 @vskip 0pt plus 1filll
@@ -47,9 +47,9 @@ General Public License for more details.
 Taking inspiration from prefix keys and prefix arguments, Transient
 implements a similar abstraction involving a prefix command, infix
 arguments and suffix commands.  We could call this abstraction a
-“transient command”, but because it always involves at least two
+``transient command'', but because it always involves at least two
 commands (a prefix and a suffix) we prefer to call it just a
-“transient”.
+``transient''.
 
 When the user calls a transient prefix command, a transient
 (temporary) keymap is activated, which binds the transient's infix
@@ -74,7 +74,7 @@ that hurdle is Psionic K's interactive tutorial, available at
 @end quotation
 
 @noindent
-This manual is for Transient version 0.4.1.
+This manual is for Transient version 0.4.3.
 
 @insertcopying
 @end ifnottex
@@ -153,9 +153,9 @@ Related Abstractions and Packages
 Taking inspiration from prefix keys and prefix arguments, Transient
 implements a similar abstraction involving a prefix command, infix
 arguments and suffix commands.  We could call this abstraction a
-“transient command”, but because it always involves at least two
+``transient command'', but because it always involves at least two
 commands (a prefix and a suffix) we prefer to call it just a
-“transient”.
+``transient''.
 
 @cindex transient prefix command
 @quotation
@@ -163,10 +163,10 @@ Transient keymaps are a feature provided by Emacs.  
Transients as
 implemented by this package involve the use of transient keymaps.
 
 Emacs provides a feature that it calls @dfn{prefix commands}.  When we
-talk about “prefix commands” in this manual, then we mean our own kind
-of “prefix commands”, unless specified otherwise.  To avoid ambiguity
+talk about ``prefix commands'' in this manual, then we mean our own kind
+of ``prefix commands'', unless specified otherwise.  To avoid ambiguity
 we sometimes use the terms @dfn{transient prefix command} for our kind and
-“regular prefix command” for Emacs' kind.
+``regular prefix command'' for Emacs' kind.
 
 @end quotation
 
@@ -217,7 +217,7 @@ looks a bit like this:
 
 @quotation
 This is a simplified version of @code{magit-tag}.  Info manuals do not
-support images or colored text, so the above “screenshot” lacks some
+support images or colored text, so the above ``screenshot'' lacks some
 information; in practice you would be able to tell whether the
 arguments @code{--force} and @code{--annotate} are enabled or not based on 
their
 color.
@@ -225,7 +225,7 @@ color.
 @end quotation
 
 @cindex command dispatchers
-Transient can be used to implement simple “command dispatchers”.  The
+Transient can be used to implement simple ``command dispatchers''.  The
 main benefit then is that the user can see all the available commands
 in a popup buffer.  That is useful by itself because it frees the user
 from having to remember all the keys that are valid after a certain
@@ -248,18 +248,18 @@ When using Transient, one can call a command with 
arguments that are
 just as complex as when calling the same function non-interactively
 from Lisp.
 
-Invoking a transient command with arguments is similar to invoking a
-command in a shell with command-line completion and history enabled.
-One benefit of the Transient interface is that it remembers history
-not only on a global level (“this command was invoked using these
-arguments, and previously it was invoked using those other arguments”),
-but also remembers the values of individual arguments independently.
-@xref{Using History}.
+Invoking a transient suffix command with arguments is similar to
+invoking a command in a shell with command-line completion and history
+enabled.  One benefit of the Transient interface is that it remembers
+history not only on a global level (``this command was invoked using
+these arguments, and previously it was invoked using those other
+arguments''), but also remembers the values of individual arguments
+independently.  See @xref{Using History}.
 
 After a transient prefix command is invoked, @kbd{C-h @var{KEY}} can be used to
 show the documentation for the infix or suffix command that @kbd{@var{KEY}} is
-bound to (@pxref{Getting Help for Suffix Commands}), and infixes and
-suffixes can be removed from the transient using @kbd{C-x l @var{KEY}}.  
Infixes
+bound to (see @ref{Getting Help for Suffix Commands}), and infixes and
+suffixes can be removed from the transient using @kbd{C-x l @var{KEY}}. Infixes
 and suffixes that are disabled by default can be enabled the same way.
 @xref{Enabling and Disabling Suffixes}.
 
@@ -270,6 +270,23 @@ Additionally, Transient provides abstractions for defining 
new types,
 which the author of Transient did not anticipate (or didn't get around
 to implementing yet).
 
+Note that suffix commands also support regular prefix arguments.  A
+suffix command may even be called with both infix and prefix arguments
+at the same time.  If you invoke a command as a suffix of a transient
+prefix command, but also want to pass prefix arguments to it, then
+first invoke the prefix command, and only after doing that invoke the
+prefix arguments, before finally invoking the suffix command.  If you
+instead began by providing the prefix arguments, then those would
+apply to the prefix command, not the suffix command.  Likewise, if you
+want to change infix arguments before invoking a suffix command with
+prefix arguments, then change the infix arguments before invoking the
+prefix arguments.  In other words, regular prefix arguments always
+apply to the next command, and since transient prefix, infix and
+suffix commands are just regular commands, the same applies to them.
+(Regular prefix keys behave differently because they are not commands
+at all, instead they are just incomplete key sequences, and those
+cannot be interrupted with prefix commands.)
+
 @node Usage
 @chapter Usage
 
@@ -365,7 +382,7 @@ suspended transients, if any.
 Like @code{transient-quit-all}, this command quits an incomplete key
 sequence, if any, and all transients.  Additionally, it saves the
 stack of transients so that it can easily be resumed (which is
-particularly useful if you quickly need to do “something else” and
+particularly useful if you quickly need to do ``something else'' and
 the stack is deeper than a single transient, and/or you have already
 changed the values of some infix arguments).
 
@@ -392,7 +409,7 @@ as well as some other commands that are all bound to 
@kbd{C-x @var{KEY}}.  After
 @kbd{C-x} is pressed, a section featuring all these common commands is
 temporarily shown in the popup buffer.  After invoking one of them,
 the section disappears again.  Note, however, that one of these
-commands is described as “Show common permanently”; invoke that if you
+commands is described as ``Show common permanently''; invoke that if you
 want the common commands to always be shown for all transients.
 
 @table @asis
@@ -572,7 +589,7 @@ displayed at any level.
 
 The levels of individual transients and/or their individual suffixes
 can be changed interactively, by invoking the transient and then
-pressing @kbd{C-x l} to enter the “edit” mode, see below.
+pressing @kbd{C-x l} to enter the ``edit'' mode, see below.
 
 The default level for both transients and their suffixes is 4.  The
 @code{transient-default-level} option only controls the default for
@@ -922,8 +939,8 @@ The following functions share a few arguments:
 @item
 @var{SUFFIX} is a transient infix or suffix specification in the same form
 as expected by @code{transient-define-prefix}.  Note that an infix is a
-special kind of suffix.  Depending on context “suffixes” means
-“suffixes (including infixes)” or “non-infix suffixes”.  Here it
+special kind of suffix.  Depending on context ``suffixes'' means
+``suffixes (including infixes)'' or ``non-infix suffixes''.  Here it
 means the former.  @xref{Suffix Specifications}.
 
 @var{SUFFIX} may also be a group in the same form as expected by
@@ -1049,7 +1066,7 @@ however, call that function only when some condition is 
satisfied.
 All transients have a (possibly @code{nil}) value, which is exported when
 suffix commands are called, so that they can consume that value.
 For some transients it might be necessary to have a sort of
-secondary value, called a “scope”.  Such a scope would usually be
+secondary value, called a ``scope''.  Such a scope would usually be
 set in the command's @code{interactive} form and has to be passed to the
 setup function:
 
@@ -1071,8 +1088,8 @@ described below.
 
 Users and third-party packages can add additional bindings using
 functions such as @code{transient-insert-suffix} (@pxref{Modifying
-Existing Transients}).  These functions take a “suffix
-specification” as one of their arguments, which has the same form as
+Existing Transients}).  These functions take a ``suffix
+specification'' as one of their arguments, which has the same form as
 the specifications used in @code{transient-define-prefix}.
 
 @menu
@@ -1207,8 +1224,8 @@ The same form is also used when later binding additional 
commands
 using functions such as @code{transient-insert-suffix}, see @ref{Modifying 
Existing Transients}.
 
 Note that an infix is a special kind of suffix. Depending on context
-“suffixes” means “suffixes (including infixes)” or “non-infix
-suffixes”.  Here it means the former.
+``suffixes'' means ``suffixes (including infixes)'' or ``non-infix
+suffixes''.  Here it means the former.
 
 Suffix specifications have this form:
 
@@ -1302,8 +1319,8 @@ argument supported by the constructor of that class.  See 
@ref{Suffix Slots}.
 @cindex defining infix commands
 
 Note that an infix is a special kind of suffix. Depending on context
-“suffixes” means “suffixes (including infixes)” or “non-infix
-suffixes”.
+``suffixes'' means ``suffixes (including infixes)'' or ``non-infix
+suffixes''.
 
 @defmac transient-define-suffix name arglist [docstring] [keyword 
value]@dots{} body@dots{}
 This macro defines @var{NAME} as a transient suffix command.
@@ -1436,7 +1453,7 @@ returned value is a symbol, the transient prefix command.
 
 @cindex transient state
 
-Invoking a transient prefix command “activates” the respective
+Invoking a transient prefix command ``activates'' the respective
 transient, i.e., it puts a transient keymap into effect, which binds
 the transient's infix and suffix commands.
 
@@ -1448,20 +1465,20 @@ Invoking an infix command does not affect the transient 
state; the
 transient remains active.
 
 @item
-Invoking a (non-infix) suffix command “deactivates” the transient
+Invoking a (non-infix) suffix command ``deactivates'' the transient
 state by removing the transient keymap and performing some
 additional cleanup.
 
 @item
 Invoking a command that is bound in a keymap other than the
 transient keymap is disallowed and trying to do so results in a
-warning.  This does not “deactivate” the transient.
+warning.  This does not ``deactivate'' the transient.
 @end itemize
 
 But these are just the defaults.  Whether a certain command
-deactivates or “exits” the transient is configurable.  There is more
-than one way in which a command can be “transient” or “non-transient”;
-the exact behavior is implemented by calling a so-called “pre-command”
+deactivates or ``exits'' the transient is configurable.  There is more
+than one way in which a command can be ``transient'' or ``non-transient'';
+the exact behavior is implemented by calling a so-called ``pre-command''
 function.  Whether non-suffix commands are allowed to be called is
 configurable per transient.
 
@@ -1489,17 +1506,17 @@ essentially equivalent to it being @code{nil}.
 
 @item
 A suffix command can be a prefix command itself, i.e., a
-“sub-prefix”.  While a sub-prefix is active we nearly always want
-@kbd{C-g} to take the user back to the “super-prefix”.  However in rare
+``sub-prefix''.  While a sub-prefix is active we nearly always want
+@kbd{C-g} to take the user back to the ``super-prefix''.  However in rare
 cases this may not be desirable, and that makes the following
 complication necessary:
 
 For @code{transient-suffix} objects the @code{transient} slot is unbound.  We 
can
 ignore that for the most part because, as stated above, @code{nil} and the
-slot being unbound are equivalent, and mean “do exit”.  That isn't
+slot being unbound are equivalent, and mean ``do exit''.  That isn't
 actually true for suffixes that are sub-prefixes though.  For such
-suffixes unbound means “do exit but allow going back”, which is the
-default, while @code{nil} means “do exit permanently”, which requires that
+suffixes unbound means ``do exit but allow going back'', which is the
+default, while @code{nil} means ``do exit permanently'', which requires that
 slot to be explicitly set to that value.
 
 @item
@@ -1514,7 +1531,7 @@ called by @code{transient--pre-command}, a function on 
@code{pre-command-hook} a
 the value that they return determines whether the transient is exited.
 To do so the value of one of the constants @code{transient--exit} or
 @code{transient--stay} is used (that way we don't have to remember if @code{t} 
means
-“exit” or “stay”).
+``exit'' or ``stay'').
 
 Additionally, these functions may change the value of @code{this-command}
 (which explains why they have to be called using @code{pre-command-hook}),
@@ -1590,7 +1607,7 @@ i.e., for sub-prefixes.
 Suspend the active transient, saving the transient stack.
 
 This is used by the command @code{transient-suspend} and optionally also by
-“external events” such as @code{handle-switch-frame}.  Such bindings should
+``external events'' such as @code{handle-switch-frame}.  Such bindings should
 be added to @code{transient-predicate-map}.
 @end defun
 
@@ -1716,7 +1733,7 @@ The abstract @code{transient-child} class is the base 
class of both
 @code{transient-group} (and therefore all groups) as well as of
 @code{transient-suffix} (and therefore all suffix and infix commands).
 
-This class exists because the elements (or “children”) of certain
+This class exists because the elements (or ``children'') of certain
 groups can be other groups instead of suffix and infix commands.
 
 @item
@@ -1726,7 +1743,7 @@ group classes.
 @item
 The @code{transient-column} class is the simplest group.
 
-This is the default “flat” group.  If the class is not specified
+This is the default ``flat'' group.  If the class is not specified
 explicitly and the first element is not a vector (i.e., not a group),
 then this class is used.
 
@@ -1742,7 +1759,7 @@ Direct elements have to be groups whose elements have to 
be commands
 or strings.  Each subgroup represents a column.  This class takes
 care of inserting the subgroups' elements.
 
-This is the default “nested” group.  If the class is not specified
+This is the default ``nested'' group.  If the class is not specified
 explicitly and the first element is a vector (i.e., a group), then
 this class is used.
 
@@ -1920,7 +1937,7 @@ function is how the value of a transient is determined so 
that the
 invoked suffix command can use it.
 
 Currently most values are strings, but that is not set in stone.
-@code{nil} is not a value, it means “no value”.
+@code{nil} is not a value, it means ``no value''.
 
 Usually only infixes have a value, but see the method for
 @code{transient-suffix}.
@@ -2010,7 +2027,7 @@ multiple sub-lists.
 
 @item
 @code{scope} For some transients it might be necessary to have a sort of
-secondary value, called a “scope”.  See @code{transient-define-prefix}.
+secondary value, called a ``scope''.  See @code{transient-define-prefix}.
 @end itemize
 
 @anchor{Internal Prefix Slots}
@@ -2486,9 +2503,9 @@ Both packages use transient keymaps to make a set of 
commands
 temporarily available and show the available commands in a popup
 buffer.
 
-A Hydra “body” is equivalent to a Transient “prefix” and a Hydra
-“head” is equivalent to a Transient “suffix”.  Hydra has no equivalent
-of a Transient “infix”.
+A Hydra ``body'' is equivalent to a Transient ``prefix'' and a Hydra
+``head'' is equivalent to a Transient ``suffix''.  Hydra has no equivalent
+of a Transient ``infix''.
 
 Both hydras and transients can be used as simple command dispatchers.
 Used like this they are similar to regular prefix commands and prefix
@@ -2559,8 +2576,6 @@ currently exist.
 @node FAQ
 @appendix FAQ
 
-
-
 @anchor{Can I control how the popup buffer is displayed?}
 @appendixsec Can I control how the popup buffer is displayed?
 
@@ -2599,14 +2614,14 @@ bindings.  The bindings that do use a prefix do so to 
avoid wasting
 too many non-prefix bindings, keeping them available for use in
 individual transients.  The bindings that do not use a prefix and that
 are @strong{not} grayed out are very important bindings that are 
@strong{always}
-available, even when invoking the “common command key prefix” or @strong{any
+available, even when invoking the ``common command key prefix'' or @strong{any
 other} transient-specific prefix.  The non-prefix keys that @strong{are} grayed
 out however, are not available when any incomplete prefix key sequence
-is active.  They do not use the “common command key prefix” because it
+is active.  They do not use the ``common command key prefix'' because it
 is likely that users want to invoke them several times in a row and
 e.g., @kbd{M-p M-p M-p} is much more convenient than @kbd{C-x M-p C-x M-p C-x 
M-p}.
 
-You may also have noticed that the “Set” command is bound to @kbd{C-x s},
+You may also have noticed that the ``Set'' command is bound to @kbd{C-x s},
 while Magit-Popup used to bind @kbd{C-c C-c} instead.  I have seen several
 users praise the latter binding (sic), so I did not change it
 willy-nilly.  The reason that I changed it is that using different
diff --git a/doc/misc/url.texi b/doc/misc/url.texi
index 2f5f994b474..b5a6cb0e6a1 100644
--- a/doc/misc/url.texi
+++ b/doc/misc/url.texi
@@ -896,8 +896,8 @@ Creates a cache file name from @var{url} using MD5 hashing.
 This is creates entries with very few cache collisions and is fast.
 @cindex MD5
 @smallexample
-(url-cache-create-filename-using-md5 "http://www.example.com/foo/bar";)
-  @result{} 
"/home/fx/.url/cache/fx/http/com/example/www/b8a35774ad20db71c7c3409a5410e74f"
+(url-cache-create-filename-using-md5 "https://www.example.com/foo/bar";)
+  @result{} 
"/home/fx/.url/cache/fx/https/com/example/www/b8a35774ad20db71c7c3409a5410e74f"
 @end smallexample
 @end defun
 
@@ -906,8 +906,8 @@ Creates a cache file name from @var{url} more obviously 
connected to
 @var{url} than for @code{url-cache-create-filename-using-md5}, but
 more likely to conflict with other files.
 @smallexample
-(url-cache-create-filename-human-readable "http://www.example.com/foo/bar";)
-  @result{} "/home/fx/.url/cache/fx/http/com/example/www/foo/bar"
+(url-cache-create-filename-human-readable "https://www.example.com/foo/bar";)
+  @result{} "/home/fx/.url/cache/fx/https/com/example/www/foo/bar"
 @end smallexample
 @end defun
 
@@ -1159,7 +1159,7 @@ opened by the URL library.
 @c linked with the resolver libraries, it will not be able to get to any
 @c machines off the local network.  This is characterized by being able
 @c to reach someplace with a raw ip number, but not its hostname
-@c (@url{http://129.79.254.191/} works, but
+@c (@url{https://129.79.254.191/} works, but
 @c @url{https://www.cs.indiana.edu/} doesn't).  This used to happen on
 @c SunOS4 and Ultrix, but is now probably now rare.  If Emacs can't be
 @c rebuilt linked against the resolver library, it can use the external
diff --git a/etc/AUTHORS b/etc/AUTHORS
index 9bd490dd3f8..27d01ed9eb9 100644
--- a/etc/AUTHORS
+++ b/etc/AUTHORS
@@ -112,9 +112,9 @@ Alan Mackenzie: wrote cc-awk.el debug-early.el
 and co-wrote cc-align.el cc-cmds.el cc-defs.el cc-engine.el cc-fonts.el
   cc-langs.el cc-mode.el cc-styles.el cc-vars.el
 and changed cc-mode.texi bytecomp.el minibuf.c window.c edebug.el
-  follow.el lisp.h display.texi eval.c keyboard.c subr.el frame.c lread.c
+  follow.el frame.c lisp.h display.texi eval.c keyboard.c subr.el lread.c
   syntax.texi xdisp.c progmodes/compile.el programs.texi font-lock.el
-  modes.texi window.el windows.texi and 190 other files
+  modes.texi window.el windows.texi and 191 other files
 
 Alan Modra: changed unexelf.c
 
@@ -389,7 +389,7 @@ and changed nnir.el gnus-sum.el nnimap.el gnus-group.el 
gnus.texi
   gnus-msg.el gnus-int.el gnus-search.el auth-source.el gnus-art.el
   gnus-cache.el gnus.el nnheader.el nnspool.el deps.mk dns.el
   fns-tests.el fns.c gnus-agent.el gnus-cloud.el gnus-registry.el
-  and 14 other files
+  and 15 other files
 
 Andrew Hall: changed paren.el
 
@@ -1190,7 +1190,7 @@ Daniel Martín: changed c-ts-mode.el nsterm.m shortdoc.el 
ns-win.el
   simple.el diff-mode-tests.el erc.texi files.el files.texi indent.erts
   msdos-xtra.texi progmodes/python.el search.texi .lldbinit basic.texi
   c-ts-mode-tests.el cmacexp.el compilation.txt compile-tests.el
-  compile.texi configure.ac and 43 other files
+  compile.texi configure.ac and 45 other files
 
 Daniel McClanahan: changed lisp-mode.el
 
@@ -1227,7 +1227,7 @@ Daniel Ralston: changed rcirc.el
 
 Daniel Schoepe: changed gnus-sum.el
 
-Daniel Semyonov: changed mairix.el
+Daniel Semyonov: changed mairix.el package-vc.el
 
 Dani Moncayo: changed msys-to-w32 Makefile.in configure.ac buffers.texi
   lists.texi mini.texi INSTALL README.W32 basic.texi custom.texi
@@ -1660,9 +1660,9 @@ Eli Zaretskii: wrote [bidirectional display in xdisp.c]
   chartab-tests.el coding-tests.el etags-tests.el rxvt.el tty-colors.el
 and co-wrote help-tests.el
 and changed xdisp.c display.texi w32.c msdos.c simple.el w32fns.c
-  files.el fileio.c keyboard.c emacs.c text.texi w32term.c dispnew.c
-  configure.ac frames.texi w32proc.c files.texi xfaces.c window.c
-  dispextern.h lisp.h and 1329 other files
+  files.el fileio.c keyboard.c emacs.c text.texi w32term.c configure.ac
+  dispnew.c frames.texi w32proc.c files.texi xfaces.c window.c
+  dispextern.h lisp.h and 1330 other files
 
 Eliza Velasquez: changed server.el
 
@@ -2170,7 +2170,7 @@ Gregor Schmid: changed intervals.c intervals.h 
tcl-mode.el textprop.c
 
 Gregory Chernov: changed nnslashdot.el
 
-Gregory Heytings: changed xdisp.c editfns.c keyboard.c buffer.c subr.el
+Gregory Heytings: changed xdisp.c editfns.c keyboard.c subr.el buffer.c
   dispextern.h lisp.h buffer.h display.texi efaq.texi files.el isearch.el
   minibuffer.el Makefile.in bytecode.c composite.c positions.texi
   bytecomp.el emake help-fns.el lread.c and 78 other files
@@ -2686,7 +2686,8 @@ and changed mml-sec.el gnus-util.el message.texi 
mml-smime.el mml1991.el
 Jens Petersen: wrote find-func.el
 and changed mule-cmds.el pcmpl-rpm.el
 
-Jens Schmidt: changed plstore.el comint.el gnus.texi isearch.el ldap.el
+Jens Schmidt: changed epa.texi plstore.el auth.texi comint.el gnus.texi
+  isearch.el ldap.el
 
 Jens Toivo Berger Thielemann: changed word-help.el
 
@@ -2919,6 +2920,8 @@ John Mastro: changed auth-source.el ibuffer.el w32heap.c
 
 John Mongan: changed progmodes/f90.el
 
+John Muhl: changed calculator.el
+
 John Paul Wallington: changed ibuffer.el ibuf-ext.el subr.el help-fns.el
   rmail.el files.el thumbs.el bindings.el fns.c xfns.c arc-mode.el
   bytecomp.el cus-theme.el font-lock.el hexl.el ibuf-macs.el info.el
@@ -3122,9 +3125,9 @@ Juri Linkov: wrote compose.el emoji.el files-x.el 
misearch.el
   repeat-tests.el replace-tests.el tab-bar-tests.el tab-bar.el
   tab-line.el
 and changed isearch.el simple.el info.el replace.el dired.el dired-aux.el
-  minibuffer.el progmodes/grep.el window.el subr.el vc.el mouse.el
+  progmodes/grep.el minibuffer.el window.el subr.el vc.el mouse.el
   outline.el diff-mode.el repeat.el image-mode.el files.el menu-bar.el
-  search.texi startup.el progmodes/compile.el and 472 other files
+  search.texi startup.el progmodes/compile.el and 473 other files
 
 Jussi Lahdenniemi: changed w32fns.c ms-w32.h msdos.texi w32.c w32.h
   w32console.c w32heap.c w32inevt.c w32term.h
@@ -3985,7 +3988,7 @@ Mauro Aranda: changed wid-edit.el cus-edit.el custom.el 
wid-edit-tests.el
   widget.texi perl-mode.el custom-tests.el checkdoc-tests.el checkdoc.el
   cperl-mode-tests.el cus-edit-tests.el cus-theme.el customize.texi
   files.texi gnus.texi octave.el pong.el align.el auth-source.el
-  autorevert.el button.el and 44 other files
+  autorevert.el button.el and 45 other files
 
 Maxime Edouard Robert Froumentin: changed gnus-art.el mml.el
 
@@ -4793,7 +4796,7 @@ Philipp Stephani: wrote callint-tests.el checkdoc-tests.el
   lread-tests.el mouse-tests.el startup-tests.el xt-mouse-tests.el
 and changed emacs-module.c emacs-module-tests.el configure.ac json.c
   process.c eval.c internals.texi json-tests.el process-tests.el alloc.c
-  emacs-module.h.in emacs.c lread.c nsterm.m bytecomp.el lisp.h pdumper.c
+  emacs-module.h.in emacs.c lread.c nsterm.m pdumper.c bytecomp.el lisp.h
   seccomp-filter.c callproc.c cl-macs.el gtkutil.c and 188 other files
 
 Phillip Dixon: changed eglot.el
@@ -5038,7 +5041,7 @@ and co-wrote cc-align.el cc-cmds.el cc-defs.el 
cc-engine.el cc-langs.el
 and changed files.el keyboard.c simple.el xterm.c xdisp.c rmail.el
   fileio.c process.c sysdep.c buffer.c xfns.c window.c subr.el
   configure.ac startup.el sendmail.el emacs.c Makefile.in editfns.c
-  info.el dired.el and 1338 other files
+  info.el dired.el and 1339 other files
 
 Richard Ryniker: changed sendmail.el
 
@@ -5089,7 +5092,7 @@ and changed configure.ac process.c blocks.awk keymap.el 
font.c
   network-stream-tests.el processes.texi custom.texi emoji-zwj.awk
   ftfont.c gtkutil.c process-tests.el unicode vc-git.el terminal.c
   char-fold.el gnutls.el keymaps.texi network-stream.el nsm.el nsterm.m
-  and 191 other files
+  and 192 other files
 
 Robert Thorpe: changed cus-start.el indent.el rmail.texi
 
@@ -5487,7 +5490,7 @@ Sławomir Nowaczyk: changed emacs.py progmodes/python.el 
TUTORIAL.pl
   flyspell.el ls-lisp.el w32proc.c
 
 Spencer Baugh: changed data-tests.el minibuffer.el alloc.c autorevert.el
-  mini.texi processes.texi
+  flymake.el menu-bar.el mini.texi processes.texi simple.el
 
 Spencer Thomas: changed dabbrev.el emacsclient.c gnus.texi server.el
   unexcoff.c
@@ -5780,9 +5783,9 @@ Theodore Jump: changed makefile.nt makefile.def 
w32-win.el w32faces.c
 Theodor Thornhill: changed typescript-ts-mode.el java-ts-mode.el
   c-ts-mode.el eglot.el js.el csharp-mode.el css-mode.el project.el
   json-ts-mode.el treesit.el c-ts-common.el eglot-tests.el EGLOT-NEWS
-  README.md c-ts-mode-tests.el compile-tests.el indent-bsd.erts
-  indent.erts maintaining.texi mwheel.el ruby-ts-mode.el
-  and 4 other files
+  README.md c-ts-mode-tests.el compile-tests.el go-ts-mode.el
+  indent-bsd.erts indent.erts maintaining.texi mwheel.el
+  and 5 other files
 
 Theresa O'Connor: wrote json.el
 and changed erc.el erc-viper.el erc-log.el erc-track.el viper.el
@@ -6105,6 +6108,8 @@ Valentin Wüstholz: changed org.el
 
 Valery Alexeev: changed cyril-util.el cyrillic.el
 
+Valtteri Vuorikoski: changed configure.ac
+
 Van L: changed subr.el
 
 Vasilij Schneidermann: changed ETAGS.EBNF cus-start.el eww.el cc-mode.el
@@ -6146,7 +6151,7 @@ Vincent Bernat: changed gnus-int.el nnimap.el xsettings.c
 
 Vincent Del Vecchio: changed info.el mh-utils.el
 
-Vincenzo Pupillo: changed java-ts-mode.el
+Vincenzo Pupillo: changed js.el typescript-ts-mode.el java-ts-mode.el
 
 Vince Salvino: changed msdos.texi w32.c w32fns.c
 
@@ -6370,7 +6375,7 @@ Yuan Fu: changed treesit.el treesit.c c-ts-mode.el 
parsing.texi
   progmodes/python.el modes.texi js.el treesit-tests.el indent.erts
   typescript-ts-mode.el css-mode.el treesit.h configure.ac
   java-ts-mode.el print.c sh-script.el c-ts-common.el gdb-mi.el
-  rust-ts-mode.el go-ts-mode.el starter-guide and 53 other files
+  rust-ts-mode.el go-ts-mode.el starter-guide and 54 other files
 
 Yuanle Song: changed rng-xsd.el
 
diff --git a/etc/ChangeLog.1 b/etc/ChangeLog.1
index de6abff2881..68c15fc6e69 100644
--- a/etc/ChangeLog.1
+++ b/etc/ChangeLog.1
@@ -2624,7 +2624,7 @@
        * images/custom/right.xpm:
        * images/custom/right-pushed.xpm: New files.
 
-2008-05-07  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-07  Eric S. Raymond  <esr@thyrsus.com>
 
        * NEWS: Support for Meta-CVS removed.
 
@@ -3015,7 +3015,7 @@
 
        * NEWS: Mention desktop locking.
 
-2007-10-10  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-10  Eric S. Raymond  <esr@thyrsus.com>
 
        * NEWS: Explain the VC fileset changes a bit better.
 
@@ -6019,7 +6019,7 @@
        * Rename termcap to termcap.src, the historical name for an
        uninstalled termcap file.
 
-1995-06-28  Eric S. Raymond  <esr@spiff.gnu.ai.mit.edu>
+1995-06-28  Eric S. Raymond  <esr@thyrsus.com>
 
        * termcap.dat, termcap.ucb: Deleted and replaced.
 
@@ -6147,11 +6147,11 @@
 
        * MACHINES: Add section for NeXT, from Thorsten Ohl.
 
-1993-04-28  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-28  Eric S. Raymond  (esr@thyrsus.com)
 
        * NEWS: Documented picture-mode improvements.
 
-1993-04-25  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-25  Eric S. Raymond  (esr@thyrsus.com)
 
        * NEWS: Described the new properties of arrow keys and
        next-line-add-newlines.  Fixed up the GUD description, it was
@@ -6160,23 +6160,23 @@
 
        * news.texi: invocation-name now exists.
 
-1993-03-27  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * MORE.STUFF: Added.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * NEWS: Preserved jimb's last change (documenting kill on
        read-only buffers).
 
        Added documentation on new info features.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * spook.lines: Alpha-sorted this, and added some new hot buttons
        for the 1990s.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * MACHINES: Deleted some VMS caveats.  If the src and lisp
        ChangeLogs are correct, dired and mail and process control are now
@@ -6188,13 +6188,13 @@
 
        * NEWS: Changed.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * sex.6: Added 900-line support.
 
        * NEWS: Added news about the package finder.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * MACHINES: `Last updated 10 Feb 1992.' was obviously wrong, so
        I nuked it.  Let the file mod date serve.  Merged in APOLLO and
@@ -6205,7 +6205,7 @@
 
        * Makefile (relock, unlock): New productions.
 
-1993-03-18  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-18  Eric S. Raymond  (esr@thyrsus.com)
 
        Augean-stable cleaning time.  Partly to save space, but mostly to
        reduce the dizzying amount of *stuff* confronting someone exploring
@@ -6245,7 +6245,7 @@
        names as per convention.  Originals of all files merged still
        exist with =-names.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * XENIX: Nuked (moved to =XENIX).  The hackery it describes is
        no longer necessary in the presence of 19's function-key-map
@@ -6254,7 +6254,7 @@
 1993-03-10  Jim Blandy  (jimb@totoro.cs.oberlin.edu)
 
        * MACHINES: Update description of SYSVr3 and r4 support, due to
-       Eric Raymond's changes.
+       Eric S. Raymond's changes.
 
 1993-03-09  Jim Blandy  (jimb@totoro.cs.oberlin.edu)
 
@@ -6272,7 +6272,7 @@
 
        * NEWS: Document included tags tables.
 
-1992-07-22  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * Corrected the news about VC to reflect reality.
 
diff --git a/etc/DEBUG b/etc/DEBUG
index c4f0852abb3..6455152bd50 100644
--- a/etc/DEBUG
+++ b/etc/DEBUG
@@ -1063,7 +1063,7 @@ recovering the contents of Emacs buffers from a core dump 
file.  You
 might also find those commands useful for displaying the list of
 buffers in human-readable format from within the debugger.
 
-*** Debugging Emacs with LLDB
+** Debugging Emacs with LLDB
 
 On systems where GDB is not available, like macOS with M1 chip, you
 can also use LLDB for Emacs debugging.
@@ -1099,6 +1099,204 @@ Please refer to the LLDB reference on the web for more 
information
 about LLDB.  If you already know GDB, you will also find a mapping
 from GDB commands to corresponding LLDB commands there.
 
+** Debugging Emacs on Android.
+
+A script located in the java/ directory automates the procedures
+necessary run Emacs under a Gdb session on an Android device connected
+to a computer using USB.
+
+Its requirements are the `adb' (Android Debug Bridge) utility and the
+Java debugger (jdb), utilized to cue the Android system to resume the
+Emacs process after the debugger attaches.
+
+If all three of those tools are present, simply run (from the Emacs
+source directory):
+
+  ../java/debug.sh -- [any extra arguments you wish to pass to gdb]
+
+Several lines of debug information will be printed, after which the
+Gdb prompt should be displayed.
+
+If there is no Gdbserver binary present on the device, then specify
+one to upload, like so:
+
+  ../java/debug.sh --gdbserver /path/to/gdbserver
+
+This Gdbserver should be statically linked or compiled using the
+Android NDK, and must target the same architecture as the debugee
+Emacs binary.  Older versions of the Android NDK (such as r24)
+distribute suitable Gdbserver binaries, usually located within
+
+  prebuilt/android-<arch>/gdbserver/gdbserver
+
+relative to the root of the NDK distribution.
+
+To attach Emacs to an existing process on a target device, use the
+`--attach-existing' argument to debug.sh:
+
+  ../java/debug.sh --attach-existing [other arguments]
+
+If multiple Emacs processes are running, debug.sh will display the
+names and PIDs of each running process, and prompt for the process
+that it should attach to.
+
+After Emacs starts, type:
+
+  (gdb) handle SIGUSR1 noprint pass
+
+to ignore the SIGUSR1 signal that is sent by the Android port's
+`select' emulation.  If this is overlooked, Emacs will stop each time
+a windowing event is received, which is probably unwanted.
+
+On top of the debugging procedure described above, Android also
+maintains a "logcat" buffer, where it prints backtraces during or
+after each crash.  Its contents are of interest when performing
+post-mortem debugging after a crash, and can also be retrieved through
+the `adb' tool, like so:
+
+  $ adb logcat
+
+There are three forms of crash messages printed by Android.  The first
+form is printed when a crash arises within Java code, and should
+resemble the following when printed in the logcat buffer:
+
+E AndroidRuntime: FATAL EXCEPTION: main
+E AndroidRuntime: Process: org.gnu.emacs, PID: 18057
+E AndroidRuntime: java.lang.RuntimeException: sample crash
+E AndroidRuntime:      at 
org.gnu.emacs.EmacsService.onCreate(EmacsService.java:308)
+E AndroidRuntime:      at 
android.app.ActivityThread.handleCreateService(ActivityThread.java:4485)
+E AndroidRuntime:      ... 9 more
+
+The second form is printed when a fatal signal (such as an abort, or
+segmentation fault) is raised within C code.  Here is an example of
+such a crash:
+
+F libc    : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x3 in 
tid 32644
+ (Emacs main thre), pid 32619 (org.gnu.emacs)
+F DEBUG   : Cmdline: org.gnu.emacs
+F DEBUG   : pid: 32619, tid: 32644, name: Emacs main thre  >>> org.gnu.emacs 
<<<
+F DEBUG   :       #00 pc 002b27b0  /.../lib/arm64/libemacs.so 
(sfnt_read_cmap_table+32)
+F DEBUG   :       #01 pc 002c4ee8  /.../lib/arm64/libemacs.so 
(sfntfont_read_cmap+84)
+F DEBUG   :       #02 pc 002c4dc4  /.../lib/arm64/libemacs.so 
(sfntfont_lookup_char+396)
+F DEBUG   :       #03 pc 002c23d8  /.../lib/arm64/libemacs.so 
(sfntfont_list+1688)
+F DEBUG   :       #04 pc 0021112c  /.../lib/arm64/libemacs.so 
(font_list_entities+864)
+F DEBUG   :       #05 pc 002138d8  /.../lib/arm64/libemacs.so 
(font_find_for_lface+1532)
+F DEBUG   :       #06 pc 00280c50  /.../lib/arm64/libemacs.so 
(fontset_find_font+2760)
+F DEBUG   :       #07 pc 0027cadc  /.../lib/arm64/libemacs.so 
(fontset_font+792)
+F DEBUG   :       #08 pc 0027c710  /.../lib/arm64/libemacs.so 
(face_for_char+412)
+F DEBUG   :       #09 pc 00217314  /.../lib/arm64/libemacs.so 
(Finternal_char_font+324)
+F DEBUG   :       #10 pc 00240d78  /.../lib/arm64/libemacs.so 
(exec_byte_code+3112)
+F DEBUG   :       #11 pc 001f5ff8  /.../lib/arm64/libemacs.so (Ffuncall+392)
+F DEBUG   :       #12 pc 001f3cf0  /.../lib/arm64/libemacs.so (eval_sub+2260)
+F DEBUG   :       #13 pc 001f853c  /.../lib/arm64/libemacs.so (Feval+80)
+F DEBUG   :       #14 pc 00240d78  /.../lib/arm64/libemacs.so 
(exec_byte_code+3112)
+F DEBUG   :       #15 pc 00240130  /.../lib/arm64/libemacs.so (Fbyte_code+120)
+F DEBUG   :       #16 pc 001f3d84  /.../lib/arm64/libemacs.so (eval_sub+2408)
+F DEBUG   :       #17 pc 00221d7c  /.../lib/arm64/libemacs.so 
(readevalloop+1748)
+F DEBUG   :       #18 pc 002201a0  /.../lib/arm64/libemacs.so (Fload+2544)
+F DEBUG   :       #19 pc 00221f3c  /.../lib/arm64/libemacs.so 
(save_match_data_load+88)
+F DEBUG   :       #20 pc 001f8414  /.../lib/arm64/libemacs.so 
(load_with_autoload_queue+252)
+F DEBUG   :       #21 pc 001f6550  /.../lib/arm64/libemacs.so 
(Fautoload_do_load+608)
+F DEBUG   :       #22 pc 00240d78  /.../lib/arm64/libemacs.so 
(exec_byte_code+3112)
+F DEBUG   :       #23 pc 001f5ff8  /.../lib/arm64/libemacs.so (Ffuncall+392)
+F DEBUG   :       #24 pc 001f1120  /.../lib/arm64/libemacs.so 
(Ffuncall_interactively+64)
+F DEBUG   :       #25 pc 001f5ff8  /.../lib/arm64/libemacs.so (Ffuncall+392)
+F DEBUG   :       #26 pc 001f8b8c  /.../lib/arm64/libemacs.so (Fapply+916)
+F DEBUG   :       #27 pc 001f137c  /.../lib/arm64/libemacs.so 
(Fcall_interactively+576)
+F DEBUG   :       #28 pc 00240d78  /.../lib/arm64/libemacs.so 
(exec_byte_code+3112)
+F DEBUG   :       #29 pc 001f5ff8  /.../lib/arm64/libemacs.so (Ffuncall+392)
+F DEBUG   :       #30 pc 0016d054  /.../lib/arm64/libemacs.so 
(command_loop_1+1344)
+F DEBUG   :       #31 pc 001f6d90  /.../lib/arm64/libemacs.so 
(internal_condition_case+92)
+F DEBUG   :       #32 pc 0016cafc  /.../lib/arm64/libemacs.so 
(command_loop_2+48)
+F DEBUG   :       #33 pc 001f6660  /.../lib/arm64/libemacs.so 
(internal_catch+84)
+F DEBUG   :       #34 pc 0016c288  /.../lib/arm64/libemacs.so 
(command_loop+264)
+F DEBUG   :       #35 pc 0016c0d8  /.../lib/arm64/libemacs.so 
(recursive_edit_1+144)
+F DEBUG   :       #36 pc 0016c4fc  /.../lib/arm64/libemacs.so 
(Frecursive_edit+348)
+F DEBUG   :       #37 pc 0016af9c  /.../lib/arm64/libemacs.so 
(android_emacs_init+7132)
+F DEBUG   :       #38 pc 002ab8d4  /.../lib/arm64/libemacs.so 
(Java_org_gnu_emacs_...+3816)
+
+Where the first line (the one containing "libc") mentions the number
+of the fatal signal, the address of any VM fault, and the name and ID
+of the thread which crashed.  Subsequent lines then contain a
+backtrace, recounting each function in the call stack culminating in
+the crash.
+
+The third form is printed when Emacs misuses the JVM in some fashion
+that is detected by the Android CheckJNI facility.  It looks like:
+
+A/art﹕ art/runtime/check_jni.cc:65] JNI DETECTED ERROR IN APPLICATION: ...
+A/art﹕ art/runtime/check_jni.cc:65]     in call to CallVoidMethodV
+A/art﹕ art/runtime/check_jni.cc:65]     from void 
android.os.MessageQueue.nativePollOnce(long, int)
+A/art﹕ art/runtime/check_jni.cc:65] "main" prio=5 tid=1 Runnable
+A/art﹕ art/runtime/check_jni.cc:65]   | group="main" sCount=0 dsCount=0 
obj=0x87d30ef0 self=0xb4f07800
+A/art﹕ art/runtime/check_jni.cc:65]   | sysTid=18828 nice=-11 cgrp=apps 
sched=0/0 handle=0xb6fdeec8
+A/art﹕ art/runtime/check_jni.cc:65]   | state=R schedstat=( 2249126546 
506089308 3210 ) utm=183 stm=41 core=3 HZ=100
+A/art﹕ art/runtime/check_jni.cc:65]   | stack=0xbe0c8000-0xbe0ca000 
stackSize=8MB
+A/art﹕ art/runtime/check_jni.cc:65]   | held mutexes= "mutator lock"(shared 
held)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #00 pc 00004640  
/system/lib/libbacktrace_libc++.so (UnwindCurrent::Unwind(unsigned int, 
ucontext*)+23)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #01 pc 00002e8d  
/system/lib/libbacktrace_libc++.so (Backtrace::Unwind(unsigned int, 
ucontext*)+8)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #02 pc 00248381  
/system/lib/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, 
std::__1::char_traits<char> >&, int, char const*, art::mirror::ArtMethod*)+68)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #03 pc 0022cd0b  
/system/lib/libart.so (art::Thread::Dump(std::__1::basic_ostream<char, 
std::__1::char_traits<char> >&) const+146)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #04 pc 000b189b  
/system/lib/libart.so (art::JniAbort(char const*, char const*)+582)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #05 pc 000b1fd5  
/system/lib/libart.so (art::JniAbortF(char const*, char const*, ...)+60)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #06 pc 000b50e5  
/system/lib/libart.so (art::ScopedCheck::ScopedCheck(_JNIEnv*, int, char 
const*)+1284)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #07 pc 000bc59f  
/system/lib/libart.so (art::CheckJNI::CallVoidMethodV(_JNIEnv*, _jobject*, 
_jmethodID*, std::__va_list)+30)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #08 pc 00063803  
/system/lib/libandroid_runtime.so (???)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #09 pc 000776bd  
/system/lib/libandroid_runtime.so 
(android::NativeDisplayEventReceiver::dispatchVsync(long long, int, unsigned 
int)+40)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #10 pc 00077885  
/system/lib/libandroid_runtime.so 
(android::NativeDisplayEventReceiver::handleEvent(int, int, void*)+80)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #11 pc 00010f6f  
/system/lib/libutils.so (android::Looper::pollInner(int)+482)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #12 pc 00011019  
/system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+92)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #13 pc 000830c1  
/system/lib/libandroid_runtime.so 
(android::NativeMessageQueue::pollOnce(_JNIEnv*, int)+22)
+A/art﹕ art/runtime/check_jni.cc:65]   native: #14 pc 000b22d7  
/system/framework/arm/boot.oat 
(Java_android_os_MessageQueue_nativePollOnce__JI+102)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
android.os.MessageQueue.nativePollOnce(Native method)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
android.os.MessageQueue.next(MessageQueue.java:143)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
android.os.Looper.loop(Looper.java:130)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
android.app.ActivityThread.main(ActivityThread.java:5832)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
java.lang.reflect.Method.invoke!(Native method)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
java.lang.reflect.Method.invoke(Method.java:372)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
+A/art﹕ art/runtime/check_jni.cc:65]   at 
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
+A/art﹕ art/runtime/check_jni.cc:65]
+
+In such situations, the first line explains what infraction Emacs
+committed, while the ensuing ones print backtraces for each running
+Java thread at the time of the error.
+
+If Emacs is executing on Android 5.0 and later, placing a breakpoint
+on
+
+  (gdb) break art::JavaVMExt::JniAbort
+
+will set a breakpoint that is hit each time such an error is detected.
+
+Since the logcat output is always rapidly being amended, it is worth
+piping it to a file or shell command buffer, and then searching for
+keywords such as "AndroidRuntime", "Fatal signal", or "JNI DETECTED
+ERROR IN APPLICATION".
+
+Once in a blue moon, it proves necessary to debug Java rather than C
+code.  To this end, the `--jdb' option will attach the Java debugger
+instead of gdbserver.  Lametably, it seems impossible to debug both C
+and Java code in concert.
+
+C code within Emacs rigorously checks for Java exceptions after
+calling any JVM function that may signal an out-of-memory error,
+employing one of the android_exception_check(_N) functions defined
+within android.c for this purpose.  These functions operate presuming
+the preceding Java code does not signal exceptions of its own, and
+report out-of-memory errors upon any type of exception, not just OOM
+errors.
+
+If Emacs protests that it is out of memory, yet you witness a
+substantial amount of free space remaining, search the log buffer for
+a string containing:
+
+  "Possible out of memory error.  The Java exception follows:"
+
+subsequent to which a reproduction of the exception precipitating the
+spurious OOM error should be located.  This exception is invariably
+indicative of a bug within Emacs that should be fixed.
+
 
 This file is part of GNU Emacs.
 
diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS
index 6a2e9051ddc..01f0498eb81 100644
--- a/etc/EGLOT-NEWS
+++ b/etc/EGLOT-NEWS
@@ -27,6 +27,12 @@ watching requests.  This change slightly reduces the number 
of file
 watcher objects requested from the operating system, which can be a
 problem, particularly on Mac OS.  See github#1228 and github#1226.
 
+** Fixed "onTypeFormatting" feature
+
+For 'newline' commands, Eglot sometimes sent the wrong character code
+to the server.  Also made this feature less chatty in the mode-line
+and messages buffer.
+
 
 * Changes in Eglot 1.15 (29/4/2023)
 
diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index cd0b8e5f823..7ee55982b17 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -14,28 +14,29 @@ GNU Emacs since Emacs version 22.1.
 
 * Changes in ERC 5.6
 
-** Module 'keep-place' has gained a more flamboyant cousin.
-Remember your place in ERC buffers a bit more easily while retaining
-the freedom to look around.  Optionally sync the indicator to any
-progress made when you haven't yet caught up to the live stream.  See
-options 'erc-keep-place-indicator-style' and friends, and try M-x
+** Module 'keep-place' has a more decorative cousin.
+Remember your place in ERC buffers a bit more easily with the help of
+a configurable, visible indicator.  Optionally sync the indicator to
+any progress made while you haven't yet caught up to the live stream.
+See options 'erc-keep-place-indicator-style' and friends, and try M-x
 keep-place-indicator-mode to see it in action.
 
-** Module 'fill' now offers a style based on 'visual-line-mode'.
-This fill style mimics the "hanging indent" look of 'erc-fill-static'
-and provides some movement and editing commands to optionally tame the
+** Module 'fill' offers an adaptive style based on 'visual-line-mode'.
+This style dynamically wraps messages to a window's width while
+mimicking the "hanging indent" look of 'erc-fill-static'.  It also
+provides some movement and editing commands to optionally tame the
 less familiar aspects of 'visual-line' behavior.  An interactive
-helper called 'erc-fill-wrap-nudge' allows for dynamic "refilling" of
-buffers on the fly.  Set 'erc-fill-function' to 'erc-fill-wrap' to get
-started.
+helper called 'erc-fill-wrap-nudge' makes easy work of adjusting the
+overhang on the fly.  Set 'erc-fill-function' to 'erc-fill-wrap' to
+get started.
 
-** A new module for nickname highlighting has joined ERC.
+** A module for nickname highlighting has joined ERC.
 Automatic nickname coloring has come to ERC core.  Users familiar with
 'erc-hl-nicks', from which this module directly descends, will already
 be familiar with its suite of handy options.  By default, each
 nickname in an ERC session receives a unique face with a unique (or
-evenly dealt) foreground color.  Add 'nicks' to 'erc-modules' to get
-started.
+uniformly dealt) foreground color.  Add 'nicks' to 'erc-modules' to
+get started.
 
 ** A unified interactive entry point.
 New users are often dismayed to discover that M-x ERC doesn't connect
@@ -87,9 +88,9 @@ users to edit the 'erc-modules' widget instead.
 Users can now add 'bufbar' to 'erc-modules' to achieve the same effect
 as toggling 'erc-status-sidebar-open' manually at the start of an IRC
 session.  The module has also been outfitted to show channels and
-queries under their respective servers by default.  To avoid
-confusion, the major mode used for the sidebar buffer itself,
-'erc-status-sidebar-mode', is no longer available interactively.
+queries under their servers by default.  To avoid confusion, the major
+mode for the actual sidebar buffer itself, 'erc-status-sidebar-mode',
+is no longer available interactively.
 
 ** A new spin on a classic integration in erc-speedbar.
 Add 'nickbar' to 'erc-modules' to spawn a dynamically updating side
@@ -102,11 +103,8 @@ side window.  Hit '<RET>' over a nick to spawn a "/QUERY" 
or a
 ** The option 'erc-timestamp-use-align-to' is more versatile.
 While this option has always offered to right-align stamps via the
 'display' text property, it's now more effective at doing so when set
-to a number indicating an offset from the right edge.  And when set to
-the symbol 'margin', it displays stamps in the right margin, although,
-at the moment, this is mostly intended for use by other modules, such
-as 'fill-wrap', described above.  For both these variants, users of
-the 'log' module may want to customize 'erc-log-filter-function' to
+to a number indicating an offset from the right edge.  Users of the
+'log' module may want to customize 'erc-log-filter-function' to
 'erc-stamp-prefix-log-filter' to avoid ragged right-hand stamps
 appearing in their saved logs.
 
@@ -140,7 +138,7 @@ This is especially handy when using the option 
'erc-fill-wrap-merge'
 to omit repeated speaker tags, which can make message boundaries less
 detectable by tired eyes.
 
-** Some keybindings are now set by modules rather than their libraries.
+** Modules rather than their libraries set major-mode keybindings.
 To put it another way, simply loading a built-in module's library no
 longer modifies 'erc-mode-map'.  Instead, modifications occur during
 module setup.  This should not impact most user configs since ERC
@@ -149,7 +147,7 @@ previously created.  Note that while all affected bindings 
still
 reside in 'erc-mode-map', future built-in modules will use their own
 minor-mode maps, and new third-party modules should do the same.
 
-** The option 'erc-timestamp-format-right' has been deprecated.
+** Option 'erc-timestamp-format-right' deprecated.
 Having to account for this option prevented other ERC modules from
 easily determining what right-hand stamps would look like before
 insertion, which is knowledge needed for certain UI decisions.  The
@@ -198,6 +196,13 @@ these changes has been the deprecation of the ancient 
option
 'erc-truncate-buffer-on-save'.  Users of the 'log' module can achieve
 the same effect by issuing a "/CLEAR" at the prompt.
 
+** The 'truncate' module no longer enables logging automatically.
+Users expecting 'truncate' to perform logging based on the option
+'erc-enable-logging' need to instead add 'log' to 'erc-modules' for
+continued integration.  With the existing design, merely loading the
+library 'erc-log' caused 'truncate' to start writing logs, possibly
+against a user's wishes.
+
 ** Miscellaneous UX changes.
 Some minor quality-of-life niceties have finally made their way to
 ERC.  For example, the function 'erc-echo-timestamp' is now
@@ -227,7 +232,8 @@ Chiefly, 'rear-sticky' has been replaced by 'erc-command', 
which
 records the IRC command (or numeric) associated with a message.  Less
 impactfully, the value of the 'field' property for ERC's prompt has
 changed from 't' to the more useful 'erc-prompt', although the
-property of the same name has been retained.
+property of the same name has been retained and now has a value of
+'hidden' when disconnected.
 
 *** Members of insert- and send-related hooks have been reordered.
 Built-in and third-party modules rely on certain hooks for adjusting
@@ -253,12 +259,21 @@ versions.
 For starters, the 'cursor-sensor-functions' property no longer
 contains unique closures and thus no longer proves effective for
 traversing messages.  To compensate, a new property, 'erc-timestamp',
-now spans message bodies but not the newlines delimiting them.
-Somewhat relatedly, the function 'erc-insert-aligned' has been
-deprecated and removed from the primary client code path.
-Additionally, the 'stamp' module now merges its 'invisible' property
-with existing ones, when present, and it includes all white space
-around stamps when doing so.
+now spans message bodies but not the newlines delimiting them.  Also
+affecting the `stamp' module is the deprecation of the function
+'erc-insert-aligned' and its removal from client code.  Additionally,
+the module now merges its 'invisible' property with existing ones and
+includes all white space around stamps when doing so.
+
+This "propertizing" of surrounding white space also extends to all
+'stamp'-applied properties, like 'field', in all intervening space
+between message text and timestamps.  Technically, this constitutes a
+breaking change from the perspective of detecting a timestamp's
+bounds.  However, ERC has always propertized leading space before
+right-sided stamps on the same line as message text but not those
+folded onto the next line.  Such inconsistency made stamp detection
+overly complex and produced uneven results when toggling stamp
+visibility.
 
 *** The role of a module's Custom group is now more clearly defined.
 Associating built-in modules with Custom groups and provided library
@@ -270,7 +285,7 @@ encouraged to keep a module's name aligned with its group's 
as well as
 the provided feature of its containing library, if only for the usual
 reasons of namespace hygiene and discoverability.
 
-*** ERC now supports arbitrary CHANTYPES.
+*** ERC supports arbitrary CHANTYPES.
 Specifically, channels can be prefixed with any predesignated
 character, mainly to afford more flexibility to specialty services,
 like bridges to other protocols.
@@ -281,11 +296,20 @@ specify a subcommand to actually carry out anything of 
consequence.
 Built-in modules can now provide more detailed help for a particular
 subcommand by telling ERC to defer to a specialized handler.
 
-*** Longtime quasi modules have been made proper.
+*** Longtime quasi modules made proper.
 The 'fill' module is now defined by 'define-erc-module'.  The same
 goes for ERC's imenu integration, which has 'imenu' now appearing in
 the default value of 'erc-modules'.
 
+*** Hidden messages contain a preceding rather than trailing newline.
+ERC has traditionally only offered to hide messages involving fools,
+but plans are to make hiding more powerful.  Anyone depending on the
+existing behavior should be aware that hidden messages now start and
+end one character earlier, so that hidden line endings precede rather
+than follow accompanying text.  However, an escape hatch is available
+in the variable 'erc-legacy-invisible-bounds-p'.  It reinstates the
+old behavior, which is unsupported by newer modules and features.
+
 *** 'erc-display-message' optionally combines faces.
 Users may notice that ERC now inserts some important error messages in
 a combination of 'erc-error-face' and 'erc-notice-face'.  This is
@@ -299,11 +323,11 @@ third-party code, the key takeaway is that more 
'font-lock-face'
 properties encountered in the wild may be combinations of faces rather
 than lone ones.
 
-*** Prompt input is split before 'erc-pre-send-functions' has a say.
-Hook members are now treated to input whose lines have already been
-adjusted to fall within the allowed length limit.  For convenience,
-third-party code can request that the final input be "re-filled" prior
-to being sent.  See doc string for details.
+*** 'erc-pre-send-functions' visits prompt input post-split.
+ERC now adjusts input lines to fall within allowed length limits
+before showing hook members the result.  For compatibility,
+third-party code can request that the final input be adjusted again
+prior to being sent.  See doc string for details.
 
 *** ERC's prompt survives the insertion of user input and messages.
 Previously, ERC's prompt and its input marker disappeared while
@@ -311,18 +335,20 @@ running hooks during message insertion, and the position 
of its
 "insert marker" (ERC's per-buffer process mark) was inconsistent
 during these spells.  To make insertion handling more predictable in
 preparation for incorporating various protocol extensions, the prompt
-and its bounding markers have become perennial fixtures.  To effect
-this change, small behavioral differences in message insertion have
-been adopted.  Crucially, 'erc-insert-marker' now has an "insertion
-type" of t, and 'erc-display-line-1' now calls 'insert' instead of
-'insert-before-prompt'.  This allows user code to leave its own
-markers via 'erc-insert-modify-hook' and 'erc-insert-post-hook'
-instead of having to resort to workarounds.  Message insertion for
-outgoing messages, in 'erc-display-msg', remains as before.  In rare
-cases, these changes may mean third-party code needs tweaking, for
-example, requiring the use of 'insert-before-markers' instead of
-'insert'.  As always, users feeling unduly inconvenienced by these
-changes are encouraged to voice their concerns on the bug list.
+and its bounding markers have become perennial fixtures.
+
+To effect this change, small behavioral differences in message
+insertion have been adopted.  Crucially, 'erc-insert-marker' now has
+an "insertion type" of t, and 'erc-display-line-1' now calls 'insert'
+instead of 'insert-before-markers.  This allows user code running on
+'erc-insert-modify-hook' and 'erc-insert-post-hook' to leave its own
+markers at the actual insertion point instead of resorting to
+workarounds.  Message insertion for outgoing messages, in
+'erc-display-msg', remains as before.  In rare cases, these changes
+may mean third-party code needs tweaking, for example, requiring the
+use of 'insert-before-markers' instead of 'insert'.  As always, users
+feeling unduly inconvenienced by these changes are encouraged to voice
+their concerns on the bug list.
 
 *** Miscellaneous changes
 Two helper macros from GNU ELPA's Compat library are now available to
diff --git a/etc/HISTORY b/etc/HISTORY
index 8b80473e321..f6df3e6fe60 100644
--- a/etc/HISTORY
+++ b/etc/HISTORY
@@ -228,7 +228,10 @@ GNU Emacs 28.1 (2022-04-04) emacs-28.1
 
 GNU Emacs 28.2 (2022-09-12) emacs-28.2
 
-GNU Emacs 28.3 (2023-02-17) emacs-28.3
+GNU Emacs 28.3 (2023-02-17) emacs-28.3-rc1
+Was not actually released.
+
+GNU Emacs 29.1 (2023-07-30) emacs-29.1
 
 
 ----------------------------------------------------------------------
diff --git a/etc/MACHINES b/etc/MACHINES
index 8c6f3f48ce7..c9f6ec265d8 100644
--- a/etc/MACHINES
+++ b/etc/MACHINES
@@ -131,6 +131,26 @@ the list at the end of this file.
   The earliest release of Haiku that will successfully compile Emacs
   is R1/Beta2.  For windowing support, R1/Beta3 or later is required.
 
+** Android
+
+  Emacs is known to run on all Android versions from 2.2 onwards, on
+  Linux kernel 2.26.29 or later.
+
+  Android 2.2 has only been tested on ARM.  mips64 has not been
+  tested, but builds.  With these exceptions, Emacs is known to run on
+  all supported versions of Android on all supported machines: arm,
+  armv7, arm64, x86, x86_64, and mips.
+
+  See the file java/INSTALL for detailed installation instructions.
+
+  It is also possible to build Emacs for Android systems without using
+  GUI capabilities provided by the Android port.  We do not know
+  exactly which configurations this works on, but the installation
+  instructions for such a build should be the same as for any Unix
+  system.  (This does in turn imply that such a build must be carried
+  out on an Android device itself utilizing development tools provided
+  by third party package repositories.)
+
 
 * Obsolete platforms
 
diff --git a/etc/NEWS b/etc/NEWS
index 3cfc36e10da..9a98db8c83a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -24,6 +24,22 @@ applies, and please also update docstrings as needed.
 
 * Installation Changes in Emacs 30.1
 
+** Emacs has been ported to the Android operating system.
+This requires Emacs to be compiled on another computer.  The Android
+NDK, SDK, and a suitable Java compiler must also be installed.
+
+See the file 'java/INSTALL' for more details.
+
+---
+** Emacs now defaults to ossaudio library for sound on NetBSD and OpenBSD.
+Previously configure used ALSA libraries if installed on the
+system when configured '--with-sound=yes' (which is the default), with
+fallback to libossaudio.  The libossaudio library included with the
+base system is now used even if ALSA is found to avoid relying on
+external packages and to resolve potential incompatibilities between
+Linux and BSD versions of ALSA.  Use '--with-sound=alsa' to build with
+ALSA on these operating systems instead.
+
 
 * Startup Changes in Emacs 30.1
 
@@ -52,6 +68,12 @@ compositing manager, Emacs will now redisplay such a frame 
even though
 'frame-visible-p' returns nil or 'icon' for it.  This can happen, for
 example, as part of preview for iconified frames.
 
+---
+** New user option 'menu-bar-close-window'.
+When non-nil, selecting "Close" from the "File" menu or clicking
+"Close" in the tool bar will result in the current window being
+closed, if possible.
+
 +++
 ** 'write-region-inhibit-fsync' now defaults to t in interactive mode,
 as it has in batch mode since Emacs 24.
@@ -92,12 +114,31 @@ plus, minus, check-mark, start, etc.
 The 'tool-bar-position' frame parameter can be set to 'bottom' on all
 window systems other than Nextstep.
 
++++
+** New global minor mode 'modifier-bar-mode'.
+When this minor mode is enabled, buttons representing modifier keys
+are displayed along the tool bar.
+
+---
+** New user option 'uniquify-dirname-transform'.
+This can be used to customize how buffer names are uniquified, by
+making arbitrary transforms on the buffer's directory name (whose
+components are used to uniquify buffer names when they clash).  You
+can use this to distinguish between buffers visiting files with the
+same base name that belong to different projects by using the provided
+transform function 'project-uniquify-dirname-transform'.
+
 ** cl-print
+
 *** You can expand the "..." truncation everywhere.
-The code that allowed "..." to be expanded in the *Backtrace* should
-now work anywhere the data is generated by `cl-print`.
+The code that allowed "..." to be expanded in the "*Backtrace*" buffer
+should now work anywhere the data is generated by 'cl-print'.
 
-*** hash-tables' contents can be expanded via the ellipsis
+*** The 'backtrace-ellipsis' button is replaced by 'cl-print-ellipsis'.
+
+*** hash-tables' contents can be expanded via the ellipsis.
+
+*** Modes can control the expansion via 'cl-print-expand-ellipsis-function'.
 
 ** Modeline elements can now be right-aligned.
 Anything following the symbol 'mode-line-format-right-align' in
@@ -105,9 +146,45 @@ Anything following the symbol 
'mode-line-format-right-align' in
 right-aligned to is controlled by the new user option
 'mode-line-right-align-edge'.
 
+** Tab Bars and Tab Lines
+
+*** New user option 'tab-bar-tab-name-format-functions'.
+It can be used to add, remove and reorder functions that change
+the appearance of every tab on the tab bar.
+
 
 * Editing Changes in Emacs 30.1
 
+---
+** New global minor mode 'kill-ring-deindent-mode'.
+When enabled, text being saved to the kill ring will be de-indented by
+the column number at its start.  For example, saving the entire
+function call within:
+
+foo ()
+{
+  long_function_with_several_arguments (argument_1_compute (),
+                                       argument_2_compute (),
+                                       argument_3_compute ());
+}
+
+will save:
+
+long_function_with_several_arguments (argument_1_compute (),
+                                     argument_2_compute (),
+                                     argument_3_compute ())
+
+to the kill ring, omitting the two columns of extra indentation that
+would otherwise be present in the second and third lines of the
+function call.
+
++++
+** Emacs now has better support for touchscreen devices.
+Many touch screen gestures are now implemented and translated into
+mouse or gesture events, and support for tapping tool bar buttons and
+opening menus has been written.  Countless packages, such as Dired and
+Custom have been adjusted to better understand touch screen input.
+
 ---
 ** On X, Emacs now supports input methods which perform "string conversion".
 This means an input method can now ask Emacs to delete text
@@ -125,6 +202,36 @@ confirmation.
 It controls the placement of point and the region after duplicating a
 region with 'duplicate-dwim'.
 
++++
+** New user option 'mouse-prefer-closest-glyph'.
+When enabled, clicking or dragging with the mouse will put the point
+or start the drag in front of the buffer position corresponding to the
+glyph with the closest X coordinate to the click or start of the drag.
+In other words, if the mouse pointer is in the right half of a glyph,
+point will be put after the buffer position corresponding to that glyph,
+whereas if the mouse pointer is in the left half of a glyph, point
+will be put in front the buffer position corresponding to that glyph.
+By default this is disabled.
+
+** Internationalization
+
+---
+*** Users in CJK locales can control width of some non-CJK characters.
+Some characters are considered by Unicode as "ambiguous" with respect
+to their display width: either "full-width" (i.e. taking 2 columns on
+display) or "narrow" (taking 1 column).  The actual width depends on
+the fonts used for these characters by Emacs or (for text-mode frames)
+by the terminal emulator.  Traditionally, font sets in CJK locales
+were set up so as to display these characters as full-width, and thus
+Emacs modified the char-width table in those locales to follow suit.
+Lately, the tendency is to display these characters as narrow.  The
+new user option 'cjk-ambiguous-chars-are-wide' allows users to control
+whether Emacs considers these characters as full-width (the default)
+or narrow (if the variable is customized to the nil value).
+
+This setting affects the results of 'string-width' and similar
+functions in CJK locales.
+
 
 * Changes in Specialized Modes and Packages in Emacs 30.1
 
@@ -194,6 +301,15 @@ using this new option.  (Or set 'display-buffer-alist' 
directly.)
 
 ** Eshell
 
++++
+*** New builtin Eshell command 'compile'.
+This command runs another command, sending its output to a compilation
+buffer when the command would output interactively.  This can be useful
+when defining aliases so that they produce a compilation buffer when
+appropriate, but still allow piping the output elsewhere if desired.
+For more information, see the "(eshell) Built-ins" node in the Eshell
+manual.
+
 +++
 *** New splice operator for Eshell dollar expansions.
 Dollar expansions in Eshell now let you splice the elements of the
@@ -202,6 +318,13 @@ of arguments into a command, such as when defining 
aliases.  For more
 information, see the "(eshell) Dollars Expansion" node in the Eshell
 manual.
 
++++
+*** You can now splice Eshell globs in-place into argument lists.
+By setting 'eshell-glob-splice-results' to a non-nil value, Eshell
+will expand glob results in-place as if you had typed each matching
+file name individually.  For more information, see the "(eshell)
+Globbing" node in the Eshell manual.
+
 +++
 *** Eshell now supports negative numbers and ranges for indices.
 Now, you can retrieve the last element of a list with '$my-list[-1]'
@@ -212,7 +335,7 @@ Eshell manual.
 +++
 *** Eshell commands can now be explicitly-remote (or local).
 By prefixing a command name in Eshell with a remote identifier, like
-"/ssh:user@remote:whoami", you can now runs commands on a particular
+"/ssh:user@remote:whoami", you can now run commands on a particular
 host no matter your current directory.  Likewise, you can run a
 command on your local system no matter your current directory via
 "/:whoami".  For more information, see the "(eshell) Remote Access"
@@ -247,6 +370,14 @@ to load the edited aliases.
 Running 'rgrep' in Eshell now uses the Emacs grep facility instead of
 calling external rgrep.
 
+** Pcomplete
+
+---
+*** New user option 'pcomplete-remote-file-ignore'.
+When this option is non-nil, remote file names are not completed by
+Pcomplete.  Packages, like 'shell-mode', could set this in order to
+suppress remote file name completion at all.
+
 ** Shell Mode
 
 +++
@@ -255,6 +386,13 @@ When this user option is non-nil, 'shell-get-old-input' 
('C-RET')
 includes multiple shell "\" continuation lines from command output.
 Default is nil.
 
+** Make mode
+
+*** The Makefile browser is now obsolete.
+The command 'makefile-switch-to-browser' command is now obsolete,
+together with related commands used in the "*Macros and Targets*"
+buffer.  We recommend using an alternative like 'imenu' instead.
+
 ** Prog Mode
 
 +++
@@ -264,6 +402,8 @@ docstring, or a comment, or (re)indents the surrounding 
defun if
 point is not in a comment or a string.  It is by default bound to
 'M-q' in 'prog-mode' and all its descendants.
 
+** Which Function Mode
+
 +++
 *** Which Function Mode can now display function names on the header line.
 The new user option 'which-func-display' allows choosing where the
@@ -300,6 +440,19 @@ sessions, respectively.
 It allows to kill only selected remote buffers, controlled by user
 option 'tramp-cleanup-some-buffers-hook'.
 
++++
+*** New command 'inhibit-remote-files'.
+This command disables the handling of file names with the special
+remote file name syntax.  It should be applied only when remote files
+won't be used in this Emacs instance.  It provides a slightly improved
+performance of file name handling in Emacs.
+
++++
+*** New macro 'without-remote-files'.
+This macro could wrap code which handles local files only.  Due to the
+temporary deactivation of remote files, it results in a slightly
+improved performance of file name handling in Emacs.
+
 ** EWW
 
 +++
@@ -327,6 +480,13 @@ the kill ring.  Alternate links are optional metadata that 
HTML pages
 use for linking to their alternative representations, such as
 translated versions or associated RSS feeds.
 
++++
+*** 'eww-open-in-new-buffer' supports the prefix argument.
+When invoked with the prefix argument ('C-u'),
+'eww-open-in-new-buffer' will not make the new buffer the current one.
+This is useful for continuing reading the URL in the current buffer
+when the new URL is fetched.
+
 ** go-ts-mode
 
 +++
@@ -352,6 +512,12 @@ this new face when DocView displays documents, customize 
this face to
 restore the colors you were used to, or to get colors more to your
 liking.
 
+---
+*** DocView buffers now display a new tool bar.
+This tool bar contains options for searching and navigating within the
+document, replacing the incompatible items for incremental search and
+editing within the default tool bar displayed in the past.
+
 ** Shortdoc
 
 +++
@@ -379,9 +545,18 @@ project, that you can quickly select using 
'project-switch-project'
 ('C-x p p').
 
 ---
-*** New user option 'package-vc-allow-side-effects'.
-When non-nil, package specifications with side-effects for building
-software will be used when building a package.
+*** New user option 'package-vc-allow-build-commands'.
+Controls for which packages Emacs runs extra build commands when
+installing directly from the package VCS repository.
+
+---
+*** New command to start an inferior Emacs loading only specific packages.
+The new command 'package-isolate' will start a new Emacs process, as
+a sub-process of Emacs where you invoke the command, in a way that
+causes the new process to load only some of the installed packages.
+The command prompts for the packages to activate in this
+sub-process, and is intended for testing Emacs and/or the packages
+in a clean environment.
 
 ** Flymake
 
@@ -411,6 +586,10 @@ instead of:
         and another_expression):
         do_something()
 
+*** New user option 'python-interpreter-args'.
+This allows the user to specify command line arguments to the non
+interactive Python interpreter specified by 'python-interpreter'.
+
 ** use-package
 
 +++
@@ -517,6 +696,21 @@ Similarly to buffer restoration by Desktop, 'recentf-mode' 
checking
 of the accessibility of remote files can now time out if
 'remote-file-name-access-timeout' is set to a positive number.
 
+** Notifications
+
++++
+*** Allow to use Icon Naming Specification for ':app-icon'.
+You can use a symbol as the value for ':app-icon' to provide icon name
+without specifying a file, like this:
+
+    (notifications-notify
+      :title "I am playing music" :app-icon 'multimedia-player)
+
+** Image Dired
+
+*** New user option 'image-dired-thumb-naming'.
+You can now configure how a thumbnail is named using this option.
+
 
 * New Modes and Packages in Emacs 30.1
 
@@ -548,6 +742,17 @@ the needs of users with red-green or blue-yellow color 
deficiency.
 The Info manual "(modus-themes) Top" describes the details and
 showcases all their customization options.
 
+** Project
+
+*** New user option 'project-file-history-behavior'.
+Customizing it to 'relativize' makes commands like 'project-find-file'
+and 'project-find-dir' display previous history entries relative to
+the current project.
+
+*** New user option 'project-key-prompt-style'.
+The look of the key prompt in the project switcher has been changed
+slightly.  To get the previous one, set this option to 'brackets'.
+
 
 * Incompatible Lisp Changes in Emacs 30.1
 
@@ -598,7 +803,16 @@ behavior back for any other reason, you can do that using 
the
 'coding-system-put' function.  For example, the following restores the
 previous behavior of showing 'U' in the mode line for 'koi8-u':
 
-     (coding-system-put 'koi8-u :mnemonic ?U)
+    (coding-system-put 'koi8-u :mnemonic ?U)
+
+---
+** 'vietnamese-tcvn' is now a coding system alias for 'vietnamese-vscii'.
+VSCII-1 and TCVN-5712 are different names for the same character
+encoding.  Therefore, the duplicate coding system definition has been
+dropped in favor of an alias.
+
+The mode-line mnemonic for 'vietnamese-vscii' and its aliases is the
+lowercase letter 'v'.
 
 +++
 ** Infinities and NaNs no longer act as symbols on non-IEEE platforms.
@@ -607,16 +821,77 @@ tokens like 0.0e+NaN and 1.0e+INF are no longer read as 
symbols.
 Instead, the Lisp reader approximates an infinity with the nearest
 finite value, and a NaN with some other non-numeric object that
 provokes an error if used numerically.
+
++++
+** X color support compatibility aliases are now marked obsolete.
+The compatibility aliases 'x-defined-colors', 'x-color-defined-p',
+'x-color-values', and 'x-display-color-p' are now obsolete.
+
++++
+** 'easy-mmode-define-{minor,global}-mode' aliases are now obsolete.
+Use 'define-minor-mode' and 'define-globalized-minor-mode' instead.
+
 
 * Lisp Changes in Emacs 30.1
 
+** 'defadvice' is marked as obsolete.
+See the "(elisp) Porting Old Advice" node for help converting them
+to use 'advice-add' or 'define-advice' instead.
+
++++
+** Desktop notifications are now supported on the Haiku operating system.
+The new function 'haiku-notifications-notify' provides a subset of the
+capabilities of the 'notifications-notify' function in a manner
+analogous to 'w32-notification-notify'.
+
++++
+** New value 'if-regular' for the REPLACE argument to 'insert-file-contents'.
+It results in 'insert-file-contents' erasing the buffer instead of
+preserving markers if the file being inserted is not a regular file,
+rather than signaling an error.
+
++++
+** New variable 'current-key-remap-sequence'.
+It is bound to the key sequence that caused a call to a function bound
+within 'function-key-map' or 'input-decode-map' around those calls.
+
++++
+** New variables describing the names of built in programs.
+The new variables 'ctags-program-name', 'ebrowse-program-name',
+'etags-program-name', 'hexl-program-name', 'emacsclient-program-name'
+'movemail-program-name', and 'rcs2log-program-name' should be used
+instead of "ctags", "ebrowse", "etags", "hexl", "emacsclient", and
+"rcs2log", when starting one of these built in programs in a
+subprocess.
+
++++
+** 'x-popup-menu' now understands touch screen events.
+When a 'touchscreen-begin' or 'touchscreen-end' event is passed as the
+POSITION argument, it will behave as if that event was a mouse event.
+
 +++
+** New functions for handling touch screen events.
+The new functions 'touch-screen-track-tap' and
+'touch-screen-track-drag' handle tracking common touch screen gestures
+from within a command.
+
 ** New user option 'safe-local-variable-directories'.
 This user option names directories in which Emacs will treat all
 directory-local variables as safe.
 
++++
+** New parameter to 'touchscreen-end' events.
+CANCEL non-nil establishes that the touch sequence has been
+intercepted by programs such as window managers and should be ignored
+with Emacs.
+
 ** New variable 'inhibit-auto-fill' to temporarily prevent auto-fill.
 
++++
+** New variable 'secondary-tool-bar-map'.
+If non-nil, this variable contains a keymap of menu items that are
+displayed along tool bar items inside 'tool-bar-map'.
+
 ** Functions and variables to transpose sexps
 
 +++
diff --git a/etc/NEWS.28 b/etc/NEWS.28
index 6ffe39b9493..89ef25bb32d 100644
--- a/etc/NEWS.28
+++ b/etc/NEWS.28
@@ -3150,7 +3150,7 @@ declared obsolete.
 This was a compatibility kludge which is no longer needed.
 
 ** Some libraries obsolete since Emacs 23 have been removed:
-ledit.el, lmenu.el, lucid.el and old-whitespace.el.
+ledit.el, levents.el, lmenu.el, lucid.el and old-whitespace.el.
 
 ** Some functions and variables obsolete since Emacs 23 have been removed:
 'GOLD-map', 'advertised-xscheme-send-previous-expression',
diff --git a/etc/NEWS.29 b/etc/NEWS.29
index 9e6f0c16bcd..e74cbee4a53 100644
--- a/etc/NEWS.29
+++ b/etc/NEWS.29
@@ -22,9 +22,42 @@ When you add a new item, use the appropriate mark if you are 
sure it
 applies, and please also update docstrings as needed.
 
 
+* Installation Changes in Emacs 29.2
+
+
+* Startup Changes in Emacs 29.2
+
+
+* Changes in Emacs 29.2
+
+
+* Editing Changes in Emacs 29.2
+
+
+* Changes in Specialized Modes and Packages in Emacs 29.2
+
+** Tramp
+
++++
+*** New user option 'tramp-show-ad-hoc-proxies'.
+When non-nil, ad-hoc definitions are kept in remote file names instead
+of showing the shortcuts.
+
+
+* New Modes and Packages in Emacs 29.2
+
+
+* Incompatible Lisp Changes in Emacs 29.2
+
+
+* Lisp Changes in Emacs 29.2
+
+
+* Changes in Emacs 29.2 on Non-Free Operating Systems
+
+
 * Installation Changes in Emacs 29.1
 
----
 ** Ahead-of-time native compilation can now be requested via configure.
 Use '--with-native-compilation=aot' to request that all the Lisp files
 in the Emacs tree should be natively compiled ahead of time.  (This is
@@ -32,7 +65,6 @@ slow on most machines.)
 
 This feature existed in Emacs 28.1, but was less easy to request.
 
-+++
 ** Emacs can be built with the tree-sitter parsing library.
 This library, together with separate grammar libraries for each
 language, provides incremental parsing capabilities for several
@@ -85,26 +117,22 @@ available from their sites, as these libraries are in 
constant
 development and occasionally add features and fix important bugs to
 follow the advances in the programming languages they support.
 
-+++
 ** Emacs can be built with built-in support for accessing SQLite databases.
 This uses the popular sqlite3 library, and can be disabled by using
 the '--without-sqlite3' option to the 'configure' script.
 
-+++
 ** Support for the WebP image format.
 This support is built by default when the libwebp library is
 available, and includes support for animated WebP images.  To disable
 WebP support, use the '--without-webp' configure flag.  Image
 specifiers can now use ':type webp'.
 
----
 ** Emacs now installs the ".pdmp" file using a unique fingerprint in the name.
 The file is typically installed using a file name akin to
 "...dir/libexec/emacs/29.1/x86_64-pc-linux-gnu/emacs-<fingerprint>.pdmp".
 If a constant file name is required, the file can be renamed to
 "emacs.pdmp", and Emacs will find it during startup anyway.
 
----
 ** Emacs on X now uses XInput 2 for input events.
 If your X server has support and you have the XInput 2 development
 headers installed, Emacs will use the X Input Extension for handling
@@ -114,7 +142,6 @@ option '--without-xinput2' to disable this support.
 '(featurep 'xinput2)' can be used to test for the presence of XInput 2
 support from Lisp programs.
 
----
 ** Emacs can now be optionally built with the Cairo XCB backend.
 Configure Emacs with the '--with-cairo-xcb' option to use the Cairo
 XCB backend; the default is not to use it.  This backend makes Emacs
@@ -124,7 +151,6 @@ a display connection to the same terminal; this could 
happen, for
 example, if you repeatedly visit files via emacsclient in a single
 client frame, each time deleting the frame with 'C-x C-c'.
 
-+++
 ** Emacs now supports being built with pure GTK.
 To use this option, make sure the GTK 3 (version 3.22.23 or later) and
 Cairo development files are installed, and configure Emacs with the
@@ -142,7 +168,6 @@ automatically switch to text-mode interface (thus emulating 
'-nw') if
 it cannot determine the default display; it will instead complain and
 ask you to invoke it with the explicit '-nw' option.
 
-+++
 ** Emacs has been ported to the Haiku operating system.
 The configuration process should automatically detect and build for
 Haiku.  There is also an optional window-system port to Haiku, which
@@ -160,7 +185,6 @@ Unlike X, there is no compile-time option to enable or 
disable
 double-buffering; it is always enabled.  To disable it, change the
 frame parameter 'inhibit-double-buffering' instead.
 
----
 ** Emacs no longer reduces the size of the Japanese dictionary.
 Building Emacs includes generation of a Japanese dictionary, which is
 used by Japanese input methods.  Previously, the build included a step
@@ -175,7 +199,6 @@ by saying
 
 after deleting "lisp/leim/ja-dic/ja-dic.el".
 
----
 ** The docstrings of preloaded files are not in "etc/DOC" any more.
 Instead, they're fetched as needed from the corresponding ".elc"
 files, as was already the case for all the non-preloaded files.
@@ -183,7 +206,6 @@ files, as was already the case for all the non-preloaded 
files.
 
 * Startup Changes in Emacs 29.1
 
-+++
 ** '--batch' and '--script' now adjust the garbage collection levels.
 These switches now set 'gc-cons-percentage' to 1.0 (up from the
 default of 0.1).  This means that batch processes will typically use
@@ -191,7 +213,6 @@ more memory than before, but use less time doing garbage 
collection.
 Batch jobs that are supposed to run for a long time should adjust the
 limit back down again.
 
-+++
 ** Emacs can now be used more easily in an executable script.
 If you start an executable script with
 
@@ -202,16 +223,13 @@ and then execute the rest of the script file as Emacs 
Lisp.  When it
 reaches the end of the script, Emacs will exit with an exit code from
 the value of the final form.
 
-+++
 ** Emacs now supports setting 'user-emacs-directory' via '--init-directory'.
 Use the '--init-directory' command-line option to set
 'user-emacs-directory'.
 
-+++
 ** Emacs now has a '--fingerprint' option.
 This will output a string identifying the current Emacs build, and exit.
 
-+++
 ** New hook 'after-pdump-load-hook'.
 This is run at the end of the Emacs startup process, and is meant to
 be used to reinitialize data structures that would normally be done at
@@ -219,14 +237,12 @@ load time.
 
 ** Native Compilation
 
-+++
 *** New command 'native-compile-prune-cache'.
 This command deletes old subdirectories of the eln cache (but not the
 ones for the current Emacs version).  Note that subdirectories of the
 system directory where the "*.eln" files are installed (usually, the
 last entry in 'native-comp-eln-load-path') are not deleted.
 
-+++
 *** New function 'startup-redirect-eln-cache'.
 This function can be called in your init files to change the
 user-specific directory where Emacs stores the "*.eln" files produced
@@ -237,7 +253,6 @@ of 'user-emacs-directory'.
 
 * Incompatible changes in Emacs 29.1
 
-+++
 ** The image commands have changed key bindings.
 In previous Emacs versions, the '+', '-' and 'r' keys were bound when
 point was over an image.  In Emacs 29.1, additional commands have been
@@ -247,7 +262,6 @@ moved to the 'i' prefix keymap, so '+' is now 'i +', '-' is 
now 'i -',
 and 'r' is now 'i r'.  In addition, these commands are now repeating,
 so you can rotate an image twice by saying 'i r r', for instance.
 
-+++
 ** Emacs now picks the correct coding-system for X input methods.
 Previously, Emacs would use 'locale-coding-system' for input
 methods, which could in some circumstances be incorrect, especially
@@ -260,7 +274,6 @@ changed the coding system used to decode X keyboard input 
must adjust
 their customizations to 'locale-coding-system' to the variable
 'x-input-coding-system' instead.
 
-+++
 ** Bookmarks no longer include context for encrypted files.
 If you're visiting an encrypted file, setting a bookmark no longer
 includes excerpts from that buffer in the bookmarks file.  This is
@@ -268,7 +281,6 @@ implemented by the new hook 
'bookmark-inhibit-context-functions',
 where packages can register a function which returns non-nil for file
 names to be excluded from adding such excerpts.
 
----
 ** 'show-paren-mode' is now disabled in 'special-mode' buffers.
 In Emacs versions previous to Emacs 28.1, 'show-paren-mode' defaulted
 off.  In Emacs 28.1, the mode was switched on in all buffers.  In
@@ -279,7 +291,6 @@ init file:
 
     (setopt show-paren-predicate t)
 
-+++
 ** Explicitly-set read-only state is preserved when reverting a buffer.
 If you use the 'C-x C-q' command to change the read-only state of the
 buffer and then revert it, Emacs would previously use the file
@@ -287,14 +298,12 @@ permission bits to determine whether the buffer should be 
read-only
 after reverting the buffer.  Emacs now remembers the decision made in
 'C-x C-q'.
 
----
 ** The Gtk selection face is no longer used for the region.
 The combination of a Gtk-controlled background and a foreground color
 controlled by the internal Emacs machinery led to low-contrast faces
 in common default setups.  Emacs now uses the same 'region' face on
 Gtk and non-Gtk setups.
 
----
 ** 'C-h f' and 'C-h x' may now require confirmation when you press 'RET'.
 If the text in the minibuffer cannot be completed to a single function
 or command, typing 'RET' will not automatically complete to the shortest
@@ -306,12 +315,10 @@ second 'RET'.
 
 ** Dired
 
----
 *** 'w' ('dired-copy-filename-as-kill') has changed behavior.
 If there are several files marked, file names containing space and
 quote characters will be quoted "like this".
 
----
 *** The 'd' command now more consistently skips dot files.
 In previous Emacs versions, commands like 'C-u 10 d' would put the "D"
 mark on the next ten files, no matter whether they were dot files
@@ -319,10 +326,8 @@ mark on the next ten files, no matter whether they were 
dot files
 mouse (in 'transient-mark-mode') and then hitting 'd' would skip dot
 files.  These now work equivalently.
 
-+++
 ** Warning about "eager macro-expansion failure" is now an error.
 
----
 ** Previously, the X "reverseVideo" value at startup was heeded for all frames.
 This meant that if you had a "reverseVideo" resource on the initial
 display, and then opened up a new frame on a display without any
@@ -330,26 +335,21 @@ explicit "reverseVideo" setting, it would get heeded 
there, too.  (This
 included terminal frames.)  In Emacs 29, the "reverseVideo" X resource
 is handled like all the other X resources, and set on a per-frame basis.
 
-+++
 ** 'E' in 'query-replace' now edits the replacement with exact case.
 Previously, this command did the same as 'e'.
 
----
 ** '/ a' in "*Packages*" buffer now limits by archive name(s) instead of 
regexp.
 
-+++
 ** Setting the goal columns now also affects '<prior>' and '<next>'.
 Previously, 'C-x C-n' only affected 'next-line' and 'previous-line',
 but it now also affects 'scroll-up-command' and 'scroll-down-command'.
 
----
 ** Isearch in "*Help*" and "*info*" now char-folds quote characters by default.
 This means that you can say 'C-s `foo' (GRAVE ACCENT) if the buffer
 contains "‘foo" (LEFT SINGLE QUOTATION MARK) and the like.  These
 quotation characters look somewhat similar in some fonts.  To switch
 this off, disable the new 'isearch-fold-quotes-mode' minor mode.
 
----
 ** Sorting commands no longer necessarily change modification status.
 In earlier Emacs versions, commands like 'sort-lines' would always
 change buffer modification status to "modified", whether they changed
@@ -357,19 +357,16 @@ something in the buffer or not.  This has been changed: 
the buffer is
 marked as modified only if the sorting ended up actually changing the
 contents of the buffer.
 
----
 ** 'string-lines' handles trailing newlines differently.
 It no longer returns an empty final string if the string ends with a
 newline.
 
----
 ** 'TAB' and '<backtab>' are now bound in 'button-map'.
 This means that if point is on a button, 'TAB' will take you to the
 next button, even if the mode has bound it to something else.  This
 also means that 'TAB' on a button in an 'outline-minor-mode' heading
 will move point instead of collapsing the outline.
 
----
 ** 'outline-minor-mode-cycle-map' is now parent of 'outline-minor-mode'.
 Instead of adding text property 'keymap' with 'outline-minor-mode-cycle'
 on outline headings in 'outline-minor-mode', the keymap
@@ -378,12 +375,10 @@ But keybindings in 'outline-minor-mode-cycle' still take 
effect
 only on outline headings because they are bound with the help of
 'outline-minor-mode-cycle--bind' that checks if point is on a heading.
 
----
 ** 'Info-default-directory-list' is no longer populated at Emacs startup.
 If you have code in your init file that removes directories from
 'Info-default-directory-list', this will no longer work.
 
----
 ** 'C-k' no longer deletes files in 'ido-mode'.
 To get the previous action back, put something like the following in
 your Init file:
@@ -391,14 +386,12 @@ your Init file:
     (require 'ido)
     (keymap-set ido-file-completion-map "C-k" #'ido-delete-file-at-head)
 
----
 ** New user option 'term-clear-full-screen-programs'.
 By default, term.el will now work like most terminals when displaying
 full-screen programs: When they exit, the output is cleared, leaving
 what was displayed in the window before the programs started.  Set
 this user option to nil to revert back to the old behavior.
 
----
 ** Support for old EIEIO functions is not autoloaded any more.
 You need an explicit '(require 'eieio-compat)' to use 'defmethod'
 and 'defgeneric' (which were made obsolete in Emacs 25.1 by
@@ -406,11 +399,9 @@ and 'defgeneric' (which were made obsolete in Emacs 25.1 by
 Similarly you might need to '(require 'eieio-compat)' before loading
 files that were compiled with an old EIEIO (Emacs<25).
 
----
 ** 'C-x 8 .' has been moved to 'C-x 8 . .'.
 This is to open up the 'C-x 8 .' map to bind further characters there.
 
----
 ** 'C-x 8 =' has been moved to 'C-x 8 = ='.
 You can now use 'C-x 8 =' to insert several characters with macron;
 for example, 'C-x 8 = a' will insert U+0101 LATIN SMALL LETTER A WITH
@@ -424,12 +415,10 @@ For consistency with remote connections, Eshell now uses 
'exec-path'
 to determine the execution path on the local or remote system, instead
 of using the PATH environment variable directly.
 
----
 *** 'source' and '.' no longer accept the '--help' option.
 This is for compatibility with the shell versions of these commands,
 which don't handle options like '--help' in any special way.
 
-+++
 *** String delimiters in argument predicates/modifiers are more restricted.
 Previously, some argument predicates/modifiers allowed arbitrary
 characters as string delimiters.  To provide more unified behavior
@@ -438,11 +427,9 @@ been restricted to "...", '...', /.../, |...|, (...), 
[...], <...>,
 and {...}.  See the "(eshell) Argument Predication and Modification"
 node in the Eshell manual for more details.
 
-+++
 *** Eshell pipelines now only pipe stdout by default.
 To pipe both stdout and stderr, use the '|&' operator instead of '|'.
 
----
 ** The 'delete-forward-char' command now deletes by grapheme clusters.
 This command is by default bound to the '<Delete>' function key
 (a.k.a. '<deletechar>').  When invoked without a prefix argument or
@@ -452,12 +439,10 @@ example, if point is before an Emoji sequence, pressing 
'<Delete>'
 will delete the entire sequence, not just a single character at its
 beginning.
 
-+++
 ** 'load-history' does not treat autoloads specially any more.
 An autoload definition appears just as a '(defun . NAME)' and the
 '(t . NAME)' entries are not generated any more.
 
----
 ** The Tamil input methods no longer insert Tamil digits.
 The input methods 'tamil-itrans' and 'tamil-inscript' no longer insert
 the Tamil digits, as those digit characters are not used nowadays by
@@ -465,7 +450,6 @@ speakers of the Tamil language.  To get back the previous 
behavior,
 use the new 'tamil-itrans-digits' and 'tamil-inscript-digits' input
 methods instead.
 
-+++
 ** New variable 'current-time-list' governing default timestamp form.
 Functions like 'current-time' now yield '(TICKS . HZ)' timestamps if
 this new variable is nil.  The variable defaults to t, which means
@@ -476,7 +460,6 @@ Developers are encouraged to test timestamp-related code 
with this
 variable set to nil, as it will default to nil in a future Emacs
 version and will be removed some time after that.
 
-+++
 ** Functions that recreate the "*scratch*" buffer now also initialize it.
 When functions like 'other-buffer' and 'server-execute' recreate
 "*scratch*", they now also insert 'initial-scratch-message' and set
@@ -484,7 +467,6 @@ the major mode according to 'initial-major-mode', like at 
Emacs
 startup.  Previously, these functions ignored
 'initial-scratch-message' and left "*scratch*" in 'fundamental-mode'.
 
----
 ** Naming of Image-Dired thumbnail files has changed.
 Names of thumbnail files generated when 'image-dired-thumbnail-storage'
 is 'image-dired' now always end in ".jpg".  This fixes various issues
@@ -493,25 +475,21 @@ will not be used in Emacs 29, and vice-versa.  If disk 
space is an
 issue, consider deleting the 'image-dired-dir' directory (usually
 "~/.emacs.d/image-dired/") after upgrading to Emacs 29.
 
----
 ** The 'rlogin' method in the URL library is now obsolete.
 Emacs will now display a warning if you request a URL like
 "rlogin://foo@example.org".
 
----
 ** Setting 'url-gateway-method' to 'rlogin' is now obsolete.
 Emacs will now display a warning when setting it to that value.
 The user options 'url-gateway-rlogin-host',
 'url-gateway-rlogin-parameters', and 'url-gateway-rlogin-user-name'
 are also obsolete.
 
----
 ** The user function 'url-irc-function' now takes a SCHEME argument.
 The user option 'url-irc-function' is now called with a sixth argument
 corresponding to the scheme portion of the target URL.  For example,
 this would be "ircs" for a URL like "ircs://irc.libera.chat".
 
----
 ** The linum.el library is now obsolete.
 We recommend using either the built-in 'display-line-numbers-mode', or
 the 'nlinum' package from GNU ELPA instead.  The former has better
@@ -536,40 +514,31 @@ performance, but the latter is closer to a drop-in 
replacement.
 
     (require 'linum)
 
----
 ** The thumbs.el library is now obsolete.
 We recommend using the 'image-dired' command instead.
 
----
 ** The autoarg.el library is now marked obsolete.
 This library provides the 'autoarg-mode' and 'autoarg-kp-mode' minor
 modes to emulate the behavior of the historical editor Twenex Emacs.
 We believe it is no longer useful.
 
----
 ** The quickurl.el library is now obsolete.
 Use 'abbrev', 'skeleton' or 'tempo' instead.
 
----
 ** The rlogin.el library, and the 'rsh' command are now obsolete.
 Use something like 'M-x shell RET ssh <host> RET' instead.
 
----
 ** The url-about.el library is now obsolete.
 
----
 ** The autoload.el library is now obsolete.
 It is superseded by the new loaddefs-gen.el library.
 
----
 ** The netrc.el library is now obsolete.
 Use the 'auth-source-netrc-parse-all' function in auth-source.el
 instead.
 
----
 ** The url-dired.el library is now obsolete.
 
----
 ** The fast-lock.el and lazy-lock.el libraries have been removed.
 They have been obsolete since Emacs 22.1.
 
@@ -577,7 +546,6 @@ The variable 'font-lock-support-mode' is occasionally 
useful for
 debugging purposes.  It is now a regular variable (instead of a user
 option) and can be set to nil to disable Just-in-time Lock mode.
 
-+++
 ** The 'utf-8-auto' coding-system now produces BOM on encoding.
 This is actually a bugfix, since this is how 'utf-8-auto' was
 documented from day one; it just didn't behave according to
@@ -594,7 +562,6 @@ encoding, only for decoding.
 
 * Changes in Emacs 29.1
 
-+++
 ** New user option 'major-mode-remap-alist' to specify favorite major modes.
 This user option lets you remap the default modes (e.g. 'perl-mode' or
 'latex-mode') to your favorite ones (e.g. 'cperl-mode' or
@@ -603,15 +570,12 @@ undesirable side effects.
 This applies to all modes specified via 'auto-mode-alist', file-local
 variables, etc.
 
----
 ** Emacs now supports Unicode Standard version 15.0.
 
----
 ** New user option 'electric-quote-replace-consecutive'.
 This allows you to disable the default behavior of consecutive single
 quotes being replaced with a double quote.
 
----
 ** Emacs is now capable of editing files with very long lines.
 The display of long lines has been optimized, and Emacs should no
 longer choke when a buffer on display contains long lines.  The
@@ -648,7 +612,6 @@ access portions of the buffer outside of the narrowed 
region.
 The new function 'long-line-optimizations-p' returns non-nil when
 these optimizations are in effect in the current buffer.
 
-+++
 ** New command to change the font size globally.
 To increase the font size, type 'C-x C-M-+' or 'C-x C-M-='; to
 decrease it, type 'C-x C-M--'; to restore the font size, type 'C-x
@@ -660,7 +623,6 @@ increase and decrease the font size globally.  
Additionally, the
 user option 'global-text-scale-adjust-resizes-frames' controls whether
 the frames are resized when the font size is changed.
 
----
 ** New config variable 'syntax-wholeline-max' to reduce the cost of long lines.
 This variable is used by some operations (mostly syntax-propertization
 and font-locking) to treat lines longer than this variable as if they
@@ -672,13 +634,11 @@ can recover the previous behavior with:
 
     (setq syntax-wholeline-max most-positive-fixnum)
 
----
 ** New bindings in 'find-function-setup-keys' for 'find-library'.
 When 'find-function-setup-keys' is enabled, 'C-x L' is now bound to
 'find-library', 'C-x 4 L' is now bound to 'find-library-other-window'
 and 'C-x 5 L' is now bound to 'find-library-other-frame'.
 
-+++
 ** New key binding after 'M-x' or 'M-X': 'M-X'.
 Emacs allows different completion predicates to be used with 'M-x'
 (i.e., 'execute-extended-command') via the
@@ -688,10 +648,8 @@ especially relevant to the current buffer.  Emacs now 
allows toggling
 between these modes while the user is inputting a command by hitting
 'M-X' while in the minibuffer.
 
----
 ** Interactively, 'kill-buffer' will now offer to save the buffer if unsaved.
 
----
 ** New commands 'duplicate-line' and 'duplicate-dwim'.
 'duplicate-line' duplicates the current line the specified number of times.
 'duplicate-dwim' duplicates the region if it is active.  If not, it
@@ -700,10 +658,8 @@ duplicated on its right-hand side.  The new user option
 'duplicate-line-final-position' specifies where to move point
 after duplicating a line.
 
----
 ** Files with the ".eld" extension are now visited in 'lisp-data-mode'.
 
-+++
 ** 'network-lookup-address-info' can now check numeric IP address validity.
 Specifying 'numeric' as the new optional HINTS argument makes it
 check if the passed address is a valid IPv4/IPv6 address (without DNS
@@ -712,23 +668,19 @@ traffic).
     (network-lookup-address-info "127.1" 'ipv4 'numeric)
     => ([127 0 0 1 0])
 
-+++
 ** New command 'find-sibling-file'.
 This command jumps to a file considered a "sibling file", which is
 determined according to the new user option 'find-sibling-rules'.
 
-+++
 ** New user option 'delete-selection-temporary-region'.
 When non-nil, 'delete-selection-mode' will only delete the temporary
 regions (usually set by mouse-dragging or shift-selection).
 
-+++
 ** New user option 'switch-to-prev-buffer-skip-regexp'.
 This should be a regexp or a list of regexps; buffers whose names
 match those regexps will be ignored by 'switch-to-prev-buffer' and
 'switch-to-next-buffer'.
 
-+++
 ** New command 'rename-visited-file'.
 This command renames the file visited by the current buffer by moving
 it to a new name or location, and also makes the buffer visit this new
@@ -736,19 +688,16 @@ file.
 
 ** Menus
 
----
 *** The entries following the buffers in the "Buffers" menu can now be altered.
 Change the 'menu-bar-buffers-menu-command-entries' variable to alter
 the entries that follow the buffer list.
 
----
 ** 'delete-process' is now a command.
 When called interactively, it will kill the process running in the
 current buffer (if any).  This can be useful if you have runaway
 output in the current buffer (from a process or a network connection),
 and want to stop it.
 
-+++
 ** New command 'restart-emacs'.
 This is like 'save-buffers-kill-emacs', but instead of just killing
 the current Emacs process at the end, it starts a new Emacs process
@@ -759,66 +708,54 @@ process.
 
 ** Drag and Drop
 
-+++
 *** New user option 'mouse-drag-mode-line-buffer'.
 If non-nil, dragging on the buffer name part of the mode-line will
 drag the buffer's associated file to other programs.  This option is
 currently only available on X, Haiku and Nextstep (GNUstep or macOS).
 
-+++
 *** New user option 'mouse-drag-and-drop-region-cross-program'.
 If non-nil, this option allows dragging text in the region from Emacs
 to another program.
 
----
 *** New user option 'mouse-drag-and-drop-region-scroll-margin'.
 If non-nil, this option allows scrolling a window while dragging text
 around without a scroll wheel.
 
-+++
 *** The value of 'mouse-drag-copy-region' can now be the symbol 'non-empty'.
 This prevents mouse drag gestures from putting empty strings onto the
 kill ring.
 
-+++
 *** New user options 'dnd-indicate-insertion-point' and 'dnd-scroll-margin'.
 These options allow adjusting point and scrolling a window when
 dragging items from another program.
 
-+++
 *** The X Direct Save (XDS) protocol is now supported.
 This means dropping an image or file link from programs such as
 Firefox will no longer create a temporary file in a random directory,
 instead asking you where to save the file first.
 
-+++
 ** New user option 'record-all-keys'.
 If non-nil, this option will force recording of all input keys,
 including those typed in response to passwords prompt (this was the
 previous behavior).  The default is nil, which inhibits recording of
 passwords.
 
-+++
 ** New function 'command-query'.
 This function makes its argument command prompt the user for
 confirmation before executing.
 
-+++
 ** The 'disabled' property of a command's symbol can now be a list.
 The first element of the list should be the symbol 'query', which will
 cause the command disabled this way prompt the user with a y/n or a
 yes/no question before executing.  The new function 'command-query' is
 a convenient method of making commands disabled in this way.
 
----
 ** 'count-words' will now report buffer totals if given a prefix.
 Without a prefix, it will only report the word count for the narrowed
 part of the buffer.
 
-+++
 ** 'count-words' will now report sentence count when used interactively.
 
-+++
 ** New user option 'set-message-functions'.
 It allows more flexible control of how echo-area messages are displayed
 by adding functions to this list.  The default value is a list of one
@@ -829,29 +766,24 @@ specifying, via 'inhibit-message-regexps', the list of 
messages whose
 display should be inhibited; and 'set-multi-message' that accumulates
 recent messages and displays them stacked together.
 
----
 ** New user option 'find-library-include-other-files'.
 If set to nil, commands like 'find-library' will only include library
 files in the completion candidates.  The default is t, which preserves
 previous behavior, whereby non-library files could also be included.
 
-+++
 ** New command 'sqlite-mode-open-file' for examining an sqlite3 file.
 This uses the new 'sqlite-mode' which allows listing the tables in a
 DB file, and examining and modifying the columns and the contents of
 those tables.
 
----
 ** 'write-file' will now copy some file mode bits.
 If the current buffer is visiting a file that is executable, the
 'C-x C-w' command will now make the new file executable, too.
 
-+++
 ** New user option 'process-error-pause-time'.
 This determines how long to pause Emacs after a process
 filter/sentinel error has been handled.
 
-+++
 ** New faces for font-lock.
 These faces are primarily meant for use with tree-sitter.  They are:
 'font-lock-bracket-face', 'font-lock-delimiter-face',
@@ -861,40 +793,33 @@ These faces are primarily meant for use with tree-sitter. 
 They are:
 'font-lock-property-use-face', 'font-lock-punctuation-face',
 'font-lock-regexp-face', and 'font-lock-variable-use-face'.
 
-+++
 ** New face 'variable-pitch-text'.
 This face is like 'variable-pitch' (from which it inherits), but is
 slightly larger, which should help with the visual size differences
 between the default, non-proportional font and proportional fonts when
 mixed.
 
-+++
 ** New face 'mode-line-active'.
 This inherits from the 'mode-line' face, but is the face actually used
 on the mode lines (along with 'mode-line-inactive').
 
-+++
 ** New face attribute pseudo-value 'reset'.
 This value stands for the value of the corresponding attribute of the
 'default' face.  It can be used to reset attribute values produced by
 inheriting from other faces.
 
-+++
 ** New X resource "borderThickness".
 This controls the thickness of the external borders of the menu bars
 and pop-up menus.
 
-+++
 ** New X resource "inputStyle".
 This controls the style of the pre-edit and status areas of X input
 methods.
 
-+++
 ** New X resources "highlightForeground" and "highlightBackground".
 Only in the Lucid build, this controls colors used for highlighted
 menu item widgets.
 
-+++
 ** On X, Emacs now tries to synchronize window resize with the window manager.
 This leads to less flicker and empty areas of a frame being displayed
 when a frame is being resized.  Unfortunately, it does not work on
@@ -902,7 +827,6 @@ some ancient buggy window managers, so if Emacs appears to 
freeze, but
 is still responsive to input, you can turn it off by setting the X
 resource "synchronizeResize" to "off".
 
-+++
 ** On X, Emacs can optionally synchronize display with the graphics hardware.
 When this is enabled by setting the X resource "synchronizeResize" to
 "extended", frame content "tearing" is drastically reduced.  This is
@@ -914,22 +838,18 @@ https://fishsoup.net/misc/wm-spec-synchronization.html).
 This behavior can be toggled on and off via the frame parameter
 'use-frame-synchronization'.
 
-+++
 ** New frame parameter 'alpha-background' and X resource "alphaBackground".
 This controls the opacity of the text background when running on a
 composited display.
 
-+++
 ** New frame parameter 'shaded'.
 With window managers which support this, it controls whether or not a
 frame's contents will be hidden, leaving only the title bar on display.
 
----
 ** New user option 'x-gtk-use-native-input'.
 This controls whether or not GTK input methods are used by Emacs,
 instead of XIM input methods.  Defaults to nil.
 
-+++
 ** New user option 'use-system-tooltips'.
 This controls whether to use the toolkit tooltips, or Emacs's own
 native implementation of tooltips as small frames.  This option is
@@ -938,13 +858,11 @@ support, and defaults to t, which makes Emacs use the 
toolkit
 tooltips.  The existing GTK-specific option
 'x-gtk-use-system-tooltips' is now an alias of this new option.
 
-+++
 ** Non-native tooltips are now supported on Nextstep.
 This means Emacs built with GNUstep or built on macOS is now able to
 display different faces and images inside tooltips when the
 'use-system-tooltips' user option is nil.
 
----
 ** New minor mode 'pixel-scroll-precision-mode'.
 When enabled, and if your mouse supports it, you can scroll the
 display up or down at pixel resolution, according to what your mouse
@@ -954,20 +872,17 @@ scrolls.
 
 ** Terminal Emacs
 
----
 *** Emacs will now use 24-bit colors on terminals that support "Tc" capability.
 This is in addition to previously-supported ways of discovering 24-bit
 color support: either via the "RGB" or "setf24" capabilities, or if
 the 'COLORTERM' environment variable is set to the value "truecolor".
 
----
 *** Select active regions with xterm selection support.
 On terminals with xterm "setSelection" support, the active region may be
 saved to the X primary selection, following the
 'select-active-regions' variable.  This support is enabled when
 'tty-select-active-regions' is non-nil.
 
----
 *** New command to set up display of unsupported characters.
 The new command 'standard-display-by-replacement-char' produces Lisp
 code that sets up the 'standard-display-table' to use a replacement
@@ -977,7 +892,6 @@ This feature is most useful with the Linux console and 
similar
 terminals, where Emacs has a reliable way of determining which
 characters have glyphs in the font loaded into the terminal's memory.
 
----
 *** New functions to set terminal output buffer size.
 The new functions 'tty--set-output-buffer-size' and
 'tty--output-buffer-size' allow setting and retrieving the output
@@ -989,89 +903,72 @@ at the end of display update.
 
 ** ERT
 
-+++
 *** New ERT variables 'ert-batch-print-length' and 'ert-batch-print-level'.
 These variables will override 'print-length' and 'print-level' when
 printing Lisp values in ERT batch test results.
 
----
 *** Redefining an ERT test in batch mode now signals an error.
 Executing 'ert-deftest' with the same name as an existing test causes
 the previous definition to be discarded, which was probably not
 intended when this occurs in batch mode.  To remedy the error, rename
 tests so that they all have unique names.
 
-+++
 *** ERT can generate JUnit test reports.
 When environment variable 'EMACS_TEST_JUNIT_REPORT' is set, ERT
 generates a JUnit test report under this file name.  This is useful
 for Emacs integration into CI/CD test environments.
 
----
 *** Unbound test symbols now signal an 'ert-test-unbound' error.
 This affects the 'ert-select-tests' function and its callers.
 
 ** Emoji
 
-+++
 *** Emacs now has several new methods for inserting Emoji.
 The Emoji commands are under the new 'C-x 8 e' prefix.
 
-+++
 *** New command 'emoji-insert' (bound to 'C-x 8 e e' and 'C-x 8 e i').
 This command guides you through various Emoji categories and
 combinations in a graphical menu system.
 
-+++
 *** New command 'emoji-search' (bound to 'C-x 8 e s').
 This command lets you search for and insert an Emoji based on names.
 
-+++
 *** New command 'emoji-list' (bound to 'C-x 8 e l').
 This command lists all Emoji (categorized by themes) in a special
 buffer and lets you choose one of them to insert.
 
----
 *** New command 'emoji-recent' (bound to 'C-x 8 e r').
 This command lets you choose among the Emoji you have recently
 inserted and insert it.
 
-+++
 *** New command 'emoji-describe' (bound to 'C-x 8 e d').
 This command will tell you the name of the Emoji at point.  (It also
 works for non-Emoji characters.)
 
----
 *** New commands 'emoji-zoom-increase' and 'emoji-zoom-decrease'.
 These are bound to 'C-x 8 e +' and 'C-x 8 e -', respectively.  They
 can be used on any character, but are mainly useful for Emoji.
 
----
 *** New command 'emoji-zoom-reset'.
 This is bound to 'C-x 8 e 0', and undoes any size changes performed by
 'emoji-zoom-increase' and 'emoji-zoom-decrease'.
 
----
 *** New input method 'emoji'.
 This allows you to enter Emoji using short strings, eg ':face_palm:'
 or ':scream:'.
 
 ** Help
 
----
 *** Variable values displayed by 'C-h v' in "*Help*" are now fontified.
 
-+++
 *** New user option 'help-clean-buttons'.
 If non-nil, link buttons in "*Help*" buffers will have any surrounding
 quotes removed.
 
----
 *** 'M-x apropos-variable' output now includes values of variables.
 Such an apropos buffer is more easily viewed with outlining after
 enabling 'outline-minor-mode' in 'apropos-mode'.
 
-+++
 *** New docstring syntax to indicate that symbols shouldn't be links.
 When displaying docstrings in "*Help*" buffers, strings that are
 "`like-this'" are made into links (if they point to a bound
@@ -1080,12 +977,10 @@ about values that are symbols that happen to have the 
same names as
 functions/variables.  To inhibit this buttonification, use the new
 "\\+`like-this'" syntax.
 
-+++
 *** New user option 'help-window-keep-selected'.
 If non-nil, commands to show the info manual and the source will reuse
 the same window in which the "*Help*" buffer is shown.
 
----
 *** Commands like 'C-h f' have changed how they describe menu bindings.
 For instance, previously a command might be described as having the
 following bindings:
@@ -1097,56 +992,45 @@ This has been changed to:
     It is bound to <open> and C-x C-f.
     It can also be invoked from the menu: File → Visit New File...
 
-+++
 *** The 'C-h .' command now accepts a prefix argument.
 'C-u C-h .' would previously inhibit displaying a warning message if
 there was no local help at point.  This has been changed to call
 'button-describe'/'widget-describe' and display button/widget help
 instead.
 
----
 *** New user option 'help-enable-variable-value-editing'.
 If enabled, 'e' on a value in "*Help*" will pop you to a new buffer
 where you can edit the value.  This is not enabled by default, because
 it is easy to make an edit that yields an invalid result.
 
----
 *** 'C-h b' uses outlining by default.
 Set 'describe-bindings-outline' to nil to get back the old behavior.
 
----
 *** Jumping to function/variable source now saves mark before moving point.
 Jumping to source from a "*Help*" buffer moves point when the source
 buffer is already open.  Now, the old point is pushed onto mark ring.
 
-+++
 *** New key bindings in "*Help*" buffers: 'n' and 'p'.
 These will take you (respectively) to the next and previous "page".
 
----
 *** 'describe-char' now also outputs the name of Emoji sequences.
 
-+++
 *** New key binding in "*Help*" buffer: 'I'.
 This will take you to the Emacs Lisp manual entry for the item
 displayed, if any.
 
----
 *** The 'C-h m' ('describe-mode') "*Help*" buffer has been reformatted.
 It now only includes local minor modes at the start, and the global
 minor modes are listed after the major mode.
 
-+++
 *** The user option 'help-window-select' now affects apropos commands.
 The apropos commands will now select the apropos window if
 'help-window-select' is non-nil.
 
----
 *** 'describe-keymap' now considers the symbol at point.
 If the symbol at point is a keymap, 'describe-keymap' suggests it as
 the default candidate.
 
----
 *** New command 'help-quick' displays an overview of common commands.
 The command pops up a buffer at the bottom of the screen with a few
 helpful commands for various tasks.  You can toggle the display using
@@ -1157,7 +1041,6 @@ See the file "etc/ORG-NEWS" for user-visible changes in 
Org.
 
 ** Outline Mode
 
-+++
 *** Support for customizing the default visibility state of headings.
 Customize the user option 'outline-default-state' to define what
 headings will be visible initially, after Outline mode is turned on.
@@ -1169,7 +1052,6 @@ subtree has long lines or is itself too long.
 
 ** Outline Minor Mode
 
-+++
 *** New user option 'outline-minor-mode-use-buttons'.
 If non-nil, Outline Minor Mode will use buttons to hide/show outlines
 in addition to the ellipsis.  The default is nil, but in 'help-mode'
@@ -1178,7 +1060,6 @@ buffer, and you can use 'RET' to cycle outline 
visibility.  When
 the value is 'in-margins', Outline Minor Mode uses the window margins
 for buttons that hide/show outlines.
 
-+++
 *** Buttons and headings now have their own keymaps.
 'outline-button-icon-map', 'outline-overlay-button-map', and
 'outline-inserted-button-map' are now available as defined keymaps
@@ -1186,19 +1067,16 @@ instead of being anonymous keymaps.
 
 ** Windows
 
-+++
 *** New commands 'split-root-window-below' and 'split-root-window-right'.
 These commands split the root window in two, and are bound to 'C-x w 2'
 and 'C-x w 3', respectively.  A number of other useful window-related
 commands are now available with key sequences that start with the
 'C-x w' prefix.
 
-+++
 *** New display action 'display-buffer-full-frame'.
 This action removes other windows from the frame when displaying a
 buffer on that frame.
 
-+++
 *** 'display-buffer' now can set up the body size of the chosen window.
 For example, a 'display-buffer-alist' entry of
 
@@ -1207,35 +1085,29 @@ For example, a 'display-buffer-alist' entry of
 will make the body of the chosen window 40 columns wide.  For the
 height use 'window-height' and 'body-lines', respectively.
 
-+++
 *** 'display-buffer' provides more options for using an existing window.
 The display buffer action functions 'display-buffer-use-some-window' and
 'display-buffer-use-least-recent-window' now honor the action alist
 entry 'window-min-height' as well as the entries listed below to make
 the display of several buffers in a row more amenable.
 
-+++
 *** New buffer display action alist entry 'lru-frames'.
 This allows specifying which frames 'display-buffer' should consider
 when using a window that shows another buffer.  It is interpreted as
 per the ALL-FRAMES argument of 'get-lru-window'.
 
-+++
 *** New buffer display action alist entry 'lru-time'.
 'display-buffer' will ignore windows with a use time higher than this
 when using a window that shows another buffer.
 
-+++
 *** New buffer display action alist entry 'bump-use-time'.
 This has 'display-buffer' bump the use time of any window it returns,
 making it a less likely candidate for displaying another buffer.
 
-+++
 *** New buffer display action alist entry 'window-min-width'.
 This allows specifying a preferred minimum width of the window used to
 display a buffer.
 
----
 *** You can specify on which window 'scroll-other-window' operates.
 This is controlled by the new 'other-window-scroll-default' variable,
 which should be set to a function that returns a window.  When this
@@ -1243,7 +1115,6 @@ variable is nil, 'next-window' is used.
 
 ** Frames
 
-+++
 *** Deleted frames can now be undeleted.
 The 16 most recently deleted frames can be undeleted with 'C-x 5 u' when
 'undelete-frame-mode' is enabled.  Without a prefix argument, undelete
@@ -1251,7 +1122,6 @@ the most recently deleted frame.  With a numerical prefix 
argument
 between 1 and 16, where 1 is the most recently deleted frame, undelete
 the corresponding deleted frame.
 
-+++
 *** The variable 'icon-title-format' can now have the value t.
 That value means to use 'frame-title-format' for iconified frames.
 This is useful with some window managers and desktop environments
@@ -1261,24 +1131,20 @@ same no matter if the frame is iconified or not.
 
 ** Tab Bars and Tab Lines
 
----
 *** New user option 'tab-bar-auto-width' to automatically determine tab width.
 This option is non-nil by default, which resizes tab-bar tabs so that
 their width is evenly distributed across the tab bar.  A companion
 option 'tab-bar-auto-width-max' controls the maximum width of a tab
 before its name on display is truncated.
 
----
 *** 'C-x t RET' creates a new tab when the provided tab name doesn't exist.
 It prompts for the name of a tab and switches to it, creating a new
 tab if no tab exists by that name.
 
----
 *** New keymap 'tab-bar-history-mode-map'.
 By default, it contains 'C-c <left>' and 'C-c <right>' to browse
 the history of tab window configurations back and forward.
 
----
 ** Better detection of text suspiciously reordered on display.
 The function 'bidi-find-overridden-directionality' has been extended
 to detect reordering effects produced by embeddings and isolates
@@ -1289,17 +1155,14 @@ suspicious and could be malicious.
 
 ** Emacs Server and Client
 
-+++
 *** New command-line option '-r'/'--reuse-frame' for emacsclient.
 With this command-line option, Emacs reuses an existing graphical client
 frame if one exists; otherwise it creates a new frame.
 
-+++
 *** New command-line option '-w N'/'--timeout=N' for emacsclient.
 With this command-line option, emacsclient will exit if Emacs does not
 respond within N seconds.  The default is to wait forever.
 
-+++
 *** 'server-stop-automatically' can be used to automatically stop the server.
 The Emacs server will be automatically stopped when certain conditions
 are met.  The conditions are determined by the argument to
@@ -1308,17 +1171,14 @@ are met.  The conditions are determined by the argument 
to
 
 ** Rcirc
 
-+++
 *** New command 'rcirc-when'.
 This shows the reception time of the message at point (if available).
 
-+++
 *** New user option 'rcirc-cycle-completion-flag'.
 Rcirc now uses the default 'completion-at-point' mechanism.  The
 conventional IRC behavior of completing by cycling through the
 available options can be restored by enabling this option.
 
-+++
 *** New user option 'rcirc-bridge-bot-alist'.
 If you are in a channel where a bot is responsible for bridging
 between networks, you can use this variable to make these messages
@@ -1334,7 +1194,6 @@ to be reformatted into
 
     09:47 <john> I am not on IRC
 
----
 *** New formatting commands.
 Most IRC clients (including rcirc) support basic formatting using
 control codes.  Under the 'C-c C-f' prefix a few commands have been
@@ -1344,15 +1203,12 @@ to be highlighted in bold.
 
 ** Imenu
 
-+++
 *** 'imenu' is now bound to 'M-g i' globally.
 
----
 *** New function 'imenu-flush-cache'.
 Use it if you want Imenu to forget the buffer's index alist and
 recreate it anew next time 'imenu' is invoked.
 
----
 ** Emacs is now capable of abandoning a window's redisplay that takes too long.
 This is controlled by the new variable 'max-redisplay-ticks'.  If that
 variable is set to a non-zero value, display of a window will be
@@ -1366,19 +1222,16 @@ lines.)
 
 * Editing Changes in Emacs 29.1
 
-+++
 ** 'M-SPC' is now bound to 'cycle-spacing'.
 Formerly it invoked 'just-one-space'.  The actions performed by
 'cycle-spacing' and their order can now be customized via the user
 option 'cycle-spacing-actions'.
 
----
 ** 'zap-to-char' and 'zap-up-to-char' are case-sensitive for upper-case chars.
 These commands now behave as case-sensitive for interactive calls when
 they are invoked with an uppercase character, regardless of the value
 of 'case-fold-search'.
 
----
 ** 'scroll-other-window' and 'scroll-other-window-down' now respect remapping.
 These commands (bound to 'C-M-v' and 'C-M-V') used to scroll the other
 windows without looking at customizations in that other window.  These
@@ -1387,7 +1240,6 @@ in that other window, and then call the remapped function 
instead.  In
 addition, these commands now also respect the
 'scroll-error-top-bottom' user option.
 
----
 ** Indentation of 'cl-flet' and 'cl-labels' has changed.
 These forms now indent like this:
 
@@ -1398,32 +1250,26 @@ These forms now indent like this:
 This change also affects 'cl-macrolet', 'cl-flet*' and
 'cl-symbol-macrolet'.
 
-+++
 ** New user option 'translate-upper-case-key-bindings'.
 Set this option to nil to inhibit the default translation of upper
 case keys to their lower case variants.
 
-+++
 ** New command 'ensure-empty-lines'.
 This command increases (or decreases) the number of empty lines before
 point.
 
----
 ** Improved mouse behavior with auto-scrolling modes.
 When clicking inside the 'scroll-margin' or 'hscroll-margin' region,
 point is now moved only when releasing the mouse button.  This no
 longer results in a bogus selection, unless the mouse has also been
 dragged.
 
-+++
 ** 'kill-ring-max' now defaults to 120.
 
----
 ** New user option 'yank-menu-max-items'.
 Customize this option to limit the number of entries in the menu
 "Edit → Paste from Kill Menu".  The default is 60.
 
----
 ** New user option 'copy-region-blink-predicate'.
 By default, when copying a region with 'kill-ring-save', Emacs only
 blinks point and mark when the region is not denoted visually, that
@@ -1435,12 +1281,10 @@ this user option to 'always'.  To disable blinking 
unconditionally,
 either set this option to 'ignore', or set 'copy-region-blink-delay'
 to 0.
 
-+++
 ** Performing a pinch gesture on a touchpad now increases the text scale.
 
 ** Show Paren Mode
 
-+++
 *** New user option 'show-paren-context-when-offscreen'.
 When non-nil, if the point is in a closing delimiter and the opening
 delimiter is offscreen, shows some context around the opening
@@ -1454,19 +1298,16 @@ echo area.
 
 ** Comint
 
-+++
 *** 'comint-term-environment' is now aware of connection-local variables.
 The user option 'comint-terminfo-terminal' and the variable
 'system-uses-terminfo' can now be set as connection-local variables to
 change the terminal used on a remote host.
 
----
 *** New user option 'comint-delete-old-input'.
 When nil, this prevents comint from deleting the current input when
 inserting previous input using '<mouse-2>'.  The default is t, to
 preserve previous behavior.
 
----
 *** New minor mode 'comint-fontify-input-mode'.
 This minor mode is enabled by default in "*shell*" and "*ielm*"
 buffers.  It fontifies input text according to 'shell-mode' or
@@ -1476,7 +1317,6 @@ you don't want to enable input fontification by default.
 
 ** Mwheel
 
----
 *** New user options for alternate wheel events.
 The user options 'mouse-wheel-down-alternate-event' and
 'mouse-wheel-up-alternate-event' as well as the variables
@@ -1486,19 +1326,16 @@ systems where two kinds of wheel events can be received.
 
 ** Internationalization
 
----
 *** The '<Delete>' function key now allows deleting the entire composed 
sequence.
 For the details, see the item about the 'delete-forward-char' command
 above.
 
----
 *** New user option 'composition-break-at-point'.
 Setting it to a non-nil value temporarily disables automatic
 composition of character sequences at point, and thus makes it easier
 to edit such sequences by allowing point to "enter" the composed
 sequence.
 
----
 *** Support for many old scripts and writing systems.
 Emacs now supports, and has language-environments and input methods,
 for several dozens of old scripts that were used in the past for
@@ -1573,48 +1410,39 @@ environments are:
 
  Mongolian-cyrillic language environment
 
----
 *** The "Oriya" language environment was renamed to "Odia".
 This is to follow the change in the official name of the script.  The
 'oriya' input method was also renamed to 'odia'.  However, the old
 name of the language environment and the input method are still
 supported.
 
----
 *** New Greek translation of the Emacs tutorial.
 Type 'C-u C-h t' to select it in case your language setup does not do
 so automatically.
 
----
 *** New Ukrainian translation of the Emacs tutorial.
 
----
 *** New Farsi/Persian translation of the Emacs tutorial.
 
----
 *** New default phonetic input method for the Tamil language environment.
 The default input method for the Tamil language environment is now
 "tamil-phonetic" which is a customizable phonetic input method.  To
 change the input method's translation rules, customize the user option
 'tamil-translation-rules'.
 
----
 *** New 'tamil99' input method for the Tamil language.
 This supports the keyboard layout specifically designed for the Tamil
 language.
 
----
 *** New input method 'slovak-qwerty'.
 This is a variant of the 'slovak' input method, which corresponds to
 the QWERTY Slovak keyboards.
 
----
 *** New input method 'cyrillic-chuvash'.
 This input method is based on the russian-computer input method, and
 is intended for typing in the Chuvash language written in the Cyrillic
 script.
 
----
 *** New input method 'cyrillic-mongolian'.
 This input method is for typing in the Mongolian language using the
 Cyrillic script.  It is the default input method for the new
@@ -1625,23 +1453,19 @@ Mongolian-cyrillic language environment, see above.
 
 ** Ecomplete
 
----
 *** New commands 'ecomplete-edit' and 'ecomplete-remove'.
 These allow you to (respectively) edit and bulk-remove entries from
 the ecomplete database.
 
----
 *** New user option 'ecomplete-auto-select'.
 If non-nil and there's only one matching option, auto-select that.
 
----
 *** New user option 'ecomplete-filter-regexp'.
 If non-nil, this user option describes what entries not to add to the
 database stored on disk.
 
 ** Auth Source
 
-+++
 *** New user option 'auth-source-pass-extra-query-keywords'.
 Whether to recognize additional keyword params, like ':max' and
 ':require', as well as accept lists of query terms paired with
@@ -1650,13 +1474,11 @@ unique to auth-source-pass, such as wildcard subdomain 
matching.
 
 ** Dired
 
-+++
 *** 'dired-guess-shell-command' moved from dired-x to dired.
 This means that 'dired-do-shell-command' will now provide smarter
 defaults without first having to require 'dired-x'.  See the node
 "(emacs) Shell Command Guessing" in the Emacs manual for more details.
 
----
 *** 'dired-clean-up-buffers-too' moved from dired-x to dired.
 This means that Dired now offers to kill buffers visiting files and
 dirs when they are deleted in Dired.  Before, you had to require
@@ -1665,20 +1487,16 @@ customize the user option 'dired-clean-up-buffers-too' 
to nil.  The
 related user option 'dired-clean-confirm-killing-deleted-buffers'
 (which see) has also been moved to 'dired'.
 
-+++
 *** 'dired-do-relsymlink' moved from dired-x to dired.
 The corresponding key 'Y' is now bound by default in Dired.
 
-+++
 *** 'dired-do-relsymlink-regexp' moved from dired-x to dired.
 The corresponding key sequence '% Y' is now bound by default in Dired.
 
----
 *** 'M-G' is now bound to 'dired-goto-subdir'.
 Before, that binding was only available if the dired-x package was
 loaded.
 
-+++
 *** 'dired-info' and 'dired-man' moved from dired-x to dired.
 The 'dired-info' and 'dired-man' commands have been moved from the
 dired-x package to dired.  They have also been renamed to
@@ -1696,29 +1514,24 @@ the following to your Init file:
       (keymap-set dired-mode-map "N" nil)
       (keymap-set dired-mode-map "I" nil))
 
----
 *** New command 'dired-do-eww'.
 This command visits the file on the current line with EWW.
 
----
 *** 'browse-url-of-dired-file' can now call the secondary browser.
 When invoked with a prefix arg, this will now call
 'browse-url-secondary-browser-function' instead of the default
 browser.  'browse-url-of-dired-file' is bound to 'W' by default in
 dired mode.
 
----
 *** New user option 'dired-omit-lines'.
 This is used by 'dired-omit-mode', and now allows you to hide based on
 other things than just the file names.
 
-+++
 *** New user option 'dired-mouse-drag-files'.
 If non-nil, dragging file names with the mouse in a Dired buffer will
 initiate a drag-and-drop session allowing them to be opened in other
 programs.
 
-+++
 *** New user option 'dired-free-space'.
 Dired will now, by default, include the free space in the first line
 instead of having it on a separate line.  To get the previous behavior
@@ -1726,13 +1539,11 @@ back, say:
 
     (setopt dired-free-space 'separate)
 
----
 *** New user option 'dired-make-directory-clickable'.
 If non-nil (which is the default), hitting 'RET' or 'mouse-1' on
 the directory components at the directory displayed at the start of
 the buffer will take you to that directory.
 
----
 *** Search and replace in Dired/Wdired supports more regexps.
 For example, the regexp ".*" will match only characters that are part
 of the file name.  Also "^.*$" can be used to match at the beginning
@@ -1743,31 +1554,26 @@ default).
 
 ** Elisp
 
----
 *** New command 'elisp-eval-region-or-buffer' (bound to 'C-c C-e').
 This command evals the forms in the active region or in the whole buffer.
 
----
 *** New commands 'elisp-byte-compile-file' and 'elisp-byte-compile-buffer'.
 These commands (bound to 'C-c C-f' and 'C-c C-b', respectively)
 byte-compile the visited file and the current buffer, respectively.
 
 ** Games
 
----
 *** New user option 'tetris-allow-repetitions'.
 This controls how randomness is implemented (whether to use pure
 randomness as before, or to use a bag).
 
 ** Battery
 
-+++
 *** New user option 'battery-update-functions'.
 This can be used to trigger actions based on the battery status.
 
 ** DocView
 
----
 *** doc-view can now generate SVG images when viewing PDF files.
 If Emacs is built with SVG support, doc-view can generate SVG files
 when using MuPDF as the converter for PDF files, which generally leads
@@ -1781,14 +1587,12 @@ or can take a long time to render.
 
 ** Enriched Mode
 
-+++
 *** New command 'enriched-toggle-markup'.
 This allows you to see the markup in 'enriched-mode' buffers (e.g.,
 the "HELLO" file).  Bound to 'M-o m' by default.
 
 ** Shell Script Mode
 
----
 *** New user option 'sh-indent-statement-after-and'.
 This controls how statements like the following are indented:
 
@@ -1801,10 +1605,8 @@ command is installed.
 
 ** CC Mode
 
----
 *** C++ Mode now supports most of the new features in the C++20 Standard.
 
----
 *** In Objective-C Mode, no extra types are recognized by default.
 The default value of 'objc-font-lock-extra-types' has been changed to
 nil, since too many identifiers were getting misfontified as types.
@@ -1814,19 +1616,16 @@ doc string.
 
 ** Cperl Mode
 
----
 *** New user option 'cperl-file-style'.
 This option determines the indentation style to be used.  It can also
 be used as a file-local variable.
 
 ** Gud
 
----
 *** 'gud-go' is now bound to 'C-c C-v'.
 If given a prefix, it will prompt for an argument to use for the
 run/continue command.
 
----
 *** 'perldb' now recognizes '-E'.
 As of Perl 5.10, 'perl -E 0' behaves like 'perl -e 0' but also activates
 all optional features of the Perl version in use.  'perldb' now uses
@@ -1834,27 +1633,23 @@ this invocation as its default.
 
 ** Customize
 
----
 *** New command 'custom-toggle-hide-all-widgets'.
 This is bound to 'H' and toggles whether to hide or show the widget
 contents.
 
 ** Diff Mode
 
----
 *** New user option 'diff-whitespace-style'.
 Sets the value of the buffer-local variable 'whitespace-style' in
 'diff-mode' buffers.  By default, this variable is '(face trailing)',
 which preserves behavior of previous Emacs versions.
 
-+++
 *** New user option 'diff-add-log-use-relative-names'.
 If non-nil insert file names in ChangeLog skeletons relative to the
 VC root directory.
 
 ** Ispell
 
----
 *** 'ispell-region' and 'ispell-buffer' now push the mark.
 These commands push onto the mark ring the location of the last
 misspelled word where corrections were offered, so that you can then
@@ -1862,73 +1657,60 @@ skip back to that location with 'C-x C-x'.
 
 ** Dabbrev
 
----
 *** New function 'dabbrev-capf' for use on 'completion-at-point-functions'.
 
-+++
 *** New user option 'dabbrev-ignored-buffer-modes'.
 Buffers with major modes in this list will be ignored.  By default,
 this includes "binary" buffers like 'archive-mode' and 'image-mode'.
 
 ** Package
 
-+++
 *** New command 'package-upgrade'.
 This command allows you to upgrade packages without using 'list-packages'.
 A package that comes with the Emacs distribution can only be upgraded
 after you install, once, a newer version from ELPA via the
 package-menu displayed by 'list-packages'.
 
-+++
 *** New command 'package-upgrade-all'.
 This command allows upgrading all packages without any queries.
 A package that comes with the Emacs distribution will only be upgraded
 by this command after you install, once, a newer version of that
 package from ELPA via the package-menu displayed by 'list-packages'.
 
-+++
 *** New commands 'package-recompile' and 'package-recompile-all'.
 These commands can be useful if the ".elc" files are out of date
 (invalid byte code and macros).
 
-+++
 *** New DWIM action on 'x' in "*Packages*" buffer.
 If no packages are marked, 'x' will install the package under point if
 it isn't already, and remove it if it is installed.  Customize the new
 option 'package-menu-use-current-if-no-marks' to the nil value to get
 back the old behavior of signaling an error in that case.
 
-+++
 *** New command 'package-vc-install'.
 Packages can now be installed directly from source by cloning from
 their repository.
 
-+++
 *** New command 'package-vc-install-from-checkout'.
 An existing checkout can now be loaded via package.el, by creating a
 symbolic link from the usual package directory to the checkout.
 
-+++
 *** New command 'package-vc-checkout'.
 Used to fetch the source of a package by cloning a repository without
 activating the package.
 
-+++
 *** New command 'package-vc-prepare-patch'.
 This command allows you to send patches to package maintainers, for
 packages checked out using 'package-vc-install'.
 
-+++
 *** New command 'package-report-bug'.
 This command helps you compose an email for sending bug reports to
 package maintainers, and is bound to 'b' in the "*Packages*" buffer.
 
-+++
 *** New user option 'package-vc-selected-packages'.
 By customizing this user option you can specify specific packages to
 install.
 
----
 *** New user option 'package-install-upgrade-built-in'.
 When enabled, 'package-install' will include in the list of
 upgradeable packages those built-in packages (like Eglot and
@@ -1950,7 +1732,6 @@ setting in your early-init file.
 
 ** Emacs Sessions (Desktop)
 
-+++
 *** New user option to load a locked desktop if locking Emacs is not running.
 The option 'desktop-load-locked-desktop' can now be set to the value
 'check-pid', which means to allow loading a locked ".emacs.desktop"
@@ -1962,7 +1743,6 @@ Emacs Sessions" node in the Emacs manual for more details.
 
 ** Miscellaneous
 
-+++
 *** New command 'scratch-buffer'.
 This command switches to the "*scratch*" buffer.  If "*scratch*" doesn't
 exist, the command creates it first.  You can use this command if you
@@ -1970,31 +1750,26 @@ inadvertently delete the "*scratch*" buffer.
 
 ** Debugging
 
----
 *** 'q' in a "*Backtrace*" buffer no longer clears the buffer.
 Instead it just buries the buffer and switches the mode from
 'debugger-mode' to 'backtrace-mode', since commands like 'e' are no
 longer available after exiting the recursive edit.
 
-+++
 *** New user option 'debug-allow-recursive-debug'.
 This user option controls whether the 'e' (in a "*Backtrace*"
 buffer or while edebugging) and 'C-x C-e' (while edebugging) commands
 lead to a (further) backtrace.  By default, this variable is nil,
 which is a change in behavior from previous Emacs versions.
 
-+++
 *** 'e' in edebug can now take a prefix arg to pretty-print the results.
 When invoked with a prefix argument, as in 'C-u e', this command will
 pop up a new buffer and show the full pretty-printed value there.
 
-+++
 *** 'C-x C-e' now interprets a non-zero prefix arg to pretty-print the results.
 When invoked with a non-zero prefix argument, as in 'C-u C-x C-e',
 this command will pop up a new buffer and show the full pretty-printed
 value there.
 
-+++
 *** You can now generate a backtrace from Lisp errors in redisplay.
 To do this, set the new variable 'backtrace-on-redisplay-error' to a
 non-nil value.  The backtrace will be written to a special buffer
@@ -2003,18 +1778,15 @@ displayed in a window.
 
 ** Compile
 
-+++
 *** New user option 'compilation-hidden-output'.
 This regular expression can be used to make specific parts of
 compilation output invisible.
 
-+++
 *** The 'compilation-auto-jump-to-first-error' user option has been extended.
 It can now have the additional values 'if-location-known' (which will
 only jump if the location of the first error is known), and
 'first-known' (which will jump to the first known error location).
 
-+++
 *** New user option 'compilation-max-output-line-length'.
 Lines longer than the value of this option will have their ends
 hidden, with a button to reveal the hidden text.  This speeds up
@@ -2023,15 +1795,12 @@ value is 400; set to nil to disable hiding.
 
 ** Flymake
 
-+++
 *** New user option 'flymake-mode-line-lighter'.
 
-+++
 ** New minor mode 'word-wrap-whitespace-mode' for extending 'word-wrap'.
 This mode switches 'word-wrap' on, and breaks on all the whitespace
 characters instead of just 'SPC' and 'TAB'.
 
----
 ** New mode, 'emacs-news-mode', for editing the NEWS file.
 This mode adds some highlighting, makes the 'M-q' command aware of the
 format of NEWS entries, and has special commands for doing maintenance
@@ -2040,13 +1809,11 @@ of the Emacs NEWS files.  In addition, this mode turns 
on
 'icon-preference') in the margins.  To disable these icons, set
 'outline-minor-mode-use-buttons' to a nil value.
 
----
 ** Kmacro
 Kmacros are now OClosures and have a new constructor 'kmacro' which
 uses the 'key-parse' syntax.  It replaces the old 'kmacro-lambda-form'
 (which is now declared obsolete).
 
----
 ** savehist.el can now truncate variables that are too long.
 An element of user option 'savehist-additional-variables' can now be
 of the form '(VARIABLE . MAX-ELTS)', which means to truncate the
@@ -2055,7 +1822,6 @@ before saving the value.
 
 ** Minibuffer and Completions
 
-+++
 *** New commands for navigating completions from the minibuffer.
 When the minibuffer is the current buffer, typing 'M-<up>' or
 'M-<down>' selects a previous/next completion candidate from the
@@ -2070,13 +1836,11 @@ exit the minibuffer.  These keys are also available for 
in-buffer
 completion, but they don't insert candidates automatically, you need
 to type 'M-RET' to insert the selected candidate to the buffer.
 
-+++
 *** Choosing a completion with a prefix argument doesn't exit the minibuffer.
 This means that typing 'C-u RET' on a completion candidate in the
 "*Completions*" buffer inserts the completion into the minibuffer,
 but doesn't exit the minibuffer.
 
-+++
 *** The "*Completions*" buffer can now be automatically selected.
 To enable this behavior, customize the user option
 'completion-auto-select' to t, then pressing 'TAB' will switch to the
@@ -2084,13 +1848,11 @@ To enable this behavior, customize the user option
 'second-tab', then the first 'TAB' will display "*Completions*", and
 the second one will switch to the "*Completions*" buffer.
 
----
 *** New user option 'completion-auto-wrap'.
 When non-nil, the commands 'next-completion' and 'previous-completion'
 automatically wrap around on reaching the beginning or the end of
 the "*Completions*" buffer.
 
-+++
 *** New values for the 'completion-auto-help' user option.
 There are two new values to control the way the "*Completions*" buffer
 behaves after pressing a 'TAB' if completion is not unique.  The value
@@ -2099,61 +1861,51 @@ to complete.  The value 'visual' is like 'always', but 
only updates
 the completions if they are already visible.  The default value t
 always hides the completion buffer after some completion is made.
 
----
 *** New commands to complete the minibuffer history.
 'minibuffer-complete-history' ('C-x <up>') is like 'minibuffer-complete'
 but completes on the history items instead of the default completion
 table.  'minibuffer-complete-defaults' ('C-x <down>') completes
 on the list of default items.
 
-+++
 *** User option 'minibuffer-eldef-shorten-default' is now obsolete.
 Customize the user option 'minibuffer-default-prompt-format' instead.
 
-+++
 *** New user option 'completions-sort'.
 This option controls the sorting of the completion candidates in
 the "*Completions*" buffer.  Available styles are no sorting,
 alphabetical (the default), or a custom sort function.
 
-+++
 *** New user option 'completions-max-height'.
 This option limits the height of the "*Completions*" buffer.
 
-+++
 *** New user option 'completions-header-format'.
 This is a string to control the header line to show in the
 "*Completions*" buffer before the list of completions.
 If it contains "%s", that is replaced with the number of completions.
 If nil, the header line is not shown.
 
-+++
 *** New user option 'completions-highlight-face'.
 When this user option names a face, the current
 candidate in the "*Completions*" buffer is highlighted with that face.
 The nil value disables this highlighting.  The default is to highlight
 using the 'completions-highlight' face.
 
-+++
 *** You can now define abbrevs for the minibuffer modes.
 'minibuffer-mode-abbrev-table' and
 'minibuffer-inactive-mode-abbrev-table' are now defined.
 
 ** Isearch and Replace
 
-+++
 *** Changes in how Isearch responds to 'mouse-yank-at-point'.
 If a user does 'C-s' and then uses '<mouse-2>' ('mouse-yank-primary')
 outside the echo area, Emacs will, by default, end the Isearch and
 yank the text at mouse cursor.  But if 'mouse-yank-at-point' is
 non-nil, the text will now be added to the Isearch instead.
 
-+++
 *** Changes for values 'no' and 'no-ding' of 'isearch-wrap-pause'.
 Now with these values the search will wrap around not only on repeating
 with 'C-s C-s', but also after typing a character.
 
-+++
 *** New user option 'char-fold-override'.
 Non-nil means that the default definitions of equivalent characters
 are overridden.
@@ -2161,7 +1913,6 @@ are overridden.
 *** New command 'describe-char-fold-equivalences'.
 It displays character equivalences used by 'char-fold-to-regexp'.
 
-+++
 *** New command 'isearch-emoji-by-name'.
 It is bound to 'C-x 8 e RET' during an incremental search.  The
 command accepts the Unicode name of an Emoji (for example, "smiling
@@ -2170,7 +1921,6 @@ completion, and adds the Emoji into the search string.
 
 ** GDB/MI
 
----
 *** New user option 'gdb-debuginfod-enable-setting'.
 On capable platforms, GDB 10.1 and later can download missing source
 and debug info files from special-purpose servers, called "debuginfod
@@ -2182,18 +1932,15 @@ that session.
 
 ** Glyphless Characters
 
-+++
 *** New minor mode 'glyphless-display-mode'.
 This allows an easy way to toggle seeing all glyphless characters in
 the current buffer.
 
----
 *** The extra slot of 'glyphless-char-display' can now have cons values.
 The extra slot of the 'glyphless-char-display' char-table can now have
 values that are cons cells, specifying separate values for text-mode
 and GUI terminals.
 
-+++
 *** "Replacement character" feature for undisplayable characters on TTYs.
 The 'acronym' method of displaying glyphless characters on text-mode
 frames treats single-character acronyms specially: they are displayed
@@ -2202,7 +1949,6 @@ without the surrounding '[..]' "box", thus in effect 
treating such
 
 ** Registers
 
-+++
 *** Buffer names can now be stored in registers.
 For instance, to enable jumping to the "*Messages*" buffer with
 'C-x r j m':
@@ -2211,53 +1957,43 @@ For instance, to enable jumping to the "*Messages*" 
buffer with
 
 ** Pixel Fill
 
-+++
 *** This is a new package that deals with filling variable-pitch text.
 
-+++
 *** New function 'pixel-fill-region'.
 This fills the region to be no wider than a specified pixel width.
 
 ** Info
 
-+++
 *** Command 'info-apropos' now takes a prefix argument to search for regexps.
 
----
 *** New command 'Info-goto-node-web' and key binding 'G'.
 This will take you to the "gnu.org" web server's version of the current
 info node.  This command only works for the Emacs and Emacs Lisp manuals.
 
 ** Shortdoc
 
----
 *** New command 'shortdoc-copy-function-as-kill' bound to 'w'.
 It copies the name of the function near point into the kill ring.
 
----
 *** 'N' and 'P' are now bound to 'shortdoc-{next,previous}-section'.
 This is in addition to the old keybindings 'C-c C-n' and 'C-c C-p'.
 
 ** VC
 
----
 *** New command 'vc-pull-and-push'.
 This commands first does a "pull" command, and if that is successful,
 does a "push" command afterwards.  Currently supported in Git and Bzr.
 
-+++
 *** 'C-x v b' prefix key is used now for branch commands.
 'vc-print-branch-log' is bound to 'C-x v b l', and new commands are
 'vc-create-branch' ('C-x v b c') and 'vc-switch-branch' ('C-x v b s').
 The VC Directory buffer now uses the prefix 'b' for these branch-related
 commands.
 
-+++
 *** New command 'vc-dir-mark-by-regexp' bound to '% m' and '* %'.
 This command marks files based on a regexp.  If given a prefix
 argument, unmark instead.
 
-+++
 *** New command 'C-x v !' ('vc-edit-next-command').
 This prefix command requests editing of the next VC shell command
 before execution.  For example, in a Git repository, you can produce a
@@ -2268,7 +2004,6 @@ The intention is that this command can be used to access 
a wide
 variety of version control system-specific functionality from VC
 without complexifying either the VC command set or the backend API.
 
----
 *** 'C-x v v' in a diffs buffer allows to commit only some of the changes.
 This command is intended to allow you to commit only some of the
 changes you have in your working tree.  Begin by creating a buffer
@@ -2277,7 +2012,6 @@ with the changes against the last commit, e.g. with 'C-x 
v D'
 want to commit.  Finally, type 'C-x v v' in that diff buffer to commit
 only part of your changes, those whose hunks were left in the buffer.
 
----
 *** 'C-x v v' on an unregistered file will now use the most specific backend.
 Previously, if you had an SVN-covered "~/" directory, and a Git-covered
 directory in "~/foo/bar", using 'C-x v v' on a new, unregistered file
@@ -2285,28 +2019,23 @@ directory in "~/foo/bar", using 'C-x v v' on a new, 
unregistered file
 in the Git repository in "~/foo/bar".  This makes this command
 consistent with 'vc-responsible-backend'.
 
----
 *** Log Edit now fontifies long Git commit summary lines.
 Writing shorter summary lines avoids truncation in contexts in which
 Git commands display summary lines.  See the two new user options
 'vc-git-log-edit-summary-target-len' and 'vc-git-log-edit-summary-max-len'.
 
----
 *** New 'log-edit-headers-separator' face.
 It is used to style the line that separates the 'log-edit' headers
 from the 'log-edit' summary.
 
----
 *** The function 'vc-read-revision' accepts a new MULTIPLE argument.
 If non-nil, multiple revisions can be queried.  This is done using
 'completing-read-multiple'.
 
----
 *** New function 'vc-read-multiple-revisions'.
 This function invokes 'vc-read-revision' with a non-nil value for
 MULTIPLE.
 
-+++
 *** New command 'vc-prepare-patch'.
 Patches for any version control system can be prepared using VC.  The
 command will query what commits to send and will compose messages for
@@ -2316,32 +2045,26 @@ modified by the user options 
'vc-prepare-patches-separately' and
 
 ** Message
 
----
 *** New user option 'mml-attach-file-at-the-end'.
 If non-nil, 'C-c C-a' will put attached files at the end of the message.
 
----
 *** Message Mode now supports image yanking.
 
-+++
 *** New user option 'message-server-alist'.
 This controls automatic insertion of the "X-Message-SMTP-Method"
 header before sending a message.
 
 ** HTML Mode
 
----
 *** HTML Mode now supports "text/html" and "image/*" yanking.
 
 ** Texinfo Mode
 
----
 *** 'texinfo-mode' now has a specialized 'narrow-to-defun' definition.
 It narrows to the current node.
 
 ** EUDC
 
-+++
 *** Deprecations planned for next release.
 After Emacs 29.1, some aspects of EUDC will be deprecated.  The goal
 of these deprecations is to simplify EUDC server configuration by
@@ -2361,20 +2084,17 @@ possible servers, instead of requiring a call to 
'eudc-set-server'
 like it does in this release.  The default value of
 'eudc-ignore-options-file' will be changed from nil to t.
 
-+++
 *** New user option 'eudc-ignore-options-file' that defaults to nil.
 The 'eudc-ignore-options-file' user option can be configured to ignore
 the 'eudc-options-file' (typically "~/.emacs.d/eudc-options").  Most
 users should configure this to t and put EUDC configuration in the
 main Emacs initialization file ("~/.emacs" or "~/.emacs.d/init.el").
 
-+++
 *** 'eudc-expansion-overwrites-query' to 'eudc-expansion-save-query-as-kill'.
 The user option 'eudc-expansion-overwrites-query' is renamed to
 'eudc-expansion-save-query-as-kill' to reflect the actual behavior of
 the user option.  The former is kept as alias.
 
-+++
 *** New command 'eudc-expand-try-all'.
 This command can be used in place of 'eudc-expand-inline'.  It takes a
 prefix argument that causes 'eudc-expand-try-all' to return matches
@@ -2382,7 +2102,6 @@ from all servers instead of just the matches from the 
first server to
 return any.  This is useful for example, if one wants to search LDAP
 for a name that happens to match a contact in one's BBDB.
 
-+++
 *** New behavior and default for user option 'eudc-inline-expansion-format'.
 EUDC inline expansion result formatting defaulted to
 
@@ -2402,25 +2121,21 @@ is called, and the returned values are used to populate 
the phrase and
 comment parts (see RFC 5322 for definitions).  In both cases, the
 phrase part will be automatically quoted if necessary.
 
-+++
 *** New function 'eudc-capf-complete' with 'message-mode' integration.
 EUDC can now contribute email addresses to 'completion-at-point' by
 adding the new function 'eudc-capf-complete' to
 'completion-at-point-functions' in 'message-mode'.
 
-+++
 *** Additional attributes of query and results in eudcb-macos-contacts.el.
 The EUDC back-end for the macOS Contacts app now provides a wider set
 of attributes to use for queries, and delivers more attributes in
 query results.
 
-+++
 *** New back-end for ecomplete.
 A new back-end for ecomplete allows information from that database to
 be queried by EUDC, too.  The attributes present in the EUDC query are
 used to select the entry type in the ecomplete database.
 
-+++
 *** New back-end for mailabbrev.
 A new back-end for mailabbrev allows information from that database to
 be queried by EUDC, too.  Only the attributes 'email', 'name', and
@@ -2428,7 +2143,6 @@ be queried by EUDC, too.  Only the attributes 'email', 
'name', and
 
 ** EWW/SHR
 
-+++
 *** New user option to automatically rename EWW buffers.
 The 'eww-auto-rename-buffer' user option can be configured to rename
 rendered web pages by using their title, URL, or a user-defined
@@ -2437,26 +2151,22 @@ of the resulting name is controlled by the user option
 'eww-buffer-name-length'.  By default, no automatic renaming is
 performed.
 
-+++
 *** New user option 'shr-allowed-images'.
 This complements 'shr-blocked-images', but allows specifying just the
 allowed images.
 
-+++
 *** New user option 'shr-use-xwidgets-for-media'.
 If non-nil (and Emacs has been built with support for xwidgets),
 display <video> elements with an xwidget.  Note that this is
 experimental; it is known to crash Emacs on some systems, and just
 doesn't work on other systems.  Also see etc/PROBLEMS.
 
-+++
 *** New user option 'eww-url-transformers'.
 These are used to alter an URL before using it.  By default it removes
 the common "utm_" trackers from URLs.
 
 ** Find Dired
 
----
 *** New command 'find-dired-with-command'.
 This enables users to run 'find-dired' with an arbitrary command,
 enabling running commands previously unsupported and also enabling new
@@ -2464,7 +2174,6 @@ commands to be built on top.
 
 ** Gnus
 
-+++
 *** Tool bar changes in Gnus/Message.
 There were previously two styles of tool bars available in Gnus and
 Message, referred to as 'gnus-summary-tool-bar-retro',
@@ -2474,42 +2183,34 @@ Message, referred to as 'gnus-summary-tool-bar-retro',
 well as the icons used), and the "gnome" tool bars are now the only
 pre-defined toolbars.
 
----
 *** 'gnus-summary-up-thread' and 'gnus-summary-down-thread' bindings removed.
 The 'gnus-summary-down-thread' binding to 'M-C-d' was shadowed by
 'gnus-summary-read-document', and these commands are also available on
 'T u' and 'T d' respectively.
 
----
 *** Gnus now uses a variable-pitch font in the headers by default.
 To get the monospace font back, you can put something like the
 following in your ".gnus" file:
 
     (set-face-attribute 'gnus-header nil :inherit 'unspecified)
 
----
 *** The default value of 'gnus-treat-fold-headers' is now 'head'.
 
----
 *** New face 'gnus-header'.
 All other 'gnus-header-*' faces inherit from this face now.
 
-+++
 *** New user option 'gnus-treat-emojize-symbols'.
 If non-nil, symbols that have an Emoji representation will be
 displayed as emojis.  The default is nil.
 
-+++
 *** New command 'gnus-article-emojize-symbols'.
 This is bound to 'W D e' and will display symbols that have Emoji
 representation as Emoji.
 
-+++
 *** New mu backend for gnus-search.
 Configuration is very similar to the notmuch and namazu backends.  It
 supports the unified search syntax.
 
----
 *** 'gnus-html-image-cache-ttl' is now a seconds count.
 Formerly it was a pair of numbers '(A B)' that represented 65536*A + B,
 to cater to older Emacs implementations that lacked bignums.
@@ -2517,7 +2218,6 @@ The older form still works but is undocumented.
 
 ** Rmail
 
----
 *** Rmail partial summaries can now be applied one on top of the other.
 You can now narrow the set of messages selected by Rmail summary's
 criteria (recipients, topic, senders, etc.) by making a summary of the
@@ -2529,19 +2229,16 @@ to your selection.  The new user option
 the filters is in effect; customize it to a non-nil value to enable
 this feature.
 
----
 *** New Rmail summary: by thread.
 The new command 'rmail-summary-by-thread' produces a summary of
 messages that belong to a single thread of discussion.
 
 ** EIEIO
 
-+++
 *** 'slot-value' can now be used to access slots of 'cl-defstruct' objects.
 
 ** Align
 
----
 *** Alignment in 'text-mode' has changed.
 Previously, 'M-x align' didn't do anything, and you had to say 'C-u
 M-x align' for it to work.  This has now been changed.  The default
@@ -2550,12 +2247,10 @@ for inexperienced users to use.
 
 ** Help
 
----
 *** New mode, 'emacs-news-view-mode', for viewing the NEWS file.
 This mode is used by the 'C-h N' command, and adds buttons to manual
 entries and symbol references.
 
----
 *** New user option 'help-link-key-to-documentation'.
 When this option is non-nil (which is the default), key bindings
 displayed in the "*Help*" buffer will be linked to the documentation
@@ -2564,7 +2259,6 @@ key bindings and functions (such as 'C-h b').
 
 ** Info Look
 
----
 *** info-look specs can now be expanded at run time instead of a load time.
 The new ':doc-spec-function' element can be used to compute the
 ':doc-spec' element when the user asks for info on that particular
@@ -2572,19 +2266,16 @@ mode (instead of at load time).
 
 ** Ansi Color
 
----
 *** Support for ANSI 256-color and 24-bit colors.
 256-color and 24-bit color codes are now handled by ANSI color
 filters and displayed with the specified color.
 
 ** Term Mode
 
----
 *** New user option 'term-bind-function-keys'.
 If non-nil, 'term-mode' will pass the function keys on to the
 underlying shell instead of using the normal Emacs bindings.
 
----
 *** Support for ANSI 256-color and 24-bit colors, italic and other fonts.
 'term-mode' can now display 256-color and 24-bit color codes.  It can
 also handle ANSI codes for faint, italic and blinking text, displaying
@@ -2592,17 +2283,14 @@ it with new 'term-{faint,italic,slow-blink,fast-blink}' 
faces.
 
 ** Project
 
-+++
 *** 'project-find-file' and 'project-or-external-find-file' can include all.
 The commands 'project-find-file' and 'project-or-external-find-file'
 now accept a prefix argument, which is interpreted to mean "include
 all files".
 
-+++
 *** New command 'project-list-buffers' bound to 'C-x p C-b'.
 This command displays a list of buffers from the current project.
 
-+++
 *** 'project-kill-buffers' can display the list of buffers to kill.
 Customize the user option 'project-kill-buffers-display-buffer-list'
 to enable the display of the buffer list.
@@ -2614,19 +2302,16 @@ or projects outside of VCS repositories.
 As a consequence, the 'VC project backend' is formally renamed to
 'VC-aware project backend'.
 
-+++
 *** New user option 'project-vc-include-untracked'.
 If non-nil, files untracked by a VCS are considered to be part of
 the project by a VC project based on that VCS.
 
 ** Xref
 
-+++
 *** New command 'xref-go-forward'.
 It is bound to 'C-M-,' and jumps to the location where you previously
 invoked 'xref-go-back' ('M-,', also known as 'xref-pop-marker-stack').
 
-+++
 *** The depth of the Xref marker stack is now infinite.
 The implementation of the Xref marker stack was changed in a way that
 allows as many places to be saved on the stack as needed, limited only
@@ -2634,19 +2319,15 @@ by the available memory.  Therefore, the variables
 'find-tag-marker-ring-length' and 'xref-marker-ring-length' are now
 obsolete and unused; setting them has no effect.
 
-+++
 *** 'xref-query-replace-in-results' prompting change.
 This command no longer prompts for FROM when called without prefix
 argument.  This makes the most common case faster: replacing entire
 matches.
 
-+++
 *** New command 'xref-find-references-and-replace' to rename one identifier.
 
----
 *** New variable 'xref-current-item' (renamed from a private version).
 
----
 *** New function 'xref-show-xrefs'.
 
 *** 'outline-minor-mode' is supported in Xref buffers.
@@ -2655,12 +2336,10 @@ You can enable outlining by adding 'outline-minor-mode' 
to
 
 ** File Notifications
 
-+++
 *** The new command 'file-notify-rm-all-watches' removes all file 
notifications.
 
 ** Sql
 
----
 *** Sql now supports sending of passwords in-process.
 To improve security, if an sql product has ':password-in-comint' set
 to t, a password supplied via the minibuffer will be sent in-process,
@@ -2668,54 +2347,44 @@ as opposed to via the command-line.
 
 ** Image Mode
 
-+++
 *** New command 'image-transform-fit-to-window'.
 This command fits the image to the current window by scaling down or
 up as necessary.  Unlike 'image-transform-fit-both', this can scale
 the image up as well as down.  It is bound to 's w' in Image Mode by
 default.
 
----
 *** New command 'image-mode-wallpaper-set'.
 This command sets the desktop background to the current image.  It is
 bound to 'W' in Image Mode by default.
 
-+++
 *** 'image-transform-fit-to-{height,width}' are now obsolete.
 Use the new command 'image-transform-fit-to-window' instead.
 The keybinding for 'image-transform-fit-to-width' is now 's i'.
 
----
 *** User option 'image-auto-resize' can now be set to 'fit-window'.
 This works like 'image-transform-fit-to-window'.
 
----
 *** New user option 'image-auto-resize-max-scale-percent'.
 The new 'fit-window' option will never scale an image more than this
 much (in percent).  It is nil by default, which means no limit.
 
----
 *** New user option 'image-text-based-formats'.
 This controls whether or not to show a message, when opening certain
 image formats, explaining how to edit it as text.  The default is to
 show this message for SVG and XPM.
 
-+++
 *** New command 'image-transform-set-percent'.
 It allows resizing the image to a percentage of its original size, and
 is bound to 's p' in Image mode.
 
-+++
 *** 'image-transform-original' renamed to 'image-transform-reset-to-original'.
 The old name was confusing, and is now an obsolete function alias.
 
-+++
 *** 'image-transform-reset' renamed to 'image-transform-reset-to-initial'.
 The old name was confusing, and is now an obsolete function alias.
 
 ** Images
 
-+++
 *** New commands 'image-crop' and 'image-cut'.
 These commands allow interactively cropping/cutting the image at
 point.  The commands are bound to keys 'i c' and 'i x' (respectively)
@@ -2723,25 +2392,21 @@ in the local keymap over images.  They rely on external 
programs, by
 default "convert" from ImageMagick, to do the actual cropping/eliding
 of the image file.
 
-+++
 *** New commands 'image-flip-horizontally' and 'image-flip-vertically'.
 These commands horizontally and vertically flip the image under point,
 and are bound to 'i h' and 'i v', respectively.
 
-+++
 *** Users can now add special image conversion functions.
 This is done via 'image-converter-add-handler'.
 
 ** Image Dired
 
-+++
 *** 'image-dired-image-mode' is now based on 'image-mode'.
 This avoids converting images in the background, and makes Image-Dired
 noticeably faster.  New keybindings from 'image-mode' are now
 available in the "*image-dired-display-image*" buffer; press '?' or
 'h' in that buffer to see the full list.
 
----
 *** Navigation and marking commands now work in image display buffer.
 The following new bindings have been added:
 - 'n', 'SPC' => 'image-dired-display-next'
@@ -2750,52 +2415,43 @@ The following new bindings have been added:
 - 'd'        => 'image-dired-flag-thumb-original-file'
 - 'u'        => 'image-dired-unmark-thumb-original-file'
 
----
 *** New command 'image-dired-unmark-all-marks'.
 It removes all marks from all files in the thumbnail and the
 associated Dired buffer, and is bound to 'U' in the thumbnail and
 display buffer.
 
----
 *** New command 'image-dired-do-flagged-delete'.
 It deletes all flagged files, and is bound to 'x' in the thumbnail
 buffer.  It replaces the command 'image-dired-delete-marked', which is
 now an obsolete alias.
 
----
 *** New command 'image-dired-copy-filename-as-kill'.
 It copies the name of the marked or current image to the kill ring,
 and is bound to 'w' in the thumbnail buffer.
 
----
 *** New command 'image-dired-wallpaper-set'.
 This command sets the desktop background to the image at point in the
 thumbnail buffer.  It is bound to 'W' by default.
 
----
 *** 'image-dired-slideshow-start' is now bound to 'S'.
 It is bound in both the thumbnail and display buffer, and no longer
 prompts for a timeout; use a numerical prefix (e.g. 'C-u 8 S') to set
 the timeout.
 
----
 *** New user option 'image-dired-marking-shows-next'.
 If this option is non-nil (the default), marking, unmarking or
 flagging an image in either the thumbnail or display buffer shows the
 next image.
 
----
 *** New face 'image-dired-thumb-flagged'.
 If 'image-dired-thumb-mark' is non-nil (the default), this face is
 used for images that are flagged for deletion in the Dired buffer
 associated with Image-Dired.
 
----
 *** Image information is now shown in the header line of the thumbnail buffer.
 This replaces the message that most navigation commands in the
 thumbnail buffer used to show at the bottom of the screen.
 
----
 *** New specifiers for 'image-dired-display-properties-format'.
 This is used to format the new header line.  The new specifiers are:
 "%d" for the name of the directory that the file is in, "%n" for
@@ -2806,7 +2462,6 @@ old format, add this to your Init file:
 
     (setopt image-dired-display-properties-format "%b: %f (%t): %c")
 
----
 *** New faces for the header line of the thumbnail buffer.
 These faces correspond to different parts of the header line, as
 specified in 'image-dired-display-properties-format':
@@ -2815,37 +2470,31 @@ specified in 'image-dired-display-properties-format':
 - 'image-dired-thumb-header-file-size'
 - 'image-dired-thumb-header-image-count'
 
----
 *** PDF support.
 Image-Dired now displays thumbnails for PDF files.  Type 'RET' on a
 PDF file in the thumbnail buffer to visit the corresponding PDF.
 
----
 *** Support GraphicsMagick command line tools.
 Support for the GraphicsMagick command line tool ("gm") has been
 added, and is used when it is available instead of ImageMagick.
 
----
 *** Support Thumbnail Managing Standard v0.9.0 (Dec 2020).
 This standard allows sharing generated thumbnails across different
 programs.  Version 0.9.0 adds two larger thumbnail sizes: 512x512 and
 1024x1024 pixels.  See the user option 'image-dired-thumbnail-storage'
 to use it; it is not enabled by default.
 
----
 *** Reduce dependency on external "exiftool" program.
 The 'image-dired-copy-with-exif-file-name' command no longer requires
 an external "exiftool" program to be available.  The user options
 'image-dired-cmd-read-exif-data-program' and
 'image-dired-cmd-read-exif-data-options' are now obsolete.
 
----
 *** Support for bookmark.el.
 The command 'bookmark-set' (bound to 'C-x r m') is now supported in
 the thumbnail view, and will create a bookmark that opens the current
 directory in Image-Dired.
 
----
 *** The 'image-dired-slideshow-start' command no longer prompts.
 It no longer inconveniently prompts for a number of images and a
 delay: it runs indefinitely, but stops automatically on any command.
@@ -2853,7 +2502,6 @@ You can set the delay with a prefix argument, or a 
negative prefix
 argument to prompt for a delay.  Customize the user option
 'image-dired-slideshow-delay' to change the default from 5 seconds.
 
-+++
 *** 'image-dired-show-all-from-dir-max-files' increased to 1000.
 This user option controls asking for confirmation when starting
 Image-Dired in a directory with many files.  Since Image-Dired creates
@@ -2861,17 +2509,13 @@ thumbnails in the background in recent versions, this 
is not as
 important as it used to be.  You can now also customize this option to
 nil to disable this confirmation completely.
 
----
 *** 'image-dired-thumb-size' increased to 128.
 
-+++
 *** 'image-dired-db-file' renamed to 'image-dired-tags-db-file'.
 
----
 *** 'image-dired-display-image-mode' renamed to 'image-dired-image-mode'.
 The corresponding keymap is now named 'image-dired-image-mode-map'.
 
-+++
 *** Some commands have been renamed to be shorter.
 - 'image-dired-display-thumbnail-original-image' has been renamed to
   'image-dired-display-this'.
@@ -2881,25 +2525,21 @@ The corresponding keymap is now named 
'image-dired-image-mode-map'.
   to 'image-dired-display-previous'.
 The old names are now obsolete aliases.
 
----
 *** 'image-dired-thumb-{height,width}' are now obsolete.
 Customize 'image-dired-thumb-size' instead, which will set both the
 height and width.
 
----
 *** HTML image gallery generation is now obsolete.
 The 'image-dired-gallery-generate' command and these user options are
 now obsolete: 'image-dired-gallery-thumb-image-root-url',
 'image-dired-gallery-hidden-tags', 'image-dired-gallery-dir',
 'image-dired-gallery-image-root-url'.
 
----
 *** 'image-dired-rotate-thumbnail-{left,right}' are now obsolete.
 Instead, use commands 'image-dired-refresh-thumb' to generate a new
 thumbnail, or 'image-rotate' to rotate the thumbnail without updating
 the thumbnail file.
 
-+++
 *** Some commands and user options are now obsolete.
 Since 'image-dired-display-image-mode' is now based on 'image-mode',
 some commands and user options are no longer needed and are now obsolete:
@@ -2913,66 +2553,54 @@ some commands and user options are no longer needed and 
are now obsolete:
 
 ** Exif
 
----
 *** New function 'exif-field'.
 This is a convenience function to extract the field data from
 'exif-parse-file' and 'exif-parse-buffer'.
 
 ** Bookmarks
 
----
 *** 'list-bookmarks' now includes a type column.
 Types are registered via a 'bookmark-handler-type' symbol property on
 the jumping function.
 
-+++
 *** 'bookmark-sort-flag' can now be set to 'last-modified'.
 This will display bookmark list from most recently set to least
 recently set.
 
----
 *** When editing a bookmark annotation, 'C-c C-k' will now cancel.
 It is bound to the new command 'bookmark-edit-annotation-cancel'.
 
----
 *** New user option 'bookmark-fringe-mark'.
 This option controls the bitmap used to indicate bookmarks in the
 fringe (or nil to disable showing this marker).
 
 ** Xwidget
 
----
 *** New user option 'xwidget-webkit-buffer-name-format'.
 This option controls how xwidget-webkit buffers are named.
 
----
 *** New user option 'xwidget-webkit-cookie-file'.
 This option controls whether the xwidget-webkit buffers save cookies
 set by web pages, and if so, in which file to save them.
 
-+++
 *** New minor mode 'xwidget-webkit-edit-mode'.
 When this mode is enabled, self-inserting characters and other common
 web browser shortcut keys are redefined to send themselves to the
 WebKit widget.
 
-+++
 *** New minor mode 'xwidget-webkit-isearch-mode'.
 This mode acts similarly to incremental search, and allows searching
 the contents of a WebKit widget.  In xwidget-webkit mode, it is bound
 to 'C-s' and 'C-r'.
 
-+++
 *** New command 'xwidget-webkit-browse-history'.
 This command displays a buffer containing the page load history of
 the current WebKit widget, and allows you to navigate it.
 
----
 *** On X, the WebKit inspector is now available inside xwidgets.
 To access the inspector, right click on the widget and select "Inspect
 Element".
 
----
 *** "Open in New Window" in a WebKit widget's context menu now works.
 The newly created buffer will be displayed via 'display-buffer', which
 can be customized through the usual mechanism of 'display-buffer-alist'
@@ -2980,31 +2608,26 @@ and friends.
 
 ** Tramp
 
-+++
 *** New connection methods "docker", "podman" and "kubernetes".
 They allow accessing containers provided by Docker and similar
 programs.
 
----
 *** Tramp supports abbreviating remote home directories now.
 When calling 'abbreviate-file-name' on a Tramp file name, the result
 will abbreviate the user's home directory, for example by abbreviating
 "/ssh:user@host:/home/user" to "/ssh:user@host:~".
 
-+++
 *** New user option 'tramp-use-scp-direct-remote-copying'.
 When set to non-nil, Tramp does not copy files between two remote
 hosts via a local copy in its temporary directory, but lets the 'scp'
 command do this job.
 
-+++
 *** Proper password prompts for methods "doas", "sudo" and "sudoedit".
 The password prompts for these methods reflect now the credentials of
 the user requesting such a connection, and not of the user who is the
 target.  This has always been needed, just the password prompt and the
 related 'auth-sources' entry were wrong.
 
-+++
 *** New user option 'tramp-completion-use-cache'.
 During user and host name completion in the minibuffer, results from
 Tramp's connection cache are taken into account.  This can be disabled
@@ -3012,41 +2635,34 @@ by setting the user option 'tramp-completion-use-cache' 
to nil.
 
 ** Browse URL
 
----
 *** New user option 'browse-url-default-scheme'.
 This user option decides which URL scheme that 'browse-url' and
 related functions will use by default.  For example, you could
 customize this to "https" to always prefer HTTPS URLs.
 
----
 *** New user option 'browse-url-irc-function'.
 This option specifies a function for opening "irc://" links.  It
 defaults to the new function 'browse-url-irc'.
 
----
 *** New function 'browse-url-irc'.
 This multipurpose autoloaded function can be used for opening "irc://"
 and "ircs://" URLS by any caller that passes a URL string as an initial
 arg.
 
----
 *** Support for the Netscape web browser has been removed.
 This support has been obsolete since Emacs 25.1.  The final version of
 the Netscape web browser was released in February, 2008.
 
----
 *** Support for the Galeon web browser has been removed.
 This support has been obsolete since Emacs 25.1.  The final version of
 the Galeon web browser was released in September, 2008.
 
----
 *** Support for the Mozilla web browser is now obsolete.
 Note that this historical web browser is different from Mozilla
 Firefox; it is its predecessor.
 
 ** Python Mode
 
-+++
 *** Project shells and a new user option 'python-shell-dedicated'.
 When called with a prefix argument, 'run-python' now offers the choice
 of creating a shell dedicated to the current project.  This shell runs
@@ -3058,13 +2674,10 @@ project-dedicated or global) is specified by the new
 
 ** Ruby Mode
 
----
 *** New user option 'ruby-toggle-block-space-before-parameters'.
 
----
 *** Support for endless methods.
 
----
 *** New user options that determine indentation logic.
 'ruby-method-params-indent', 'ruby-block-indent',
 'ruby-after-operator-indent', 'ruby-method-call-indent',
@@ -3073,7 +2686,6 @@ explanations and examples.
 
 ** Eshell
 
-+++
 *** New feature to easily bypass Eshell's own pipelining.
 Prefixing '|', '<' or '>' with an asterisk, i.e. '*|', '*<' or '*>',
 will cause the whole command to be passed to the operating system
@@ -3082,7 +2694,6 @@ support for pipelines which will move a lot of data.  See 
section
 "Running Shell Pipelines Natively" in the Eshell manual, node
 "(eshell) Pipelines".
 
-+++
 *** New module to help supplying absolute file names to remote commands.
 After enabling the new 'eshell-elecslash' module, typing a forward
 slash as the first character of a command line argument will
@@ -3093,40 +2704,34 @@ commands are Lisp function or external when supplying 
absolute file
 name arguments.  See the "(eshell) Electric forward slash" node in the
 Eshell manual for details.
 
-+++
 *** Improved support for redirection operators in Eshell.
 Eshell now supports a wider variety of redirection operators.  For
 example, you can now redirect both stdout and stderr via '&>' or
 duplicate one output handle to another via 'NEW-FD>&OLD-FD'.  For more
 information, see the "(eshell) Redirection" node in the Eshell manual.
 
-+++
 *** New eshell built-in command 'doas'.
 The privilege-escalation program 'doas' has been added to the existing
 'su' and 'sudo' commands from the 'eshell-tramp' module.  The external
 command may still be accessed by using '*doas'.
 
-+++
 *** Double-quoting an Eshell expansion now treats the result as a single 
string.
 If an Eshell expansion like '$FOO' is surrounded by double quotes, the
 result will always be a single string, no matter the type that would
 otherwise be returned.
 
-+++
 *** Concatenating Eshell expansions now works more similarly to other shells.
 When concatenating an Eshell expansion that returns a list, "adjacent"
 elements of each operand are now concatenated together,
 e.g. '$(list "a" "b")c' returns '("a" "bc")'.  See the "(eshell)
 Expansion" node in the Eshell manual for more details.
 
-+++
 *** Eshell subcommands with multiline numeric output return lists of numbers.
 If every line of the output of an Eshell subcommand like '${COMMAND}'
 is numeric, the result will be a list of numbers (or a single number
 if only one line of output).  Previously, this only converted numbers
 when there was a single line of output.
 
----
 *** Built-in Eshell commands now follow Posix/GNU argument syntax conventions.
 Built-in commands in Eshell now accept command-line options with
 values passed as a single token, such as '-oVALUE' or
@@ -3134,13 +2739,11 @@ values passed as a single token, such as '-oVALUE' or
 'eshell-eval-using-options' macro.  See "Defining new built-in
 commands" in the "(eshell) Built-ins" node of the Eshell manual.
 
----
 *** Eshell globs ending with "/" now match only directories.
 Additionally, globs ending with "**/" or "***/" no longer raise an
 error, and now expand to all directories recursively (following
 symlinks in the latter case).
 
-+++
 *** Lisp forms in Eshell now treat a nil result as a failed exit status.
 When executing a command that looks like '(lisp form)' and returns
 nil, Eshell will set the exit status (available in the '$?'
@@ -3150,12 +2753,10 @@ conditionals.  To change this behavior, customize the 
new
 
 ** Shell
 
----
 *** New user option 'shell-kill-buffer-on-exit'.
 Enabling this will automatically kill a "*shell*" buffer as soon as
 the shell session terminates.
 
----
 *** New minor mode 'shell-highlight-undef-mode'.
 Customize 'shell-highlight-undef-enable' to t if you want to enable
 this minor mode in "*shell*" buffers.  It will highlight undefined
@@ -3163,13 +2764,11 @@ commands with a warning face as you type.
 
 ** Calc
 
-+++
 *** New user option 'calc-kill-line-numbering'.
 Set it to nil to exclude line numbering from kills and copies.
 
 ** Hierarchy
 
-+++
 *** Tree Display can delay computation of children.
 'hierarchy-add-tree' and 'hierarchy-add-trees' have an optional
 argument which allows tree-widget display to be activated and computed
@@ -3177,7 +2776,6 @@ only when the user expands the node.
 
 ** Proced
 
----
 *** proced.el shows system processes of remote hosts.
 When 'default-directory' is remote, and 'proced' is invoked with a
 negative argument like 'C-u - proced', the system processes of that
@@ -3185,7 +2783,6 @@ remote host are shown.  Alternatively, the user option
 'proced-show-remote-processes' can be set to non-nil.
 'proced-signal-function' has been marked obsolete.
 
----
 *** Proced can now optionally show process details in color.
 New user option 'proced-enable-color-flag' enables coloring of Proced
 buffers.  This option is disabled by default; customize it to a
@@ -3193,36 +2790,30 @@ non-nil value to enable colors.
 
 ** Miscellaneous
 
----
 *** New user option 'webjump-use-internal-browser'.
 When non-nil, WebJump will use an internal browser to open web pages,
 instead of the default external browser.
 
-+++
 *** New user option 'font-lock-ignore'.
 This option provides a mechanism to selectively disable font-lock
 keyword-driven fontifications.
 
----
 *** New user option 'auto-save-visited-predicate'.
 This user option is a predicate function which is called by
 'auto-save-visited-mode' to decide whether or not to save a buffer.
 You can use it to automatically save only specific buffers, for
 example buffers using a particular mode or in some directory.
 
----
 *** New user option 'remote-file-name-inhibit-auto-save-visited'.
 If this user option is non-nil, 'auto-save-visited-mode' will not
 auto-save remote buffers.  The default is nil.
 
-+++
 *** New package vtable.el for formatting tabular data.
 This package allows formatting data using variable-pitch fonts.
 The resulting tables can display text in variable pitch fonts, text
 using fonts of different sizes, and images.  See the "(vtable) Top"
 manual for more details.
 
----
 *** New minor mode 'elide-head-mode'.
 Enabling this minor mode turns on hiding header material, like
 'elide-head' does; disabling it shows the header.  The commands
@@ -3241,27 +2832,22 @@ filtered out.
 The list of handlers (already covering OSC 7 and 8) has been extended
 with a handler for OSC 2, the command to set a window title.
 
----
 *** 'recentf-mode' now uses abbreviated file names by default.
 This means that e.g. "/home/foo/bar" is now displayed as "~/bar".
 Customize the user option 'recentf-filename-handlers' to nil to get
 back the old behavior.
 
----
 *** New command 'recentf-open'.
 This command prompts for a recently opened file in the minibuffer, and
 visits it.
 
----
 *** 'ffap-machine-at-point' no longer pings hosts by default.
 It will now simply look at a hostname to determine if it is valid,
 instead of also trying to ping it.  Customize the user option
 'ffap-machine-p-known' to 'ping' to get the old behavior back.
 
----
 *** The 'run-dig' command is now obsolete; use 'dig' instead.
 
----
 *** Some 'bib-mode' commands and variables have been renamed.
 To respect Emacs naming conventions, the variable 'unread-bib-file'
 has been renamed to 'bib-unread-file'.  The following commands have
@@ -3271,36 +2857,29 @@ also been renamed:
     'mark-bib'         to  'bib-mark'
     'unread-bib'       to  'bib-unread'
 
----
 *** 'outlineify-sticky' command is renamed to 'allout-outlinify-sticky'.
 The old name is still available as an obsolete function alias.
 
----
 *** The url-irc library now understands "ircs://" links.
 
----
 *** New command 'world-clock-copy-time-as-kill' for 'world-clock-mode'.
 It copies the current line into the kill ring.
 
----
 *** 'edit-abbrevs' now uses font-locking.
 The new face 'abbrev-table-name' is used to display the abbrev table
 name.
 
----
 *** New key binding 'O' in "*Buffer List*".
 This key is now bound to 'Buffer-menu-view-other-window', which will
 view this line's buffer in View mode in another window.
 
 ** Scheme Mode
 
----
 *** Auto-detection of Scheme library files.
 Emacs now automatically enables the Scheme mode when opening R6RS
 Scheme Library Source (".sls") files and R7RS Scheme Library
 Definition (".sld") files.
 
----
 *** Imenu members for R6RS and R7RS library members.
 Imenu now lists the members directly nested in R6RS Scheme libraries
 ('library') and R7RS libraries ('define-library').
@@ -3308,7 +2887,6 @@ Imenu now lists the members directly nested in R6RS 
Scheme libraries
 
 * New Modes and Packages in Emacs 29.1
 
-+++
 ** Eglot: Emacs Client for the Language Server Protocol.
 Emacs now comes with the Eglot package, which enhances various Emacs
 features, such as completion, documentation, error detection, etc.,
@@ -3320,7 +2898,6 @@ If you want to be able to use 'package-install' to 
upgrade Eglot to
 newer versions released on GNU ELPA, customize the new option
 'package-install-upgrade-built-in' to a non-nil value.
 
-+++
 ** use-package: Declarative package configuration.
 use-package is now shipped with Emacs.  It provides the 'use-package'
 macro, which allows you to isolate package configuration in your init
@@ -3331,7 +2908,6 @@ If you want to be able to use 'package-install' to 
upgrade use-package
 to newer versions released on GNU ELPA, customize the new option
 'package-install-upgrade-built-in' to a non-nil value.
 
----
 ** New package 'wallpaper'.
 This package provides the command 'wallpaper-set', which sets the
 desktop background image.  Depending on the system and the desktop,
@@ -3341,7 +2917,6 @@ detected automatically in most cases.  It can also be 
customized
 manually if needed, using the new user options 'wallpaper-command' and
 'wallpaper-command-args'.
 
-+++
 ** New package 'oclosure'.
 This allows the creation of OClosures, which are "functions with
 slots" or "function objects" that expose additional information about
@@ -3349,33 +2924,27 @@ themselves.  Use the new macros 'oclosure-define' and
 'oclosure-lambda' to create OClosures.  See the "(elisp) OClosures"
 node for more information.
 
-+++
 *** New generic function 'oclosure-interactive-form'.
 Used by 'interactive-form' when called on an OClosure.
 This allows specific OClosure types to compute their interactive specs
 on demand rather than precompute them when created.
 
----
 ** New theme 'leuven-dark'.
 This is a dark version of the 'leuven' theme.
 
-+++
 ** New mode 'erts-mode'.
 This mode is used to edit files geared towards testing actions in
 Emacs buffers, like indentation and the like.  The new ert function
 'ert-test-erts-file' is used to parse these files.
 
----
 ** New major mode 'js-json-mode'.
 This is a lightweight variant of 'js-mode' that is used by default
 when visiting JSON files.
 
-+++
 ** New major mode 'csharp-mode'.
 A major mode based on CC Mode for editing programs in the C# language.
 This mode is auto-enabled for files with the ".cs" extension.
 
-+++
 ** New major modes based on the tree-sitter library.
 These new major modes are available if Emacs was built with the
 tree-sitter library.  They provide support for font-locking,
@@ -3417,96 +2986,77 @@ If a language grammar library required by a mode is not 
found in any
 of the above places, the mode will display a warning when you try to
 turn it on.
 
-+++
 *** New major mode 'typescript-ts-mode'.
 A major mode based on the tree-sitter library for editing programs
 in the TypeScript language.
 
-+++
 *** New major mode 'tsx-ts-mode'.
 A major mode based on the tree-sitter library for editing programs
 in the TypeScript language, with support for TSX.
 
-+++
 *** New major mode 'c-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the C language.
 
-+++
 *** New major mode 'c++-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the C++ language.
 
-+++
 *** New command 'c-or-c++-ts-mode'.
 A command that automatically guesses the language of a header file,
 and enables either 'c-ts-mode' or 'c++-ts-mode' accordingly.
 
-+++
 *** New major mode 'java-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the Java language.
 
-+++
 *** New major mode 'python-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the Python language.
 
-+++
 *** New major mode 'css-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 CSS (Cascading Style Sheets).
 
-+++
 *** New major mode 'json-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the JSON language.
 
-+++
 *** New major mode 'csharp-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the C# language.
 
-+++
 *** New major mode 'bash-ts-mode'.
 Am optional major mode based on the tree-sitter library for editing
 Bash shell scripts.
 
-+++
 *** New major mode 'dockerfile-ts-mode'.
 A major mode based on the tree-sitter library for editing
 Dockerfiles.
 
-+++
 *** New major mode 'cmake-ts-mode'.
 A major mode based on the tree-sitter library for editing CMake files.
 
-+++
 *** New major mode 'toml-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 files written in TOML, a format for writing configuration files.
 
-+++
 *** New major mode 'go-ts-mode'.
 A major mode based on the tree-sitter library for editing programs in
 the Go language.
 
-+++
 *** New major mode 'go-mod-ts-mode'.
 A major mode based on the tree-sitter library for editing "go.mod"
 files.
 
-+++
 *** New major mode 'yaml-ts-mode'.
 A major mode based on the tree-sitter library for editing files
 written in YAML.
 
-+++
 *** New major mode 'rust-ts-mode'.
 A major mode based on the tree-sitter library for editing programs in
 the Rust language.
 
----
 *** New major mode 'ruby-ts-mode'.
 An optional major mode based on the tree-sitter library for editing
 programs in the Ruby language.
@@ -3514,7 +3064,6 @@ programs in the Ruby language.
 
 * Incompatible Lisp Changes in Emacs 29.1
 
-+++
 ** The implementation of overlays has changed.
 Emacs now uses an implementation of overlays that is much more
 efficient than the original one, and should speed up all the
@@ -3528,7 +3077,6 @@ or user level, with the exception of better performance 
and the order
 of overlays returned by functions that don't promise any particular
 order.
 
----
 *** The function 'overlay-recenter' is now a no-op.
 This function does nothing, and in particular has no effect on the
 value returned by 'overlay-lists'.  The purpose of 'overlay-recenter'
@@ -3538,30 +3086,25 @@ is efficient regardless of their position, and there's 
no longer any
 need to "optimize" the lookup, nor any notion of a "center" of the
 overlays.
 
----
 *** The function 'overlay-lists' returns one unified list of overlays.
 This function used to return a cons of two lists, one with overlays
 before the "center" position, the other after that "center".  It now
 returns a list whose 'car' is the list of all the buffer overlays, and
 whose 'cdr' is always nil.
 
-+++
 ** 'format-prompt' now uses 'substitute-command-keys'.
 This means that both the prompt and 'minibuffer-default-prompt-format'
 will have key definitions and single quotes handled specially.
 
-+++
 ** New function 'substitute-quotes'.
 This function works like 'substitute-command-keys' but only
 substitutes quote characters.
 
----
 ** 'find-image' now uses 'create-image'.
 This means that images found through 'find-image' also have
 auto-scaling applied.  (This only makes a difference on HiDPI
 displays.)
 
-+++
 ** Changes in how "raw" in-memory XBM images are specified.
 Some years back Emacs gained the ability to scale images, and you
 could then specify ':width' and ':height' when using 'create-image' on all
@@ -3573,7 +3116,6 @@ has been changed, and ':width'/':height' now works as 
with all other image
 formats, and the way to specify the width/height of the "raw"
 in-memory format is now by using ':data-width' and ':data-height'.
 
-+++
 ** "loaddefs.el" generation has been reimplemented.
 The various "loaddefs.el" files in the Emacs tree (which contain
 information about autoloads, built-in packages and package prefixes)
@@ -3589,19 +3131,16 @@ Previously, ';;;###' specs inside a top-level form 
(i.e., something
 like '(when ... ;;;### ...)' would be ignored.  They are now parsed as
 usual.
 
----
 ** Themes have special autoload cookies.
 All built-in themes are scraped for ';;;###theme-autoload' cookies
 that are loaded along with the regular auto-loaded code.
 
-+++
 ** 'buffer-modified-p' has been extended.
 This function was previously documented to return only nil or t.  This
 has been changed to nil/'autosaved'/non-nil.  The new 'autosaved'
 value means that the buffer is modified, but that it hasn't been
 modified since the time of last auto-save.
 
----
 ** 'with-silent-modifications' also restores buffer autosave status.
 'with-silent-modifications' is a macro meant to be used by the font
 locking machinery to allow applying text properties without changing
@@ -3610,7 +3149,6 @@ buffer autosave status, so applying font locking to a 
modified buffer
 that had already been auto-saved would trigger another auto-saving.
 This is no longer the case.
 
----
 ** 'prin1' doesn't always escape "." and "?" in symbols any more.
 Previously, symbols like 'foo.bar' would be printed by 'prin1' as
 "foo\.bar".  This now prints as "foo.bar" instead.  The Emacs Lisp
@@ -3626,19 +3164,16 @@ If the "." and "?" characters are the first character 
in the symbol,
 they will still be escaped, so the '.foo' symbol is still printed as
 "\.foo" and the '?bar' symbol is still printed as "\?bar".
 
-+++
 ** Remapping 'mode-line' face no longer works as expected.
 'mode-line' is now the parent face of the new 'mode-line-active' face,
 and remapping parent of basic faces does not work reliably.
 Instead of remapping 'mode-line', you have to remap 'mode-line-active'.
 
-+++
 ** 'make-process' has been extended to support ptys when ':stderr' is set.
 Previously, setting ':stderr' to a non-nil value would force the
 process's connection to use pipes.  Now, Emacs will use a pty for
 stdin and stdout if requested no matter the value of ':stderr'.
 
----
 ** User option 'mail-source-ignore-errors' is now obsolete.
 The whole mechanism for prompting users to continue in case of
 mail-source errors has been removed, so this option is no longer
@@ -3646,7 +3181,6 @@ needed.
 
 ** Fonts
 
----
 *** Emacs now supports 'medium' fonts.
 Emacs previously didn't distinguish between the 'regular'/'normal'
 weight and the 'medium' weight, but it now also supports the (heavier)
@@ -3655,7 +3189,6 @@ weight and the 'medium' weight, but it now also supports 
the (heavier)
 font spec.  In these cases, replacing ":weight 'normal" with ":weight
 'medium" should fix the issue.
 
----
 ** Keymap descriptions by Help commands have changed.
 'help--describe-command', 'C-h b' and associated functions that output
 keymap descriptions have changed.  In particular, prefix commands are
@@ -3664,7 +3197,6 @@ functions output "[closure]"/"[lambda]".  You can get 
back the old
 behavior of including prefix commands by customizing the new option
 'describe-bindings-show-prefix-commands' to a non-nil value.
 
----
 ** 'downcase' details have changed slightly.
 In certain locales, changing the case of an ASCII-range character may
 turn it into a multibyte character, most notably with "I" in Turkish
@@ -3676,13 +3208,11 @@ get proper locale-dependent downcasing, the string has 
to be converted
 to multibyte first.  (This goes for the other case-changing functions,
 too.)
 
----
 ** Functions in 'tramp-foreign-file-name-handler-alist' have changed.
 Functions to determine which Tramp file name handler to use are now
 passed a file name in dissected form (via 'tramp-dissect-file-name')
 instead of in string form.
 
----
 ** 'def' indentation changes.
 In 'emacs-lisp-mode', forms with a symbol with a name that start with
 "def" have been automatically indented as if they were 'defun'-like
@@ -3702,33 +3232,26 @@ like:
 
     (put 'defzot 'lisp-indent-function 'defun)
 
----
 ** The 'inhibit-changing-match-data' variable is now obsolete.
 Instead, functions like 'string-match' and 'looking-at' now take an
 optional INHIBIT-MODIFY argument.
 
----
 ** 'gnus-define-keys' is now obsolete.
 Use 'define-keymap' instead.
 
----
 ** MozRepl has been removed from js.el.
 MozRepl was removed from Firefox in 2017, so this code doesn't work
 with recent versions of Firefox.
 
----
 ** The function 'image-dired-get-exif-data' is now obsolete.
 Use 'exif-parse-file' and 'exif-field' instead.
 
----
 ** 'insert-directory' alternatives should not change the free disk space line.
 This change is now applied in 'dired-insert-directory'.
 
----
 ** 'compilation-last-buffer' is (finally) declared obsolete.
 It has been obsolete since Emacs 22.1, actually.
 
----
 ** Calling 'lsh' now elicits a byte-compiler warning.
 'lsh' behaves in somewhat surprising and platform-dependent ways for
 negative arguments, and is generally slower than 'ash', which should be
@@ -3736,7 +3259,6 @@ used instead.  This warning can be suppressed by 
surrounding calls to
 'lsh' with the construct '(with-suppressed-warnings ((suspicious lsh)) ...)',
 but switching to 'ash' is generally much preferable.
 
----
 ** Some functions and variables obsolete since Emacs 24 have been removed:
 'Buffer-menu-buffer+size-width', 'Electric-buffer-menu-mode',
 'Info-edit-map', 'allout-abbreviate-flattened-numbering',
@@ -3834,7 +3356,6 @@ but switching to 'ash' is generally much preferable.
 'which-func-mode' (function), 'window-system-version',
 'winner-mode-leave-hook', 'x-cut-buffer-or-selection-value'.
 
----
 ** Some functions and variables obsolete since Emacs 23 have been removed:
 'find-emacs-lisp-shadows', 'newsticker-cache-filename',
 'process-filter-multibyte-p', 'redisplay-end-trigger-functions',
@@ -3842,17 +3363,14 @@ but switching to 'ash' is generally much preferable.
 'unify-8859-on-decoding-mode', 'unify-8859-on-encoding-mode',
 'vc-arch-command', 'window-redisplay-end-trigger', 'x-selection'.
 
----
 ** Some functions and variables obsolete since Emacs 21 or 22 have been 
removed:
 'c-toggle-auto-state', 'find-file-not-found-hooks',
 'ls-lisp-dired-ignore-case', 'query-replace-regexp-eval'.
 
-+++
 ** New generic function 'function-documentation'.
 It can dynamically generate a raw docstring depending on the type of a
 function.  Used mainly for docstrings of OClosures.
 
-+++
 ** Base64 encoding no longer tolerates latin-1 input.
 The functions 'base64-encode-string', 'base64url-encode-string',
 'base64-encode-region' and 'base64url-encode-region' no longer accept
@@ -3860,24 +3378,20 @@ characters in the range U+0080..U+00FF as substitutes 
for single bytes
 in the range 128..255, but signal an error for all multibyte characters.
 The input must be unibyte encoded text.
 
-+++
 ** The 'clone-indirect-buffer-hook' is now run by 'make-indirect-buffer'.
 It was previously only run by 'clone-indirect-buffer' and
 'clone-indirect-buffer-other-window'.  Since 'make-indirect-buffer' is
 called by both of these, the hook is now run by all 3 of these
 functions.
 
----
 ** '?\' at the end of a line now signals an error.
 Previously, it produced a nonsense value, -1, that was never intended.
 
----
 ** Some libraries obsolete since Emacs 24.1 and 24.3 have been removed:
 abbrevlist.el, assoc.el, complete.el, cust-print.el,
 erc-hecomplete.el, mailpost.el, mouse-sel.el, old-emacs-lock.el,
 patcomp.el, pc-mode.el, pc-select.el, s-region.el, and sregex.el.
 
-+++
 ** Many seldom-used generalized variables have been made obsolete.
 Emacs has a number of rather obscure generalized variables defined,
 that, for instance, allowed you to say things like:
@@ -3903,7 +3417,6 @@ The following generalized variables have been made 
obsolete:
 'standard-case-table', 'syntax-table', 'visited-file-modtime',
 'window-height', 'window-width', and 'x-get-secondary-selection'.
 
----
 ** The 'dotimes' loop variable can no longer be manipulated in the loop body.
 Previously, the 'dotimes' loop counter could be modified inside the
 loop body, but only in code using dynamic binding.  Now the behavior
@@ -3919,7 +3432,6 @@ now always prints the numbers 0 .. 9.
 
 * Lisp Changes in Emacs 29.1
 
-+++
 ** Interpreted closures are "safe for space".
 As was already the case for byte-compiled closures, instead of capturing
 the whole current lexical environment, interpreted closures now only
@@ -3928,55 +3440,45 @@ The previous behavior could occasionally lead to memory 
leaks or
 to problems where a printed closure would not be 'read'able because
 of an un'read'able value in an unrelated lexical variable.
 
-+++
 ** New accessor function 'file-attribute-file-identifier'.
 It returns the list of the inode number and device identifier
 retrieved by 'file-attributes'.  This value can be used to identify a
 file uniquely.  The device identifier can be a single number or (for
 remote files) a cons of 2 numbers.
 
-+++
 ** New macro 'while-let'.
 This is like 'when-let', but repeats until a binding form is nil.
 
-+++
 ** New function 'make-obsolete-generalized-variable'.
 This can be used to mark setters used by 'setf' as obsolete, and the
 byte-compiler will then warn about using them.
 
-+++
 ** New functions 'pos-eol' and 'pos-bol'.
 These are like 'line-end-position' and 'line-beginning-position'
 (respectively), but ignore fields (and are more efficient).
 
-+++
 ** New function 'compiled-function-p'.
 This returns non-nil if its argument is either a built-in, or a
 byte-compiled, or a natively-compiled function object, or a function
 loaded from a dynamic module.
 
----
 ** 'deactivate-mark' can have new value 'dont-save'.
 This value means that Emacs should deactivate the mark as usual, but
 without setting the primary selection, if 'select-active-regions' is
 enabled.
 
-+++
 ** New 'declare' form 'interactive-args'.
 This can be used to specify what forms to put into 'command-history'
 when executing commands interactively.
 
-+++
 ** The FORM argument of 'time-convert' is mandatory.
 'time-convert' can still be called without it, as before, but the
 compiler now emits a warning about this deprecated usage.
 
-+++
 ** Emacs now supports user-customizable and themable icons.
 These can be used for buttons in buffers and the like.  See the
 "(elisp) Icons" and "(emacs) Icons" nodes in the manuals for details.
 
-+++
 ** New arguments MESSAGE and TIMEOUT of 'set-transient-map'.
 MESSAGE specifies a message to display after activating the transient
 map, including a special formatting spec to list available keys.
@@ -3984,7 +3486,6 @@ TIMEOUT is the idle time after which to deactivate the 
transient map.
 The default timeout value can be defined by the new variable
 'set-transient-map-timeout'.
 
-+++
 ** New forms 'with-restriction' and 'without-restriction'.
 These forms can be used as enhanced alternatives to the
 'save-restriction' form combined with, respectively,
@@ -3994,88 +3495,70 @@ See the "(elisp) Narrowing" node for details.
 
 ** Connection Local Variables
 
-+++
 *** Some connection-local variables are now user options.
 The variables 'connection-local-profile-alist' and
 'connection-local-criteria-alist' are now user options, in order to
 make it more convenient to inspect and modify them.
 
-+++
 *** New function 'connection-local-update-profile-variables'.
 This function allows to modify the settings of an existing
 connection-local profile.
 
-+++
 *** New macro 'with-connection-local-application-variables'.
 This macro works like 'with-connection-local-variables', but it allows
 using another application instead of 'tramp'.  This is useful when
 running code in a buffer where Tramp has already set some
 connection-local variables.
 
-+++
 *** New macro 'setq-connection-local'.
 This allows dynamically setting variable values for a particular
 connection within the body of 'with-connection-local-{application-}variables'.
 See the "(elisp) Connection Local Variables" node in the Lisp
 Reference manual for more information.
 
-+++
 ** 'plist-get', 'plist-put' and 'plist-member' are no longer limited to 'eq'.
 These function now take an optional comparison PREDICATE argument.
 
-+++
 ** 'read-multiple-choice' can now use long-form answers.
 
-+++
 ** 'M-s c' in 'read-regexp' now toggles case folding.
 
-+++
 ** 'completing-read' now allows a function as its REQUIRE-MATCH argument.
 This function is called to see whether what the user has typed is a
 match.  This is also available from functions that call
 'completing-read', like 'read-file-name'.
 
-+++
 ** 'posn-col-row' can now give position data based on windows.
 Previously, it reported data only based on the frame.
 
-+++
 ** 'file-expand-wildcards' can now also take a regexp as PATTERN argument.
 
----
 ** vc-mtn (the VC backend for Monotone) has been made obsolete.
 
-+++
 ** 'gui-set-selection' can specify different values for different data types.
 If DATA is a string, then its text properties are searched for values
 for each specific data type while the selection is being converted.
 
----
 ** New eldoc function 'elisp-eldoc-var-docstring-with-value'.
 This function includes the current value of the variable in eldoc display
 and can be used as a more detailed alternative to 'elisp-eldoc-var-docstring'.
 
-+++
 ** 'save-some-buffers' can now be extended to save other things.
 Traditionally, 'save-some-buffers' saved buffers, and also saved
 abbrevs.  This has been generalized via the
 'save-some-buffers-functions' variable, and packages can now register
 things to be saved.
 
-+++
 ** New function 'string-equal-ignore-case'.
 This compares strings ignoring case differences.
 
-+++
 ** 'symbol-file' can now report natively-compiled ".eln" files.
 If Emacs was built with native-compilation enabled, Lisp programs can
 now call 'symbol-file' with the new optional 3rd argument non-nil to
 request the name of the ".eln" file which defined a given symbol.
 
-+++
 ** New macro 'with-memoization' provides a very primitive form of memoization.
 
-+++
 ** 'max-char' can now report the maximum codepoint according to Unicode.
 When called with a new optional argument UNICODE non-nil, 'max-char'
 will now report the maximum valid codepoint defined by the Unicode
@@ -4083,46 +3566,37 @@ Standard.
 
 ** Seq
 
-+++
 *** New function 'seq-split'.
 This returns a list of sub-sequences of the specified sequence.
 
-+++
 *** New function 'seq-remove-at-position'.
 This function returns a copy of the specified sequence with the
 element at a given (zero-based) index removed.
 
-+++
 *** New function 'seq-positions'.
 This returns a list of the (zero-based) indices of elements matching a
 given predicate in the specified sequence.
 
-+++
 *** New function 'seq-keep'.
 This is like 'seq-map', but removes all nil results from the returned
 list.
 
 ** Themes
 
----
 *** New hooks 'enable-theme-functions' and 'disable-theme-functions'.
 These are run after enabling and disabling a theme, respectively.
 
----
 *** Themes can now be made obsolete.
 Using 'make-obsolete' on a theme is now supported.  This will make
 'load-theme' issue a warning when loading the theme.
 
-+++
 ** New hook 'display-monitors-changed-functions'.
 It is called whenever the configuration of different monitors on a
 display changes.
 
-+++
 ** 'prin1' and 'prin1-to-string' now take an optional OVERRIDES argument.
 This argument can be used to override values of print-related settings.
 
-+++
 ** New minor mode 'header-line-indent-mode'.
 This is meant to be used by Lisp programs that show a header line
 which should be kept aligned with the buffer contents when the user
@@ -4130,12 +3604,10 @@ switches 'display-line-numbers-mode' on or off, and 
when the width of
 line-number display changes.  See the "(elisp) Header Lines" node in
 the Emacs Lisp Reference manual for more information.
 
-+++
 ** New global minor mode 'lost-selection-mode'.
 This global minor mode makes Emacs deactivate the mark in all buffers
 when the primary selection is obtained by another program.
 
----
 ** On X, Emacs will try to preserve selection ownership when a frame is 
deleted.
 This means that if you make Emacs the owner of a selection, such as by
 selecting some text into the clipboard or primary selection, and then
@@ -4144,13 +3616,11 @@ contents of that selection into other programs as long 
as another
 frame is open on the same display.  This behavior can be disabled by
 setting the user option 'x-auto-preserve-selections' to nil.
 
-+++
 ** New predicate 'char-uppercase-p'.
 This returns non-nil if its argument its an uppercase character.
 
 ** Byte Compilation
 
----
 *** Byte compilation will now warn about some quoting mistakes in docstrings.
 When writing code snippets that contains the "'" character (APOSTROPHE),
 that quote character has to be escaped to avoid Emacs displaying it as
@@ -4164,7 +3634,6 @@ QUOTATION MARK directly.  In both these cases, if these 
characters
 should really be present in the docstring, they should be quoted with
 "\=".
 
----
 *** Byte compilation will now warn about some malformed 'defcustom' types.
 It is very common to write 'defcustom' types on the form:
 
@@ -4174,137 +3643,111 @@ I.e., double-quoting the 'bar', which is almost never 
the correct
 value.  The byte compiler will now issue a warning if it encounters
 these forms.
 
-+++
 ** 'restore-buffer-modified-p' can now alter buffer auto-save state.
 With a FLAG value of 'autosaved', it will mark the buffer as having
 been auto-saved since the time of last modification.
 
----
 ** New minor mode 'isearch-fold-quotes-mode'.
 This sets up 'search-default-mode' so that quote characters are
 char-folded into each other.  It is used, by default, in "*Help*" and
 "*info*" buffers.
 
-+++
 ** New macro 'buffer-local-set-state'.
 This is a helper macro to be used by minor modes that wish to restore
 buffer-local variables back to their original states when the mode is
 switched off.
 
----
 ** New macro 'with-buffer-unmodified-if-unchanged'.
 If the buffer is marked as unmodified, and code does modifications
 that, in total, means that the buffer is identical to the buffer
 before, mark the buffer as unmodified again.
 
----
 ** New function 'malloc-trim'.
 This function allows returning unused memory back to the operating
 system, and is mainly meant as a debugging tool.  It is currently
 available only when Emacs was built with glibc as the C library.
 
----
 ** 'x-show-tip' no longer hard-codes a timeout default.
 The new variable 'x-show-tooltip-timeout' allows the user to alter
 this for packages that don't use 'tooltip-show', but instead call the
 lower level function directly.
 
----
 ** New function 'current-cpu-time'.
 It gives access to the CPU time used by the Emacs process, for
 example for benchmarking purposes.
 
----
 ** New function 'string-edit'.
 This is meant to be used when the user has to edit a (potentially)
 long string.  It pops up a new buffer where you can edit the string,
 and the provided callback is called when the user types 'C-c C-c'.
 
-+++
 ** New function 'read-string-from-buffer'.
 This is a modal version of 'string-edit', and can be used as an
 alternative to 'read-string'.
 
-+++
 ** The return value of 'clear-message-function' is not ignored anymore.
 If the function returns 'dont-clear-message', then the message is not
 cleared, with the assumption that the function cleared it itself.
 
-+++
 ** The local variables section now supports defining fallback modes.
 This was previously only available when using a property line (i.e.,
 putting the modes on the first line of a file).
 
-+++
 ** New function 'flush-standard-output'.
 This enables display of lines that don't end in a newline from
 batch-based Emacs scripts.
 
-+++
 ** New convenience function 'buttonize-region'.
 This works like 'buttonize', but for a region instead of a string.
 
-+++
 ** 'macroexp-let2*' can omit TEST argument and use single-var bindings.
 
-+++
 ** New macro-writing macros, 'cl-with-gensyms' and 'cl-once-only'.
 See the "(cl) Macro-Writing Macros" manual section for descriptions.
 
-+++
 ** New variable 'last-event-device' and new function 'device-class'.
 On X Windows, 'last-event-device' specifies the input extension device
 from which the last input event originated, and 'device-class' can be
 used to determine the type of an input device.
 
-+++
 ** Variable 'track-mouse' can have a new value 'drag-source'.
 This means the same as 'dropping', but modifies the mouse position
 list in reported motion events if there is no frame underneath the
 mouse pointer.
 
-+++
 ** New functions for dragging items from Emacs to other programs.
 The new functions 'x-begin-drag', 'dnd-begin-file-drag',
 'dnd-begin-drag-files', and 'dnd-direct-save' allow dragging contents
 (such as files and text) from Emacs to other programs.
 
----
 ** New function 'ietf-drums-parse-date-string'.
 This function parses RFC5322 (and RFC822) date strings, and should be
 used instead of 'parse-time-string' when parsing data that's standards
 compliant.
 
-+++
 ** New macro 'setopt'.
 This is like 'setq', but is meant to be used for user options instead
 of plain variables, and uses 'custom-set'/'set-default' to set them.
 
-+++
 ** New utility predicate 'mode-line-window-selected-p'.
 This is meant to be used from ':eval' mode line constructs to create
 different mode line looks for selected and unselected windows.
 
-+++
 ** New variable 'messages-buffer-name'.
 This variable (defaulting to "*Messages*") allows packages to override
 where messages are logged.
 
-+++
 ** New function 'readablep'.
 This function says whether an object can be written out and then
 read back by the Emacs Lisp reader.
 
-+++
 ** New variable 'print-unreadable-function'.
 This variable allows changing how Emacs prints unreadable objects.
 
----
 ** The user option 'polling-period' now accepts floating point values.
 This means Emacs can now poll for input during Lisp execution more
 frequently than once in a second.
 
----
 ** New function 'bidi-string-strip-control-characters'.
 This utility function is meant for displaying strings when it is
 essential that there's no bidirectional context.  It removes all the
@@ -4312,67 +3755,54 @@ bidirectional formatting control characters (such as 
RLM, LRO, PDF,
 etc.) from its argument string.  The characters it removes are listed
 in the value of 'bidi-control-characters'.
 
----
 ** The Gnus range functions have been moved to a new library, range.el.
 All the old names have been made obsolete.
 
-+++
 ** New function 'function-alias-p'.
 This predicate says whether an object is a function alias, and if it
 is, the alias chain is returned.
 
-+++
 ** New variable 'lisp-directory' holds the directory of Emacs's own Lisp files.
 
-+++
 ** New facility for handling session state: 'multisession-value'.
 This can be used as a convenient way to store (simple) application
 state, and the command 'list-multisession-values' allows users to list
 (and edit) this data.
 
-+++
 ** New function 'get-display-property'.
 This is like 'get-text-property', but works on the 'display' text
 property.
 
-+++
 ** New function 'add-display-text-property'.
 This is like 'put-text-property', but works on the 'display' text
 property.
 
-+++
 ** New 'min-width' 'display' property.
 This allows setting a minimum display width for a region of text.
 
-+++
 ** New 'cursor-face' text property.
 This uses 'cursor-face' instead of the default face when cursor is on or
 near the character and 'cursor-face-highlight-mode' is enabled.  The
 user option 'cursor-face-highlight-nonselected-window' is similar to
 'highlight-nonselected-windows', but for this property.
 
-+++
 ** New event type 'touch-end'.
 This event is sent whenever the user's finger moves off the mouse
 wheel on some mice, or when the user's finger moves off the touchpad.
 
-+++
 ** New event type 'pinch'.
 This event is sent when a user performs a pinch gesture on a touchpad,
 which is comprised of placing two fingers on the touchpad and moving
 them towards or away from each other.
 
-+++
 ** New hook 'x-pre-popup-menu-hook'.
 This hook is run before 'x-popup-menu' is about to display a
 deck-of-cards menu on screen.
 
----
 ** New hook 'post-select-region-hook'.
 This hook is run immediately after 'select-active-regions'.  It causes
 the region to be set as the primary selection.
 
-+++
 ** New function 'buffer-match-p'.
 Check if a buffer satisfies some condition.  Some examples for
 conditions can be regular expressions that match a buffer name, a
@@ -4380,19 +3810,16 @@ cons-cell like '(major-mode . shell-mode)' that matches 
any buffer
 where 'major-mode' is 'shell-mode' or a combination with a condition
 like '(and "\\`\\*.+\\*\\'" (major-mode . special-mode))'.
 
-+++
 ** New function 'match-buffers'.
 It uses 'buffer-match-p' to gather a list of buffers that match a
 condition.
 
----
 ** New optional arguments TEXT-FACE and DEFAULT-FACE for 'tooltip-show'.
 They allow changing the faces used for the tooltip text and frame
 colors of the resulting tooltip frame from the default 'tooltip' face.
 
 ** Text Security and Suspiciousness
 
-+++
 *** New library textsec.el.
 This library contains a number of checks for whether a string is
 "suspicious".  This usually means that the string contains characters
@@ -4400,7 +3827,6 @@ that have glyphs that can be confused with other, more 
commonly used
 glyphs, or contains bidirectional (or other) formatting characters
 that may be used to confuse a user.
 
-+++
 *** New user option 'textsec-check'.
 If non-nil (which is the default), Emacs packages that are vulnerable
 to attackers trying to confuse the users will use the textsec library
@@ -4410,7 +3836,6 @@ Message mode will query the user if the user is sending 
mail to a
 suspicious address.  If this variable is nil, these checks are
 disabled.
 
-+++
 *** New function 'textsec-suspicious-p'.
 This is the main function Emacs applications should be using to check
 whether a string is suspicious.  It heeds the 'textsec-check' user
@@ -4418,13 +3843,11 @@ option.
 
 ** Keymaps and Key Definitions
 
-+++
 *** 'where-is-internal' can now filter events marked as non key events.
 If a command maps to a key binding like '[some-event]', and 'some-event'
 has a symbol plist containing a non-nil 'non-key-event' property, then
 that binding is ignored by 'where-is-internal'.
 
-+++
 *** New functions for defining and manipulating keystrokes.
 These all take the syntax defined by 'key-valid-p', which is basically
 the same syntax as the one accepted by the 'kbd' macro.  None of the
@@ -4432,68 +3855,51 @@ older functions have been deprecated or altered, but 
they are now
 de-emphasized in the documentation, and we encourage Lisp programs to
 switch to these new functions.
 
-+++
 *** Use 'keymap-set' instead of 'define-key'.
 
-+++
 *** Use 'keymap-global-set' instead of 'global-set-key'.
 
-+++
 *** Use 'keymap-local-set' instead of 'local-set-key'.
 
-+++
 *** Use 'keymap-global-unset' instead of 'global-unset-key'.
 
-+++
 *** Use 'keymap-local-unset' instead of 'local-unset-key'.
 
-+++
 *** Use 'keymap-substitute' instead of 'substitute-key-definition'.
 
-+++
 *** Use 'keymap-set-after' instead of 'define-key-after'.
 
-+++
 *** Use 'keymap-lookup' instead of 'lookup-key' and 'key-binding'.
 
-+++
 *** Use 'keymap-local-lookup' instead of 'local-key-binding'.
 
-+++
 *** Use 'keymap-global-lookup' instead of 'global-key-binding'.
 
-+++
 *** 'define-key' now takes an optional REMOVE argument.
 If non-nil, remove the definition from the keymap.  This is subtly
 different from setting a definition to nil: when the keymap has a
 parent such a definition will shadow the parent's definition.
 
-+++
 *** 'read-multiple-choice' now takes an optional SHOW-HELP argument.
 If non-nil, show the help buffer immediately, before any user input.
 
-+++
 *** New function 'key-valid-p'.
 The 'kbd' function is quite permissive, and will try to return
 something usable even if the syntax of the argument isn't completely
 correct.  The 'key-valid-p' predicate does a stricter check of the
 syntax.
 
----
 *** New function 'key-parse'.
 This is like 'kbd', but only returns vectors instead of a mix of
 vectors and strings.
 
-+++
 *** New ':type' for 'defcustom' for keys.
 The new 'key' type can be used for options that should be a valid key
 according to 'key-valid-p'.  The type 'key-sequence' is now obsolete.
 
-+++
 ** New function 'define-keymap'.
 This function allows defining a number of keystrokes with one form.
 
-+++
 ** New macro 'defvar-keymap'.
 This macro allows defining keymap variables more conveniently.
 
@@ -4503,11 +3909,9 @@ advanced usage:
 
     :repeat (:enter (commands ...) :exit (commands ...))
 
----
 ** 'kbd' can now be used in built-in, preloaded libraries.
 It no longer depends on edmacro.el and cl-lib.el.
 
-+++
 ** New substitution in docstrings and 'substitute-command-keys'.
 Use \\`KEYSEQ' to insert a literal key sequence "KEYSEQ" (for example
 \\`C-k') in a docstring or when calling 'substitute-command-keys',
@@ -4516,7 +3920,6 @@ be used only when a key sequence has no corresponding 
command, for
 example when it is read directly with 'read-key-sequence'.  It must be
 a valid key sequence according to 'key-valid-p'.
 
----
 ** 'lookup-key' is more permissive when searching for extended menu items.
 In Emacs 28.1, the behavior of 'lookup-key' was changed: when looking
 for a menu item '[menu-bar Foo-Bar]', first try to find an exact
@@ -4528,61 +3931,49 @@ an exact match, then the lowercased '[menu-bar foo\ 
bar]' and finally
 '[menu-bar foo-bar]'.  This further improves backwards-compatibility
 when converting menus to use 'easy-menu-define'.
 
-+++
 ** New function 'file-name-split'.
 This returns a list of all the components of a file name.
 
-+++
 ** New function 'file-name-parent-directory'.
 This returns the parent directory of a file name.
 
-+++
 ** New macro 'with-undo-amalgamate'.
 It records a particular sequence of operations as a single undo step.
 
-+++
 ** New command 'yank-media'.
 This command supports yanking non-plain-text media like images and
 HTML from other applications into Emacs.  It is only supported in
 modes that have registered support for it, and only on capable
 platforms.
 
-+++
 ** New command 'yank-media-types'.
 This command lets you examine all data in the current selection and
 the clipboard, and insert it into the buffer.
 
-+++
 ** New variable 'yank-transform-functions'.
 This variable allows the user to alter the string to be inserted.
 
----
 ** New command 'yank-in-context'.
 This command tries to preserve string/comment syntax when yanking.
 
----
 ** New function 'minibuffer-lazy-highlight-setup'.
 This function allows setting up the minibuffer so that lazy
 highlighting of its content is applied in the original window.
 
-+++
 ** New text property 'inhibit-isearch'.
 If set, 'isearch' will skip these areas, which can be useful (for
 instance) when covering huge amounts of data (that has no meaningful
 searchable data, like image data) with a 'display' text property.
 
-+++
 ** 'insert-image' now takes an INHIBIT-ISEARCH optional argument.
 It marks the image with the 'inhibit-isearch' text property, which
 inhibits 'isearch' matching the STRING argument.
 
----
 ** New variable 'replace-regexp-function'.
 Function to call to convert the entered FROM string to an Emacs
 regexp in 'query-replace' and similar commands.  It can be used to
 implement a different regexp syntax for search/replace.
 
----
 ** New variables to customize defaults of FROM for 'query-replace*' commands.
 The new variable 'query-replace-read-from-default' can be set to a
 function that returns the default value of FROM when 'query-replace'
@@ -4598,59 +3989,49 @@ default value from the previous FROM-TO pair.
 
 ** Lisp pretty-printer ('pp')
 
----
 *** New function 'pp-emacs-lisp-code'.
 'pp' formats general Lisp sexps.  This function does much the same,
 but applies formatting rules appropriate for Emacs Lisp code.  Note
 that this could currently be quite slow, and is thus appropriate only
 for relatively small code fragments.
 
----
 *** New user option 'pp-use-max-width'.
 If non-nil, 'pp' and all 'pp-*' commands that format the results, will
 attempt to limit the line length when formatting long lists and
 vectors.  This uses 'pp-emacs-lisp-code', and thus could be slow for
 large lists.
 
-+++
 ** New function 'file-has-changed-p'.
 This convenience function is useful when writing code that parses
 files at run-time, and allows Lisp programs to re-parse files only
 when they have changed.
 
-+++
 ** 'abbreviate-file-name' now respects magic file name handlers.
 
----
 ** New function 'font-has-char-p'.
 This can be used to check whether a specific font has a glyph for a
 character.
 
-+++
 ** 'window-text-pixel-size' now accepts a new argument IGNORE-LINE-AT-END.
 This controls whether or not the last screen line of the text being
 measured will be counted for the purpose of calculating the text
 dimensions.
 
-+++
 ** 'window-text-pixel-size' understands a new meaning of FROM.
 Specifying a cons as the FROM argument allows to start measuring text
 from a specified amount of pixels above or below a position.
 
-+++
 ** 'window-body-width' and 'window-body-height' can use remapped faces.
 Specifying 'remap' as the PIXELWISE argument now checks if the default
 face was remapped, and if so, uses the remapped face to determine the
 character width/height.
 
-+++
 ** 'set-window-vscroll' now accepts a new argument PRESERVE-VSCROLL-P.
 This means the vscroll will not be reset when set on a window that is
 "frozen" due to a mini-window being resized.
 
 ** XDG Support
 
----
 *** New function 'xdg-state-home'.
 It returns the new 'XDG_STATE_HOME' environment variable.  It should
 point to a file name that "contains state data that should persist
@@ -4659,7 +4040,6 @@ enough to the user that it should be stored in 
$XDG_DATA_HOME".
 (This variable was introduced in the XDG Base Directory Specification
 version 0.8 released on May 8, 2021.)
 
----
 *** New function 'xdg-current-desktop'.
 It returns a list of strings, corresponding to the colon-separated
 list of names in the 'XDG_CURRENT_DESKTOP' environment variable, which
@@ -4667,83 +4047,68 @@ identify the current desktop environment.
 (This variable was introduced in XDG Desktop Entry Specification
 version 1.2.)
 
----
 *** New function 'xdg-session-type'.
 It returns the 'XDG_SESSION_TYPE' environment variable.  (This is not
 part of any official standard; see the man page pam_systemd(8) for
 more information.)
 
-+++
 ** New macro 'with-delayed-message'.
 This macro is like 'progn', but will output the specified message if
 the body takes longer to execute than the specified timeout.
 
----
 ** New function 'funcall-with-delayed-message'.
 This function is like 'funcall', but will output the specified message
 if the function takes longer to execute than the specified timeout.
 
 ** Locale
 
----
 *** New variable 'current-locale-environment'.
 This holds the value of the previous call to 'set-locale-environment'.
 
----
 *** New macro 'with-locale-environment'.
 This macro can be used to change the locale temporarily while
 executing code.
 
 ** Table
 
----
 *** New user option 'table-latex-environment'.
 This allows switching between "table" and "tabular".
 
 ** Tabulated List Mode
 
-+++
 *** A column can now be set to an image descriptor.
 The 'tabulated-list-entries' variable now supports using an image
 descriptor, which means to insert an image in that column instead of
 text.  See the documentation string of that variable for details.
 
-+++
 ** ':keys' in 'menu-item' can now be a function.
 If so, it is called whenever the menu is computed, and can be used to
 calculate the keys dynamically.
 
-+++
 ** New major mode 'clean-mode'.
 This is a new major mode meant for debugging.  It kills absolutely all
 local variables and removes overlays and text properties.
 
-+++
 ** 'kill-all-local-variables' can now kill all local variables.
 If given the new optional KILL-PERMANENT argument, it also kills
 permanent local variables.
 
-+++
 ** Third 'mapconcat' argument SEPARATOR is now optional.
 An explicit nil always meant the empty string, now it can be left out.
 
-+++
 ** New function 'image-at-point-p'.
 This function returns t if point is on a valid image, and nil
 otherwise.
 
-+++
 ** New function 'buffer-text-pixel-size'.
 This is similar to 'window-text-pixel-size', but can be used when the
 buffer isn't displayed.
 
-+++
 ** New function 'string-pixel-width'.
 This returns the width of a string in pixels.  This can be useful when
 dealing with variable pitch fonts and glyphs that have widths that
 aren't integer multiples of the default font.
 
-+++
 ** New function 'string-glyph-split'.
 This function splits a string into a list of strings representing
 separate glyphs.  This takes into account combining characters and
@@ -4752,13 +4117,11 @@ display as a single unit.
 
 ** Xwidget
 
-+++
 *** The function 'make-xwidget' now accepts an optional RELATED argument.
 This argument is used as another widget for the newly created WebKit
 widget to share settings and subprocesses with.  It must be another
 WebKit widget.
 
-+++
 *** New function 'xwidget-perform-lispy-event'.
 This function allows you to send events to xwidgets.  Usually, some
 equivalent of the event will be sent, but there is no guarantee of
@@ -4766,33 +4129,27 @@ what the widget will actually receive.
 
 On GTK+, only key and function key events are implemented.
 
-+++
 *** New function 'xwidget-webkit-load-html'.
 This function is used to load HTML text into WebKit xwidgets
 directly, in contrast to creating a temporary file to hold the
 markup, and passing the URI of the file as an argument to
 'xwidget-webkit-goto-uri'.
 
-+++
 *** New functions for performing searches on WebKit xwidgets.
 Some new functions, such as 'xwidget-webkit-search', have been added
 for performing searches on WebKit xwidgets.
 
-+++
 *** New function 'xwidget-webkit-back-forward-list'.
 This function returns the history of page-loads in a WebKit xwidget.
 
-+++
 *** New function 'xwidget-webkit-estimated-load-progress'.
 This function returns the estimated progress of page loading in a
 WebKit xwidget.
 
-+++
 *** New function 'xwidget-webkit-stop-loading'.
 This function terminates all data transfer during page loads in a
 WebKit xwidget.
 
-+++
 *** 'load-changed' xwidget events are now more detailed.
 In particular, they can now have different arguments based on the
 state of the WebKit widget.  'load-finished' is sent when a load has
@@ -4800,84 +4157,68 @@ completed, 'load-started' when a load first starts, 
'load-redirected'
 after a redirect, and 'load-committed' when the WebKit widget first
 commits to the load.
 
-+++
 *** New event type 'xwidget-display-event'.
 These events are sent whenever an xwidget requests that Emacs displays
 another xwidget.  The only arguments to this event are the xwidget
 that should be displayed, and the xwidget that asked to display it.
 
-+++
 *** New function 'xwidget-webkit-set-cookie-storage-file'.
 This function is used to control where and if an xwidget stores
 cookies set by web pages on disk.
 
----
 ** New variable 'help-buffer-under-preparation'.
 This variable is bound to t during the preparation of a "*Help*" buffer.
 
-+++
 ** Timestamps like '(1 . 1000)' now work without warnings being generated.
 For example, '(time-add nil '(1 . 1000))' no longer warns that the
 '(1 . 1000)' acts like '(1000 . 1000000)'.  This warning, which was a
 temporary transition aid for Emacs 27, has served its purpose.
 
-+++
 ** 'encode-time' now also accepts a 6-element list with just time and date.
 '(encode-time (list SECOND MINUTE HOUR DAY MONTH YEAR))' is now short for
 '(encode-time (list SECOND MINUTE HOUR DAY MONTH YEAR nil -1 nil))'.
 
-+++
 ** 'date-to-time' now accepts arguments that lack month, day, or time.
 The function now assumes the earliest possible values if its argument
 lacks month, day, or time.  For example, (date-to-time "2021-12-04")
 now assumes a time of "00:00" instead of signaling an error.
 
-+++
 ** 'format-seconds' now allows suppressing zero-value trailing elements.
 The new "%x" non-printing control character will suppress zero-value
 elements that appear after "%x".
 
-+++
 ** New events for taking advantage of touchscreen devices.
 The events 'touchscreen-begin', 'touchscreen-update', and
 'touchscreen-end' have been added to take better advantage of
 touch-capable display panels.
 
-+++
 ** New error symbol 'permission-denied'.
 This is a subcategory of 'file-error', and is signaled when some file
 operation fails because the OS doesn't allow Emacs to access a file or
 a directory.
 
-+++
 ** The ':underline' face attribute now accepts a new property.
 The property ':position' now specifies the position of the underline
 when used as part of a property list specification for the
 ':underline' attribute.
 
-+++
 ** 'defalias' records a more precise history of definitions.
 This is recorded in the 'function-history' symbol property.
 
----
 ** New hook 'save-place-after-find-file-hook'.
 This is called at the end of 'save-place-find-file-hook'.
 
----
 ** 'indian-tml-base-table' no longer translates digits.
 Use 'indian-tml-base-digits-table' if you want digits translation.
 
----
 ** 'indian-tml-itrans-v5-hash' no longer translates digits.
 Use 'indian-tml-itrans-digits-v5-hash' if you want digits
 translation.
 
-+++
 ** 'shell-quote-argument' has a new optional argument POSIX.
 This is useful when quoting shell arguments for a remote shell
 invocation.  Such shells are POSIX conformant by default.
 
-+++
 ** 'make-process' can set connection type independently for input and output.
 When calling 'make-process', communication via pty can be enabled
 selectively for just input or output by passing a cons cell for
@@ -4886,14 +4227,12 @@ later, you can determine whether a particular stream 
for a process
 uses a pty by passing one of 'stdin', 'stdout', or 'stderr' as the
 second argument to 'process-tty-name'.
 
-+++
 ** 'signal-process' now consults the list 'signal-process-functions'.
 This is to determine which function has to be called in order to
 deliver the signal.  This allows Tramp to send the signal to remote
 asynchronous processes.  The hitherto existing implementation has been
 moved to 'internal-default-signal-process'.
 
-+++
 ** Some system information functions honor remote systems now.
 'list-system-processes' returns remote process IDs.
 'memory-info' returns memory information of remote systems.
@@ -4905,21 +4244,17 @@ remote.  In order to preserve the old behavior, bind
     (let ((default-directory temporary-file-directory))
       (list-system-processes))
 
-+++
 ** New functions 'take' and 'ntake'.
 '(take N LIST)' returns the first N elements of LIST; 'ntake' does
 the same but works by modifying LIST destructively.
 
----
 ** 'string-split' is now an alias for 'split-string'.
 
-+++
 ** 'format-spec' now accepts functions in the replacement.
 The function is called only when used in the format string.  This is
 useful to avoid side-effects such as prompting, when the value is not
 actually being used for anything.
 
-+++
 ** The variable 'max-specpdl-size' has been made obsolete.
 Now 'max-lisp-eval-depth' alone is used for limiting Lisp recursion
 and stack usage.  'max-specpdl-size' is still present as a plain
@@ -4933,11 +4268,9 @@ set is too big to transfer to Emacs every time a 
completion is
 needed.  The table uses new 'external' completion style exclusively
 and cannot work with regular styles such as 'basic' or 'flex'.
 
-+++
 ** Magic file name handlers for 'make-directory-internal' are no longer needed.
 Instead, Emacs uses the already-existing 'make-directory' handlers.
 
-+++
 ** '(make-directory DIR t)' returns non-nil if DIR already exists.
 This can let a caller know whether it created DIR.  Formerly,
 'make-directory's return value was unspecified.
@@ -4947,7 +4280,6 @@ This can let a caller know whether it created DIR.  
Formerly,
 
 ** MS-Windows
 
----
 *** Emacs now supports double-buffering on MS-Windows to reduce display 
flicker.
 (This was supported on Free systems since Emacs 26.1.)
 
@@ -4962,14 +4294,12 @@ selected frame by evaluating
 
     (modify-frame-parameters nil '((inhibit-double-buffering . t)))
 
-+++
 *** Emacs now supports system dark mode.
 On Windows 10 (version 1809 and higher) and Windows 11, Emacs will now
 follow the system's dark mode: GUI frames use the appropriate light or
 dark title bar and scroll bars, based on the user's Windows-wide color
 settings.
 
----
 *** Emacs now uses native image APIs to display some image formats.
 On Windows 2000 and later, Emacs now defaults to using the native
 image APIs for displaying the BMP, GIF, JPEG, PNG, and TIFF images.
@@ -4982,12 +4312,10 @@ The use of native image APIs is controlled by the 
variable
 'w32-use-native-image-API', whose value now defaults to t on systems
 where those APIs are available.
 
-+++
 *** Emacs now supports display of BMP images using native image APIs.
 When 'w32-use-native-image-API' is non-nil, Emacs on MS-Windows now
 has built-in support for displaying BMP images.
 
----
 *** GUI Yes/No dialogs now include a "Cancel" button.
 The "Cancel" button is in addition to "Yes" and "No", and is intended
 to allow users to quit the dialog, as an equivalent of 'C-g' when Emacs
@@ -4998,12 +4326,10 @@ two buttons: "Yes" and "No".
 
 ** Cygwin
 
----
 *** 'process-attributes' is now implemented.
 
 ** macOS
 
-+++
 *** The 'ns-popup-font-panel' command has been removed.
 Use the general command 'M-x menu-set-font' instead.
 
diff --git a/etc/PROBLEMS b/etc/PROBLEMS
index 9bdfe2329ba..c139f25e086 100644
--- a/etc/PROBLEMS
+++ b/etc/PROBLEMS
@@ -516,7 +516,7 @@ directory copy is ineffective.
 This is due to an arbitrary limit in certain versions of awk.
 The solution is to use gawk (GNU awk).
 
-*** Saving, via EasyPG, a file encrypted with GnuPG hangs
+*** Saving a file encrypted with GnuPG via EasyPG hangs.
 
 This is known to happen with GnuPG v2.4.1.  The only known workaround
 is to downgrade to a version of GnuPG older than 2.4.1 (or, in the
@@ -575,6 +575,20 @@ This can happen with CVS versions 1.12.8 and 1.12.9.  
Upgrade to CVS
 
 ** Miscellaneous problems
 
+*** 'set-mouse-color' and the '-ms' command line argument do not work.
+
+Systems where the default cursors are not simple 1 bit-per-pixel
+bitmaps usually forbid recoloring the cursor, since it is unclear
+which colors should replace those already present within each cursor
+image.  For example, 'set-mouse-color' and '-ms' have no function on X
+systems with GNOME, KDE, and other recent desktop environments
+employing cursor images containing colors and partial transparency.
+
+Changing the cursor color is also impossible on MS-Windows and PGTK
+systems.  In the former case, it is because the prerequisite code has
+yet to be written.  In the latter, it is because GTK does not provide
+for changing the color of cursor images.
+
 *** Display artifacts on GUI frames on X-based systems.
 
 This is known to be caused by using double-buffering (which is enabled
@@ -1163,43 +1177,6 @@ do anything about it.
 
 ** International characters aren't displayed under X.
 
-*** Missing X fonts
-
-XFree86 4 contains many fonts in iso10646-1 encoding which have
-minimal character repertoires (whereas the encoding part of the font
-name is meant to be a reasonable indication of the repertoire
-according to the XLFD spec).  Emacs may choose one of these to display
-characters from the mule-unicode charsets and then typically won't be
-able to find the glyphs to display many characters.  (Check with C-u
-C-x = .)  To avoid this, you may need to use a fontset which sets the
-font for the mule-unicode sets explicitly.  E.g. to use GNU unifont,
-include in the fontset spec:
-
-mule-unicode-2500-33ff:-gnu-unifont-*-iso10646-1,\
-mule-unicode-e000-ffff:-gnu-unifont-*-iso10646-1,\
-mule-unicode-0100-24ff:-gnu-unifont-*-iso10646-1
-
-** The UTF-8/16/7 coding systems don't encode CJK (Far Eastern) characters.
-
-Emacs directly supports the Unicode BMP whose code points are in the
-ranges 0000-33ff and e000-ffff, and indirectly supports the parts of
-CJK characters belonging to these legacy charsets:
-
-    GB2312, Big5, JISX0208, JISX0212, JISX0213-1, JISX0213-2, KSC5601
-
-The latter support is done in Utf-Translate-Cjk mode (turned on by
-default).   Which Unicode CJK characters are decoded into which Emacs
-charset is decided by the current language environment.  For instance,
-in Chinese-GB, most of them are decoded into chinese-gb2312.
-
-If you read UTF-8 data with code points outside these ranges, the
-characters appear in the buffer as raw bytes of the original UTF-8
-(composed into a single quasi-character) and they will be written back
-correctly as UTF-8, assuming you don't break the composed sequences.
-If you read such characters from UTF-16 or UTF-7 data, they are
-substituted with the Unicode 'replacement character', and you lose
-information.
-
 ** Accented ISO-8859-1 characters are displayed as | or _.
 
 Try other font set sizes (S-mouse-1).  If the problem persists with
@@ -1237,6 +1214,16 @@ In your ~/.Xresources file, then run
 
 And restart Emacs.
 
+** Emacs hangs when using XIM
+
+This is due to an old bug in the implementation of the X protocol's
+XIM transport: when an input method crashes for some reason, Xlib
+cannot recover.  Emacs cannot do anything about this except wait for
+input method developers to fix their crashes.  You can work around
+these problems by disabling XIM in your X resources:
+
+  Emacs.useXIM: false
+
 ** On Haiku, BeCJK doesn't work properly with Emacs
 
 Some popular Haiku input methods such BeCJK are known to behave badly
@@ -3361,6 +3348,171 @@ Compose key to stop working.
 On X Windows, users should not use Emacs configured with PGTK, since
 this and many other problems do not exist on the regular X builds.
 
+* Runtime problems specific to Android
+
+** Text displayed in the default monospace font looks horrible.
+
+Droid Sans Mono (the default Monospace font which comes with Android)
+incorporates instruction code designed for Microsoft's proprietary
+TrueType font scaler.  When this code is executed by Emacs to instruct
+a glyph containing more than one component, it tries to address
+"reference points" which are set to the values of two extra "phantom
+points" in the glyph, that are a proprietary extension of the MS font
+scaler.
+
+Emacs does not support these extensions, and as a result characters
+such as
+
+  ĥ
+
+display incorrectly, with the right most edge of the `h' component
+stretched very far out to the right, on some low density displays.
+
+The solution is to replace the MS-specific hinting code in Droid Sans
+Mono with automatically generated code from the FreeType project's
+"ttfautohint" program.  First, extract
+'/system/fonts/DroidSansMono.ttf' from your device:
+
+  $ adb pull /system/fonts/DroidSansMono.ttf
+  /system/fonts/DroidSansMono.ttf: 1 file pulled, 0 skipped.
+  23.1 MB/s (90208 bytes in 0.004s)
+
+install the "ttfautohint" program:
+
+  http://freetype.org/ttfautohint/
+
+generate a font file with new hinting instructions:
+
+  $ ttfautohint DroidSansMono.ttf > DroidSansMono.ttf.rpl
+
+and upload them to your device, either back to /system/fonts (which is
+allowed by free versions of Android, such as Replicant):
+
+  $ adb root
+  $ adb remount
+  $ adb push DroidSansMono.ttf.rpl /system/fonts/DroidSansMono.ttf
+
+or to the user fonts directory described in the "Android Fonts" node
+of the Emacs manual.  You may want to perform this procedure even if
+you are not seeing problems with character display, as the
+automatically generated instructions result in superior display
+results that are easier to read.
+
+We have been told that the default Sans font under Android 2.3.7,
+named "Droid Sans", also exhibits this problem.  The procedure for
+repairing the font is identical to the procedure outlined above,
+albeit with "DroidSansMono" replaced by simply "DroidSans".
+
+** The "Anonymous Pro" font displays incorrectly.
+
+Glyph instruction code within the Anonymous Pro font relies on
+undocumented features of the Microsoft TrueType font scaler, namely
+that the scaler always resets the "projection" and "freedom" vector
+interpreter control registers after the execution of the font
+pre-program, which sets them to a value that is perpendicular to the
+horizontal plane of movement.
+
+Since Emacs does not provide this "feature", various points inside
+glyphs are moved vertically rather than horizontally when a glyph
+program later executes an instruction such as "MIRP" (Move Indirect
+Relative Point) that moves and measures points along the axis
+specified by those registers.
+
+This can be remedied in two ways; the first (and the easiest) is to
+replace its instruction code with that supplied by "ttfautohint", as
+depicted above.  The second is to patch the instruction code inside
+the font itself, using the "ttx" utility:
+
+  https://fonttools.readthedocs.io/en/latest/ttx.html
+
+First, convert the font to its XML representation:
+
+  $ ttx Anonymous_Pro.ttf
+
+then, find the end of the section labeled 'prep':
+
+  <prep>
+    <assembly>
+      [...]
+      ROUND[01]        /* Round */
+      RTG[ ]   /* RoundToGrid */
+      WCVTP[ ] /* WriteCVTInPixels */
+    </assembly>
+  </prep>
+
+and insert the following instruction immediately before the closing
+'/assembly' tag, so as to reset the interpreter control registers back
+to their default values prior to the completion of the pre-program:
+
+     SVTCA[1]  /* Set Vector registers to Control Axis X */
+
+Then, reassemble the font from the modified XML:
+
+  $ ttx Anonymous_Pro.ttx
+
+which should produce a modified font by the name of
+Anonymous_Pro#1.ttf.
+
+** The "IBM Plex Mono" font displays incorrectly.
+
+This problem is precipitated by an attempt to exploit the undocumented
+feature of the MS font scaler explicated within the previous heading.
+
+Its remedy is also unsurprisingly alike the fix described there: both
+patching the preprogram to reset the point movement vectors and
+replacing the instruction code with code generated by "ttfautohint"
+will adequately resolve the problem.
+
+** Glyphs are missing within the "Arial" font or it does not load.
+
+On account of its origins at Microsoft, instruction code included
+within this font is awash with references to behavior specific to the
+MS scaler.  It is incorrigibly broken, to a degree that even
+"ttfautohint" cannot repair; your only recourse is to select some
+other font.
+
+This issue may extend beyond Arial to encompass a larger selection of
+fonts designed by Microsoft.
+
+** Some TrueType test fonts don't work.
+
+It is unlikely that any of these fonts will really prove useful for
+text editing tasks, since they are designed for the express purpose of
+testing a TrueType font scaler.  The following explanation is present
+only to satisfy a cat-like curiosity.
+
+Most TrueType test fonts "hide" points by moving them to a
+preposterous location outside the confines of the glyph bounding box.
+The Microsoft scaler and FreeType promptly disregard such points.
+
+Nothing in the TrueType specifications implies that points "hidden" in
+this fashion should be afforded any special treatment, and thus Emacs
+eschews doing so.  Consequentially, black streaks are displayed as
+Emacs interpolates glyph edges between points within the glyph and
+points the test font attempts to hide.
+
+Since this behavior does not influence the display of real fonts, no
+action will be taken to address this problem.
+
+** CJK text does not display in Emacs, but does in other programs.
+
+When inserting CJK text into a buffer or visiting a file containing
+CJK text, Emacs often fails to locate a suitable font.  This problem
+manifests itself as hollow squares with numbers and letters within
+being displayed in lieu of the text itself.
+
+The reason for this is Emacs's absence of support for OpenType fonts
+utilizing CFF (Compact Font Format) outlines, which the CJK fonts
+bundled with Android have been distributed as since Android 7.0.
+
+The solution is to install a TrueType CJK font to the user fonts
+directory detailed in the "Android Fonts" node of the Emacs manual.
+
+Introducing support for the byzantine CFF font format into the Android
+port is a large undertaking that we are looking for volunteers to
+perform.  If you are interested in taking responsibility for this
+task, please contact <emacs-devel@gnu.org>.
+
 * Build-time problems
 
 ** Configuration
diff --git a/etc/TODO b/etc/TODO
index f097e76b917..a918f496863 100644
--- a/etc/TODO
+++ b/etc/TODO
@@ -1748,6 +1748,19 @@ The former is based on the GVFS archive backend, which 
makes it
 available on GNU/Linux only.  That implementation has further
 drawbacks like it doesn't support to write into archives.
 
+** Provide support for CFF outlines in the Android port.
+
+The file src/sfnt.c supplies the font backend for the Android port.
+It is presently a self contained TrueType scaler, implementing both a
+grayscale outline generator and an instruction code interpreter.
+
+Support for CFF (Compact Font Format) outlines will facilitate
+utilizing fonts distributed as ".otf" files, a category that currently
+encompasses all CJK and some Middle Eastern and Indic fonts
+distributed with Android, obviating the present requirement for users
+of such scripts to actively install TrueType versions of fonts
+otherwise bundled with the system.
+
 * Other known bugs
 
 ** 'make-frame' forgets unhandled parameters, at least for X11 frames
diff --git a/etc/images/alt.pbm b/etc/images/alt.pbm
new file mode 100644
index 00000000000..7d12a48b552
Binary files /dev/null and b/etc/images/alt.pbm differ
diff --git a/etc/images/ctrl.pbm b/etc/images/ctrl.pbm
new file mode 100644
index 00000000000..c3ff817dc7a
Binary files /dev/null and b/etc/images/ctrl.pbm differ
diff --git a/etc/images/hyper.pbm b/etc/images/hyper.pbm
new file mode 100644
index 00000000000..fdb79c2f3a9
Binary files /dev/null and b/etc/images/hyper.pbm differ
diff --git a/etc/images/last-page.pbm b/etc/images/last-page.pbm
new file mode 100644
index 00000000000..d25ce022183
Binary files /dev/null and b/etc/images/last-page.pbm differ
diff --git a/etc/images/last-page.xpm b/etc/images/last-page.xpm
new file mode 100644
index 00000000000..5704143aa2e
--- /dev/null
+++ b/etc/images/last-page.xpm
@@ -0,0 +1,122 @@
+/* XPM */
+static char *last_page[] = {
+/* columns rows colors chars-per-pixel */
+"24 24 92 1 ",
+"  c None",
+". c black",
+"X c gray15",
+"o c #2F4050",
+"O c #344353",
+"+ c #3B4F63",
+"@ c #384F66",
+"# c #3A5067",
+"$ c #3C5064",
+"% c #3C5065",
+"& c #3E5166",
+"* c #3F5266",
+"= c #3A5168",
+"- c #3B5269",
+"; c #3D526A",
+": c #3E546A",
+"> c #3F556B",
+", c #3E5975",
+"< c #3F5A76",
+"1 c #464646",
+"2 c #494949",
+"3 c #405367",
+"4 c #405468",
+"5 c #40566C",
+"6 c #41576D",
+"7 c #42586E",
+"8 c #44586F",
+"9 c #45596F",
+"0 c #465B70",
+"q c #415B77",
+"w c #425C78",
+"e c #435E79",
+"r c #445F7A",
+"t c #46607B",
+"y c #47617B",
+"u c #47617C",
+"i c #48627D",
+"p c #49637D",
+"a c #4B647E",
+"s c #4C647F",
+"d c #4C657F",
+"f c gray38",
+"g c #6A6A6A",
+"h c #616A73",
+"j c #68727D",
+"k c #7C7C7C",
+"l c #4E6780",
+"z c #4F6881",
+"x c #506982",
+"c c #526A83",
+"v c #556D85",
+"b c #5B7289",
+"n c #7D8185",
+"m c #77838F",
+"M c #868788",
+"N c #888888",
+"B c #8B8B8B",
+"V c #8F9296",
+"C c #8F9396",
+"Z c #8F9397",
+"A c #909397",
+"S c #959595",
+"D c #91969C",
+"F c #91979C",
+"G c #92979C",
+"H c #92979D",
+"J c #9C9FA1",
+"K c #9D9FA2",
+"L c #A2A3A4",
+"P c #A6A6A6",
+"I c #ACACAC",
+"U c gray68",
+"Y c #B0B0B0",
+"T c #B2B2B2",
+"R c gray71",
+"E c #B6B6B6",
+"W c gray75",
+"Q c #C5C5C5",
+"! c gray79",
+"~ c gray80",
+"^ c LightGray",
+"/ c #D6D6D6",
+"( c #D8D8D8",
+") c #DADADA",
+"_ c #DEDEDE",
+"` c gray89",
+"' c #E5E5E5",
+"] c #E6E6E6",
+"[ c #EEEEEE",
+"{ c #F2F2F2",
+"} c #F6F6F6",
+"| c white",
+/* pixels */
+"                        ",
+"                        ",
+"   ........   ........  ",
+"  .vU/_][`!N2gNT~)]{|b. ",
+"  .@T/_][}||PUTW~)]{|t. ",
+"  .#T/_][}`|PUTW~)]{|t. ",
+"  .-T/_][).|PUTW~)]{|t. ",
+"  .-T/_]^..|PUTW~)]{|t. ",
+"  .;T/_~...|PUTW~)]{|u. ",
+"  .>T/Q.....X1fkSR]{|i. ",
+"  .>T/_~...|PUTW~)]{|i. ",
+"  .5T/_]^..|PUTW~)]{|a. ",
+"  .5T/_][).|PUTW~)]{|d. ",
+"  .7T/_][}`|PUTW~)]{|d. ",
+"  .8T/_][}||PUTW~)]{|l. ",
+"  .8R/_][}||PUTW~)]{|z. ",
+"  .0MAZZZZZKPLHHHHHKmc. ",
+"  .O43&&&&+hnjtrwwq<<c. ",
+"  ..........o.........  ",
+"           ...          ",
+"                        ",
+"                        ",
+"                        ",
+"                        "
+};
diff --git a/etc/images/meta.pbm b/etc/images/meta.pbm
new file mode 100644
index 00000000000..4d4c55c85c7
Binary files /dev/null and b/etc/images/meta.pbm differ
diff --git a/etc/images/shift.pbm b/etc/images/shift.pbm
new file mode 100644
index 00000000000..fbb1f4abe06
Binary files /dev/null and b/etc/images/shift.pbm differ
diff --git a/etc/images/super.pbm b/etc/images/super.pbm
new file mode 100644
index 00000000000..aa126755f99
Binary files /dev/null and b/etc/images/super.pbm differ
diff --git a/etc/refcards/orgcard.tex b/etc/refcards/orgcard.tex
index 07463ee6a33..dd8cae5ce5e 100644
--- a/etc/refcards/orgcard.tex
+++ b/etc/refcards/orgcard.tex
@@ -1,5 +1,5 @@
 % Reference Card for Org Mode
-\def\orgversionnumber{9.6.6}
+\def\orgversionnumber{9.6.7}
 \def\versionyear{2023}          % latest update
 \input emacsver.tex
 
diff --git a/etc/themes/deeper-blue-theme.el b/etc/themes/deeper-blue-theme.el
index 40d5f18a011..20da432c75a 100644
--- a/etc/themes/deeper-blue-theme.el
+++ b/etc/themes/deeper-blue-theme.el
@@ -64,8 +64,8 @@
    `(ediff-fine-diff-B ((,class (:background "cyan4" :foreground "white"))))
    `(ediff-odd-diff-A ((,class (:background "Grey50" :foreground "White"))))
    `(error ((,class (:foreground "red"))))
-   `(flymake-errline ((,class (:background nil :underline "red"))))
-   `(flymake-warnline ((,class (:background nil :underline "magenta3"))))
+   `(flymake-errline ((,class (:background unspecified :underline "red"))))
+   `(flymake-warnline ((,class (:background unspecified :underline 
"magenta3"))))
    `(font-lock-builtin-face ((,class (:foreground "LightCoral"))))
    `(font-lock-comment-delimiter-face ((,class (:foreground "gray50"))))
    `(font-lock-comment-face ((,class (:foreground "gray50"))))
@@ -84,7 +84,7 @@
    `(highlight ((,class (:background "DodgerBlue4"))))
    `(ido-first-match ((,class (:weight normal :foreground "orange"))))
    `(ido-only-match ((,class (:foreground "green"))))
-   `(ido-subdir ((,class (:foreground nil :inherit font-lock-keyword-face))))
+   `(ido-subdir ((,class (:foreground unspecified :inherit 
font-lock-keyword-face))))
    `(image-dired-thumb-flagged ((,class (:background "Red1"))))
    `(image-dired-thumb-mark ((,class (:background "dodgerblue3"))))
    `(info-header-node ((,class (:foreground "DeepSkyBlue1"))))
@@ -98,7 +98,7 @@
    `(match ((,class (:background "DeepPink4"))))
    `(minibuffer-prompt ((,class (:foreground "CadetBlue1"))))
    `(mode-line ((,class (:background "gray75" :foreground "black" :box 
(:line-width 1 :style released-button)))))
-   `(mode-line-buffer-id ((,class (:weight bold :background nil :foreground 
"blue4"))))
+   `(mode-line-buffer-id ((,class (:weight bold :background unspecified 
:foreground "blue4"))))
    `(mode-line-inactive ((,class (:background "gray40" :foreground "black" 
:box (:line-width 1 :color "gray40" :style nil)))))
    `(outline-1 ((,class (:foreground "SkyBlue1"))))
    `(outline-2 ((,class (:foreground "CadetBlue1"))))
diff --git a/etc/themes/leuven-dark-theme.el b/etc/themes/leuven-dark-theme.el
index fda00f1282f..33a15945e71 100644
--- a/etc/themes/leuven-dark-theme.el
+++ b/etc/themes/leuven-dark-theme.el
@@ -95,7 +95,7 @@ CONTROL can be a number, nil, or t.  When t, use 
DEFAULT-HEIGHT."
 
 ;;;###theme-autoload
 (deftheme leuven-dark
-  "Face colors with a light background.
+  "Face colors with a dark background.
 Basic, Font Lock, Isearch, Gnus, Message, Org mode, Diff, Ediff,
 Flyspell, Semantic, and Ansi-Color faces are included -- and much
 more..."
@@ -621,11 +621,11 @@ more..."
    `(helm-source-header ((,class (:weight bold :box (:line-width 1 :color 
"#3d3842") :background "#433e48" :foreground "#ffffff"))))
    `(helm-swoop-target-line-block-face ((,class (:background "#3833ff" 
:foreground "#e0dde3"))))
    `(helm-swoop-target-line-face ((,class (:background "#38330b"))))
-   `(helm-swoop-target-word-face ((,class (:weight bold :foreground nil 
:background "#0742d2"))))
+   `(helm-swoop-target-word-face ((,class (:weight bold :foreground 
unspecified :background "#0742d2"))))
    `(helm-visible-mark ((,class ,marked-line)))
    `(helm-w3m-bookmarks-face ((,class (:underline t :foreground "#ff010b"))))
-   `(highlight-changes ((,class (:foreground nil)))) ;; blue "#d4f754"
-   `(highlight-changes-delete ((,class (:strike-through nil :foreground 
nil)))) ;; red "#4ff7d7"
+   `(highlight-changes ((,class (:foreground unspecified)))) ;; blue "#d4f754"
+   `(highlight-changes-delete ((,class (:strike-through nil :foreground 
unspecified)))) ;; red "#4ff7d7"
    `(highlight-symbol-face ((,class (:background "#252080"))))
    `(hl-line ((,class ,highlight-yellow))) ; Highlight current line.
    `(hl-tags-face ((,class ,highlight-current-tag))) ; ~ Pair highlighting 
(matching tags).
@@ -643,7 +643,7 @@ more..."
    `(info-file ((,class (:family "Sans Serif" :height 1.8 :weight bold :box 
(:line-width 1 :color "#ffff3d") :foreground "#9f6a1c" :background "#563c2a"))))
    `(info-header-node ((,class (:underline t :foreground "#065aff")))) ; nodes 
in header
    `(info-header-xref ((,class (:underline t :foreground "#e46f0b")))) ; cross 
references in header
-   `(info-index-match ((,class (:weight bold :foreground nil :background 
"#0742d2")))) ; when using `i'
+   `(info-index-match ((,class (:weight bold :foreground unspecified 
:background "#0742d2")))) ; when using `i'
    `(info-menu-header ((,class ,ol2))) ; menu titles (headers) -- major topics
    `(info-menu-star ((,class (:foreground "#ffffff")))) ; every 3rd menu item
    `(info-node ((,class (:underline t :foreground "#ffff0b")))) ; node names
diff --git a/etc/themes/leuven-theme.el b/etc/themes/leuven-theme.el
index 7747d1e7315..f7d454381d7 100644
--- a/etc/themes/leuven-theme.el
+++ b/etc/themes/leuven-theme.el
@@ -618,11 +618,11 @@ more..."
    `(helm-source-header ((,class (:weight bold :box (:line-width 1 :color 
"#C7C7C7") :background "#DEDEDE" :foreground "black"))))
    `(helm-swoop-target-line-block-face ((,class (:background "#CCCC00" 
:foreground "#222222"))))
    `(helm-swoop-target-line-face ((,class (:background "#CCCCFF"))))
-   `(helm-swoop-target-word-face ((,class (:weight bold :foreground nil 
:background "#FDBD33"))))
+   `(helm-swoop-target-word-face ((,class (:weight bold :foreground 
unspecified :background "#FDBD33"))))
    `(helm-visible-mark ((,class ,marked-line)))
    `(helm-w3m-bookmarks-face ((,class (:underline t :foreground "cyan1"))))
-   `(highlight-changes ((,class (:foreground nil)))) ;; blue "#2E08B5"
-   `(highlight-changes-delete ((,class (:strike-through nil :foreground 
nil)))) ;; red "#B5082E"
+   `(highlight-changes ((,class (:foreground unspecified)))) ;; blue "#2E08B5"
+   `(highlight-changes-delete ((,class (:strike-through nil :foreground 
unspecified)))) ;; red "#B5082E"
    `(highlight-symbol-face ((,class (:background "#FFFFA0"))))
    `(hl-line ((,class ,highlight-yellow))) ; Highlight current line.
    `(hl-tags-face ((,class ,highlight-current-tag))) ; ~ Pair highlighting 
(matching tags).
@@ -642,7 +642,7 @@ more..."
    `(info-file ((,class (:family "Sans Serif" :height 1.8 :weight bold :box 
(:line-width 1 :color "#0000CC") :foreground "cornflower blue" :background 
"LightSteelBlue1"))))
    `(info-header-node ((,class (:underline t :foreground "orange")))) ; nodes 
in header
    `(info-header-xref ((,class (:underline t :foreground "dodger blue")))) ; 
cross references in header
-   `(info-index-match ((,class (:weight bold :foreground nil :background 
"#FDBD33")))) ; when using `i'
+   `(info-index-match ((,class (:weight bold :foreground unspecified 
:background "#FDBD33")))) ; when using `i'
    `(info-menu-header ((,class ,ol2))) ; menu titles (headers) -- major topics
    `(info-menu-star ((,class (:foreground "black")))) ; every 3rd menu item
    `(info-node ((,class (:underline t :foreground "blue")))) ; node names
diff --git a/etc/themes/manoj-dark-theme.el b/etc/themes/manoj-dark-theme.el
index 1c3e23908d1..26627a29c70 100644
--- a/etc/themes/manoj-dark-theme.el
+++ b/etc/themes/manoj-dark-theme.el
@@ -526,8 +526,8 @@ jarring angry fruit salad look to reduce eye fatigue."
  '(widget-mouse-face  ((t (:background "darkseagreen2" :foreground "blue"))))
 
  '(highlight-beyond-fill-column-face ((t (:underline t))))
- '(highlight-changes ((t (:foreground nil :background "#382f2f"))))
- '(highlight-changes-delete ((t (:foreground nil :background "#916868"))))
+ '(highlight-changes ((t (:foreground unspecified :background "#382f2f"))))
+ '(highlight-changes-delete ((t (:foreground unspecified :background 
"#916868"))))
 
  '(holiday ((t (:background "chocolate4"))))
  '(holiday-face ((t (:background "chocolate4"))))
diff --git a/etc/themes/whiteboard-theme.el b/etc/themes/whiteboard-theme.el
index adbd69f1c6f..b52996c24c0 100644
--- a/etc/themes/whiteboard-theme.el
+++ b/etc/themes/whiteboard-theme.el
@@ -44,8 +44,8 @@
    `(cursor ((,class (:background "Green4"))))
    `(default ((,class (:background "whitesmoke" :foreground "black"))))
    `(dired-marked ((,class (:background "dodgerblue3" :foreground "white"))))
-   `(flymake-errline ((,class (:background nil :underline "red"))))
-   `(flymake-warnline ((,class (:background nil :underline "magenta3"))))
+   `(flymake-errline ((,class (:background unspecified :underline "red"))))
+   `(flymake-warnline ((,class (:background unspecified :underline 
"magenta3"))))
    `(font-lock-builtin-face ((,class (:foreground "DarkOrange3"))))
    `(font-lock-comment-delimiter-face ((,class (:foreground "gray50"))))
    `(font-lock-comment-face ((,class (:foreground "gray50"))))
@@ -65,7 +65,7 @@
    `(highlight ((,class (:background "SkyBlue1"))))
    `(ido-first-match ((,class (:weight normal :foreground "DarkOrange3"))))
    `(ido-only-match ((,class (:foreground "SeaGreen4"))))
-   `(ido-subdir ((,class (:foreground nil :inherit font-lock-keyword-face))))
+   `(ido-subdir ((,class (:foreground unspecified :inherit 
font-lock-keyword-face))))
    `(image-dired-thumb-flagged ((,class :background "Red1")))
    `(image-dired-thumb-mark ((,class :background "dodgerblue3")))
    `(info-header-node ((,class (:foreground "DeepSkyBlue1"))))
@@ -79,7 +79,7 @@
    `(match ((,class (:background "LightPink1"))))
    `(minibuffer-prompt ((,class (:foreground "DodgerBlue4"))))
    `(mode-line ((,class (:background "gray75" :foreground "black" :box 
(:line-width 1 :style released-button)))))
-   `(mode-line-buffer-id ((,class (:weight bold :background nil :foreground 
"blue4"))))
+   `(mode-line-buffer-id ((,class (:weight bold :background unspecified 
:foreground "blue4"))))
    `(mode-line-inactive ((,class (:background "gray40" :foreground "black" 
:box (:line-width 1 :color "gray40" :style nil)))))
    `(outline-1 ((,class (:foreground "Blue3"))))
    `(outline-2 ((,class (:foreground "DodgerBlue"))))
diff --git a/exec/Makefile.in b/exec/Makefile.in
new file mode 100644
index 00000000000..00e59771337
--- /dev/null
+++ b/exec/Makefile.in
@@ -0,0 +1,140 @@
+### @configure_input@
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# Configure build directory information.
+
+     srcdir = @srcdir@
+      VPATH = @srcdir@
+   builddir = @builddir@
+
+# Set up compilation tools.
+
+        CC = @CC@
+        AS = @AS@
+        LD = @LD@
+        M4 = @M4@
+       CPP = @CPP@
+    ASFLAGS = @ASFLAGS@
+    ARFLAGS = @ARFLAGS@
+     CFLAGS = @CFLAGS@
+   CPPFLAGS = @CPPFLAGS@
+    LDFLAGS = @LDFLAGS@
+LOADERFLAGS = @LOADERFLAGS@
+FIND_DELETE = @FIND_DELETE@
+
+# Set up object files.
+
+     LOADER = @exec_loader@
+       OBJS = @OBJS@
+   LOADOBJS = $(patsubst %.s,%.o,$(LOADER))
+
+# Set up automatic dependency tracking.
+
+AUTO_DEPEND = @AUTO_DEPEND@
+DEPDIR = deps
+ifeq ($(AUTO_DEPEND),yes)
+DEPFLAGS = -MMD -MF $(DEPDIR)/$*.d -MP
+-include $(OBJS:%.o=$(DEPDIR)/%.d)
+-include $(DEPDIR)/test.d
+-include $(DEPDIR)/exec1.d
+else
+DEPFLAGS =
+include $(srcdir)/deps.mk
+endif
+
+# Set up the appropriate targets.
+
+all: libexec.a loader
+
+# Set up automatic Makefile regeneration.
+
+$(srcdir)/configure: $(srcdir)/configure.ac
+       cd $(srcdir) && autoreconf
+
+config.status: $(srcdir)/configure
+       if [ -x config.status ]; then   \
+         ./config.status --recheck;    \
+       else                            \
+         $(srcdir)/configure;          \
+       fi
+
+Makefile: config.status Makefile.in
+       MAKE="$(MAKE)" ./config.status
+
+# Set up rules to build targets.
+
+.SUFFIXES: .c .s
+.c.o:
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEPFLAGS) -I. -I$(srcdir) $< -o $@
+.s.o:
+       $(M4) $< > $(notdir $<).s
+       $(AS) $(ASFLAGS) $(notdir $<).s -o $@
+
+# Set up dependencies for config-mips.m4.
+
+config-mips.m4: config-mips.m4.in
+       cd $(srcdir) && ./config.status $@
+$(LOADOBJS): config-mips.m4
+
+# Set up rules to build libexec.a.
+
+libexec.a: $(OBJS)
+       $(AR) cru $(ARFLAGS) $@ $^
+
+# And loader.
+
+loader: $(LOADOBJS)
+       $(LD) -o $@ $(LOADERFLAGS) $(LOADOBJS)
+
+# And test.
+
+test: test.o libexec.a
+       $(CC) $(LDFLAGS) $< libexec.a -o $@
+
+# And exec1.
+
+exec1: exec1.o libexec.a
+       $(CC) $(LDFLAGS) $< libexec.a -o $@
+
+# Set up targets for cleaning.
+
+.PHONY: clean distclean maintainer-clean extraclean bootstrap-clean
+clean:
+       rm -f *.o *.a loader test exec1 *.s.s
+ifeq ($(AUTO_DEPEND),yes)
+       rm -rf deps/*.d
+endif
+
+distclean: clean
+       rm -f Makefile config.status config.h config-mips.m4
+
+maintainer-clean: distclean
+
+### This doesn't actually appear in the coding standards, but Karl
+### says GCC supports it, and that's where the configuration part of
+### the coding standards seem to come from.  It's like distclean, but
+### it deletes backup and autosave files too.
+
+extraclean: maintainer-clean
+       -rm -f config-tmp-* $(srcdir)/aclocal.m4 $(srcdir)/configure \
+         $(srcdir)/src/config.in
+       -[ "$(srcdir)" = "." ] || \
+         find $(srcdir) '(' -name '*~' -o -name '#*' ')' $(FIND_DELETE)
+       -find . '(' -name '*~' -o -name '#*' ')' $(FIND_DELETE)
+bootstrap-clean: extraclean
diff --git a/exec/README b/exec/README
new file mode 100644
index 00000000000..f7eb21cfc84
--- /dev/null
+++ b/exec/README
@@ -0,0 +1,3 @@
+This directory holds the source code to a library used to replace the
+`execve' and `execveat' system calls, used by the Android port of
+Emacs to start executables without intervention from the system.
diff --git a/exec/config-mips.m4.in b/exec/config-mips.m4.in
new file mode 100644
index 00000000000..72632765bd0
--- /dev/null
+++ b/exec/config-mips.m4.in
@@ -0,0 +1,42 @@
+dnl  Assembler templates for MIPS computers.
+dnl
+dnl Copyright (C) 2023 Free Software Foundation, Inc.
+dnl
+dnl  This file is part of GNU Emacs.
+dnl
+dnl  GNU Emacs is free software: you can redistribute it and/or modify
+dnl  it under the terms of the GNU General Public License as published by
+dnl  the Free Software Foundation, either version 3 of the License, or
+dnl  (at your option) any later version.
+dnl
+dnl  GNU Emacs is distributed in the hope that it will be useful,
+dnl  but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl  GNU General Public License for more details.
+dnl
+dnl  You should have received a copy of the GNU General Public License
+dnl  along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+define(`SYSCALL_open', `ifelse(`@MIPS_N32@',`yes',`6002',`4005')')
+define(`SYSCALL_close', `ifelse(`@MIPS_N32@',`yes',`6003',`4006')')
+define(`SYSCALL_mmap', `ifelse(`@MIPS_N32@',`yes',`6009',`4090')')
+define(`SYSCALL_nanosleep', `ifelse(`@MIPS_N32@',`yes',`6034',`4166')')
+define(`SYSCALL_exit', `ifelse(`@MIPS_N32@',`yes',`6058',`4001')')
+define(`SYSCALL_prctl', `ifelse(`@MIPS_N32@',`yes',`6153',`4192')')
+
+define(`SYSCALL', `ifelse(`@MIPS_N32@',`yes',` move    $a4, $1
+       move    $a5, $2
+       move    $a6, $3
+       move    $a7, $4',`      addi    $sp, -32
+       sw      $1, 16($sp)
+       sw      $2, 20($sp)
+       sw      $3, 24($sp)
+       sw      $4, 28($sp)')')
+
+define(`RESTORE', `ifelse(`@MIPS_N32@',`yes',` nop',`  addi    $sp, 32')')
+
+dnl For mips64.  Some assemblers don't want to assemble `daddi'.
+define(`DADDI2', `ifelse(`@DADDI_BROKEN@',`yes',`      li      $at, $2
+dadd            $1, $1, $at',` daddi   $1, $2')')
+define(`DADDI3', `ifelse(`@DADDI_BROKEN@',`yes',`      li      $at, $3
+dadd            $1, $2, $at',` daddi   $1, $2, $3')')
diff --git a/build-aux/config.guess b/exec/config.guess
similarity index 98%
copy from build-aux/config.guess
copy to exec/config.guess
index 354a8ccde42..c7f17e8fb97 100755
--- a/build-aux/config.guess
+++ b/exec/config.guess
@@ -1,10 +1,10 @@
-#! /bin/sh
+#!/usr/bin/sh
 # Attempt to guess a canonical system name.
-#   Copyright 1992-2023 Free Software Foundation, Inc.
+#   Copyright 1992-2022 Free Software Foundation, Inc.
 
 # shellcheck disable=SC2006,SC2268 # see below for rationale
 
-timestamp='2023-06-23'
+timestamp='2022-05-25'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -47,7 +47,7 @@ me=`echo "$0" | sed -e 's,.*/,,'`
 usage="\
 Usage: $0 [OPTION]
 
-Output the configuration name of the system '$me' is run on.
+Output the configuration name of the system \`$me' is run on.
 
 Options:
   -h, --help         print this help, then exit
@@ -60,13 +60,13 @@ version="\
 GNU config.guess ($timestamp)
 
 Originally written by Per Bothner.
-Copyright 1992-2023 Free Software Foundation, Inc.
+Copyright 1992-2022 Free Software Foundation, Inc.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
 
 help="
-Try '$me --help' for more information."
+Try \`$me --help' for more information."
 
 # Parse command line
 while test $# -gt 0 ; do
@@ -102,8 +102,8 @@ GUESS=
 # temporary files to be created and, as you can see below, it is a
 # headache to deal with in a portable fashion.
 
-# Historically, 'CC_FOR_BUILD' used to be named 'HOST_CC'. We still
-# use 'HOST_CC' if defined, but it is deprecated.
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
 
 # Portable tmp directory creation inspired by the Autoconf team.
 
@@ -459,7 +459,7 @@ case 
$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in
                UNAME_RELEASE=`uname -v`
                ;;
        esac
-       # Japanese Language versions have a version number like '4.1.3-JL'.
+       # Japanese Language versions have a version number like `4.1.3-JL'.
        SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
        GUESS=sparc-sun-sunos$SUN_REL
        ;;
@@ -966,12 +966,6 @@ EOF
        GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
        GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC
        ;;
-    x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*)
-       GUESS="$UNAME_MACHINE-pc-managarm-mlibc"
-       ;;
-    *:[Mm]anagarm:*:*)
-       GUESS="$UNAME_MACHINE-unknown-managarm-mlibc"
-       ;;
     *:Minix:*:*)
        GUESS=$UNAME_MACHINE-unknown-minix
        ;;
@@ -1042,7 +1036,7 @@ EOF
     k1om:Linux:*:*)
        GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
        ;;
-    loongarch32:Linux:*:* | loongarch64:Linux:*:*)
+    loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
        GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
        ;;
     m32r*:Linux:*:*)
@@ -1197,7 +1191,7 @@ EOF
        GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
        ;;
     i*86:OS/2:*:*)
-       # If we were able to find 'uname', then EMX Unix compatibility
+       # If we were able to find `uname', then EMX Unix compatibility
        # is probably installed.
        GUESS=$UNAME_MACHINE-pc-os2-emx
        ;;
@@ -1338,7 +1332,7 @@ EOF
                GUESS=ns32k-sni-sysv
        fi
        ;;
-    PENTIUM:*:4.0*:*)  # Unisys 'ClearPath HMP IX 4000' SVR4/MP effort
+    PENTIUM:*:4.0*:*)  # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
                        # says <Richard.M.Bartel@ccMail.Census.GOV>
        GUESS=i586-unisys-sysv4
        ;;
diff --git a/build-aux/config.sub b/exec/config.sub
similarity index 96%
copy from build-aux/config.sub
copy to exec/config.sub
index 9865d6ea4d1..b41da55df45 100755
--- a/build-aux/config.sub
+++ b/exec/config.sub
@@ -1,10 +1,10 @@
-#! /bin/sh
+#!/usr/bin/sh
 # Configuration validation subroutine script.
-#   Copyright 1992-2023 Free Software Foundation, Inc.
+#   Copyright 1992-2022 Free Software Foundation, Inc.
 
 # shellcheck disable=SC2006,SC2268 # see below for rationale
 
-timestamp='2023-06-26'
+timestamp='2022-01-03'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -76,13 +76,13 @@ Report bugs and patches to <config-patches@gnu.org>."
 version="\
 GNU config.sub ($timestamp)
 
-Copyright 1992-2023 Free Software Foundation, Inc.
+Copyright 1992-2022 Free Software Foundation, Inc.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
 
 help="
-Try '$me --help' for more information."
+Try \`$me --help' for more information."
 
 # Parse command line
 while test $# -gt 0 ; do
@@ -130,7 +130,7 @@ IFS=$saved_IFS
 # Separate into logical components for further validation
 case $1 in
        *-*-*-*-*)
-               echo "Invalid configuration '$1': more than four components" >&2
+               echo Invalid configuration \`"$1"\': more than four components 
>&2
                exit 1
                ;;
        *-*-*-*)
@@ -145,8 +145,7 @@ case $1 in
                        nto-qnx* | linux-* | uclinux-uclibc* \
                        | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | 
netbsd*-gnu* \
                        | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
-                       | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \
-                       | windows-* )
+                       | storm-chaos* | os2-emx* | rtmk-nova*)
                                basic_machine=$field1
                                basic_os=$maybe_os
                                ;;
@@ -944,7 +943,7 @@ $basic_machine
 EOF
                IFS=$saved_IFS
                ;;
-       # We use 'pc' rather than 'unknown'
+       # We use `pc' rather than `unknown'
        # because (1) that's what they normally are, and
        # (2) the word "unknown" tends to confuse beginning users.
        i*86 | x86_64)
@@ -1076,7 +1075,7 @@ case $cpu-$vendor in
        pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
                cpu=i586
                ;;
-       pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*)
+       pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
                cpu=i686
                ;;
        pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
@@ -1208,7 +1207,7 @@ case $cpu-$vendor in
                        | k1om \
                        | le32 | le64 \
                        | lm32 \
-                       | loongarch32 | loongarch64 \
+                       | loongarch32 | loongarch64 | loongarchx32 \
                        | m32c | m32r | m32rle \
                        | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | 
m68k \
                        | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
@@ -1286,7 +1285,7 @@ case $cpu-$vendor in
                                ;;
 
                        *)
-                               echo "Invalid configuration '$1': machine 
'$cpu-$vendor' not recognized" 1>&2
+                               echo Invalid configuration \`"$1"\': machine 
\`"$cpu-$vendor"\' not recognized 1>&2
                                exit 1
                                ;;
                esac
@@ -1342,10 +1341,6 @@ EOF
                kernel=linux
                os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
                ;;
-       managarm*)
-               kernel=managarm
-               os=`echo "$basic_os" | sed -e 's|managarm|mlibc|'`
-               ;;
        *)
                kernel=
                os=$basic_os
@@ -1759,7 +1754,7 @@ case $os in
             | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
             | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
             | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
-            | fiwix* | mlibc* )
+            | fiwix* )
                ;;
        # This one is extra strict with allowed versions
        sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
@@ -1767,11 +1762,8 @@ case $os in
                ;;
        none)
                ;;
-       kernel* | msvc* )
-               # Restricted further below
-               ;;
        *)
-               echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2
+               echo Invalid configuration \`"$1"\': OS \`"$os"\' not 
recognized 1>&2
                exit 1
                ;;
 esac
@@ -1780,30 +1772,14 @@ esac
 # (given a valid OS), if there is a kernel.
 case $kernel-$os in
        linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
-                  | linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* 
)
+                  | linux-musl* | linux-relibc* | linux-uclibc* )
                ;;
        uclinux-uclibc* )
                ;;
-       managarm-mlibc* | managarm-kernel* )
-               ;;
-       windows*-gnu* | windows*-msvc*)
-               ;;
-       -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* )
+       -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
                # These are just libc implementations, not actual OSes, and thus
                # require a kernel.
-               echo "Invalid configuration '$1': libc '$os' needs explicit 
kernel." 1>&2
-               exit 1
-               ;;
-       -kernel* )
-               echo "Invalid configuration '$1': '$os' needs explicit kernel." 
1>&2
-               exit 1
-               ;;
-       *-kernel* )
-               echo "Invalid configuration '$1': '$kernel' does not support 
'$os'." 1>&2
-               exit 1
-               ;;
-       *-msvc* )
-               echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2
+               echo "Invalid configuration \`$1': libc \`$os' needs explicit 
kernel." 1>&2
                exit 1
                ;;
        kfreebsd*-gnu* | kopensolaris*-gnu*)
@@ -1820,7 +1796,7 @@ case $kernel-$os in
                # Blank kernel with real OS is always fine.
                ;;
        *-*)
-               echo "Invalid configuration '$1': Kernel '$kernel' not known to 
work with OS '$os'." 1>&2
+               echo "Invalid configuration \`$1': Kernel \`$kernel' not known 
to work with OS \`$os'." 1>&2
                exit 1
                ;;
 esac
diff --git a/exec/configure.ac b/exec/configure.ac
new file mode 100644
index 00000000000..180c200d13d
--- /dev/null
+++ b/exec/configure.ac
@@ -0,0 +1,537 @@
+dnl  Autoconf script for GNU Emacs's exec library.
+dnl To rebuild the 'configure' script from this, execute the command
+dnl    autoconf
+dnl in the directory containing this script.
+dnl If you changed any AC_DEFINES, also run autoheader.
+dnl
+dnl Copyright (C) 2023 Free Software Foundation, Inc.
+dnl
+dnl  This file is part of GNU Emacs.
+dnl
+dnl  GNU Emacs is free software: you can redistribute it and/or modify
+dnl  it under the terms of the GNU General Public License as published by
+dnl  the Free Software Foundation, either version 3 of the License, or
+dnl  (at your option) any later version.
+dnl
+dnl  GNU Emacs is distributed in the hope that it will be useful,
+dnl  but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl  GNU General Public License for more details.
+dnl
+dnl  You should have received a copy of the GNU General Public License
+dnl  along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+AC_PREREQ([2.65])
+AC_INIT([libexec], [30.0.50], [bug-gnu-emacs@gnu.org], [],
+  [https://www.gnu.org/software/emacs/])
+
+AH_TOP([/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. */])
+
+AC_ARG_WITH([reentrancy],
+  [AS_HELP_STRING([--with-reentrancy],
+    [Generate library which can be used within a signal handler.])],
+  [AC_DEFINE([REENTRANT], [1])])
+
+AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+
+AC_TYPE_UINT8_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT64_T
+AC_TYPE_UINTPTR_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_PID_T
+
+AC_HEADER_STDBOOL
+AC_CHECK_FUNCS([getpagesize stpcpy])
+AC_CHECK_DECLS([stpcpy])
+AC_CHECK_FUNC([process_vm_readv],
+  [AC_CHECK_FUNC([process_vm_writev],
+    [AC_CHECK_DECL([process_vm_readv],
+      [AC_DEFINE([HAVE_PROCESS_VM], [1],
+        [Define to 1 if process_vm_readv is available.])],
+      [], [[
+#include <sys/uio.h>
+      ]])])])
+AC_CHECK_HEADERS([sys/param.h sys/uio.h])
+AC_CHECK_MEMBERS([siginfo_t.si_syscall], [], [],
+  [[
+#include <signal.h>
+  ]])
+
+AH_BOTTOM([
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# ifndef HAVE__BOOL
+#  ifdef __cplusplus
+typedef bool _Bool;
+#  else
+#   define _Bool signed char
+#  endif
+# endif
+# define bool _Bool
+# define false 0
+# define true 1
+# define __bool_true_false_are_defined 1
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif /* HAVE_SYS_PARAM_H */
+
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif /* MAX */
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif /* MIN */
+])
+
+AC_C_BIGENDIAN
+
+AH_TEMPLATE([SYSCALL_HEADER], [Define to header holding system call numbers.])
+AH_TEMPLATE([USER_HEADER], [Define to header holding USER_REGS_STRUCT.])
+AH_TEMPLATE([USER_REGS_STRUCT], [Define to structure holding user registers.])
+AH_TEMPLATE([SYSCALL_NUM_REG], [Define to register holding the system call 
number.])
+AH_TEMPLATE([SYSCALL_ARG_REG], [Define to register holding arg0 to system 
calls.])
+AH_TEMPLATE([SYSCALL_ARG1_REG], [Define to register holding arg1 to system 
calls.])
+AH_TEMPLATE([SYSCALL_ARG2_REG], [Define to register holding arg2 to system 
calls.])
+AH_TEMPLATE([SYSCALL_ARG3_REG], [Define to register holding arg3 to system 
calls.])
+AH_TEMPLATE([SYSCALL_RET_REG], [Define to register holding value of system 
calls.])
+AH_TEMPLATE([STACK_POINTER], [Define to register holding the stack pointer.])
+AH_TEMPLATE([EXEC_SYSCALL], [Define to number of the `exec' system call.])
+AH_TEMPLATE([USER_WORD], [Define to word type used by tracees.])
+AH_TEMPLATE([EXEC_64], [Define to 1 if the system utilizes 64-bit ELF.])
+AH_TEMPLATE([STACK_GROWS_DOWNWARDS], [Define to 1 if the stack grows 
downwards.])
+AH_TEMPLATE([ABI_RED_ZONE], [Define to number of reserved bytes past the stack 
frame.])
+AH_TEMPLATE([EXECUTABLE_BASE], [Virtual address for loading PIC executables])
+AH_TEMPLATE([INTERPRETER_BASE], [Virtual address for loading PIC interpreters])
+AH_TEMPLATE([CLONE_SYSCALL], [Define to number of the `clone' system call.])
+AH_TEMPLATE([CLONE3_SYSCALL], [Define to number of the `clone3' system call.])
+AH_TEMPLATE([READLINK_SYSCALL], [Define to number of the `readlink' system 
call.])
+AH_TEMPLATE([READLINKAT_SYSCALL], [Define to number of the `readlinkat' system 
call.])
+AH_TEMPLATE([REENTRANT], [Define to 1 if the library is used within a signal 
handler.])
+
+AC_CANONICAL_HOST
+
+# Check whether or not sys/user exists.  If it doesn't, try
+# asm/user.h, and croak if that doesn't exist either.
+AS_CASE([$host], [*mips*], [], [*],
+  [AC_CHECK_HEADER([sys/user.h], [user_h="<sys/user.h>"],
+    [AC_CHECK_HEADER([asm/user.h], [user_h="<asm/user.h>"],
+      [AC_MSG_ERROR([Can not find working user.h])])])])
+
+# Look for required tools.
+
+AC_ARG_VAR([M4], [`m4' preprocessor command.])
+AC_ARG_VAR([AS], [`as' assembler command.])
+AC_ARG_VAR([LD], [`ld' linker command.])
+
+# Check for a working m4.
+AC_CHECK_PROGS([M4], [gm4 m4],
+  [AC_MSG_ERROR([Cannot find m4])])
+
+# Check for a working assembler.
+AC_CHECK_TOOL([AS], [as],
+  [AC_MSG_ERROR([Cannot find a working assembler])])
+
+# And ar.
+AC_CHECK_TOOL([AR], [ar],
+  [AC_MSG_ERROR([Cannot find a working ar])])
+
+# And ld.
+AC_CHECK_TOOL([LD], [ld],
+  [AC_MSG_ERROR([Cannot find a working linker])])
+
+# Now check if ld is a C compiler.
+LDPREFIX=
+AC_CACHE_CHECK([whether ld is a C compiler],
+  [exec_cv_ld_is_cc],
+  [cat <<_ACEOF > conftest.c
+AC_LANG_PROGRAM(,)
+_ACEOF
+   exec_cv_ld_is_cc=yes
+   $LD -c conftest.c -o conftest.$OBJEXT >&AS_MESSAGE_LOG_FD 2>&1 \
+   || exec_cv_ld_is_cc=no
+   rm -f conftest.c conftest.$OBJEXT])
+
+# And if as is a C compiler.
+AC_CACHE_CHECK([whether as is a C compiler],
+  [exec_cv_as_is_cc],
+  [cat <<_ACEOF > conftest.c
+AC_LANG_PROGRAM(,)
+_ACEOF
+   exec_cv_as_is_cc=yes
+   $AS -c conftest.c -o conftest.$OBJEXT >&AS_MESSAGE_LOG_FD 2>&1 \
+   || exec_cv_as_is_cc=no
+   rm -f conftest.c conftest.$OBJEXT])
+
+# If ld is a C compiler, pass `-nostdlib', `-nostartfiles', and
+# `-static'.  Also, set LDPREFIX to -Wl,
+AS_IF([test "x$exec_cv_ld_is_cc" = "xyes"],
+  [LOADERFLAGS="$LOADERFLAGS -nostdlib -nostartfiles -static"
+   LDPREFIX=-Wl,])
+
+# If as is a C compiler, add `-c' to ASFLAGS.
+AS_IF([test "x$exec_cv_as_is_cc" = "xyes"],
+  [ASFLAGS="$ASFLAGS -c"])
+
+AC_DEFUN([exec_CHECK_LINUX_CLONE3],
+[
+AC_CHECK_DECL([__NR_clone3],
+  [AC_DEFINE([CLONE3_SYSCALL], [__NR_clone3])],
+  [], [[
+#include <asm/unistd.h>
+]])
+])
+
+AC_DEFUN([exec_CHECK_MIPS_NABI],
+[
+AC_CACHE_CHECK([whether MIPS NABI calling convention is used],
+  [exec_cv_mips_nabi],
+  [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sgidefs.h>
+]], [[
+#ifndef  __mips64__
+#if _MIPS_SIM == _ABIO32
+OABI in use.
+#endif /* _MIPS_SIM == _ABIO32 */
+#endif /* !__mips64__ */
+]])], [exec_cv_mips_nabi=yes],
+  [exec_cv_mips_nabi=no])])
+
+dnl mips64 systems use N64 calling convention, a variant of nabi
+dnl calling convention.
+AS_IF([test "x$exec_cv_mips_nabi" != "xno"],
+  [AC_DEFINE([MIPS_NABI], [1],
+    [Define to 1 if MIPS NABI calling convention is being used.])],
+   [OBJS="$OBJS mipsfpu.o"])
+])
+
+# Determine the system type and define appropriate macros.
+exec_loader=
+is_mips=
+OBJS="exec.o trace.o"
+DADDI_BROKEN=no
+
+AS_CASE([$host], [x86_64-*linux*],
+  [AC_CHECK_MEMBER([struct user_regs_struct.rdi],
+    [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+     AC_DEFINE_UNQUOTED([USER_HEADER], [$user_h])
+     AC_DEFINE([USER_REGS_STRUCT], [struct user_regs_struct])
+     AC_DEFINE([SYSCALL_NUM_REG], [orig_rax])
+     AC_DEFINE([SYSCALL_RET_REG], [rax])
+     AC_DEFINE([SYSCALL_ARG_REG], [rdi])
+     AC_DEFINE([SYSCALL_ARG1_REG], [rsi])
+     AC_DEFINE([SYSCALL_ARG2_REG], [rdx])
+     AC_DEFINE([SYSCALL_ARG3_REG], [r10])
+     AC_DEFINE([STACK_POINTER], [rsp])
+     AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+     AC_DEFINE([USER_WORD], [uintptr_t])
+     AC_DEFINE([EXEC_64], [1])
+     AC_DEFINE([ABI_RED_ZONE], [128])
+     AC_DEFINE([EXECUTABLE_BASE], [0x555555554000])
+     AC_DEFINE([INTERPRETER_BASE], [0x600000000000])
+     AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+     AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+     AC_DEFINE([READLINK_SYSCALL], [__NR_readlink])
+     AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+     exec_CHECK_LINUX_CLONE3
+     # Make sure the loader doesn't conflict with other position
+     # dependent code.
+     LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0x200000000000"
+     exec_loader=loader-x86_64.s],
+    [AC_MSG_ERROR([Missing `rdi' in user_regs_struct])],
+    [[
+#include $user_h
+    ]])], [i[[34567]]86-*linux*],
+  [AC_CHECK_MEMBER([struct user_regs_struct.edi],
+    [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+     AC_DEFINE_UNQUOTED([USER_HEADER], [$user_h])
+     AC_DEFINE([USER_REGS_STRUCT], [struct user_regs_struct])
+     AC_DEFINE([SYSCALL_NUM_REG], [orig_eax])
+     AC_DEFINE([SYSCALL_RET_REG], [eax])
+     AC_DEFINE([SYSCALL_ARG_REG], [ebx])
+     AC_DEFINE([SYSCALL_ARG1_REG], [ecx])
+     AC_DEFINE([SYSCALL_ARG2_REG], [edx])
+     AC_DEFINE([SYSCALL_ARG3_REG], [esi])
+     AC_DEFINE([STACK_POINTER], [esp])
+     AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+     AC_DEFINE([USER_WORD], [uintptr_t])
+     AC_DEFINE([EXECUTABLE_BASE], [0x0f000000])
+     AC_DEFINE([INTERPRETER_BASE], [0xaf000000])
+     AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+     AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+     AC_DEFINE([READLINK_SYSCALL], [__NR_readlink])
+     AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+     exec_CHECK_LINUX_CLONE3
+     # Make sure the loader doesn't conflict with other position
+     # dependent code.
+     LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0xa0000000"
+     exec_loader=loader-x86.s],
+    [AC_MSG_ERROR([Missing `edi' in user_regs_struct])],
+    [[
+#include $user_h
+    ]])], [aarch64-*linux*],
+  [AC_CHECK_MEMBER([struct user_regs_struct.sp],
+    [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+     AC_DEFINE_UNQUOTED([USER_HEADER], [$user_h])
+     AC_DEFINE([USER_REGS_STRUCT], [struct user_regs_struct])
+     AC_DEFINE([SYSCALL_NUM_REG], [[regs[8]]])
+     AC_DEFINE([SYSCALL_RET_REG], [[regs[0]]])
+     AC_DEFINE([SYSCALL_ARG_REG], [[regs[0]]])
+     AC_DEFINE([SYSCALL_ARG1_REG], [[regs[1]]])
+     AC_DEFINE([SYSCALL_ARG2_REG], [[regs[2]]])
+     AC_DEFINE([SYSCALL_ARG3_REG], [[regs[3]]])
+     AC_DEFINE([STACK_POINTER], [sp])
+     AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+     AC_DEFINE([USER_WORD], [uintptr_t])
+     AC_DEFINE([EXEC_64], [1])
+     AC_DEFINE([EXECUTABLE_BASE], [0x3000000000])
+     AC_DEFINE([INTERPRETER_BASE], [0x3f00000000])
+     AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+     AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+     # Note that aarch64 has no `readlink'.
+     AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+     exec_CHECK_LINUX_CLONE3
+     # Make sure the loader doesn't conflict with other position
+     # dependent code.  ARM places rather significant restrictions on
+     # virtual addresses for a 64 bit architecture.
+     LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0x2000000000"
+     exec_loader=loader-aarch64.s],
+    [AC_MSG_ERROR([Missing `sp' in user_regs_struct])],
+    [[
+#include $user_h
+    ]])], [arm*linux*eabi* | armv7*linux*],
+  [AC_CHECK_MEMBER([struct user_regs.uregs],
+    [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+     AC_DEFINE_UNQUOTED([USER_HEADER], [$user_h])
+     AC_DEFINE([USER_REGS_STRUCT], [struct user_regs])
+     AC_DEFINE([SYSCALL_NUM_REG], [[uregs[7]]])
+     AC_DEFINE([SYSCALL_RET_REG], [[uregs[0]]])
+     AC_DEFINE([SYSCALL_ARG_REG], [[uregs[0]]])
+     AC_DEFINE([SYSCALL_ARG1_REG], [[uregs[1]]])
+     AC_DEFINE([SYSCALL_ARG2_REG], [[uregs[2]]])
+     AC_DEFINE([SYSCALL_ARG3_REG], [[uregs[3]]])
+     AC_DEFINE([STACK_POINTER], [[uregs[13]]])
+     AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+     AC_DEFINE([USER_WORD], [uintptr_t])
+     AC_DEFINE([EXECUTABLE_BASE], [0x0f000000])
+     AC_DEFINE([INTERPRETER_BASE], [0x1f000000])
+     AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+     AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+     AC_DEFINE([READLINK_SYSCALL], [__NR_readlink])
+     AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+     exec_CHECK_LINUX_CLONE3
+     LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0x20000000"
+     exec_loader=loader-armeabi.s],
+    [AC_CHECK_MEMBER([struct pt_regs.uregs],
+      [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+       AC_DEFINE_UNQUOTED([USER_HEADER], [<asm/ptrace.h>])
+       AC_DEFINE([USER_REGS_STRUCT], [struct pt_regs])
+       AC_DEFINE([SYSCALL_NUM_REG], [[uregs[7]]])
+       AC_DEFINE([SYSCALL_RET_REG], [[uregs[0]]])
+       AC_DEFINE([SYSCALL_ARG_REG], [[uregs[0]]])
+       AC_DEFINE([SYSCALL_ARG1_REG], [[uregs[1]]])
+       AC_DEFINE([SYSCALL_ARG2_REG], [[uregs[2]]])
+       AC_DEFINE([SYSCALL_ARG3_REG], [[uregs[3]]])
+       AC_DEFINE([STACK_POINTER], [[uregs[13]]])
+       AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+       AC_DEFINE([USER_WORD], [uintptr_t])
+       AC_DEFINE([EXECUTABLE_BASE], [0x0f000000])
+       AC_DEFINE([INTERPRETER_BASE], [0x1f000000])
+       AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+       AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+       AC_DEFINE([READLINK_SYSCALL], [__NR_readlink])
+       AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+       exec_CHECK_LINUX_CLONE3
+       LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0x20000000"
+       exec_loader=loader-armeabi.s],
+      [AC_MSG_ERROR([Missing `uregs' in user_regs_struct or pt_regs])],
+      [[
+#include <asm/ptrace.h>
+      ]])],
+    [[
+#include $user_h
+    ]])], [mipsel*linux*],
+  [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+   AC_DEFINE([USER_HEADER], ["mipsel-user.h"])
+   AC_DEFINE([USER_REGS_STRUCT], [struct mipsel_regs])
+   AC_DEFINE([SYSCALL_NUM_REG], [[gregs[2]]]) # v0
+   AC_DEFINE([SYSCALL_RET_REG], [[gregs[4]]]) # a0
+   AC_DEFINE([SYSCALL_ARG_REG], [[gregs[4]]]) # a0
+   AC_DEFINE([SYSCALL_ARG1_REG], [[gregs[5]]]) # a1
+   AC_DEFINE([SYSCALL_ARG2_REG], [[gregs[4]]]) # a2
+   AC_DEFINE([SYSCALL_ARG3_REG], [[gregs[5]]]) # a3
+   AC_DEFINE([STACK_POINTER], [[gregs[29]]]) # sp
+   AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+   AC_DEFINE([USER_WORD], [uintptr_t])
+   AC_DEFINE([EXECUTABLE_BASE], [0x0f000000])
+   AC_DEFINE([INTERPRETER_BASE], [0x1f000000])
+   AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+   AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+   AC_DEFINE([READLINK_SYSCALL], [__NR_readlink])
+   AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+   AC_CHECK_DECL([_MIPS_SIM], [exec_CHECK_MIPS_NABI],
+     [AC_MSG_ERROR([_MIPS_SIM could not be determined]),
+     [[
+#include <sgidefs.h>
+]]])
+   exec_CHECK_LINUX_CLONE3
+   LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0x20000000"
+   is_mips=yes
+   exec_loader=loader-mipsel.s], [mips64el*linux*],
+  [AC_DEFINE([SYSCALL_HEADER], [<asm/unistd.h>])
+   AC_DEFINE([USER_HEADER], ["mipsel-user.h"])
+   AC_DEFINE([USER_REGS_STRUCT], [struct mipsel_regs])
+   AC_DEFINE([SYSCALL_NUM_REG], [[gregs[2]]]) # v0
+   AC_DEFINE([SYSCALL_RET_REG], [[gregs[4]]]) # a0
+   AC_DEFINE([SYSCALL_ARG_REG], [[gregs[4]]]) # a0
+   AC_DEFINE([SYSCALL_ARG1_REG], [[gregs[5]]]) # a1
+   AC_DEFINE([SYSCALL_ARG2_REG], [[gregs[4]]]) # a2
+   AC_DEFINE([SYSCALL_ARG3_REG], [[gregs[5]]]) # a3
+   AC_DEFINE([STACK_POINTER], [[gregs[29]]]) # sp
+   AC_DEFINE([EXEC_SYSCALL], [__NR_execve])
+   AC_DEFINE([USER_WORD], [uintptr_t])
+   AC_DEFINE([EXEC_64], [1])
+   AC_DEFINE([EXECUTABLE_BASE], [0x400000])
+   AC_DEFINE([INTERPRETER_BASE], [0x3f00000000])
+   AC_DEFINE([STACK_GROWS_DOWNWARDS], [1])
+   AC_DEFINE([CLONE_SYSCALL], [__NR_clone])
+   AC_DEFINE([READLINK_SYSCALL], [__NR_readlink])
+   AC_DEFINE([READLINKAT_SYSCALL], [__NR_readlinkat])
+   AC_CACHE_CHECK([whether as understands `daddi'],
+     [exec_cv_as_daddi],
+     [exec_cv_as_daddi=no
+      cat <<_ACEOF >conftest.s
+      .section text
+      .global __start
+__start:
+       li      $t0, 0
+       li      $t1, 0
+       daddi   $t0, $t1, 1
+       daddi   $t0, $t1, -1
+       daddi   $t0, -1
+       daddi   $t0, 1
+
+_ACEOF
+      $AS $ASFLAGS conftest.s -o conftest.$OBJEXT \
+       >&AS_MESSAGE_LOG_FD 2>&1                  \
+       && exec_cv_as_daddi=yes
+      rm -f conftest.s conftest.$OBJEXT])
+   AS_IF([test "x$exec_cv_as_daddi" != "xyes"],
+     [DADDI_BROKEN=yes])
+   exec_CHECK_LINUX_CLONE3
+   exec_CHECK_MIPS_NABI
+   LOADERFLAGS="$LOADERFLAGS $LDPREFIX-Ttext=0x3e00000000"
+   is_mips=yes
+   exec_loader=loader-mips64el.s], [*],
+  [AC_MSG_ERROR([Please port libexec to $host])])
+
+AC_SUBST([DADDI_BROKEN])
+
+MIPS_N32=$exec_cv_mips_nabi
+
+AC_ARG_VAR([LOADERFLAGS], [Flags used to link the loader.])
+AC_ARG_VAR([ARFLAGS], [Flags for the archiver.])
+AC_ARG_VAR([ASFLAGS], [Flags for the assembler.])
+
+# Make the assembler optimize for code size.  Don't do this on MIPS,
+# as the assembler code manages branch delays manually.
+
+AC_CACHE_CHECK([whether as understands -O],
+  [exec_cv_as_O],
+  [exec_cv_as_O=no
+   cat <<_ACEOF >conftest.s
+   .section text
+   .global _start
+_start:
+
+_ACEOF
+   $AS $ASFLAGS -O conftest.s -o conftest.$OBJEXT \
+     >&AS_MESSAGE_LOG_FD 2>&1                    \
+     && exec_cv_as_O=yes
+   rm -f conftest.s conftest.$OBJEXT])
+
+AS_IF([test "$exec_cv_as_O" = "yes" \
+       && test "$is_mips" != "yes"],
+  [ASFLAGS="$ASFLAGS -O"])
+
+# Make the assembler generate debug information.
+
+AC_CACHE_CHECK([whether as understands -g],
+  [exec_cv_as_g],
+  [exec_cv_as_g=no
+   cat <<_ACEOF >conftest.s
+   .section text
+   .global _start
+_start:
+
+_ACEOF
+   $AS $ASFLAGS -g conftest.s -o conftest.$OBJEXT \
+     >&AS_MESSAGE_LOG_FD 2>&1                    \
+     && exec_cv_as_g=yes
+   rm -f conftest.s conftest.$OBJEXT])
+AS_IF([test "$exec_cv_as_g" = "yes"], [ASFLAGS="$ASFLAGS -g"])
+
+# Check for the ability to automatically generate dependencies for C
+# source files.
+AUTO_DEPEND=no
+AS_IF([test "x$GCC" = xyes],
+  [AC_CACHE_CHECK([whether gcc understands -MMD -MF],
+     [exec_cv_autodepend],
+     [SAVE_CFLAGS="$CFLAGS"
+      CFLAGS="$CFLAGS -MMD -MF deps.d -MP"
+      AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
+       [exec_cv_autodepend=yes],
+       [exec_cv_autodepend=no])
+     CFLAGS="$SAVE_CFLAGS"
+     test -f deps.d || emacs_cv_autodepend=no
+     rm -rf deps.d])
+   AS_IF([test "x$exec_cv_autodepend" = xyes],
+     [AUTO_DEPEND=yes
+      AS_MKDIR_P([deps])])])
+
+# Now check for some other stuff.
+
+AC_CACHE_CHECK([for 'find' args to delete a file],
+  [exec_cv_find_delete],
+  [AS_IF([touch conftest.tmp && find conftest.tmp -delete 2>/dev/null &&
+      test ! -f conftest.tmp], [exec_cv_find_delete="-delete"],
+   [exec_cv_find_delete="-exec rm -f {} ';'"])])
+FIND_DELETE=$exec_cv_find_delete
+AC_SUBST([FIND_DELETE])
+
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([Makefile config-mips.m4])
+
+AC_SUBST([AUTO_DEPEND])
+AC_SUBST([LOADERFLAGS])
+AC_SUBST([ARFLAGS])
+AC_SUBST([ASFLAGS])
+AC_SUBST([exec_loader])
+AC_SUBST([MIPS_N32])
+AC_SUBST([OBJS])
+
+AC_OUTPUT
diff --git a/exec/deps.mk b/exec/deps.mk
new file mode 100644
index 00000000000..20fcd2dbc5a
--- /dev/null
+++ b/exec/deps.mk
@@ -0,0 +1,21 @@
+### deps.mk
+
+## Copyright (C) 2023 Free Software Foundation, Inc.
+
+## This file is part of GNU Emacs.
+
+## GNU Emacs is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## GNU Emacs is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+exec.o: exec.h config.h
+trace.o: exec.h config.h
diff --git a/exec/exec.c b/exec/exec.c
new file mode 100644
index 00000000000..dae05755675
--- /dev/null
+++ b/exec/exec.c
@@ -0,0 +1,1168 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <sys/ptrace.h>
+#include <sys/param.h>
+#include <sys/mman.h>
+
+#include "exec.h"
+
+#if defined __mips__ && !defined MIPS_NABI
+#include "mipsfpu.h"
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+
+
+
+/* Define replacements for required string functions.  */
+
+#if !defined HAVE_STPCPY || !defined HAVE_DECL_STPCPY
+
+/* Copy SRC to DEST, returning the address of the terminating '\0' in
+   DEST.  */
+
+static char *
+rpl_stpcpy (char *dest, const char *src)
+{
+  register char *d;
+  register const char *s;
+
+  d = dest;
+  s = src;
+
+  do
+    *d++ = *s;
+  while (*s++ != '\0');
+
+  return d - 1;
+}
+
+#define stpcpy rpl_stpcpy
+#endif /* !defined HAVE_STPCPY || !defined HAVE_DECL_STPCPY */
+
+
+
+/* Executable reading functions.
+   These functions extract information from an executable that is
+   about to be loaded.
+
+   `exec_0' takes the name of the program, determines whether or not
+   its format is correct, and if so, returns the list of actions that
+   the loader should perform.
+
+   The actions include:
+
+     - Making the stack executable, if PT_GNU_STACK.
+     - Mapping PT_LOAD sections into the executable with the correct
+       memory protection.
+     - On MIPS, setting the floating point register size.
+     - Transferring control to the interpreter or executable.  */
+
+
+/* Check whether or not FD starts with a #!, and return the executable
+   to load if it does.  Value is NAME if no interpreter character was
+   found, or the interpreter otherwise.  Value is NULL upon an IO
+   error.
+
+   If an additional command line argument is specified, place it in
+   *EXTRA.  */
+
+static const char *
+check_interpreter (const char *name, int fd, const char **extra)
+{
+  static char buffer[PATH_MAX], *start;
+  char first[2], *end, *ws;
+  ssize_t rc;
+
+  /* Read the first character.  */
+  rc = read (fd, &first, 2);
+
+  if (rc != 2)
+    goto fail;
+
+  if (first[0] != '#' || first[1] != '!')
+    goto nomatch;
+
+  rc = read (fd, buffer, PATH_MAX);
+
+  if (rc < 0)
+    goto fail;
+
+  /* Strip leading whitespace.  */
+  start = buffer;
+  while (*start && ((unsigned char) *start) < 128 && isspace (*start))
+    ++start;
+
+  /* Look for a newline character.  */
+  end = memchr (start, '\n', rc);
+
+  if (!end)
+    goto fail;
+
+  /* The string containing the interpreter is now in start.  NULL
+     terminate it.  */
+  *end = '\0';
+
+  /* Now look for any whitespace characters.  */
+  ws = strchr (start, ' ');
+
+  /* If there's no whitespace, return the entire start.  */
+
+  if (!ws)
+    {
+      if (lseek (fd, 0, SEEK_SET))
+       goto fail;
+
+      return start;
+    }
+
+  /* Otherwise, split the string at the whitespace and return the
+     additional argument.  */
+  *ws = '\0';
+
+  if (lseek (fd, 0, SEEK_SET))
+    goto fail;
+
+  *extra = ws + 1;
+  return start;
+
+ nomatch:
+  /* There's no interpreter.  */
+  if (lseek (fd, 0, SEEK_SET))
+    goto fail;
+
+  return name;
+
+ fail:
+  errno = ENOEXEC;
+  return NULL;
+}
+
+/* Static area used to store data placed on the loader's stack.  */
+static char loader_area[65536];
+
+/* Number of bytes used in that area.  */
+static int loader_area_used;
+
+
+
+/* Structure definitions for commands placed in the loader area.
+   Arrange these so that each member is naturally aligned.  */
+
+struct exec_open_command
+{
+  /* Word identifying the type of this command.  */
+  USER_WORD command;
+
+  /* NULL-terminated file name follows, padded to the size of a user
+     word.  */
+};
+
+struct exec_map_command
+{
+  /* Word identifying the type of this command.  */
+  USER_WORD command;
+
+  /* Where the file will be mapped.  */
+  USER_WORD vm_address;
+
+  /* Offset into the file to map from.  */
+  USER_WORD file_offset;
+
+  /* Memory protection for mprotect.  */
+  USER_WORD protection;
+
+  /* Number of bytes to be mapped.  */
+  USER_WORD length;
+
+  /* Flags for mmap.  */
+  USER_WORD flags;
+
+  /* Number of bytes to clear at the end of this mapping.  */
+  USER_WORD clear;
+};
+
+struct exec_jump_command
+{
+  /* Word identifying the type of this command.  */
+  USER_WORD command;
+
+  /* Address to jump to.  */
+  USER_WORD entry;
+
+  /* The value of AT_ENTRY inside the aux vector.  */
+  USER_WORD at_entry;
+
+  /* The value of AT_PHENT inside the aux vector.  */
+  USER_WORD at_phent;
+
+  /* The value of AT_PHNUM inside the aux vector.  */
+  USER_WORD at_phnum;
+
+  /* The value of AT_PHDR inside the aux vector.  */
+  USER_WORD at_phdr;
+
+  /* The value of AT_BASE inside the aux vector.  */
+  USER_WORD at_base;
+
+#if defined __mips__ && !defined MIPS_NABI
+  /* The FPU mode to apply.  */
+  USER_WORD fpu_mode;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+};
+
+
+
+/* Write a command to open the file NAME to the loader area.
+   If ALTERNATE is true, then use the command code 16 instead
+   of 0.  Value is 1 upon failure, else 0.  */
+
+static int
+write_open_command (const char *name, bool alternate)
+{
+  struct exec_open_command command;
+  size_t size;
+
+  /* First, write the command to open NAME.  This is followed by NAME
+     itself, padded to sizeof (USER_WORD) bytes.  */
+
+  command.command = alternate ? 16 : 0;
+  if (sizeof loader_area - loader_area_used < sizeof command)
+    return 1;
+  memcpy (loader_area + loader_area_used, &command, sizeof command);
+  loader_area_used += sizeof command;
+
+  /* Calculate the length of NAME.  */
+  size = strlen (name) + 1;
+
+  /* Round it up.  */
+  size = ((size + (sizeof (USER_WORD) - 1))
+         & ~(sizeof (USER_WORD) - 1));
+
+  if (sizeof loader_area - loader_area_used < size)
+    return 1;
+
+  /* Now copy name to the loader area, filling the padding with NULL
+     bytes.  */
+  strncpy (loader_area + loader_area_used, name, size);
+
+  /* Increase loader_area_used.  */
+  loader_area_used += size;
+  return 0;
+}
+
+/* Write the commands necessary to map the executable file into memory
+   for the given PT_LOAD program HEADER.  Value is 1 upon failure,
+   else 0.  If USE_ALTERNATE, use the command code 17 instead of
+   1.
+
+   Apply the given OFFSET to virtual addresses that will be mapped.  */
+
+static int
+write_load_command (program_header *header, bool use_alternate,
+                   USER_WORD offset)
+{
+  struct exec_map_command command;
+  struct exec_map_command command1;
+  USER_WORD start, end;
+  bool need_command1;
+  static long pagesize;
+
+  /* First, write the commands necessary to map the specified segment
+     itself.
+
+     This is the area between header->p_vaddr and header->p_filesz,
+     rounded up to the page size.  */
+
+#ifndef PAGE_MASK
+  /* This system doesn't define a fixed page size.  */
+
+#ifdef HAVE_GETPAGESIZE
+  if (!pagesize)
+    pagesize = getpagesize ();
+#else /* HAVE_GETPAGESIZE */
+  if (!pagesize)
+    pagesize = sysconf (_SC_PAGESIZE);
+
+#define PAGE_MASK (~(pagesize - 1))
+#define PAGE_SIZE (pagesize)
+#endif /* HAVE_GETPAGESIZE */
+#endif /* PAGE_MASK */
+
+  start = header->p_vaddr & PAGE_MASK;
+  end = ((header->p_vaddr + header->p_filesz
+         + PAGE_SIZE)
+        & PAGE_MASK);
+
+  command.command = use_alternate ? 17 : 1;
+  command.vm_address = start;
+  command.file_offset = header->p_offset & PAGE_MASK;
+  command.protection = 0;
+  command.length = end - start;
+  command.clear = 0;
+  command.flags = MAP_PRIVATE | MAP_FIXED;
+
+  /* Apply the memory protection specified in the header.  */
+
+  if (header->p_flags & 4) /* PF_R */
+    command.protection |= PROT_READ;
+
+  if (header->p_flags & 2) /* PF_W */
+    command.protection |= PROT_WRITE;
+
+  if (header->p_flags & 1) /* PF_X */
+    command.protection |= PROT_EXEC;
+
+  /* Next, write any command necessary to map pages in the area
+     between p_filesz and p_memsz.  */
+  need_command1 = false;
+
+  if (header->p_memsz > header->p_filesz)
+    {
+      /* If there are bytes after end which need to be initialized, do
+        that now.  */
+      command.clear = end - header->p_vaddr - header->p_filesz;
+      start = end;
+      end = header->p_vaddr + header->p_memsz + PAGE_SIZE;
+      end &= PAGE_MASK;
+
+      if (end > start)
+       {
+         command1.command = 4;
+         command1.vm_address = start;
+         command1.file_offset = 0;
+         command1.length = end - start;
+         command1.clear = 0;
+         command1.protection = command.protection;
+         command1.flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED;
+         need_command1 = true;
+       }
+    }
+
+  /* Apply the offset to both commands if necessary.  */
+
+  if (offset)
+    {
+      if (need_command1)
+       command1.vm_address += offset;
+
+      command.vm_address += offset;
+    }
+
+  /* Write both commands.  */
+
+  if (sizeof loader_area - loader_area_used < sizeof command)
+    return 1;
+
+  memcpy (loader_area + loader_area_used, &command,
+         sizeof command);
+  loader_area_used += sizeof command;
+
+  if (!need_command1)
+    return 0;
+
+  if (sizeof loader_area - loader_area_used < sizeof command1)
+    return 1;
+
+  memcpy (loader_area + loader_area_used, &command1,
+         sizeof command1);
+  loader_area_used += sizeof command1;
+
+  return 0;
+}
+
+#if defined __mips__ && !defined MIPS_NABI
+
+/* Static storage used for MIPS ABI flags.  */
+static struct mips_elf_abi_flags exec_abi, interpreter_abi;
+
+/* Static storage for interpreter headers.  */
+static elf_header exec_interpreter_header;
+
+/* Pointer to the ELF header of this executable's interpreter.  */
+static elf_header *interpreter_header;
+
+/* Pointer to any PT_MIPS_ABIFLAGS program header found in the
+   executable itself.  */
+static struct mips_elf_abi_flags *exec_abiflags;
+
+/* Pointer to any PT_MIPS_ABIFLAGS program header found in the
+   executable's ELF interpreter.  */
+static struct mips_elf_abi_flags *interpreter_abiflags;
+
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+/* Process the specified program HEADER; HEADER is from the ELF
+   interpreter of another executable.  FD is the executable file from
+   which it is being read, NAME is its file name, and ELF_HEADER is
+   its header.
+
+   If ELF_HEADER->e_type is ET_DYN, add the base address for position
+   independent interpreter code to virtual addresses.
+
+   Value is 1 upon failure, else 0.  */
+
+static int
+process_interpreter_1 (const char *name, int fd,
+                      program_header *header,
+                      elf_header *elf_header)
+{
+  int rc;
+#if defined __mips__ && !defined MIPS_NABI
+  ssize_t rc1;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+  switch (header->p_type)
+    {
+    default: /* PT_NULL, PT_NOTE, PT_DYNAMIC, PT_INTERP, et cetera */
+      rc = 0;
+      break;
+
+    case 1: /* PT_LOAD */
+      /* This describes a segment in the file that must be loaded.
+        Write the appropriate load command.  */
+
+      if (elf_header->e_type == 3) /* ET_DYN */
+       rc = write_load_command (header, true,
+                                INTERPRETER_BASE);
+      else
+       rc = write_load_command (header, true, 0);
+
+      break;
+
+#if defined __mips__ && !defined MIPS_NABI
+    case 0x70000003: /* PT_MIPS_ABIFLAGS */
+      /* Record this header for later use.  */
+      rc1 = pread (fd, &interpreter_abi, sizeof interpreter_abi,
+                  header->p_offset);
+
+      if (rc1 != sizeof interpreter_abi)
+       return 1;
+
+      interpreter_abiflags = &interpreter_abi;
+      rc = 0;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+    }
+
+  return rc;
+}
+
+/* Read the ELF interpreter specified in the given program header from
+   FD, and append the commands necessary to load it to the load area.
+   Then, return the interpreter entry point in *ENTRY.
+
+   Value is 1 upon failure, else 0.  */
+
+static int
+process_interpreter (int fd, program_header *prog_header,
+                    USER_WORD *entry)
+{
+  char buffer[PATH_MAX + 1];
+  int rc, size, i;
+  elf_header header;
+  program_header program;
+
+  /* Read the interpreter name.  */
+  size = MIN (prog_header->p_filesz, PATH_MAX);
+  rc = pread (fd, buffer, size, prog_header->p_offset);
+  if (rc < size)
+    return 1;
+
+  /* Make sure the name is NULL terminated.  */
+  buffer[size] = '\0';
+
+  /* Check if the file is executable.  This is unfortunately not
+     atomic.  */
+
+  if (access (buffer, X_OK))
+    return 1;
+
+  /* Read the interpreter's header much like exec_0.
+
+     However, use special command codes in `process_program_header' if
+     it is position independent.  That way, the loader knows it should
+     use the open interpreter instead.  */
+
+  fd = open (buffer, O_RDONLY);
+
+  if (fd < 0)
+    return 1;
+
+  rc = read (fd, &header, sizeof header);
+
+  if (rc < sizeof header)
+    goto fail;
+
+#if defined __mips__ && !defined MIPS_NABI
+  /* Record this interpreter's header for later use determining the
+     floating point ABI.  */
+  exec_interpreter_header = header;
+  interpreter_header = &exec_interpreter_header;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+  /* Verify that this is indeed an ELF file.  */
+
+  if (header.e_ident[0] != 0x7f
+      || header.e_ident[1] != 'E'
+      || header.e_ident[2] != 'L'
+      || header.e_ident[3] != 'F')
+    goto fail;
+
+  /* Now check that the class is correct.  */
+#ifdef EXEC_64
+  if (header.e_ident[4] != 2)
+    goto fail;
+#else /* !EXEC_64 */
+  if (header.e_ident[4] != 1)
+    goto fail;
+#endif /* EXEC_64 */
+
+  /* And the endianness.  */
+#ifndef WORDS_BIGENDIAN
+  if (header.e_ident[5] != 1)
+    goto fail;
+#else /* WORDS_BIGENDIAN */
+  if (header.e_ident[5] != 2)
+    goto fail;
+#endif /* EXEC_64 */
+
+  /* Check that this is an executable.  */
+  if (header.e_type != 2 && header.e_type != 3)
+    goto fail;
+
+  /* Now check that the ELF program header makes sense.  */
+  if (header.e_phnum > 0xffff
+      || (header.e_phentsize
+         != sizeof (program_header)))
+    goto fail;
+
+  if (write_open_command (buffer, true))
+    goto fail;
+
+  for (i = 0; i < header.e_phnum; ++i)
+    {
+      rc = read (fd, &program, sizeof program);
+      if (rc < sizeof program)
+       goto fail;
+
+      if (process_interpreter_1 (buffer, fd, &program,
+                                &header))
+       goto fail;
+    }
+
+  if (header.e_type == 3) /* ET_DYN */
+    *entry = header.e_entry + INTERPRETER_BASE;
+  else
+    *entry = header.e_entry;
+
+  close (fd);
+  return 0;
+
+ fail:
+  close (fd);
+  return 1;
+}
+
+/* Process the specified program HEADER.  FD is the executable file
+   from which it is being read, NAME is its file name, and ELF_HEADER
+   is its header.
+
+   If ELF_HEADER->e_type is ET_DYN, add the base address for position
+   independent code to virtual addresses.
+
+   If OFFSET is non-NULL, and *OFFSET is -1, write the virtual address
+   of HEADER if it describes a PT_LOAD segment.
+
+   If an interpreter is found, set *ENTRY to its entry point.
+
+   Value is 1 upon failure, else 0.  */
+
+static int
+process_program_header (const char *name, int fd,
+                       program_header *header,
+                       elf_header *elf_header,
+                       USER_WORD *entry,
+                       USER_WORD *offset)
+{
+  int rc;
+#if defined __mips__ && !defined MIPS_NABI
+  ssize_t rc1;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+  switch (header->p_type)
+    {
+    default: /* PT_NULL, PT_NOTE, PT_DYNAMIC, et cetera */
+      rc = 0;
+      break;
+
+    case 1: /* PT_LOAD */
+      /* This describes a segment in the file that must be loaded.
+        Write the appropriate load command.  */
+
+      if (elf_header->e_type == 3) /* ET_DYN */
+       {
+         rc = write_load_command (header, false,
+                                  EXECUTABLE_BASE);
+
+         if (!rc && offset && *offset == (USER_WORD) -1)
+           *offset = EXECUTABLE_BASE + header->p_vaddr;
+       }
+      else
+       {
+         rc = write_load_command (header, false, 0);
+
+         if (!rc && offset && *offset == (USER_WORD) -1)
+           *offset = header->p_vaddr;
+       }
+
+      break;
+
+    case 3: /* PT_INTERP */
+      /* This describes another executable that must be loaded.  Open
+        the interpreter and process each of its headers as well.  */
+      rc = process_interpreter (fd, header, entry);
+      break;
+
+    case 1685382481: /* PT_GNU_STACK */
+      /* TODO */
+      rc = 0;
+      break;
+
+#if defined __mips__ && !defined MIPS_NABI
+    case 0x70000003: /* PT_MIPS_ABIFLAGS */
+      /* Record this header for later use.  */
+      rc1 = pread (fd, &exec_abi, sizeof exec_abi,
+                  header->p_offset);
+
+      if (rc1 != sizeof exec_abi)
+       return 1;
+
+      exec_abiflags = &exec_abi;
+      rc = 0;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+    }
+
+  return rc;
+}
+
+/* Prepend one or two extra arguments ARG1 and ARG2 to a pending
+   execve system call.  Replace the argument immediately after
+   with ARG3.
+
+   TRACEE is the tracee performing the system call, and REGS are its
+   current user registers.  Value is 1 upon failure, else 0.  */
+
+static int
+insert_args (struct exec_tracee *tracee, USER_REGS_STRUCT *regs,
+            const char *arg1, const char *arg2, const char *arg3)
+{
+  USER_WORD argv, argc, word, new;
+  USER_WORD new1, new2, new3, i;
+  size_t text_size, effective_size;
+  USER_REGS_STRUCT original;
+
+  /* First, get a pointer to the current argument vector.  */
+  argv = regs->SYSCALL_ARG1_REG;
+
+  /* Now figure out how many arguments there are.  */
+  argc = 0;
+  while (true)
+    {
+      /* Clear errno.  PTRACE_PEEKDATA returns the word read the same
+        way failure indications are returned, so the only way to
+        catch IO errors is by clearing errno before the call to
+        ptrace and checking it afterwards.  */
+
+      errno = 0;
+      word = ptrace (PTRACE_PEEKDATA, tracee->pid,
+                    (void *) argv, NULL);
+      argv += sizeof (USER_WORD);
+
+      if (errno)
+       return 1;
+
+      if (!word)
+       break;
+
+      ++argc;
+    };
+
+  /* Allocate enough to hold that many arguments, alongside the argc
+     text.  */
+
+  text_size = (strlen (arg1) + 1
+              + (arg2 ? strlen (arg2) + 1 : 0)
+              + strlen (arg3) + 1);
+
+  /* Round it up to the user word size.  */
+  text_size += sizeof (USER_WORD) - 1;
+  text_size &= ~(sizeof (USER_WORD) - 1);
+
+  /* Now allocate the new argv.  Make sure argc is at least 1; it
+     needs to hold ARG3.  */
+
+  effective_size = sizeof word * (MAX (1, argc) + 2) + text_size;
+
+  if (arg2)
+    effective_size += sizeof word;
+
+  /* Copy regs to original so that user_alloca knows it should append
+     the ABI red zone.  */
+
+  memcpy (&original, regs, sizeof *regs);
+  new = user_alloca (tracee, &original, regs,
+                    effective_size);
+
+  if (!new)
+    goto fail;
+
+  /* Figure out where argv starts.  */
+
+  new3 = new + text_size;
+
+  /* Now write the first two strings.  */
+
+  new1 = new + strlen (arg1) + 1;
+  new2 = new1 + (arg2 ? strlen (arg2) + 1 : 0);
+
+  if (user_copy (tracee, (const unsigned char *) arg1,
+                new, new1 - new))
+    goto fail;
+
+  if (arg2 && user_copy (tracee, (const unsigned char *) arg2,
+                        new1, new2 - new1))
+    goto fail;
+
+  /* Write the replacement arg3, the file name of the executable.  */
+
+  if (user_copy (tracee, (const unsigned char *) arg3,
+                new2, new3 - new2))
+    goto fail;
+
+  /* Start copying argv back to new2.  First, write the one or two new
+     arguments.  */
+
+  if (ptrace (PTRACE_POKETEXT, tracee->pid,
+             (void *) new3, (void *) new))
+    goto fail;
+
+  new3 += sizeof new3;
+
+  if (arg2 && ptrace (PTRACE_POKETEXT, tracee->pid,
+                     (void *) new3, (void *) new1))
+    goto fail;
+  else if (arg2)
+    new3 += sizeof new3;
+
+  /* Next, write the third argument.  */
+
+  if (ptrace (PTRACE_POKETEXT, tracee->pid, (void *) new3,
+             (void *) new2))
+    goto fail;
+
+  new3 += sizeof new3;
+
+  /* Copy the remaining arguments back.  */
+
+  argv = regs->SYSCALL_ARG1_REG;
+
+  if (argc)
+    {
+      /* Make sure the trailing NULL is included.  */
+      argc += 1;
+
+      /* Now copy each argument in argv, starting from argv[1].  */
+
+      for (i = 1; i < argc; ++i)
+       {
+         /* Read one argument.  */
+         word = ptrace (PTRACE_PEEKDATA, tracee->pid,
+                        (void *) (argv + i * sizeof argv), NULL);
+
+         /* Write one argument, then increment new3.  */
+
+         if (ptrace (PTRACE_POKETEXT, tracee->pid,
+                     (void *) new3, (void *) word))
+           goto fail;
+
+         new3 += sizeof new3;
+       }
+    }
+  else
+    {
+      /* Just write the trailing NULL.  */
+
+      if (ptrace (PTRACE_POKETEXT, tracee->pid,
+                 (void *) new3, (void *) 0))
+       goto fail;
+
+      new3 += sizeof new3;
+    }
+
+  /* Assert that new3 is not out of bounds.  */
+  assert (new3 == new + effective_size);
+
+  /* And that it is properly aligned.  */
+  assert (!(new3 & (sizeof new3 - 2)));
+
+  /* Now modify the system call argument to point to new +
+     text_size.  */
+
+  regs->SYSCALL_ARG1_REG = new + text_size;
+
+#ifdef __aarch64__
+  if (aarch64_set_regs (tracee->pid, regs, false))
+    goto fail;
+#else /* !__aarch64__ */
+  if (ptrace (PTRACE_SETREGS, tracee->pid, NULL, regs))
+    goto fail;
+#endif /* __aarch64__ */
+
+  /* Success.  */
+
+  return 0;
+
+ fail:
+  /* Restore the original stack pointer.  */
+#ifdef __aarch64__
+  aarch64_set_regs (tracee->pid, &original, false);
+#else /* !__aarch64__ */
+  ptrace (PTRACE_SETREGS, tracee->pid, NULL, &original);
+#endif /* __aarch64__ */
+  errno = ENOMEM;
+  return 1;
+}
+
+
+
+/* Format PID, an unsigned process identifier, in base 10.  Place the
+   result in *IN, and return a pointer to the byte after the
+   result.  REM should be NULL.  */
+
+static char *
+format_pid (char *in, unsigned int pid)
+{
+  unsigned int digits[32], *fill;
+
+  fill = digits;
+
+  for (; pid != 0; pid = pid / 10)
+    *fill++ = pid % 10;
+
+  /* Insert 0 if the number would otherwise be empty.  */
+
+  if (fill == digits)
+    *fill++ = 0;
+
+  while (fill != digits)
+    {
+      --fill;
+      *in++ = '0' + *fill;
+    }
+
+  *in = '\0';
+  return in;
+}
+
+/* Return a sequence of actions required to load the executable under
+   the file NAME for the given TRACEE.  First, see if the file starts
+   with #!; in that case, find the program to open and use that
+   instead.
+
+   If REENTRANT is not defined, NAME is actually a buffer of size
+   PATH_MAX + 80.  In that case, copy over the file name actually
+   opened.
+
+   Next, read the executable header, and add the necessary memory
+   mappings for each file.  Finally, return the action data and its
+   size in *SIZE.
+
+   Finally, use REGS to add the required interpreter arguments to the
+   caller's argv.
+
+   Value is NULL upon failure, with errno set accordingly.  */
+
+char *
+exec_0 (char *name, struct exec_tracee *tracee,
+       size_t *size, USER_REGS_STRUCT *regs)
+{
+  int fd, rc, i;
+  elf_header header;
+  const char *interpreter_name, *extra;
+  program_header program;
+  USER_WORD entry, program_entry, offset;
+  USER_WORD header_offset;
+  struct exec_jump_command jump;
+#if defined __mips__ && !defined MIPS_NABI
+  int fpu_mode;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+  char buffer[80], buffer1[PATH_MAX + 80], *rewrite;
+  ssize_t link_size;
+  size_t remaining;
+
+  /* If the process is trying to run /proc/self/exe, make it run
+     itself instead.  */
+
+  if (!strcmp (name, "/proc/self/exe") && tracee->exec_file)
+    {
+      strncpy (name, tracee->exec_file, PATH_MAX - 1);
+      name[PATH_MAX] = '\0';
+    }
+  else
+    {
+      /* If name is not absolute, then make it relative to TRACEE's
+        cwd.  Do not use sprintf at it is not reentrant and it
+        mishandles results longer than INT_MAX.  */
+
+      if (name[0] && name[0] != '/')
+       {
+         /* Clear both buffers.  */
+         memset (buffer, 0, sizeof buffer);
+         memset (buffer1, 0, sizeof buffer1);
+
+         /* Copy over /proc, the PID, and /cwd/.  */
+         rewrite = stpcpy (buffer, "/proc/");
+         rewrite = format_pid (rewrite, tracee->pid);
+         strcpy (rewrite, "/cwd");
+
+         /* Resolve this symbolic link.  */
+
+         link_size = readlink (buffer, buffer1,
+                               PATH_MAX + 1);
+
+         if (link_size < 0)
+           return NULL;
+
+         /* Check that the name is a reasonable size.  */
+
+         if (link_size > PATH_MAX)
+           {
+             /* The name is too long.  */
+             errno = ENAMETOOLONG;
+             return NULL;
+           }
+
+         /* Add a directory separator if necessary.  */
+
+         if (!link_size || buffer1[link_size - 1] != '/')
+           buffer1[link_size] = '/', link_size++;
+
+         rewrite = buffer1 + link_size;
+         remaining = buffer1 + sizeof buffer1 - rewrite - 1;
+         memcpy (rewrite, name, strnlen (name, remaining));
+
+         /* Replace name with buffer1.  */
+#ifndef REENTRANT
+         strcpy (name, buffer1);
+#endif /* REENTRANT */
+       }
+    }
+
+  /* Check that the file is accessible and executable.  */
+
+  if (access (name, X_OK))
+    return NULL;
+
+  fd = open (name, O_RDONLY);
+  if (fd < 0)
+    return NULL;
+
+  /* Now read the header.  */
+
+  extra = NULL;
+  interpreter_name = check_interpreter (name, fd, &extra);
+  if (!interpreter_name)
+    goto fail;
+
+  /* Open the interpreter instead, if necessary.  */
+  if (interpreter_name != name)
+    {
+      close (fd);
+      fd = open (interpreter_name, O_RDONLY);
+      if (fd < 0)
+       return NULL;
+
+      /* Now, rewrite the argument list to include `interpreter_name'
+        and perhaps `extra'.  */
+
+      if (insert_args (tracee, regs, interpreter_name,
+                      extra, name))
+       goto fail1;
+    }
+
+  rc = read (fd, &header, sizeof header);
+
+  if (rc < sizeof header)
+    goto fail1;
+
+  /* Verify that this is indeed an ELF file.  */
+
+  if (header.e_ident[0] != 0x7f
+      || header.e_ident[1] != 'E'
+      || header.e_ident[2] != 'L'
+      || header.e_ident[3] != 'F')
+    goto fail1;
+
+  /* Now check that the class is correct.  */
+#ifdef EXEC_64
+  if (header.e_ident[4] != 2)
+    goto fail1;
+#else /* !EXEC_64 */
+  if (header.e_ident[4] != 1)
+    goto fail1;
+#endif /* EXEC_64 */
+
+  /* And the endianness.  */
+#ifndef WORDS_BIGENDIAN
+  if (header.e_ident[5] != 1)
+    goto fail1;
+#else /* WORDS_BIGENDIAN */
+  if (header.e_ident[5] != 2)
+    goto fail1;
+#endif /* EXEC_64 */
+
+  /* Check that this is an executable.  */
+  if (header.e_type != 2 && header.e_type != 3)
+    goto fail1;
+
+  /* Now check that the ELF program header makes sense.  */
+  if (header.e_phnum > 0xffff
+      || (header.e_phentsize
+         != sizeof (program_header)))
+    goto fail1;
+
+  /* Seek to the first program header and read each one.  */
+  rc = lseek (fd, header.e_phoff, SEEK_SET);
+  if (rc < 0)
+    goto fail1;
+  loader_area_used = 0;
+
+  /* Write the command used to open the executable.  */
+  if (write_open_command (interpreter_name, false))
+    goto fail1;
+
+  /* Apply base addresses for PIC code.  */
+
+  if (header.e_type == 3) /* ET_DYN */
+    offset = EXECUTABLE_BASE;
+  else
+    offset = 0;
+
+  /* entry and program_entry are initially the same, but entry may be
+     set to that of the interpreter if one is present.  */
+
+  entry = header.e_entry + offset;
+  program_entry = header.e_entry;
+
+#if defined __mips__ && !defined MIPS_NABI
+  /* Clear MIPS ABI flags.  */
+  exec_abiflags = NULL;
+  interpreter_abiflags = NULL;
+  interpreter_header = NULL;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+  /* Set header_offset to -1; `process_program_header' then updates it
+     to that of the first mapping.  */
+  header_offset = -1;
+
+  for (i = 0; i < header.e_phnum; ++i)
+    {
+      rc = read (fd, &program, sizeof program);
+      if (rc < sizeof program)
+       goto fail1;
+
+      if (process_program_header (interpreter_name, fd,
+                                 &program, &header,
+                                 &entry, &header_offset))
+       goto fail1;
+    }
+
+  /* Write the entry point and program entry.  */
+
+  jump.command = 3;
+  jump.entry = entry;
+
+  /* Now calculate values for the aux vector.  */
+
+  jump.at_entry = program_entry + offset;
+  jump.at_phent = header.e_phentsize;
+  jump.at_phnum = header.e_phnum;
+  jump.at_base = (entry == header.e_entry + offset
+                 ? EXECUTABLE_BASE
+                 : INTERPRETER_BASE);
+
+#if defined __mips__ && !defined MIPS_NABI
+  /* Finally, calculate the FPU mode wanted by the executable.  */
+
+  if (determine_fpu_mode (&header, interpreter_header,
+                         &fpu_mode, exec_abiflags,
+                         interpreter_abiflags))
+    /* N.B. that `determine_fpu_mode' sets errno.  */
+    goto fail;
+
+  /* If the processor is too new to support FR0 operation, place the
+     executable in floating point emulation mode.  */
+
+  if (fpu_mode == FP_FR0 && !cpu_supports_fr0_p ())
+    fpu_mode = FP_FRE;
+
+  jump.fpu_mode = fpu_mode;
+#endif /* defined __mips__ && !defined MIPS_NABI */
+
+  /* The offset used for at_phdr should be that of the first
+     mapping.  */
+
+  if (header_offset == (USER_WORD) -1)
+    header_offset = 0;
+
+  jump.at_phdr = header.e_phoff + header_offset;
+
+  if (sizeof loader_area - loader_area_used < sizeof jump)
+    goto fail1;
+
+  memcpy (loader_area + loader_area_used, &jump,
+         sizeof jump);
+  loader_area_used += sizeof jump;
+
+  /* Close the file descriptor and return the number of bytes
+     used.  */
+
+  close (fd);
+  *size = loader_area_used;
+
+  /* Make sure the loader area is properly aligned.  */
+  assert (!(loader_area_used & (sizeof (USER_WORD) - 1)));
+  return loader_area;
+
+ fail1:
+  errno = ENOEXEC;
+ fail:
+  close (fd);
+  return NULL;
+}
diff --git a/exec/exec.h b/exec/exec.h
new file mode 100644
index 00000000000..8ee74d7ca8b
--- /dev/null
+++ b/exec/exec.h
@@ -0,0 +1,201 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+
+
+#ifndef _EXEC_H_
+#define _EXEC_H_
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif /* HAVE_STDINT_H */
+
+#include <sys/types.h>
+
+#include USER_HEADER
+
+/* Define a replacement for `uint64_t' if it's not present in the C
+   library.  */
+
+#ifndef UINT64_MAX
+
+typedef struct
+{
+  uint32_t word1;
+  uint32_t word2;
+} xint64_t;
+
+#else /* UINT64_MAX */
+typedef uint64_t xint64_t;
+#endif /* !UINT64_MAX */
+
+
+
+/* 32-bit ELF headers.  */
+
+struct elf_header_32
+{
+  unsigned char e_ident[16];
+  uint16_t      e_type;
+  uint16_t      e_machine;
+  uint32_t      e_version;
+  uint32_t      e_entry;
+  uint32_t      e_phoff;
+  uint32_t      e_shoff;
+  uint32_t      e_flags;
+  uint16_t      e_ehsize;
+  uint16_t      e_phentsize;
+  uint16_t      e_phnum;
+  uint16_t      e_shentsize;
+  uint16_t      e_shnum;
+  uint16_t      e_shstrndx;
+};
+
+struct program_header_32
+{
+  uint32_t p_type;
+  uint32_t p_offset;
+  uint32_t p_vaddr;
+  uint32_t p_paddr;
+  uint32_t p_filesz;
+  uint32_t p_memsz;
+  uint32_t p_flags;
+  uint32_t p_align;
+};
+
+struct dt_entry_32
+{
+  uint32_t d_tag;
+  uint32_t d_val;
+};
+
+
+
+struct elf_header_64
+{
+  unsigned char e_ident[16];
+  uint16_t      e_type;
+  uint16_t      e_machine;
+  uint32_t      e_version;
+  xint64_t      e_entry;
+  xint64_t      e_phoff;
+  xint64_t      e_shoff;
+  uint32_t      e_flags;
+  uint16_t      e_ehsize;
+  uint16_t      e_phentsize;
+  uint16_t      e_phnum;
+  uint16_t      e_shentsize;
+  uint16_t      e_shnum;
+  uint16_t      e_shstrndx;
+};
+
+struct program_header_64
+{
+  uint32_t p_type;
+  uint32_t p_flags;
+  xint64_t p_offset;
+  xint64_t p_vaddr;
+  xint64_t p_paddr;
+  xint64_t p_filesz;
+  xint64_t p_memsz;
+  xint64_t p_align;
+};
+
+struct dt_entry_64
+{
+  xint64_t d_tag;
+  xint64_t d_val;
+};
+
+
+
+/* Define some types to the correct values.  */
+
+#ifdef EXEC_64
+typedef struct elf_header_64 elf_header;
+typedef struct program_header_64 program_header;
+typedef struct dt_entry_64 dt_entry;
+#else /* !EXEC_64 */
+typedef struct elf_header_32 elf_header;
+typedef struct program_header_32 program_header;
+typedef struct dt_entry_32 dt_entry;
+#endif /* EXEC_64 */
+
+
+
+/* Defined in trace.c.  */
+
+/* Structure describing a process being traced.  */
+
+struct exec_tracee
+{
+  /* The next process being traced.  */
+  struct exec_tracee *next;
+
+  /* The thread ID of this process.  */
+  pid_t pid;
+
+  /* Whether or not the tracee is currently waiting for a system call
+     to complete.  */
+  bool waiting_for_syscall : 1;
+
+  /* Whether or not the tracee has been created but is not yet
+     processed by `handle_clone'.  */
+  bool new_child : 1;
+
+#ifndef REENTRANT
+  /* Name of the executable being run.  */
+  char *exec_file;
+#endif /* !REENTRANT */
+};
+
+
+
+#ifdef __aarch64__
+
+extern int aarch64_get_regs (pid_t, USER_REGS_STRUCT *);
+extern int aarch64_set_regs (pid_t, USER_REGS_STRUCT *, bool);
+
+#endif /* __aarch64__ */
+
+
+
+extern USER_WORD user_alloca (struct exec_tracee *, USER_REGS_STRUCT *,
+                             USER_REGS_STRUCT *, USER_WORD);
+extern int user_copy (struct exec_tracee *, const unsigned char *,
+                     USER_WORD, USER_WORD);
+extern void exec_init (const char *);
+
+
+
+extern int tracing_execve (const char *, char *const *,
+                          char *const *);
+extern int after_fork (pid_t);
+extern pid_t exec_waitpid (pid_t, int *, int);
+
+
+
+/* Defined in exec.c.  */
+
+extern char *exec_0 (char *, struct exec_tracee *,
+                    size_t *, USER_REGS_STRUCT *);
+
+
+
+#endif /* _EXEC_H_ */
diff --git a/exec/exec1.c b/exec/exec1.c
new file mode 100644
index 00000000000..d77ca8adf54
--- /dev/null
+++ b/exec/exec1.c
@@ -0,0 +1,94 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+#include "exec.h"
+
+/* exec1 is a program which takes another program and its arguments,
+   forks, and executes that program, all while tracing it and its
+   children to use the program execution mechanism defined in exec.c.
+
+   This is necessary to bypass security restrictions which prohibit
+   Emacs from loading executables from certain directories, by, in
+   effect, replacing the executable loader in the Linux kernel.  */
+
+
+
+int
+main (int argc, char **argv)
+{
+  pid_t pid, pid1;
+  extern char **environ;
+  int wstatus;
+
+  pid1 = getpid ();
+  pid = fork ();
+
+  if (!pid)
+    {
+      /* Set the process group used to the parent.  */
+      if (setpgid (0, pid1))
+       perror ("setpgid");
+
+      tracing_execve (argv[2], argv + 2, environ);
+
+      /* An error occured.  Exit with failure.  */
+      exit (127);
+    }
+  else
+    {
+      /* Provide the file name of the loader.  */
+      exec_init (argv[1]);
+
+      if (after_fork (pid))
+       exit (127);
+
+      /* Start waiting for the process to exit.  */
+
+      while (true)
+       {
+         pid1 = exec_waitpid (-1, &wstatus, 0);
+
+         /* If the child process exits normally, exit with its status
+            code.  If not, raise the signal that caused it to
+            exit.  */
+
+         if (pid == pid1)
+           {
+             if (WIFEXITED (wstatus))
+               exit (WEXITSTATUS (wstatus));
+             else /* if WIFSIGNALED (wstatus) */
+               {
+                 raise (WTERMSIG (wstatus));
+
+                 /* Just in case the signal raised doesn't cause an
+                    exit.  */
+                 exit (127);
+               }
+           }
+
+         /* Otherwise, continue looping.  */
+       }
+    }
+}
diff --git a/exec/install-sh b/exec/install-sh
new file mode 100755
index 00000000000..e046efdf0a3
--- /dev/null
+++ b/exec/install-sh
@@ -0,0 +1,541 @@
+#!/usr/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2020-11-14.01; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+tab='  '
+nl='
+'
+IFS=" $tab$nl"
+
+# Set DOITPROG to "echo" to test this script.
+
+doit=${DOITPROG-}
+doit_exec=${doit:-exec}
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+# Create dirs (including intermediate dirs) using mode 755.
+# This is like GNU 'install' as of coreutils 8.32 (2020).
+mkdir_umask=22
+
+backupsuffix=
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+is_target_a_directory=possibly
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
+
+  -c            (ignored)
+  -C            install only if different (preserve data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -p            pass -p to $cpprog.
+  -s            $stripprog installed files.
+  -S SUFFIX     attempt to back up existing files, with suffix SUFFIX.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+
+By default, rm is invoked with -f; when overridden with RMPROG,
+it's up to you to specify -f if you want it.
+
+If -S is not specified, no backups are attempted.
+
+Email bug reports to bug-automake@gnu.org.
+Automake home page: https://www.gnu.org/software/automake/
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
+
+    -C) copy_on_change=true;;
+
+    -d) dir_arg=true;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+        shift;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+        case $mode in
+          *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
+            echo "$0: invalid mode: $mode" >&2
+            exit 1;;
+        esac
+        shift;;
+
+    -o) chowncmd="$chownprog $2"
+        shift;;
+
+    -p) cpprog="$cpprog -p";;
+
+    -s) stripcmd=$stripprog;;
+
+    -S) backupsuffix="$2"
+        shift;;
+
+    -t)
+        is_target_a_directory=always
+        dst_arg=$2
+        # Protect names problematic for 'test' and other utilities.
+        case $dst_arg in
+          -* | [=\(\)!]) dst_arg=./$dst_arg;;
+        esac
+        shift;;
+
+    -T) is_target_a_directory=never;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --) shift
+        break;;
+
+    -*) echo "$0: invalid option: $1" >&2
+        exit 1;;
+
+    *)  break;;
+  esac
+  shift
+done
+
+# We allow the use of options -d and -T together, by making -d
+# take the precedence; this is for compatibility with GNU install.
+
+if test -n "$dir_arg"; then
+  if test -n "$dst_arg"; then
+    echo "$0: target directory not allowed when installing a directory." >&2
+    exit 1
+  fi
+fi
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+    # Protect names problematic for 'test' and other utilities.
+    case $dst_arg in
+      -* | [=\(\)!]) dst_arg=./$dst_arg;;
+    esac
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call 'install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  if test $# -gt 1 || test "$is_target_a_directory" = always; then
+    if test ! -d "$dst_arg"; then
+      echo "$0: $dst_arg: Is not a directory." >&2
+      exit 1
+    fi
+  fi
+fi
+
+if test -z "$dir_arg"; then
+  do_exit='(exit $ret); exit $ret'
+  trap "ret=129; $do_exit" 1
+  trap "ret=130; $do_exit" 2
+  trap "ret=141; $do_exit" 13
+  trap "ret=143; $do_exit" 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+        u_plus_rw=
+      else
+        u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+        u_plus_rw=
+      else
+        u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names problematic for 'test' and other utilities.
+  case $src in
+    -* | [=\(\)!]) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+    # Don't chown directories that already exist.
+    if test $dstdir_status = 0; then
+      chowncmd=""
+    fi
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+    dst=$dst_arg
+
+    # If destination is a directory, append the input filename.
+    if test -d "$dst"; then
+      if test "$is_target_a_directory" = never; then
+        echo "$0: $dst_arg: Is a directory" >&2
+        exit 1
+      fi
+      dstdir=$dst
+      dstbase=`basename "$src"`
+      case $dst in
+       */) dst=$dst$dstbase;;
+       *)  dst=$dst/$dstbase;;
+      esac
+      dstdir_status=0
+    else
+      dstdir=`dirname "$dst"`
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  case $dstdir in
+    */) dstdirslash=$dstdir;;
+    *)  dstdirslash=$dstdir/;;
+  esac
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+        # With -d, create the new directory with the user-specified mode.
+        # Otherwise, rely on $mkdir_umask.
+        if test -n "$dir_arg"; then
+          mkdir_mode=-m$mode
+        else
+          mkdir_mode=
+        fi
+
+        posix_mkdir=false
+       # The $RANDOM variable is not portable (e.g., dash).  Use it
+       # here however when possible just to lower collision chance.
+       tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+
+       trap '
+         ret=$?
+         rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null
+         exit $ret
+       ' 0
+
+       # Because "mkdir -p" follows existing symlinks and we likely work
+       # directly in world-writeable /tmp, make sure that the '$tmpdir'
+       # directory is successfully created first before we actually test
+       # 'mkdir -p'.
+       if (umask $mkdir_umask &&
+           $mkdirprog $mkdir_mode "$tmpdir" &&
+           exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
+       then
+         if test -z "$dir_arg" || {
+              # Check for POSIX incompatibilities with -m.
+              # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+              # other-writable bit of parent directory when it shouldn't.
+              # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+              test_tmpdir="$tmpdir/a"
+              ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
+              case $ls_ld_tmpdir in
+                d????-?r-*) different_mode=700;;
+                d????-?--*) different_mode=755;;
+                *) false;;
+              esac &&
+              $mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
+                ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
+                test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+              }
+            }
+         then posix_mkdir=:
+         fi
+         rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
+       else
+         # Remove any dirs left behind by ancient mkdir implementations.
+         rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
+       fi
+       trap '' 0;;
+    esac
+
+    if
+      $posix_mkdir && (
+        umask $mkdir_umask &&
+        $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+        /*) prefix='/';;
+        [-=\(\)!]*) prefix='./';;
+        *)  prefix='';;
+      esac
+
+      oIFS=$IFS
+      IFS=/
+      set -f
+      set fnord $dstdir
+      shift
+      set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+        test X"$d" = X && continue
+
+        prefix=$prefix$d
+        if test -d "$prefix"; then
+          prefixes=
+        else
+          if $posix_mkdir; then
+            (umask $mkdir_umask &&
+             $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+            # Don't fail if two instances are running concurrently.
+            test -d "$prefix" || exit 1
+          else
+            case $prefix in
+              *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+              *) qprefix=$prefix;;
+            esac
+            prefixes="$prefixes '$qprefix'"
+          fi
+        fi
+        prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+        # Don't fail if two instances are running concurrently.
+        (umask $mkdir_umask &&
+         eval "\$doit_exec \$mkdirprog $prefixes") ||
+          test -d "$dstdir" || exit 1
+        obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=${dstdirslash}_inst.$$_
+    rmtmp=${dstdirslash}_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask &&
+     { test -z "$stripcmd" || {
+        # Create $dsttmp read-write so that cp doesn't create it read-only,
+        # which would cause strip to fail.
+        if test -z "$doit"; then
+          : >"$dsttmp" # No need to fork-exec 'touch'.
+        else
+          $doit touch "$dsttmp"
+        fi
+       }
+     } &&
+     $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"     2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp"  2>/dev/null` &&
+       set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       set +f &&
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # If $backupsuffix is set, and the file being installed
+      # already exists, attempt a backup.  Don't worry if it fails,
+      # e.g., if mv doesn't support -f.
+      if test -n "$backupsuffix" && test -f "$dst"; then
+        $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null
+      fi
+
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+        # Now remove or move aside any old file at destination location.
+        # We try this two ways since rm can't unlink itself on some
+        # systems and the destination file might be busy for other
+        # reasons.  In this case, the final cleanup might fail but the new
+        # file should still install successfully.
+        {
+          test ! -f "$dst" ||
+          $doit $rmcmd "$dst" 2>/dev/null ||
+          { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+            { $doit $rmcmd "$rmtmp" 2>/dev/null; :; }
+          } ||
+          { echo "$0: cannot unlink or rename $dst" >&2
+            (exit 1); exit 1
+          }
+        } &&
+
+        # Now rename the file to the real destination.
+        $doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/exec/loader-aarch64.s b/exec/loader-aarch64.s
new file mode 100644
index 00000000000..da8ec1f4977
--- /dev/null
+++ b/exec/loader-aarch64.s
@@ -0,0 +1,187 @@
+// Copyright (C) 2023 Free Software Foundation, Inc.
+//
+// This file is part of GNU Emacs.
+//
+// GNU Emacs is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// GNU Emacs is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+// Notice that aarch64 requires that sp be aligned to 16 bytes while
+// accessing memory from sp, so x20 is used to chase down the load
+// area.
+
+       .section .text
+       .global _start
+_start:
+       //mov   x8, 101                 // SYS_nanosleep
+       //adr   x0, timespec            // req
+       //mov   x1, #0                  // rem
+       //svc   #0                      // syscall
+       mov     x20, sp                 // x20 = sp
+       ldr     x10, [x20]              // x10 = original SP
+       add     x20, x20, #16           // x20 = start of load area
+       mov     x28, #-1                // x28 = secondary fd
+.next_action:
+       ldr     x11, [x20]              // action number
+       and     x12, x11, #-17          // actual action number
+       cbz     x12, .open_file         // open file?
+       cmp     x12, #3                 // jump?
+       beq     .rest_of_exec
+       cmp     x12, #4                 // anonymous mmap?
+       beq     .do_mmap_anon
+.do_mmap:
+       ldr     x0, [x20, 8]            // vm_address
+       ldr     x1, [x20, 32]           // length
+       ldr     x2, [x20, 24]           // protection
+       ldr     x3, [x20, 40]           // flags
+       tst     x11, #16                // primary fd?
+       mov     x4, x29                 // primary fd
+       beq     .do_mmap_1
+       mov     x4, x28                 // secondary fd
+.do_mmap_1:
+       mov     x8, #222                // SYS_mmap
+       ldr     x5, [x20, 16]           // file_offset
+       svc     #0                      // syscall
+       ldr     x9, [x20, 8]            // length
+       cmp     x0, x9                  // mmap result
+       bne     .perror                 // print error
+       ldr     x3, [x20, 48]           // clear
+       add     x1, x1, x0              // x1 = vm_address + end
+       sub     x3, x1, x3              // x3 = x1 - clear
+       mov     x0, #0                  // x0 = 0
+.fill64:
+       sub     x2, x1, x3              // x2 = x1 - x3
+       cmp     x2, #63                 // x2 >= 64?
+       ble     .fillb                  // start filling bytes
+       stp     x0, x0, [x3]            // x3[0] = 0, x3[1] = 0
+       stp     x0, x0, [x3, 16]        // x3[2] = 0, x3[3] = 0
+       stp     x0, x0, [x3, 32]        // x3[4] = 0, x3[5] = 0
+       stp     x0, x0, [x3, 48]        // x3[6] = 0, x3[7] = 0
+       add     x3, x3, #64             // x3 += 8
+       b       .fill64
+.fillb:
+       cmp     x1, x3                  // x1 == x3?
+       beq     .continue               // done
+       strb    w0, [x3], #1            // ((char *) x3)++ = 0
+       b       .fillb
+.continue:
+       add     x20, x20, #56           // next action
+       b       .next_action
+.do_mmap_anon:
+       ldr     x0, [x20, 8]            // vm_address
+       ldr     x1, [x20, 32]           // length
+       ldr     x2, [x20, 24]           // protection
+       ldr     x3, [x20, 40]           // flags
+       mov     x4, #-1                 // fd
+       b       .do_mmap_1
+.open_file:
+       mov     x8, #56                 // SYS_openat
+       mov     x0, #-100               // AT_FDCWD
+       add     x1, x20, #8             // file name
+       mov     x2, #0                  // O_RDONLY
+       mov     x3, #0                  // mode
+       svc     #0                      // syscall
+       cmp     x0, #-1                 // rc < 0?
+       ble     .perror
+       mov     x19, x1                 // x19 == x1
+.nextc:
+       ldrb    w2, [x1], #1            // b = *x1++
+       cmp     w2, #47                 // dir separator?
+       bne     .nextc1                 // not dir separator
+       mov     x19, x1                 // x19 = char past separator
+.nextc1:
+       cbnz    w2, .nextc              // b?
+       add     x1, x1, #7              // round up x1
+       and     x20, x1, #-8            // mask for round, set x20
+       tst     x11, #16                // primary fd?
+       bne     .secondary              // secondary fd
+       mov     x29, x0                 // primary fd
+       mov     x8, #167                // SYS_prctl
+       mov     x0, #15                 // PR_SET_NAME
+       mov     x1, x19                 // basename
+       mov     x2, #0                  // arg2
+       mov     x3, #0                  // arg3
+       mov     x4, #0                  // arg4
+       mov     x5, #0                  // arg5
+       svc     #0                      // syscall
+       b       .next_action            // next action
+.secondary:
+       mov     x28, x0                 // secondary fd
+       b       .next_action            // next action.
+.perror:
+       mov     x8, #93                 // SYS_exit
+       mvn     x0, x0                  // x1 = ~x0
+       add     x0, x0, 1               // x1 += 1
+       svc     #0                      // exit
+.rest_of_exec:
+       mov     x7, x20                 // x7 = x20
+       mov     x20, x10                // x20 = x10
+       ldr     x9, [x20]               // argc
+       add     x9, x9, #2              // x9 += 2
+       lsl     x9, x9, #3              // argc * 8
+       add     x20, x20, x9            // now past argv
+.skipenv:
+       ldr     x9, [x20], #8           // x9 = *envp++
+       cbnz    x9, .skipenv            // x9?
+.one_auxv:
+       ldr     x9, [x20], #16          // x9 = *sp, sp += 2
+       cbz     x9, .cleanup            // !x9?
+       cmp     x9, #3                  // is AT_PHDR?
+       beq     .replace_phdr           // replace
+       cmp     x9, #4                  // is AT_PHENT?
+       beq     .replace_phent          // replace
+       cmp     x9, #5                  // is AT_PHNUM?
+       beq     .replace_phnum          // replace
+       cmp     x9, #9                  // is AT_ENTRY?
+       beq     .replace_entry          // replace
+       cmp     x9, #7                  // is AT_BASE?
+       beq     .replace_base           // replace
+       b       .one_auxv               // next auxv
+.replace_phdr:
+       ldr     x9, [x7, 40]            // at_phdr
+       str     x9, [x20, -8]           // store value
+       b       .one_auxv
+.replace_phent:
+       ldr     x9, [x7, 24]            // at_phent
+       str     x9, [x20, -8]           // store value
+       b       .one_auxv
+.replace_phnum:
+       ldr     x9, [x7, 32]            // at_phnum
+       str     x9, [x20, -8]           // store value
+       b       .one_auxv
+.replace_entry:
+       ldr     x9, [x7, 16]            // at_entry
+       str     x9, [x20, -8]           // store value
+       b       .one_auxv
+.replace_base:
+       ldr     x9, [x7, 48]            // at_base
+       str     x9, [x20, -8]           // store value
+       b       .one_auxv
+.cleanup:
+       cmp     x28, #-1                // is secondary fd set?
+       bne     .cleanup1               // not set
+       mov     x8, #57                 // SYS_close
+       mov     x0, x28                 // secondary fd
+       svc     #0                      // syscall
+.cleanup1:
+       mov     x8, #57                 // SYS_close
+       mov     x0, x29                 // primary fd
+       svc     #0                      // syscall
+.enter:
+       mov     sp, x10                 // restore original SP
+       mov     x0, #0                  // clear rtld_fini
+       ldr     x1, [x7, 8]             // branch to code
+       br      x1
+
+timespec:
+       .quad 10
+       .quad 10
diff --git a/exec/loader-armeabi.s b/exec/loader-armeabi.s
new file mode 100644
index 00000000000..32b2a5268d6
--- /dev/null
+++ b/exec/loader-armeabi.s
@@ -0,0 +1,204 @@
+@ Copyright (C) 2023 Free Software Foundation, Inc.
+@
+@ This file is part of GNU Emacs.
+@
+@ GNU Emacs is free software: you can redistribute it and/or modify
+@ it under the terms of the GNU General Public License as published
+@ by the Free Software Foundation, either version 3 of the License,
+@ or (at your option) any later version.
+@
+@ GNU Emacs is distributed in the hope that it will be useful, but
+@ WITHOUT ANY WARRANTY; without even the implied warranty of
+@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+@ General Public License for more details.
+@
+@ You should have received a copy of the GNU General Public License
+@ along with GNU Emacs.  If not, see <https:@www.gnu.org/licenses/>.
+
+       .section .text
+       .global _start
+_start:
+       @mov    r7, #162                @ SYS_nanosleep
+       @adr    r0, timespec            @ req
+       @mov    r1, #0                  @ rem
+       @swi    #0                      @ syscall
+       mov     r8, sp                  @ r8 = sp
+       ldr     r9, [r8], #8            @ r9 = original sp, r8 += 8
+       mov     r14, #-1                @ r14 = secondary fd
+.next_action:
+       ldr     r11, [r8]               @ r11 = action number
+       and     r12, r11, #-17          @ actual action number
+       cmp     r12, #0                 @ open file?
+       beq     .open_file              @ open file.
+       cmp     r12, #3                 @ jump?
+       beq     .rest_of_exec           @ jump to code.
+       cmp     r12, #4                 @ anonymous mmap?
+       beq     .do_mmap_anon           @ anonymous mmap.
+.do_mmap:
+       add     r6, r8, #4              @ r6 = r8 + 4
+       ldm     r6!, {r0, r5}           @ vm_address, file_offset
+       ldm     r6!, {r1, r2}           @ protection, length
+       mov     r3, r1                  @ swap
+       lsr     r5, #12                 @ divide file offset by page size
+       mov     r1, r2                  @ swap
+       mov     r2, r3                  @ swap
+       ldm     r6!, {r3, r12}          @ flags, clear
+       tst     r11, #16                @ primary fd?
+       mov     r4, r10                 @ primary fd
+       beq     .do_mmap_1
+       mov     r4, r14                 @ secondary fd
+.do_mmap_1:
+       mov     r7, #192                @ SYS_mmap2
+       swi     #0                      @ syscall
+       ldr     r2, [r8, #4]            @ vm_address
+       cmp     r2, r0                  @ rc == vm_address?
+       bne     .perror
+       add     r0, r1, r2              @ r0 = length + vm_address
+       sub     r3, r0, r12             @ r3 = r0 - clear
+       mov     r1, #0                  @ r1 = 0
+.align:
+       cmp     r0, r3                  @ r0 == r3?
+       beq     .continue               @ continue
+       tst     r3, #3                  @ r3 & 3?
+       bne     .fill32                 @ fill aligned
+       strb    r1, [r3], #1            @ fill byte
+       b       .align                  @ align again
+.fill32:
+       sub     r2, r0, r3              @ r2 = r0 - r3
+       cmp     r2, #31                 @ r2 >= 32?
+       ble     .fillb                  @ start filling bytes
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       str     r1, [r3], #4            @ *r3++ = 0
+       b       .fill32
+.fillb:
+       cmp     r0, r3                  @ r0 == r3
+       beq     .continue               @ done
+       strb    r1, [r3], #1            @ ((char *) r3)++ = 0
+       b       .fillb
+.continue:
+       add     r8, r8, #28             @ next action
+       b       .next_action
+.do_mmap_anon:
+       add     r6, r8, #4              @ r6 = r8 + 4
+       ldm     r6!, {r0, r5}           @ vm_address, file_offset
+       ldm     r6!, {r1, r2}           @ protection, length
+       mov     r3, r1                  @ swap
+       lsr     r5, #12                 @ divide file offset by page size
+       mov     r1, r2                  @ swap
+       mov     r2, r3                  @ swap
+       ldm     r6!, {r3, r12}          @ flags, clear
+       mov     r4, #-1                 @ fd
+       b       .do_mmap_1
+.open_file:
+       mov     r7, #5                  @ SYS_open
+       add     r0, r8, #4              @ file name
+       mov     r1, #0                  @ O_RDONLY
+       mov     r2, #0                  @ mode
+       swi     #0                      @ syscall
+       cmp     r0, #-1                 @ r0 <= -1?
+       ble     .perror
+       add     r8, r8, #4              @ r8 = start of string
+       mov     r1, r8                  @ r1 = r8
+.nextc:
+       ldrb    r2, [r8], #1            @ b = *r0++
+       cmp     r2, #47                 @ dir separator?
+       bne     .nextc1                 @ not dir separator
+       mov     r1, r8                  @ r1 = char past separator
+.nextc1:
+       cmp     r2, #0                  @ b?
+       bne     .nextc                  @ next character
+       add     r8, r8, #3              @ round up r8
+       and     r8, r8, #-4             @ mask for round, set r8
+       tst     r11, #16                @ primary fd?
+       bne     .secondary              @ secondary fd
+       mov     r10, r0                 @ primary fd
+       mov     r7, #172                @ SYS_prctl
+       mov     r0, #15                 @ PR_SET_NAME, r1 = name
+       mov     r2, #0                  @ arg2
+       mov     r3, #0                  @ arg3
+       mov     r4, #0                  @ arg4
+       mov     r5, #0                  @ arg5
+       swi     #0                      @ syscall
+       b       .next_action            @ next action
+.secondary:
+       mov     r14, r0                 @ secondary fd
+       b       .next_action            @ next action
+.perror:
+       mov     r7, #1                  @ SYS_exit
+       mvn     r0, r0                  @ r0 = ~r0
+       add     r0, r0, #1              @ r0 += 1
+       swi     #0
+.rest_of_exec:
+       mov     r7, r9                  @ r7 = original SP
+       ldr     r6, [r7]                @ argc
+       add     r6, r6, #2              @ argc + 2
+       lsl     r6, r6, #2              @ argc *= 4
+       add     r7, r7, r6              @ now past argv
+.skipenv:
+       ldr     r6, [r7], #4            @ r6 = *r7++
+       cmp     r6, #0                  @ r6?
+       bne     .skipenv                @ r6?
+.one_auxv:
+       ldr     r6, [r7], #8            @ r6 = *r7, r7 += 2
+       cmp     r6, #0                  @ !r6?
+       beq     .cleanup                @ r6?
+       cmp     r6, #3                  @ is AT_PHDR?
+       beq     .replace_phdr           @ replace
+       cmp     r6, #4                  @ is AT_PHENT?
+       beq     .replace_phent          @ replace
+       cmp     r6, #5                  @ is AT_PHNUM?
+       beq     .replace_phnum          @ replace
+       cmp     r6, #9                  @ is AT_ENTRY?
+       beq     .replace_entry          @ replace
+       cmp     r6, #7                  @ is AT_BASE?
+       beq     .replace_base           @ replace
+       b       .one_auxv               @ next auxv
+.replace_phdr:
+       ldr     r6, [r8, #20]           @ at_phdr
+       str     r6, [r7, #-4]           @ store value
+       b       .one_auxv
+.replace_phent:
+       ldr     r6, [r8, #12]           @ at_phent
+       str     r6, [r7, #-4]           @ store value
+       b       .one_auxv
+.replace_phnum:
+       ldr     r6, [r8, #16]           @ at_phnum
+       str     r6, [r7, #-4]           @ store value
+       b       .one_auxv
+.replace_entry:
+       ldr     r6, [r8, #8]            @ at_entry
+       str     r6, [r7, #-4]           @ store value
+       b       .one_auxv
+.replace_base:
+       ldr     r6, [r8, #24]           @ at_base
+       str     r6, [r7, #-4]           @ store value
+       b       .one_auxv
+.cleanup:
+       cmp     r14, #-1                @ secondary fd set?
+       bne     .cleanup1               @ not set
+       mov     r7, #6                  @ SYS_close
+       mov     r0, r14                 @ secondary fd
+       swi     #0                      @ syscall
+.cleanup1:
+       mov     r7, #6                  @ SYS_close
+       mov     r0, r10                 @ primary fd
+       swi     #0                      @ syscall
+.enter:
+       mov     sp, r9                  @ restore original SP
+       mov     r0, #0                  @ clear rtld_fini
+       ldr     r1, [r8, #4]            @ branch to code
+       bx      r1
+
+timespec:
+       .long 10
+       .long 10
+
+@ Local Variables:
+@ asm-comment-char: 64
+@ End:
diff --git a/exec/loader-mips64el.s b/exec/loader-mips64el.s
new file mode 100644
index 00000000000..f4a6f918497
--- /dev/null
+++ b/exec/loader-mips64el.s
@@ -0,0 +1,234 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of GNU Emacs.
+#
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License,
+# or (at your option) any later version.
+#
+# GNU Emacs is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.         If not, see <https://www.gnu.org/licenses/>.
+
+include(`config-mips.m4')
+
+       .set noreorder                  # delay slots managed by hand
+       .set noat                       # no assembler macros
+       .section .text
+       .global __start
+__start:
+dnl    li      $v0, 5034               # SYS_nanosleep
+dnl    dla     $a0, .timespec          # rqtp
+dnl    li      $a1, 0                  # rmtp
+dnl    syscall                         # syscall
+       ld      $s2, ($sp)              # original stack pointer
+       DADDI3( $s0, $sp, 16)           # start of load area
+       DADDI2( $sp, -16)               # primary fd, secondary fd
+       li      $t0, -1                 # secondary fd
+       sd      $t0, 8($sp)             # initialize secondary fd
+.next_action:
+       ld      $s1, ($s0)              # action number
+       andi    $t0, $s1, 15            # t0 = action number & 15
+       beqz    $t0, .open_file         # open file?
+       nop                             # delay slot
+       DADDI2( $t0, -3)                # t0 -= 3
+       beqz    $t0, .rest_of_exec      # jump to code
+       nop                             # delay slot
+       li      $t1, 1
+       beq     $t0, $t1, .do_mmap_anon # anonymous mmap?
+       nop                             # delay slot
+.do_mmap:
+       ld      $t0, 8($s0)             # vm address
+       ld      $t1, 16($s0)            # file_offset
+       ld      $t2, 24($s0)            # protection
+       ld      $t3, 32($s0)            # length
+       ld      $v0, 40($s0)            # flags
+       ld      $v1, ($sp)              # primary fd
+       andi    $s3, $s1, 16            # s1 & 16?
+       beqz    $s3, .do_mmap_1         # secondary fd?
+       nop                             # delay slot
+       ld      $v1, 8($sp)             # secondary fd
+.do_mmap_1:
+       move    $a0, $t0                # syscall arg
+       move    $a1, $t3                # syscall arg
+       move    $a2, $t2                # syscall arg
+       move    $a3, $v0                # syscall arg
+       move    $a4, $v1                # syscall arg
+       move    $a5, $t1                # syscall arg
+       li      $v0, 5009               # SYS_mmap
+       syscall                         # syscall
+       bne     $a3, $zero, .perror     # perror?
+       nop                             # delay slot
+       ld      $t1, 48($s0)            # clear
+       dadd    $t0, $a0, $a1           # t0 = end of mapping
+       dsub    $t1, $t0, $t1           # t1 = t0 - clear
+.align:
+       beq     $t0, $t1, .continue     # already finished
+       nop                             # delay slot
+       andi    $t2, $t1, 7             # t1 & 7?
+       bnez    $t2, .filld             # start filling longs
+       nop                             # delay slot
+.filld:
+       dsub    $t2, $t0, $t1           # t2 = t0 - t1
+       sltiu   $t2, $t2, 64            # t2 < 64?
+       bne     $t2, $zero, .fillb      # fill bytes
+       nop                             # delay slot
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       sd      $zero, ($t1)            # zero doubleword
+       DADDI2( $t1, 8)                 # next doubleword
+       j       .filld                  # fill either doubleword or byte
+       nop                             # delay slot
+.fillb:
+       beq     $t0, $t1, .continue     # already finished?
+       nop                             # delay slot
+       sb      $zero, ($t1)            # clear byte
+       DADDI2( $t1, 1)                 # t1++
+.continue:
+       DADDI2( $s0, 56)                # s0 = next action
+       j       .next_action            # next action
+       nop                             # delay slot
+.do_mmap_anon:
+       ld      $t0, 8($s0)             # vm address
+       ld      $t1, 16($s0)            # file_offset
+       ld      $t2, 24($s0)            # protection
+       ld      $t3, 32($s0)            # length
+       ld      $v0, 40($s0)            # flags
+       li      $v1, -1                 # fd
+       j       .do_mmap_1              # do mmap
+       nop                             # branch delay slot
+.open_file:
+       li      $v0, 5002               # SYS_open
+       DADDI3( $a0, $s0, 8)            # start of name
+       move    $a1, $zero              # flags = O_RDONLY
+       move    $a2, $zero              # mode = 0
+       syscall                         # syscall
+       bne     $a3, $zero, .perror     # perror
+       nop                             # delay slot
+       DADDI2( $s0, 8)                 # start of string
+       move    $t3, $s0                # t3 = s0
+.nextc:
+       lb      $t0, ($s0)              # load byte
+       DADDI2( $s0, 1)                 # s0++
+       li      $t1, 47                 # directory separator `/'
+       bne     $t0, $t1, .nextc1       # is separator char?
+       nop                             # delay slot
+       move    $t3, $s0                # t3 = char past separator
+.nextc1:
+       bnez    $t0, .nextc             # next character?
+       nop                             # delay slot
+       DADDI2( $s0, 7)                 # adjust for round
+       li      $t2, -8                 # t2 = -8
+       and     $s0, $s0, $t2           # mask for round
+       andi    $t0, $s1, 16            # t1 = s1 & 16
+       move    $t1, $sp                # address of primary fd
+       beqz    $t0, .primary           # primary fd?
+       nop                             # delay slot
+       DADDI2( $t1, 8)                 # address of secondary fd
+       sd      $v0, ($t1)              # store fd
+       j       .next_action            # next action
+       nop                             # delay slot
+.primary:
+       sd      $v0, ($t1)              # store fd
+       li      $v0, 5153               # SYS_prctl
+       li      $a0, 15                 # PR_SET_NAME
+       move    $a1, $t3                # char past separator
+       move    $a2, $zero              # a2
+       move    $a3, $zero              # a3
+       move    $a4, $zero              # a4
+       move    $a5, $zero              # a5
+       syscall                         # syscall
+       j       .next_action            # next action
+       nop                             # delay slot
+.perror:
+       move    $a0, $v0                # errno
+       li      $v0, 5058               # SYS_exit
+       syscall                         # syscall
+.rest_of_exec:
+       move    $s1, $s2                # original SP
+       ld      $t0, ($s1)              # argc
+       dsll    $t0, $t0, 3             # argc *= 8
+       DADDI2( $t0, 16)                # argc += 16
+       dadd    $s1, $s1, $t0           # s1 = start of envp
+.skipenv:
+       ld      $t0, ($s1)              # t0 = *s1
+       DADDI2( $s1, 8)                 # s1++
+       bne     $t0, $zero, .skipenv    # skip again
+       nop                             # delay slot
+       dla     $t3, .auxvtab           # address of auxv table
+.one_auxv:
+       ld      $t0, ($s1)              # t0 = auxv type
+       li      $t1, 10                 # t1 = 10
+       beqz    $t0, .finish            # is AT_IGNORE?
+       nop                             # delay slot
+       sltu    $t1, $t0, $t1           # t1 = t0 < num offsets
+       beqz    $t1, .next              # next auxv
+       nop                             # delay slot
+       dsll    $t1, $t0, 2             # t1 = t0 * 4
+       dadd    $t1, $t3, $t1           # t1 = .auxvtab + t1
+       lw      $t2, ($t1)              # t2 = *t1
+       beqz    $t2, .next              # skip auxv
+       nop                             # delay slot
+       dadd    $t2, $s0, $t2           # t2 = s0 + t2
+       ld      $t2, ($t2)              # t2 = *t2
+       sd      $t2, 8($s1)             # set auxv value
+.next:
+       DADDI2( $s1, 16)                # next auxv
+       j       .one_auxv               # next auxv
+       nop                             # delay slot
+.finish:
+       ld      $t0, 8($sp)             # secondary fd
+       li      $t1, -1                 # t1 = -1
+       ld      $s1, ($sp)              # s1 = primary fd
+       li      $v0, 5003               # SYS_close
+       beq     $t0, $t2, .finish1      # secondary fd set?
+       nop                             # delay slot
+       move    $a0, $t0                # secondary fd
+       syscall                         # syscall
+       li      $v0, 5003               # SYS_close
+.finish1:
+       move    $a0, $s1                # primary fd
+       syscall                         # syscall
+.jump:
+       move    $v0, $zero              # rtld_fini
+       ld      $t0, 8($s0)             # entry
+       move    $sp, $s2                # restore stack pointer, delay slot
+       jr      $t0                     # enter
+       nop                             # delay slot
+
+.auxvtab:
+       .long   0                       # 0
+       .long   0                       # 1
+       .long   0                       # 2
+       .long   40                      # 3 AT_PHDR
+       .long   24                      # 4 AT_PHENT
+       .long   32                      # 5 AT_PHNUM
+       .long   0                       # 6
+       .long   48                      # 7 AT_BASE
+       .long   0                       # 8
+       .long   16                      # 9 AT_ENTRY
+
+.timespec:
+       .quad   10
+       .quad   10
+
+# Local Variables:
+# asm-comment-char: 35
+# End:
diff --git a/exec/loader-mipsel.s b/exec/loader-mipsel.s
new file mode 100644
index 00000000000..baba3f05a94
--- /dev/null
+++ b/exec/loader-mipsel.s
@@ -0,0 +1,236 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This file is part of GNU Emacs.
+#
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License,
+# or (at your option) any later version.
+#
+# GNU Emacs is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.         If not, see <https://www.gnu.org/licenses/>.
+
+include(`config-mips.m4')
+
+# Make sure not to use t4 through t7, in order to maintain portability
+# with N32 ABI systems.
+
+       .set noreorder                  # delay slots managed by hand
+       .section .text
+       .global __start
+__start:
+dnl    li      $v0, SYSCALL_nanosleep  # SYS_nanosleep
+dnl    la      $a0, .timespec          # rqtp
+dnl    li      $a1, 0                  # rmtp
+dnl    syscall                         # syscall
+       lw      $s6, ($sp)              # original stack pointer
+       addi    $s0, $sp, 8             # start of load area
+       addi    $sp, -8                 # primary fd, secondary fd
+       li      $t0, -1                 # secondary fd
+       sw      $t0, 4($sp)             # initialize secondary fd
+.next_action:
+       lw      $s2, ($s0)              # action number
+       nop                             # delay slot
+       andi    $t0, $s2, 15            # t0 = s2 & 15
+       beqz    $t0, .open_file         # open file?
+       li      $t1, 3                  # t1 = 3, delay slot
+       beq     $t0, $t1, .rest_of_exec # jump to code
+       li      $t1, 4                  # t1 = 4, delay slot
+       beq     $t0, $t1, .do_mmap_anon # anonymous mmap
+.do_mmap:
+       lw      $a0, 4($s0)             # vm_address, delay slot
+       lw      $v1, 8($s0)             # file_offset
+       lw      $a2, 12($s0)            # protection
+       lw      $a1, 16($s0)            # length
+       lw      $a3, 20($s0)            # flags
+       lw      $v0, ($sp)              # primary fd
+       andi    $t1, $s2, 16            # t1 = s2 & 16
+       beqz    $t1, .do_mmap_1         # secondary fd?
+       nop                             # delay slot
+       lw      $v0, 4($sp)             # secondary fd
+       nop                             # delay slot
+.do_mmap_1:
+SYSCALL(`$v0',`$v1',`$zero',`$zero')   # syscall args
+       li      $v0, SYSCALL_mmap       # SYS_mmap
+       syscall                         # syscall
+       bne     $a3, $zero, .perror     # perror
+RESTORE()                              # delay slot, restore sp
+       lw      $s5, 24($s0)            # clear
+       add     $t0, $a0, $a1           # t0 = length + vm_address, delay slot
+       sub     $t1, $t0, $s5           # t1 = t0 - clear
+.align:
+       beq     $t0, $t1, .continue     # already finished?
+       nop                             # delay slot
+       andi    $t2, $t1, 3             # t1 & 3?
+       bnez    $t2, .fillw             # start filling longs
+       nop                             # delay slot
+       sb      $zero, ($t1)            # clear byte
+       addi    $t1, $t1, 1             # t1++
+       j       .align                  # continue
+       nop                             # delay slot
+.fillw:
+       sub     $t2, $t0, $t1           # t2 = t0 - t1
+       sltiu   $t2, $t2, 32            # r2 < 32?
+       bne     $t2, $zero, .fillb      # fill bytes
+       nop                             # delay slot
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       sw      $zero, ($t1)            # zero word
+       addi    $t1, $t1, 4             # next word
+       j       .fillw                  # fill either word or byte
+       nop                             # delay slot
+.fillb:
+       beq     $t0, $t1, .continue     # already finished?
+       nop                             # delay slot
+       sb      $zero, ($t1)            # clear byte
+       addi    $t1, $t1, 1             # t1++
+.continue:
+       addi    $s0, $s0, 28            # s0 = next action
+       j       .next_action            # next action
+       nop                             # delay slot
+.do_mmap_anon:
+       lw      $v1, 8($s0)             # file_offset
+       lw      $a2, 12($s0)            # protection
+       lw      $a1, 16($s0)            # length
+       lw      $a3, 20($s0)            # flags
+       li      $t4, -1                 # fd
+       j       .do_mmap_1              # do mmap
+       nop                             # delay slot
+.open_file:
+       li      $v0, SYSCALL_open       # SYS_open
+       addi    $a0, $s0, 4             # start of name
+       move    $a1, $zero              # flags = O_RDONLY
+       move    $a2, $zero              # mode = 0
+       syscall                         # syscall
+       bne     $a3, $zero, .perror     # perror
+       addi    $s0, $s0, 4             # start of string, delay slot
+       move    $t3, $s0                # t3 = char past separator
+.nextc:
+       lb      $t0, ($s0)              # load byte
+       addi    $s0, $s0, 1             # s0++
+       li      $t1, 47                 # directory separator `/'
+       bne     $t0, $t1, .nextc1       # is separator char?
+       nop                             # delay slot
+       move    $t3, $s0                # t3 = char past separator
+.nextc1:
+       bnez    $t0, .nextc             # next character?
+       nop                             # delay slot
+       addi    $s0, $s0, 3             # adjust for round
+       li      $t2, -4                 # t2 = -4
+       and     $s0, $s0, $t2           # mask for round
+       andi    $t0, $s2, 16            # t1 = s2 & 16
+       beqz    $t0, .primary           # primary fd?
+       move    $t0, $sp                # address of primary fd, delay slot
+       addi    $t0, $t0, 4             # address of secondary fd
+       j       .next_action            # next action
+.primary:
+       sw      $v0, ($t0)              # store fd, delay slot
+       li      $v0, SYSCALL_prctl      # SYS_prctl
+       li      $a0, 15                 # PR_SET_NAME
+       move    $a1, $t3                # name
+       move    $a2, $zero              # arg1
+       move    $a3, $zero              # arg2
+SYSCALL(`$a2',`$a2',`$a2',`$a2')       # syscall args
+       syscall                         # syscall
+RESTORE()                              # restore sp
+       j       .next_action            # next action
+       nop                             # delay slot
+.perror:
+       move    $a0, $v0                # errno
+       li      $v0, SYSCALL_exit       # SYS_exit
+       syscall                         # syscall
+.rest_of_exec:
+       move    $s1, $s6                # s1 = original SP
+       lw      $t0, ($s1)              # argc
+       nop                             # delay slot
+       sll     $t0, $t0, 2             # argc *= 4
+       addi    $t0, $t0, 8             # argc += 8
+       add     $s1, $s1, $t0           # s1 = start of envp
+.skipenv:
+       lw      $t0, ($s1)              # t0 = *s1
+       addi    $s1, $s1, 4             # s1++
+       bne     $t0, $zero, .skipenv    # skip again
+       nop                             # delay slot
+       la      $s2, .auxvtab           # address of auxv table
+.one_auxv:
+       lw      $t0, ($s1)              # t0 = auxv type
+       li      $t1, 10                 # t1 = 10, delay slot
+       beqz    $t0, .finish            # is AT_IGNORE?
+       sltu    $t1, $t0, $t1           # t1 = t0 < num offsets, delay slot
+       beq     $t1, $zero, .next       # next auxv
+       sll     $t1, $t0, 2             # t1 = t0 * 4, delay slot
+       add     $t1, $s2, $t1           # t1 = .auxvtab + t1
+       lw      $t2, ($t1)              # t2 = *t1
+       nop                             # delay slot
+       beqz    $t2, .next              # skip auxv
+       add     $t2, $s0, $t2           # t2 = s0 + t2
+       lw      $t2, ($t2)              # t2 = *t2
+       nop                             # delay slot
+       sw      $t2, 4($s1)             # set auxv value
+.next:
+       addi    $s1, $s1, 8             # next auxv
+       j       .one_auxv               # next auxv
+       nop                             # delay slot
+.finish:
+       lw      $t0, 4($sp)             # secondary fd
+       lw      $s1, ($sp)              # primary fd, delay slot, preserved
+       li      $t2, -1                 # immediate -1
+       beq     $t0, $t2, .finish1      # secondary fd set?
+       li      $v0, SYSCALL_close      # SYS_close, delay slot
+       move    $a0, $t0                # fd
+       syscall                         # syscall
+       li      $v0, SYSCALL_close      # SYS_close
+.finish1:
+       move    $a0, $s1                # primary fd
+       syscall                         # syscall
+       li      $v0, SYSCALL_prctl      # SYS_prctl
+       li      $a0, 45                 # PR_SET_FP_MODE
+       lw      $a1, 28($s0)            # fpu_mode
+       move    $a2, $zero              # arg3
+       move    $a3, $zero              # arg4
+SYSCALL(`$a2',`$a2',`$a2',`$a2')       # syscall args
+       syscall                         # syscall
+RESTORE()                              # restore sp
+.jump:
+       move    $v0, $zero              # rtld_fini
+       lw      $t0, 4($s0)             # entry
+       move    $sp, $s6                # restore stack pointer, delay slot
+       jr      $t0                     # enter
+       nop                             # delay slot
+
+.auxvtab:
+       .long   0                       # 0
+       .long   0                       # 1
+       .long   0                       # 2
+       .long   20                      # 3 AT_PHDR
+       .long   12                      # 4 AT_PHENT
+       .long   16                      # 5 AT_PHNUM
+       .long   0                       # 6
+       .long   24                      # 7 AT_BASE
+       .long   0                       # 8
+       .long   8                       # 9 AT_ENTRY
+
+.timespec:
+       .long   10
+       .long   10
+
+# Local Variables:
+# asm-comment-char: 35
+# End:
diff --git a/exec/loader-x86.s b/exec/loader-x86.s
new file mode 100644
index 00000000000..6329e7f33b1
--- /dev/null
+++ b/exec/loader-x86.s
@@ -0,0 +1,203 @@
+define(`CC', `
+dnl')
+
+CC Copyright (C) 2023 Free Software Foundation, Inc.
+CC
+CC This file is part of GNU Emacs.
+CC
+CC GNU Emacs is free software: you can redistribute it and/or modify
+CC it under the terms of the GNU General Public License as published
+CC by the Free Software Foundation, either version 3 of the License,
+CC or (at your option) any later version.
+CC
+CC GNU Emacs is distributed in the hope that it will be useful, but
+CC WITHOUT ANY WARRANTY; without even the implied warranty of
+CC MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+CC General Public License for more details.
+CC
+CC You should have received a copy of the GNU General Public License
+CC along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+       .section .text
+       .global _start
+_start:
+dnl    movl    $162, %eax              CC SYS_nanosleep
+dnl    leal    timespec, %ebx
+dnl    xorl    %ecx, %ecx
+dnl    int     $0x80
+       leal    8(%esp), %ebp           CC ebp = start of load area
+       subl    $8, %esp                CC (%esp) = primary fd, 4(%esp) = 
secondary fd
+       movl    $-1, 4(%esp)
+.next_action:
+       movl    (%ebp), %edx            CC edx = action number
+       andl    $-17, %edx
+       cmpl    $0, %edx                CC open file?
+       je      .open_file
+       cmpl    $3, %edx                CC jump?
+       je      .rest_of_exec
+       cmpl    $4, %edx                CC anonymous mmap?
+       je      .do_mmap_anon
+.do_mmap:
+       subl    $24, %esp
+       movl    $90, %eax               CC SYS_old_mmap
+       movl    %esp, %ebx
+       movl    4(%ebp), %ecx           CC address
+       movl    %ecx, (%esp)
+       movl    16(%ebp), %ecx          CC length
+       movl    %ecx, 4(%esp)
+       movl    12(%ebp), %ecx          CC protection
+       movl    %ecx, 8(%esp)
+       movl    20(%ebp), %ecx          CC flags
+       movl    %ecx, 12(%esp)
+       testl   $16, (%ebp)             CC primary?
+       movl    28(%esp), %ecx
+       cmovzl  24(%esp), %ecx
+       movl    %ecx, 16(%esp)          CC fd
+       movl    8(%ebp), %ecx           CC offset
+       movl    %ecx, 20(%esp)
+.do_mmap_1:
+       int     $0x80
+       addl    $24, %esp               CC restore esp
+       cmpl    $-1, %eax               CC mmap failed?
+       je      .perror
+       movl    24(%ebp), %ecx          CC clear
+       testl   %ecx, %ecx
+       jz      .continue
+       movl    4(%ebp), %esi           CC start of mapping
+       addl    16(%ebp), %esi          CC end of mapping
+       subl    %ecx, %esi              CC start of clear area
+.again:
+       testl   %ecx, %ecx
+       jz      .continue
+       subl    $1, %ecx
+       movb    $0, (%esi, %ecx, 1)
+       jmp     .again
+.continue:
+       leal    28(%ebp), %ebp
+       jmp     .next_action
+.do_mmap_anon:
+       subl    $24, %esp
+       movl    $90, %eax               CC SYS_old_mmap
+       movl    %esp, %ebx
+       movl    4(%ebp), %ecx           CC address
+       movl    %ecx, (%esp)
+       movl    16(%ebp), %ecx          CC length
+       movl    %ecx, 4(%esp)
+       movl    12(%ebp), %ecx          CC protection
+       movl    %ecx, 8(%esp)
+       movl    20(%ebp), %ecx          CC flags
+       movl    %ecx, 12(%esp)
+       movl    $-1, 16(%esp)           CC fd
+       movl    8(%ebp), %ecx           CC offset
+       movl    %ecx, 20(%esp)
+       jmp     .do_mmap_1
+.open_file:
+       movl    $5, %eax                CC SYS_open
+       leal    4(%ebp), %ebx           CC ebx = %esp + 8
+       pushl   %ebx
+       xorl    %ecx, %ecx              CC flags = O_RDONLY
+       xorl    %edx, %edx              CC mode = 0
+       int     $0x80
+       cmpl    $-1, %eax               CC open failed?
+       jle     .perror
+       movl    %ebp, %esi              CC (esi) = original action number
+       popl    %ebp                    CC ebp = start of string
+       movl    %ebp, %ecx              CC char past separator
+       decl    %ebp
+.nextc:
+       incl    %ebp
+       movb    (%ebp), %dl             CC dl = *ebp
+       cmpb    $47, %dl                CC dl == '\?'?
+       jne     .nextc1
+       leal    1(%ebp), %ecx           CC ecx = char past separator
+.nextc1:
+       cmpb    $0, %dl                 CC dl == 0?
+       jne     .nextc
+       addl    $4, %ebp                CC adjust past ebp prior to rounding
+       andl    $-4, %ebp               CC round ebp up to the next long
+       testl   $16, (%esi)             CC original action number & 16?
+       jz      .primary
+       movl    %eax, 4(%esp)           CC secondary fd = eax
+       jmp     .next_action
+.primary:
+       pushl   %ebp
+       xorl    %esi, %esi              CC arg3
+       movl    %eax, 4(%esp)           CC primary fd = eax
+       xorl    %edx, %edx              CC arg2
+       movl    $15, %ebx               CC PR_SET_NAME, arg1 = ecx
+       xorl    %edi, %edi              CC arg4
+       movl    $172, %eax              CC SYS_prctl
+       xorl    %ebp, %ebp              CC arg5
+       int     $0x80                   CC syscall
+       popl    %ebp
+       jmp     .next_action
+.perror:
+       movl    %eax, %ebx
+       negl    %ebx
+       movl    $1, %eax
+       int     $0x80
+.rest_of_exec:
+       movl    8(%esp), %ecx           CC ecx = original stack pointer
+       movl    (%ecx), %esi            CC esi = argc
+       leal    8(%ecx, %esi, 4), %ecx  CC ecx = start of environ
+.skip_environ:
+       movl    (%ecx), %esi            CC envp[N]
+       addl    $4, %ecx
+       testl   %esi, %esi              CC envp[n] ?
+       jnz     .skip_environ           CC otherwise, esi is now at the start 
of auxv
+.one_auxv:
+       movl    (%ecx), %esi            CC auxv type
+       leal    8(%ecx), %ecx           CC skip to next auxv
+       testl   %esi, %esi              CC is 0?
+       jz      .cleanup
+       cmpl    $3, %esi                CC is AT_PHDR
+       je      .replace_phdr
+       cmpl    $4, %esi                CC is AT_PHENT?
+       je      .replace_phent
+       cmpl    $5, %esi                CC is AT_PHNUM?
+       je      .replace_phnum
+       cmpl    $9, %esi                CC is AT_ENTRY?
+       je      .replace_entry
+       cmpl    $7, %esi                CC is AT_BASE
+       je      .replace_base
+       jmp     .one_auxv
+.replace_phdr:
+       movl    20(%ebp), %esi
+       movl    %esi, -4(%ecx)
+       jmp     .one_auxv
+.replace_phent:
+       movl    12(%ebp), %esi
+       movl    %esi, -4(%ecx)
+       jmp     .one_auxv
+.replace_phnum:
+       movl    16(%ebp), %esi
+       movl    %esi, -4(%ecx)
+       jmp     .one_auxv
+.replace_entry:
+       movl    8(%ebp), %esi
+       movl    %esi, -4(%ecx)
+       jmp     .one_auxv
+.replace_base:
+       movl    24(%ebp), %esi
+       movl    %esi, -4(%ecx)
+       jmp     .one_auxv
+.cleanup:
+       movl    $6, %eax                CC SYS_close
+       cmpl    $-1, 4(%esp)            CC see if interpreter fd is set
+       je      .cleanup_1
+       movl    4(%esp), %ebx
+       int     $0x80
+       movl    $6, %eax                CC SYS_close
+.cleanup_1:
+       movl    (%esp), %ebx
+       int     $0x80
+.enter:
+       pushl   $0
+       popfl                           CC restore floating point state
+       movl    8(%esp), %esp           CC restore initial stack pointer
+       xorl    %edx, %edx              CC clear rtld_fini
+       jmpl    *4(%ebp)                CC entry
+
+timespec:
+       .long   10
+       .long   10
diff --git a/exec/loader-x86_64.s b/exec/loader-x86_64.s
new file mode 100644
index 00000000000..acba609b202
--- /dev/null
+++ b/exec/loader-x86_64.s
@@ -0,0 +1,195 @@
+define(`CC', `
+dnl')
+
+CC Copyright (C) 2023 Free Software Foundation, Inc.
+CC
+CC This file is part of GNU Emacs.
+CC
+CC GNU Emacs is free software: you can redistribute it and/or modify
+CC it under the terms of the GNU General Public License as published
+CC by the Free Software Foundation, either version 3 of the License,
+CC or (at your option) any later version.
+CC
+CC GNU Emacs is distributed in the hope that it will be useful, but
+CC WITHOUT ANY WARRANTY; without even the implied warranty of
+CC MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+CC General Public License for more details.
+CC
+CC You should have received a copy of the GNU General Public License
+CC along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+       .section .text
+       .global _start
+_start:
+dnl    movq    $35, %rax               CC SYS_nanosleep
+dnl    leaq    timespec(%rip), %rdi
+dnl    xorq    %rsi, %rsi
+dnl    syscall
+       popq    %r13                    CC original SP
+       popq    %r15                    CC size of load area.
+       movq    $-1, %r12               CC r12 is the interpreter fd
+.next_action:
+       movq    (%rsp), %r14            CC action number
+       movq    %r14, %r15              CC original action number
+       andq    $-17, %r14
+       cmpq    $0, %r14                CC open file?
+       je      .open_file
+       cmpq    $3, %r14                CC jump?
+       je      .rest_of_exec
+       cmpq    $4, %r14                CC anonymous mmap?
+       je      .do_mmap_anon
+.do_mmap:
+       movq    $9, %rax                CC SYS_mmap
+       movq    8(%rsp), %rdi           CC address
+       movq    16(%rsp), %r9           CC offset
+       movq    24(%rsp), %rdx          CC protection
+       movq    32(%rsp), %rsi          CC length
+       movq    40(%rsp), %r10          CC flags
+                                       CC set r8 to the primary fd unless r15 
& 16
+       testq   $16, %r15
+       movq    %r12, %r8
+       cmovzq  %rbx, %r8
+.do_mmap_1:
+       syscall
+       cmpq    $-1, %rax               CC mmap failed
+       je      .perror
+       movq    48(%rsp), %r9           CC clear
+       testq   %r9, %r9
+       jz      .continue
+       movq    8(%rsp), %r10           CC start of mapping
+       addq    32(%rsp), %r10          CC end of mapping
+       subq    %r9, %r10               CC start of clear area
+.again:
+       testq   %r9, %r9
+       jz      .continue
+       subq    $1, %r9
+       movb    $0, (%r10, %r9, 1)
+       jmp     .again
+.continue:
+       leaq    56(%rsp), %rsp
+       jmp     .next_action
+.do_mmap_anon:
+       movq    $9, %rax                CC SYS_mmap
+       movq    8(%rsp), %rdi           CC address
+       movq    16(%rsp), %r9           CC offset
+       movq    24(%rsp), %rdx          CC protection
+       movq    32(%rsp), %rsi          CC length
+       movq    40(%rsp), %r10          CC flags
+       movq    $-1, %r8                CC -1
+       jmp     .do_mmap_1
+.open_file:
+       movq    $2, %rax                CC SYS_open
+       leaq    8(%rsp), %rdi           CC rdi = %rsp + 8
+       xorq    %rsi, %rsi              CC flags = O_RDONLY
+       xorq    %rdx, %rdx              CC mode = 0
+       syscall
+       cmpq    $-1, %rax               CC open failed
+       jle     .perror
+       movq    %rdi, %rsp              CC rsp = start of string
+       subq    $1, %rsp
+       movq    %rsp, %r14              CC r14 = start of string
+.nextc:
+       addq    $1, %rsp
+       movb    (%rsp), %dil            CC rdi = *rsp
+       cmpb    $47, %dil               CC *rsp == '/'?
+       jne     .nextc1
+       movq    %rsp, %r14              CC r14 = rsp
+       addq    $1, %r14                CC r14 = char past separator
+.nextc1:
+       cmpb    $0, %dil                CC *rsp == 0?
+       jne     .nextc
+       addq    $8, %rsp                CC adjust past rsp prior to rounding
+       andq    $-8, %rsp               CC round rsp up to the next quad
+       testq   $16, %r15               CC r15 & 16?
+       jz      .primary
+       movq    %rax, %r12              CC otherwise, move fd to r12
+       jmp     .next_action
+.primary:
+       movq    %rax, %rbx              CC if not, move fd to rbx
+       movq    $157, %rax              CC SYS_prctl
+       movq    $15, %rdi               CC PR_SET_NAME
+       movq    %r14, %rsi              CC arg1
+       xorq    %rdx, %rdx              CC arg2
+       xorq    %r10, %r10              CC arg3
+       xorq    %r8, %r8                CC arg4
+       xorq    %r9, %r9                CC arg5
+       syscall
+       jmp     .next_action
+.perror:
+       movq    %rax, %r12              CC error code
+       negq    %r12
+       movq    $1, %rax                CC SYS_write
+       movq    $1, %rdi                CC stdout
+       leaq    error(%rip), %rsi       CC buffer
+       movq    $23, %rdx               CC count
+       syscall
+       movq    $60, %rax               CC SYS_exit
+       movq    %r12, %rdi              CC code
+       syscall
+.rest_of_exec:                         CC rsp now points to six quads:
+       movq    %rsp, %r8               CC now, they are r8
+       movq    %r13, %rsp              CC restore SP
+       popq    %r10                    CC argc
+       leaq    8(%rsp,%r10,8), %rsp    CC now at start of environ
+.skip_environ:
+       popq    %r10                    CC envp[N]
+       testq   %r10, %r10              CC envp[n]?
+       jnz     .skip_environ           CC otherwise, rsp is now at the start 
of auxv
+.one_auxv:
+       popq    %rcx                    CC auxv type
+       addq    $8, %rsp                CC skip value
+       testq   %rcx, %rcx              CC is 0?
+       jz      .cleanup
+       cmpq    $3, %rcx                CC is AT_PHDR?
+       je      .replace_phdr
+       cmpq    $4, %rcx                CC is AT_PHENT?
+       je      .replace_phent
+       cmpq    $5, %rcx                CC is AT_PHNUM?
+       je      .replace_phnum
+       cmpq    $9, %rcx                CC is AT_ENTRY?
+       je      .replace_entry
+       cmpq    $7, %rcx                CC is AT_BASE?
+       je      .replace_base
+       jmp     .one_auxv
+.replace_phdr:
+       movq    40(%r8), %r9
+       movq    %r9, -8(%rsp)           CC set at_phdr
+       jmp     .one_auxv
+.replace_phent:
+       movq    24(%r8), %r9
+       movq    %r9, -8(%rsp)           CC set at_phent
+       jmp     .one_auxv
+.replace_phnum:
+       movq    32(%r8), %r9
+       movq    %r9, -8(%rsp)           CC set at_phnum
+       jmp     .one_auxv
+.replace_entry:
+       movq    16(%r8), %r9
+       movq    %r9, -8(%rsp)           CC set at_entry
+       jmp     .one_auxv
+.replace_base:
+       movq    48(%r8), %r9
+       movq    %r9, -8(%rsp)           CC set at_base
+       jmp     .one_auxv
+.cleanup:
+       movq    $3, %rax                CC SYS_close
+       cmpq    $-1, %r12               CC see if interpreter fd is set
+       je      .cleanup_1
+       movq    %r12, %rdi
+       syscall
+       movq    $3, %rax                CC SYS_close
+.cleanup_1:
+       movq    %rbx, %rdi
+       syscall
+.enter:
+       pushq   $0
+       popfq                           CC clear FP state
+       movq    %r13, %rsp              CC restore SP
+       xorq    %rdx, %rdx              CC clear rtld_fini
+       jmpq    *8(%r8)                 CC entry
+
+error:
+       .ascii  "_start: internal error."
+timespec:
+       .quad   10
+       .quad   10
diff --git a/exec/mipsel-user.h b/exec/mipsel-user.h
new file mode 100644
index 00000000000..9c5a445c9aa
--- /dev/null
+++ b/exec/mipsel-user.h
@@ -0,0 +1,42 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+
+
+#ifndef _MIPSEL_USER_H_
+#define _MIPSEL_USER_H_
+
+#include <sys/user.h>
+
+#ifndef ELF_NGREG
+#define ELF_NGREG       45
+#endif /* ELF_NGREG */
+
+
+
+/* This file defines a structure containing user mode general purpose
+   registers on 32-bit mipsel systems.  */
+
+struct mipsel_regs
+{
+  /* General purpose registers.  */
+  uint64_t gregs[ELF_NGREG];
+};
+
+#endif /* _MIPSEL_USER_H_ */
diff --git a/exec/mipsfpu.c b/exec/mipsfpu.c
new file mode 100644
index 00000000000..f5fa5720804
--- /dev/null
+++ b/exec/mipsfpu.c
@@ -0,0 +1,289 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <errno.h>
+
+#include "mipsfpu.h"
+
+
+
+/* OABI MIPS systems support several different modes of execution.
+   Each mode differs in the size and utilization of the hardware
+   floating-point registers.
+
+   Linux normally sets the floating point mode to one appropriate for
+   execution, taking into account the floating point modes of the
+   interpreter and executable binaries.  However, this logic is
+   forsaken when the `execve' system call is overwritten.
+
+   Thus, the correct floating point mode must be determined and set
+   within the loader binary.  */
+
+
+
+/* Various constants used throughout this code.  */
+
+#define MIPS_ABI_FP_ANY                0       /* FP ABI doesn't matter */
+#define MIPS_ABI_FP_DOUBLE     1       /* -mdouble-float */
+#define MIPS_ABI_FP_SINGLE     2       /* -msingle-float */
+#define MIPS_ABI_FP_SOFT       3       /* -msoft-float */
+#define MIPS_ABI_FP_OLD_64     4       /* -mips32r2 -mfp64 */
+#define MIPS_ABI_FP_XX         5       /* -mfpxx */
+#define MIPS_ABI_FP_64         6       /* -mips32r2 -mfp64 */
+#define MIPS_ABI_FP_64A                7       /* -mips32r2 -mfp64 
-mno-odd-spreg */
+
+#define EF_MIPS_NOREORDER      1     /* A .noreorder directive was used.  */
+#define EF_MIPS_PIC            2     /* Contains PIC code.  */
+#define EF_MIPS_CPIC           4     /* Uses PIC calling sequence.  */
+#define EF_MIPS_XGOT           8
+#define EF_MIPS_64BIT_WHIRL    16
+#define EF_MIPS_ABI2           32
+#define EF_MIPS_ABI_ON32       64
+#define EF_MIPS_FP64           512  /* Uses FP64 (12 callee-saved).  */
+#define EF_MIPS_NAN2008                1024  /* Uses IEEE 754-2008 NaN 
encoding.  */
+#define EF_MIPS_ARCH           0xf0000000 /* MIPS architecture level.  */
+
+
+
+/* Structure describing the requirements of a single floating-point
+   ABI.  */
+
+struct mode_description
+{
+  /* Whether or not the ABI only executes single precision
+     instructions, and can operate in both 32-bit or 64-bit floating
+     point mode.  */
+  bool single;
+
+  /* Whether or not the ABI performs floating point operations in
+     software, using integer registers.  */
+  bool soft;
+
+  /* Whether or not the ABI requires the use of 64-bit floating point
+     registers.  */
+  bool fr1;
+
+  /* Whether or not the ABI requires the use of 64-bit floating point
+     registers on NABI systems, and 32-bit ones on OABI systems.  */
+  bool frdefault;
+
+  /* Whether or not this ABI requires single precision floating point
+     emulation.  */
+  bool fre;
+};
+
+static struct mode_description fpu_reqs[] =
+  {
+    [MIPS_ABI_FP_ANY]    = { true,  true,  true,  true,  true,  },
+    [MIPS_ABI_FP_DOUBLE] = { false, false, false, true,  true,  },
+    [MIPS_ABI_FP_SINGLE] = { true,  false, false, false, false, },
+    [MIPS_ABI_FP_SOFT]   = { false, true,  false, false, false, },
+    [MIPS_ABI_FP_OLD_64] = { false, false, false, false, false, },
+    [MIPS_ABI_FP_XX]     = { false, false, true,  true,  true,  },
+    [MIPS_ABI_FP_64]     = { false, false, true,  false, false, },
+    [MIPS_ABI_FP_64A]    = { false, false, true,  false, true,  },
+  };
+
+
+
+/* Return whether or not the given floating-point ABI is valid.  */
+
+static bool
+valid_abi_p (int abi)
+{
+  switch (abi)
+    {
+    case MIPS_ABI_FP_ANY:
+    case MIPS_ABI_FP_DOUBLE:
+    case MIPS_ABI_FP_SINGLE:
+    case MIPS_ABI_FP_SOFT:
+    case MIPS_ABI_FP_OLD_64:
+    case MIPS_ABI_FP_XX:
+    case MIPS_ABI_FP_64:
+    case MIPS_ABI_FP_64A:
+      return true;
+
+    default:
+      return false;
+    }
+}
+
+/* Return the floating point mode appropriate for the specified
+   floating point ABI.  */
+
+static int
+fp_mode_for_abi (int abi)
+{
+  struct mode_description *desc;
+
+  desc = &fpu_reqs[abi];
+
+  if (desc->fre)
+    return FP_FRE;
+  else if (desc->fr1)
+    return FP_FR1;
+
+  return FP_FR0;
+}
+
+/* Determine whether or not the CPU is capable of operating in FR0
+   floating point mode.  */
+
+bool
+cpu_supports_fr0_p (void)
+{
+#if defined __mips_isa_rev && __mips_isa_rev >= 6
+  return true;
+#else /* !defined __mips_isa_rev | mips_isa_rev < 6 */
+  return false;
+#endif /* defined __mips_isa_rev && mips_isa_rev >= 6 */
+}
+
+/* Determine the FPU mode for the executable whose ELF header is
+   HEADER.  If INTERPRETER is non-NULL, also take an interpreter whose
+   header is INTERPRETER into account.
+
+   ABIFLAGS should be HEADER's corresponding PT_MIPS_ABIFLAGS program
+   header, and ABIFLAGS1 should be that of INTERPRETER, if set.  Both
+   fields may be NULL if no PT_MIPS_ABIFLAGS header is present; in
+   that case, use HEADER->e_flags to determine the ABI instead.
+
+   Return the FPU mode in *MODE.  Value is 0 upon success, 1
+   otherwise, with errno set.  */
+
+int
+determine_fpu_mode (elf_header *header, elf_header *interpreter,
+                   int *mode, struct mips_elf_abi_flags *abiflags,
+                   struct mips_elf_abi_flags *abiflags1)
+{
+  int exec_abi, interpreter_abi;
+  struct mode_description *exec_desc, *interpreter_desc, common;
+
+  /* Figure out the executable's floating point ABI.  First, consult
+     header->e_flags, and use the old 64-bit floating point ABI if it
+     is specified.  */
+
+  exec_abi = MIPS_ABI_FP_ANY;
+
+  /* First, check HEADER->e_flags.  */
+
+  if (header->e_flags & EF_MIPS_FP64)
+    exec_abi = MIPS_ABI_FP_OLD_64;
+
+  /* Next, use ABIFLAGS if it exists.  */
+
+  if (abiflags && valid_abi_p (abiflags->fp_abi))
+    exec_abi = abiflags->fp_abi;
+  else if (abiflags)
+    {
+      errno = ENOEXEC;
+      return 1;
+    }
+
+  /* Now determine that of the interpreter.  */
+
+  interpreter_abi = MIPS_ABI_FP_ANY;
+
+  if (interpreter)
+    {
+      if (interpreter->e_flags & EF_MIPS_FP64)
+       interpreter_abi = MIPS_ABI_FP_OLD_64;
+
+      if (abiflags1 && valid_abi_p (abiflags->fp_abi))
+       interpreter_abi = abiflags->fp_abi;
+      else if (abiflags1)
+       {
+         errno = ELIBBAD;
+         return 1;
+       }
+    }
+
+  /* If no interpreter flag is set, just return that of the
+     executable.  */
+
+  if (!interpreter)
+    {
+      *mode = fp_mode_for_abi (exec_abi);
+      return 0;
+    }
+
+  /* Otherwise, compare both ABIs and try to find one which will run
+     both kinds of code.
+
+     First, see if there's an easy way out: both ABIs are identical,
+     or one ABI is MIPS_ABI_FP_ANY.  */
+
+  if (exec_abi == interpreter_abi)
+    {
+      *mode = fp_mode_for_abi (exec_abi);
+      return 0;
+    }
+  else if (exec_abi == MIPS_ABI_FP_ANY)
+    {
+      *mode = fp_mode_for_abi (interpreter_abi);
+      return 0;
+    }
+  else if (interpreter_abi == MIPS_ABI_FP_ANY)
+    {
+      *mode = fp_mode_for_abi (exec_abi);
+      return 0;
+    }
+
+  /* If that doesn't work, compare various characteristics of both
+     ABIs and select an appropriate floating point mode.  */
+
+  exec_desc = &fpu_reqs[exec_abi];
+  interpreter_desc = &fpu_reqs[interpreter_abi];
+
+  /* Merge both sets of requirements.  */
+  common.single = exec_desc->single && interpreter_desc->single;
+  common.soft = exec_desc->soft && interpreter_desc->soft;
+  common.fr1 = exec_desc->fr1 && interpreter_desc->fr1;
+  common.frdefault = exec_desc->frdefault && interpreter_desc->frdefault;
+  common.fre = exec_desc->fre && interpreter_desc->fre;
+
+  /* Default to a mode capable of running code expecting 32-bit
+     registers.  */
+
+  if (!(header->e_flags & EF_MIPS_ABI2))
+    *mode = FP_FR0;
+  else
+    /* But in this case, use FR1.  */
+    *mode = FP_FR1;
+
+  if (common.fre && !common.frdefault && !common.fr1)
+    /* Floating point emulation mode is required.  */
+    *mode = FP_FRE;
+  else if ((common.fr1 && common.frdefault)
+          || (common.single && !common.frdefault)
+          || common.fr1)
+    /* 64-bit mode is required.  */
+    *mode = FP_FR1;
+  else if (!common.fre && !common.frdefault
+          && !common.fr1 && !common.single
+          && !common.soft)
+    {
+      /* The floating point modes specified are incompatible.  */
+      errno = ELIBBAD;
+      return -1;
+    }
+
+  return 0;
+}
diff --git a/exec/mipsfpu.h b/exec/mipsfpu.h
new file mode 100644
index 00000000000..2315db59e93
--- /dev/null
+++ b/exec/mipsfpu.h
@@ -0,0 +1,82 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+
+
+#ifndef _MIPSFPU_H_
+#define _MIPSFPU_H_
+
+#include "exec.h"
+
+struct mips_elf_abi_flags
+{
+  /* Version of flags structure.  */
+  uint16_t version;
+
+  /* The level of the ISA: 1-5, 32, 64.  */
+  uint8_t isa_level;
+
+  /* The revision of ISA: 0 for MIPS V and below, 1-n otherwise.  */
+  uint8_t isa_rev;
+
+  /* The size of general purpose registers.  */
+  uint8_t gpr_size;
+
+  /* The size of co-processor 1 registers.  */
+  uint8_t cpr1_size;
+
+  /* The size of co-processor 2 registers.  */
+  uint8_t cpr2_size;
+
+  /* The floating-point ABI.  */
+  uint8_t fp_abi;
+
+  /* Mask of processor-specific extensions.  */
+  uint32_t isa_ext;
+
+  /* Mask of ASEs used.  */
+  uint32_t ases;
+
+  /* Mask of general flags.  */
+  uint32_t flags1;
+
+  /* Mask of general flags.  */
+  uint32_t flags2;
+};
+
+
+
+/* Floating point modes.  */
+
+#define FP_FR0         0
+#define FP_FR1         1
+#define FP_FRE         3
+
+
+
+/* Defined in mipsfpu.c.  */
+
+extern bool cpu_supports_fr0_p (void);
+extern int determine_fpu_mode (elf_header *, elf_header *,
+                              int *, struct mips_elf_abi_flags *,
+                              struct mips_elf_abi_flags *);
+
+
+
+#endif /* _MIPSFPU_H_ */
diff --git a/exec/test.c b/exec/test.c
new file mode 100644
index 00000000000..fa2a848837c
--- /dev/null
+++ b/exec/test.c
@@ -0,0 +1,105 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include <sys/wait.h>
+
+#include "exec.h"
+
+
+
+static void
+print_usage (void)
+{
+  fprintf (stderr, "test loader-name program [args...]\n"
+          "Run the given program using the specified loader.\n");
+}
+
+
+
+extern char **environ;
+
+/* This program uses libexec to wrap the execution of a child
+   process.  */
+
+int
+main (int argc, char **argv)
+{
+  pid_t pid, child;
+  int sig;
+  sigset_t sigset;
+
+  /* Check that there are a sufficient number of arguments.  */
+
+  if (argc < 3)
+    {
+      print_usage ();
+      return 1;
+    }
+
+  exec_init (argv[1]);
+
+  /* Block SIGCHLD to avoid reentrant modification of the child
+     process list.  */
+
+  sigemptyset (&sigset);
+  sigaddset (&sigset, SIGCHLD);
+  sigprocmask (SIG_BLOCK, &sigset, NULL);
+
+  if (!(pid = fork ()))
+    {
+      tracing_execve (argv[2], argv + 2, environ);
+      fprintf (stderr, "tracing_execve: %s\n",
+              strerror (errno));
+      exit (1);
+    }
+  else if (after_fork (pid))
+    {
+      fprintf (stderr, "after_fork: %s\n",
+              strerror (errno));
+      exit (1);
+    }
+
+  /* Now start waiting for child processes to exit.  */
+
+  while (true)
+    {
+      child = exec_waitpid (-1, &sig, 0);
+
+      /* If pid is -1, a system call has been handled.  */
+
+      if (child == -1)
+       continue;
+
+      /* If the main process exits, then exit as well.  */
+
+      if (child == pid && !WIFSTOPPED (sig))
+       return (WIFEXITED (sig)
+               ? WEXITSTATUS (sig)
+               : WTERMSIG (sig));
+    }
+}
diff --git a/exec/trace.c b/exec/trace.c
new file mode 100644
index 00000000000..3b384792d0a
--- /dev/null
+++ b/exec/trace.c
@@ -0,0 +1,1432 @@
+/* Program execution for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "exec.h"
+
+#include SYSCALL_HEADER
+#include USER_HEADER
+
+#ifdef __aarch64__
+#include <sys/uio.h> /* for struct iovec */
+#include <linux/elf.h> /* for NT_* */
+#endif /* __aarch64__ */
+
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h> /* for process_vm_readv */
+#endif /* HAVE_SYS_UIO_H */
+
+#ifndef SYS_SECCOMP
+#define SYS_SECCOMP 1
+#endif /* SYS_SECCOMP */
+
+#ifndef PTRACE_GETEVENTMSG
+#define PTRACE_GETEVENTMSG 0x4201
+#endif /* PTRACE_GETEVENTMSG */
+
+
+
+/* Program tracing functions.
+
+   The main entry point is the function `tracing_execve', which traces
+   the thread and calls exec.  Each time that thread calls `clone',
+   the new child is traced as well.
+
+   Instead of calling `waitpid', call `exec_waitpid' instead.  */
+
+
+
+/* Number of tracees children are allowed to create.  */
+#define MAX_TRACEES 4096
+
+#ifdef __aarch64__
+
+/* Place PID's registers into *REGS.  Return 1 upon failure, else
+   0.  */
+
+int
+aarch64_get_regs (pid_t pid, USER_REGS_STRUCT *regs)
+{
+  struct iovec iov;
+
+  iov.iov_base = regs;
+  iov.iov_len = sizeof *regs;
+
+  return (ptrace (PTRACE_GETREGSET, pid, NT_PRSTATUS,
+                 &iov) != 0);
+}
+
+/* Set PID's registers to *REGS.  If SYSCALL_P, also update the
+   current system call number to the `x8' register.
+
+   Value is 1 upon failure, else 0.  */
+
+int
+aarch64_set_regs (pid_t pid, USER_REGS_STRUCT *regs,
+                 bool syscall_p)
+{
+  struct iovec iov;
+  USER_WORD callno;
+  long rc;
+
+  /* Write the user registers.  */
+
+  iov.iov_base = regs;
+  iov.iov_len = sizeof *regs;
+
+  rc = ptrace (PTRACE_SETREGSET, pid, NT_PRSTATUS,
+              &iov);
+  if (rc < 0)
+    return 1;
+
+  /* Now, write the system call number if necessary.  */
+
+  if (syscall_p)
+    {
+      callno = regs->regs[8];
+      iov.iov_base = &callno;
+      iov.iov_len = sizeof callno;
+
+      return (ptrace (PTRACE_SETREGSET, pid, NT_ARM_SYSTEM_CALL,
+                     &iov) != 0);
+    }
+
+  return 0;
+}
+
+#endif /* __aarch64__ */
+
+
+
+/* List of all processes which are being traced.  */
+static struct exec_tracee *tracing_processes;
+
+
+
+/* Read N bytes from TRACEE's memory, starting at the specified user
+   ADDRESS.  Return its contents in BUFFER.
+
+   If there are unreadable pages within ADDRESS + N, the contents of
+   BUFFER after the first such page becomes undefined.  */
+
+static void
+read_memory (struct exec_tracee *tracee, char *buffer,
+            USER_WORD n, USER_WORD address)
+{
+  USER_WORD word, n_words, n_bytes, i;
+  long rc;
+#ifdef HAVE_PROCESS_VM
+  struct iovec iov, remote;
+
+  /* If `process_vm_readv' is available, use it instead.  */
+
+  iov.iov_base = buffer;
+  iov.iov_len = n;
+  remote.iov_base = (void *) address;
+  remote.iov_len = n;
+
+  /* Return immediately if successful.  As long as some bytes were
+     read, consider the read to have been a success.  */
+
+  if (n <= SSIZE_MAX
+      && ((size_t) process_vm_readv (tracee->pid, &iov, 1,
+                                    &remote, 1, 0) != -1))
+    return;
+
+#endif /* HAVE_PROCESS_VM */
+
+  /* First, read entire words from the tracee.  */
+  n_words = n & ~(sizeof (USER_WORD) - 1);
+
+  /* Next, determine the number of bytes to read from the last
+     word.  */
+  n_bytes = n & (sizeof (USER_WORD) - 1);
+
+  /* Start reading words.  */
+  i = 0;
+  while (n_words)
+    {
+      rc = ptrace (PTRACE_PEEKTEXT, tracee->pid,
+                  (void *) address + i, NULL);
+      word = rc;
+      memcpy (buffer, &word, sizeof word);
+      buffer += sizeof word;
+      i += sizeof word;
+      n_words -= sizeof word;
+    }
+
+  /* Now, read the remaining bytes.  */
+  assert (n_bytes < sizeof (word));
+
+  if (n_bytes)
+    {
+      rc = ptrace (PTRACE_PEEKTEXT, tracee->pid,
+                  (void *) address + i, NULL);
+      word = rc;
+
+      /* Copy only n_bytes to the caller.  */
+      memcpy (buffer, &word, n_bytes);
+    }
+}
+
+/* Allocate N bytes of memory from TRACEE's stack.  Return the address
+   of that memory upon success, else 0.
+
+   Place the updated user-mode registers of TRACEE in *NEW_REGS, which
+   should initially contain the current stack pointer of TRACEE.
+
+   REGS should contain the user mode registers of TRACEE prior to the
+   system call starting; it is not updated to reflect any changes.  */
+
+USER_WORD
+user_alloca (struct exec_tracee *tracee, USER_REGS_STRUCT *regs,
+            USER_REGS_STRUCT *new_regs, USER_WORD n)
+{
+  USER_WORD sp, old_sp;
+
+  /* Get the current stack pointer.  */
+  old_sp = sp = new_regs->STACK_POINTER;
+
+#if RED_ZONE_SIZE
+  /* Some ABI rules specify a ``red zone'' around the stack pointer
+     that is reserved for compiler optimizations.  */
+
+#ifdef STACK_GROWS_DOWNWARDS
+  if (sp == regs->STACK_POINTER)
+    sp -= RED_ZONE_SIZE;
+#else /* !STACK_GROWS_DOWNWARDS */
+  if (sp == regs->STACK_POINTER)
+    sp += RED_ZONE_SIZE;
+#endif /* STACK_GROWS_DOWNWARDS */
+#endif /* RED_ZONE_SIZE */
+
+  /* Now take N off the stack.  */
+
+#ifdef STACK_GROWS_DOWNWARDS
+  sp = sp - n;
+
+  /* Check for overflow.  */
+
+  if (sp > new_regs->STACK_POINTER)
+    return 0;
+#else /* !STACK_GROWS_DOWNWARDS */
+  sp = sp + n;
+
+  /* Check for overflow.  */
+
+  if (sp < new_regs->STACK_POINTER)
+    return 0;
+#endif /* STACK_GROWS_DOWNWARDS */
+
+  /* Set the stack pointer.  */
+  new_regs->STACK_POINTER = sp;
+
+#ifdef __aarch64__
+  if (aarch64_set_regs (tracee->pid, new_regs, false))
+    goto fail;
+#else /* !__aarch64__ */
+  if (ptrace (PTRACE_SETREGS, tracee->pid, NULL,
+             new_regs))
+    goto fail;
+#endif /* __aarch64__ */
+
+  /* Now return the start of the new area.  */
+#ifdef STACK_GROWS_DOWNWARDS
+  return sp;
+#else /* !STACK_GROWS_DOWNWARDS */
+  return sp - n;
+#endif /* STACK_GROWS_DOWNWARDS */
+
+ fail:
+  /* Restore the old stack pointer.  */
+  new_regs->STACK_POINTER = old_sp;
+  return 0;
+}
+
+/* Copy N bytes to ADDRESS in TRACEE's address space from BUFFER.
+   Value is 0 upon success, else 1.  */
+
+int
+user_copy (struct exec_tracee *tracee, const unsigned char *buffer,
+          USER_WORD address, USER_WORD n)
+{
+  USER_WORD start, end, word;
+  unsigned char *bytes;
+#ifdef HAVE_PROCESS_VM
+  struct iovec iov, remote;
+
+  /* Try to use `process_vm_writev' if possible, but fall back to
+     ptrace if something bad happens.  */
+
+  iov.iov_base = (void *) buffer;
+  iov.iov_len = n;
+  remote.iov_base = (void *) address;
+  remote.iov_len = n;
+
+  if (n <= SSIZE_MAX
+      && ((size_t) process_vm_writev (tracee->pid, &iov, 1,
+                                     &remote, 1, 0) == n))
+    return 0;
+#endif /* HAVE_PROCESS_VM */
+
+  /* Calculate the start and end positions for the write.  */
+
+  start = address;
+  end = address + n;
+
+  /* Write from start to the last word.  */
+
+  while (start < end)
+    {
+      if (start + sizeof word <= end)
+       {
+         /* Write a word by itself and increment start.  */
+         memcpy (&word, buffer, sizeof word);
+         buffer += sizeof word;
+
+         if (ptrace (PTRACE_POKEDATA, tracee->pid,
+                     (void *) start, (void *) word))
+           return 1;
+
+         start += sizeof word;
+       }
+      else
+       {
+         /* Only end - start bytes should be written.
+            Read the word at start from tracee->pid, then write
+            it back with changes.  */
+
+         word = ptrace (PTRACE_PEEKDATA, tracee->pid,
+                        (void *) start, NULL);
+         bytes = (unsigned char *) &word;
+         memcpy (bytes, buffer, end - start);
+
+         if (ptrace (PTRACE_POKEDATA, tracee->pid,
+                     (void *) start, (void *) word))
+           return 1;
+
+         /* Writing was successful.  */
+         return 0;
+       }
+    }
+
+  return 0;
+}
+
+
+
+/* Chain of free exec_tracee structures.  */
+static struct exec_tracee *free_tracees;
+
+/* Remove the specified TRACEE from the chain of all processes being
+   traced.  */
+
+static void
+remove_tracee (struct exec_tracee *tracee)
+{
+  struct exec_tracee **last;
+
+  last = &tracing_processes;
+  while (*last)
+    {
+      if (*last == tracee)
+       {
+         *last = tracee->next;
+
+         /* Link the tracee onto the list of free tracees.  */
+         tracee->next = free_tracees;
+
+#ifndef REENTRANT
+         /* Free the exec file, if any.  */
+         free (tracee->exec_file);
+         tracee->exec_file = NULL;
+#endif /* REENTRANT */
+
+         free_tracees = tracee;
+
+         return;
+       }
+      else
+       last = &(*last)->next;
+    }
+}
+
+
+
+/* Child process tracing.  */
+
+/* Array of `struct exec_tracees' that they are allocated from.  */
+static struct exec_tracee static_tracees[MAX_TRACEES];
+
+/* Number of tracees currently allocated.  */
+static int tracees;
+
+/* Return the `struct exec_tracee' corresponding to the specified
+   PROCESS.  */
+
+static struct exec_tracee *
+find_tracee (pid_t process)
+{
+  struct exec_tracee *tracee;
+
+  for (tracee = tracing_processes; tracee; tracee = tracee->next)
+    {
+      if (tracee->pid == process)
+       return tracee;
+    }
+
+  return NULL;
+}
+
+/* Prepare to handle the completion of a `clone' system call.
+
+   If the new clone is not yet being traced, create a new tracee for
+   PARENT's child, copying over its current command line.  Then, set
+   `new_child' in the new tracee.  Otherwise, continue it until the
+   next syscall.  */
+
+static void
+handle_clone_prepare (struct exec_tracee *parent)
+{
+#ifndef REENTRANT
+  long rc;
+  unsigned long pid;
+  struct exec_tracee *tracee;
+
+  rc = ptrace (PTRACE_GETEVENTMSG, parent->pid, NULL,
+              &pid);
+  if (rc)
+    return;
+
+  /* See if the tracee already exists.  */
+  tracee = find_tracee (pid);
+
+  if (tracee)
+    {
+      /* Continue the tracee.  Record its command line, as that has
+        not yet been done.  */
+
+      assert (tracee->new_child);
+      tracee->new_child = false;
+      tracee->exec_file = NULL;
+      ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0);
+
+      if (parent->exec_file)
+       tracee->exec_file = strdup (parent->exec_file);
+      return;
+    }
+
+  if (free_tracees)
+    {
+      tracee = free_tracees;
+      free_tracees = free_tracees->next;
+    }
+  else if (tracees < MAX_TRACEES)
+    {
+      tracee = &static_tracees[tracees];
+      tracees++;
+    }
+#ifndef REENTRANT
+  /* Try to allocate a tracee using `malloc' if this library is
+     not being built to run inside a signal handler.  */
+  else if ((tracee = malloc (sizeof *tracee)))
+    ;
+#endif /* REENTRANT */
+  else
+    return;
+
+  tracee->pid = pid;
+  tracee->next = tracing_processes;
+  tracee->waiting_for_syscall = false;
+  tracee->new_child = true;
+  tracee->exec_file = NULL;
+  tracing_processes = tracee;
+
+  /* Copy over the command line.  */
+
+  if (parent->exec_file)
+    tracee->exec_file = strdup (parent->exec_file);
+#endif /* REENTRANT */
+}
+
+/* Handle the completion of a `clone' or `clone3' system call,
+   resulting in the creation of the process PID.  If TRACEE is NULL,
+   allocate a new tracee structure from a static area for the
+   processes's pid, then set TRACEE->new_child to true and await the
+   parent's corresponding ptrace event to arrive; otherwise, just
+   clear TRACEE->new_child.
+
+   Value is 0 upon success, 2 if TRACEE should remain suspended until
+   the parent's ptrace-stop, and 1 otherwise.  */
+
+static int
+handle_clone (struct exec_tracee *tracee, pid_t pid)
+{
+  long rc;
+  int flags, value;
+
+  /* Now allocate a new tracee, either from static_tracees or the free
+     list, if no tracee was supplied.  */
+
+  value = 0;
+
+  if (!tracee)
+    {
+      if (free_tracees)
+       {
+         tracee = free_tracees;
+         free_tracees = free_tracees->next;
+       }
+      else if (tracees < MAX_TRACEES)
+       {
+         tracee = &static_tracees[tracees];
+         tracees++;
+       }
+#ifndef REENTRANT
+      /* Try to allocate a tracee using `malloc' if this library is
+        not being built to run inside a signal handler.  */
+      else if ((tracee = malloc (sizeof *tracee)))
+       ;
+#endif /* REENTRANT */
+      else
+       return 1;
+
+      tracee->pid = pid;
+      tracee->next = tracing_processes;
+      tracee->waiting_for_syscall = false;
+#ifndef REENTRANT
+      tracee->exec_file = NULL;
+#endif /* REENTRANT */
+      tracing_processes = tracee;
+      tracee->new_child = true;
+
+      /* Wait for the ptrace-stop to happen in the parent.  */
+      value = 2;
+    }
+  else
+    /* Clear the flag saying that this is a newly created child
+       process.  */
+    tracee->new_child = false;
+
+  /* Apply required options to the child, so that the kernel
+     automatically traces children and makes it easy to differentiate
+     between system call traps and other kinds of traps.  */
+
+  flags  = PTRACE_O_TRACECLONE;
+  flags |= PTRACE_O_TRACEVFORK;
+  flags |= PTRACE_O_TRACEFORK;
+  flags |= PTRACE_O_TRACESYSGOOD;
+  flags |= PTRACE_O_TRACEEXIT;
+
+  rc = ptrace (PTRACE_SETOPTIONS, pid, 0, flags);
+
+  if (rc)
+    goto bail;
+
+  if (value != 2)
+    {
+      /* The new tracee is currently stopped.  Continue it until the next
+        system call.  */
+
+      rc = ptrace (PTRACE_SYSCALL, pid, 0, 0);
+
+      if (rc)
+       goto bail;
+    }
+
+  return value;
+
+ bail:
+  remove_tracee (tracee);
+  return 1;
+}
+
+
+
+/* NOTICE: none of these functions should ever call `malloc' or
+   another async signal unsafe function.  */
+
+/* File name of the loader binary.  */
+static const char *loader_name;
+
+
+
+/* Return whether or not the trap signal described by SIGNAL is
+   generated by a system call being attempted by a tracee.  */
+
+static bool
+syscall_trap_p (siginfo_t *signal)
+{
+  /* SIGTRAP delivered by the kernel means this is a system call
+     stop.  */
+  return (signal->si_code == SIGTRAP
+         || signal->si_code == (SIGTRAP | SI_KERNEL));
+}
+
+/* Check if the wait status STATUS indicates a system call trap.
+   TRACEE is the process whose stop STATUS describes.  If TRACEE exits
+   while this information is being determined, return -1; if STATUS
+   indicates some other kind of stop, return 1 after continuing
+   TRACEE.  Value is 0 otherwise.  */
+
+static int
+check_signal (struct exec_tracee *tracee, int status)
+{
+  siginfo_t siginfo;
+
+  switch ((status & 0xfff00) >> 8)
+    {
+    case SIGTRAP:
+      /* Now, use PTRACE_GETSIGINFO to determine whether or not the
+        signal was delivered in response to a system call.  */
+
+      if (ptrace (PTRACE_GETSIGINFO, tracee->pid, 0, &siginfo))
+       return -1;
+
+      if (!syscall_trap_p (&siginfo))
+       {
+         if (siginfo.si_code < 0)
+           /* SIGTRAP delivered from userspace.  Pass it on.  */
+           ptrace (PTRACE_SYSCALL, tracee->pid, 0, SIGTRAP);
+         else
+           ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0);
+
+         return 1;
+       }
+
+    case SIGTRAP | 0x80: /* SIGTRAP | 0x80 specifically refers to
+                           system call traps.  */
+      break;
+
+#ifdef SIGSYS
+    case SIGSYS:
+      if (ptrace (PTRACE_GETSIGINFO, tracee->pid, 0, &siginfo))
+       return -1;
+
+      /* Continue the process until the next syscall, but don't
+        pass through the signal if an emulated syscall led to
+        it.  */
+#ifdef HAVE_SIGINFO_T_SI_SYSCALL
+#ifndef __arm__
+      ptrace (PTRACE_SYSCALL, tracee->pid,
+             0, ((siginfo.si_code == SYS_SECCOMP
+                  && siginfo.si_syscall == -1)
+                 ? 0 : status));
+#else /* __arm__ */
+      ptrace (PTRACE_SYSCALL, tracee->pid,
+             0, ((siginfo.si_code == SYS_SECCOMP
+                  && siginfo.si_syscall == 222)
+                 ? 0 : status));
+#endif /* !__arm__ */
+#else /* !HAVE_SIGINFO_T_SI_SYSCALL */
+      /* Drop this signal, since what caused it is unknown.  */
+      ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0);
+#endif /* HAVE_SIGINFO_T_SI_SYSCALL */
+      return 1;
+#endif /* SIGSYS */
+
+    default:
+      /* Continue the process until the next syscall.  */
+      ptrace (PTRACE_SYSCALL, tracee->pid, 0, status);
+      return 1;
+    }
+
+  return 0;
+}
+
+
+
+/* Handle an `exec' system call from the given TRACEE.  REGS are the
+   tracee's current user-mode registers.
+
+   Rewrite the system call arguments to use the loader binary.  Then,
+   continue the system call until the loader is loaded.  Write the
+   information necessary to load the original executable into the
+   loader's stack.
+
+   Value is 0 upon success, 1 upon a generic failure before the loader
+   is loaded, 2 if the process has stopped, and 3 if something failed,
+   but it is too late to handle it.
+
+   Set errno appropriately upon returning a generic failure.  */
+
+static int
+handle_exec (struct exec_tracee *tracee, USER_REGS_STRUCT *regs)
+{
+  char buffer[PATH_MAX + 80], *area;
+  USER_REGS_STRUCT original;
+  size_t size, loader_size;
+  USER_WORD loader, size1, sp;
+  int rc, wstatus;
+  siginfo_t siginfo;
+
+  /* Save the old stack pointer.  */
+  sp = regs->STACK_POINTER;
+
+  /* Read the file name.  */
+  read_memory (tracee, buffer, PATH_MAX,
+              regs->SYSCALL_ARG_REG);
+
+  /* Make sure BUFFER is NULL terminated.  */
+
+  if (!memchr (buffer, '\0', PATH_MAX))
+    {
+      errno = ENAMETOOLONG;
+      return 1;
+    }
+
+  /* Copy over the registers as they originally were.  */
+  memcpy (&original, regs, sizeof *regs);
+
+  /* Figure out what the loader needs to do.  */
+ again1:
+  area = exec_0 (buffer, tracee, &size, regs);
+
+  if (!area)
+    {
+      /* Handle SIGINTR errors caused by IO.  */
+      if (errno == EINTR)
+       goto again1;
+
+      return 1;
+    }
+
+  /* Rewrite the first argument to point to the loader.  */
+
+  loader_size = strlen (loader_name) + 1;
+  loader = user_alloca (tracee, &original, regs,
+                       loader_size);
+
+  if (!loader)
+    {
+      errno = ENOMEM;
+      return 1;
+    }
+
+  if (user_copy (tracee, (unsigned char *) loader_name,
+                loader, loader_size))
+    {
+      errno = EIO;
+      return 1;
+    }
+
+  regs->SYSCALL_ARG_REG = loader;
+
+#ifdef __aarch64__
+
+  if (aarch64_set_regs (tracee->pid, regs, false))
+    {
+      errno = EIO;
+      return 1;
+    }
+
+#else /* !__aarch64__ */
+
+  if (ptrace (PTRACE_SETREGS, tracee->pid, NULL,
+             regs))
+    {
+      errno = EIO;
+      return 1;
+    }
+
+#endif /* __aarch64__ */
+
+  /* Continue the system call until loader starts.  */
+
+  if (ptrace (PTRACE_SYSCALL, tracee->pid, NULL, NULL))
+    {
+      errno = EIO;
+      return 1;
+    }
+
+#ifndef REENTRANT
+  /* Now that the loader has started, record the value to use for
+     /proc/self/exe.  Don't give up just because strdup fails.
+
+     Note that exec_0 copies the absolute file name into buffer.  */
+
+  if (tracee->exec_file)
+    free (tracee->exec_file);
+  tracee->exec_file = strdup (buffer);
+#endif /* REENTRANT */
+
+ again:
+  rc = waitpid (tracee->pid, &wstatus, __WALL);
+  if (rc == -1 && errno == EINTR)
+    goto again;
+
+  if (rc < 0)
+    return 1;
+
+  if (!WIFSTOPPED (wstatus))
+    /* The process has been killed in response to a signal.
+       In this case, simply return 2.  */
+    return 2;
+  else
+    {
+      /* Then, check if STATUS is not a syscall-stop, and try again if
+        it isn't.  */
+      rc = check_signal (tracee, wstatus);
+
+      if (rc == -1)
+       return 2;
+      else if (rc)
+       goto again;
+
+      /* Retrieve the signal information and determine whether or not
+        the system call has completed.  */
+
+      if (ptrace (PTRACE_GETSIGINFO, tracee->pid, 0,
+                 &siginfo))
+       return 3;
+
+      if (!syscall_trap_p (&siginfo))
+       {
+         /* Continue.  */
+         if (ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0))
+           return 3;
+
+         goto again;
+       }
+    }
+
+#ifdef __aarch64__
+
+  if (aarch64_get_regs (tracee->pid, &original))
+    return 3;
+
+#else /* !__aarch64__ */
+
+  /* The system call has now completed.  Get the registers again.  */
+
+  if (ptrace (PTRACE_GETREGS, tracee->pid, NULL,
+             &original))
+    return 3;
+
+#endif /* __aarch64__ */
+
+  *regs = original;
+
+  /* Upon failure, wait for the next system call and return
+     success.  */
+
+  if (original.SYSCALL_RET_REG)
+    {
+      /* Restore the original stack pointer.  */
+      regs->STACK_POINTER = sp;
+
+#ifdef __aarch64__
+      aarch64_set_regs (tracee->pid, regs, false);
+#else /* !__aarch64__ */
+      ptrace (PTRACE_SETREGS, tracee->pid, NULL, regs);
+#endif /* __aarch64__ */
+
+      goto exec_failure;
+    }
+
+  /* Write the loader area to the stack, followed by its size and the
+     original stack pointer.  */
+
+  loader = user_alloca (tracee, &original, regs,
+                       size + sizeof loader * 2);
+  if (!loader)
+    return 3;
+
+  size1 = size;
+
+#ifndef STACK_GROWS_DOWNWARDS
+
+  NOT_IMPLEMENTED;
+
+#else /* STACK_GROWS_DOWNWARDS */
+
+  if (user_copy (tracee, (unsigned char *) area,
+                loader + sizeof size1 * 2, size)
+      || user_copy (tracee, (unsigned char *) &size1,
+                   loader + sizeof size1, sizeof size1))
+    return 3;
+
+  size1 = original.STACK_POINTER;
+
+  if (user_copy (tracee, (unsigned char *) &size1,
+                loader, sizeof size1))
+    return 3;
+
+#endif /* STACK_GROWS_DOWNWARDS */
+
+  /* Continue.  */
+  if (ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0))
+    return 3;
+
+  return 0;
+
+ exec_failure:
+  return 3;
+}
+
+/* Handle a `readlink' or `readlinkat' system call.
+
+   CALLNO is the system call number, and REGS are the current user
+   registers of the TRACEE.
+
+   If the file name specified in either a `readlink' or `readlinkat'
+   system call is `/proc/self/exe', write the name of the executable
+   being run into the buffer specified in the system call.  Do not
+   handle relative file names at the moment.
+
+   Return the number of bytes written to the tracee's buffer in
+   *RESULT.
+
+   Value is 0 upon success.  Value is 1 upon failure, and 2 if the
+   system call has been emulated.  */
+
+static int
+handle_readlinkat (USER_WORD callno, USER_REGS_STRUCT *regs,
+                  struct exec_tracee *tracee, USER_WORD *result)
+{
+#ifdef REENTRANT
+  /* readlinkat cannot be handled specially when the library is built
+     to be reentrant, as the file name information cannot be
+     recorded.  */
+  return 0;
+#else /* !REENTRANT */
+
+  char buffer[PATH_MAX + 1];
+  USER_WORD address, return_buffer, size;
+  size_t length;
+
+  /* Read the file name.  */
+
+#ifdef READLINK_SYSCALL
+  if (callno == READLINK_SYSCALL)
+    {
+      address = regs->SYSCALL_ARG_REG;
+      return_buffer = regs->SYSCALL_ARG1_REG;
+      size = regs->SYSCALL_ARG2_REG;
+    }
+  else
+#endif /* READLINK_SYSCALL */
+    {
+      address = regs->SYSCALL_ARG1_REG;
+      return_buffer = regs->SYSCALL_ARG2_REG;
+      size = regs->SYSCALL_ARG3_REG;
+    }
+
+  read_memory (tracee, buffer, PATH_MAX, address);
+
+  /* Make sure BUFFER is NULL terminated.  */
+
+  if (!memchr (buffer, '\0', PATH_MAX))
+    {
+      errno = ENAMETOOLONG;
+      return 1;
+    }
+
+  /* Now check if the caller is looking for /proc/self/exe.
+
+     dirfd can be ignored, as for now only absolute file names are
+     handled.  FIXME.  */
+
+  if (strcmp (buffer, "/proc/self/exe") || !tracee->exec_file)
+    return 0;
+
+  /* Copy over tracee->exec_file.  Truncate it to PATH_MAX, length, or
+     size, whichever is less.  */
+
+  length = strlen (tracee->exec_file);
+  length = MIN (size, MIN (PATH_MAX, length));
+  strncpy (buffer, tracee->exec_file, length);
+
+  if (user_copy (tracee, (unsigned char *) buffer,
+                return_buffer, length))
+    {
+      errno = EIO;
+      return 1;
+    }
+
+  *result = length;
+  return 2;
+#endif /* REENTRANT */
+}
+
+/* Process the system call at which TRACEE is stopped.  If the system
+   call is not known or not exec, send TRACEE on its way.  Otherwise,
+   rewrite it to load the loader and perform an appropriate action.  */
+
+static void
+process_system_call (struct exec_tracee *tracee)
+{
+  USER_REGS_STRUCT regs;
+  int rc, wstatus, save_errno;
+  USER_WORD callno, sp;
+  USER_WORD result;
+  bool reporting_error;
+
+#ifdef __aarch64__
+  rc = aarch64_get_regs (tracee->pid, &regs);
+#else /* !__aarch64__ */
+  rc = ptrace (PTRACE_GETREGS, tracee->pid, NULL,
+              &regs);
+#endif /* __aarch64__ */
+
+  /* TODO: what to do if this fails? */
+  if (rc < 0)
+    return;
+
+  /* Save the stack pointer.  */
+  sp = regs.STACK_POINTER;
+
+  /* Now dispatch based on the system call.  */
+  callno = regs.SYSCALL_NUM_REG;
+  switch (callno)
+    {
+    case EXEC_SYSCALL:
+
+      /* exec system calls should be handled synchronously.  */
+      assert (!tracee->waiting_for_syscall);
+      rc = handle_exec (tracee, &regs);
+
+      switch (rc)
+       {
+       case 3:
+         /* It's too late to do anything about this error,.  */
+         break;
+
+       case 2:
+         /* The process has gone away.  */
+         remove_tracee (tracee);
+         break;
+
+       case 1:
+         /* An error has occured; errno is set to the error.  */
+         goto report_syscall_error;
+       }
+
+      break;
+
+#ifdef READLINK_SYSCALL
+    case READLINK_SYSCALL:
+#endif /* READLINK_SYSCALL */
+    case READLINKAT_SYSCALL:
+
+      /* Handle this readlinkat system call.  */
+      rc = handle_readlinkat (callno, &regs, tracee,
+                             &result);
+
+      /* rc means the same as in `handle_exec'.  */
+
+      if (rc == 1)
+       goto report_syscall_error;
+      else if (rc == 2)
+       goto emulate_syscall;
+
+      /* Fallthrough.  */
+
+    default:
+      /* Don't wait for the system call to finish; instead, the system
+        will DTRT upon the next call to PTRACE_SYSCALL after the
+        syscall-trap signal is delivered.  */
+
+      rc = ptrace (PTRACE_SYSCALL, tracee->pid,
+                  NULL, NULL);
+      if (rc < 0)
+       return;
+
+      tracee->waiting_for_syscall = !tracee->waiting_for_syscall;
+    }
+
+  return;
+
+ report_syscall_error:
+  reporting_error = true;
+  goto common;
+
+ emulate_syscall:
+  reporting_error = false;
+ common:
+
+  /* Reporting an error or emulating a system call works by setting
+     the system call number to -1, letting it continue, and then
+     substituting errno for ENOSYS in the case of an error.
+
+     Make sure that the stack pointer is restored to its original
+     position upon exit, or bad things can happen.  */
+
+  /* First, save errno; system calls below will clobber it.  */
+  save_errno = errno;
+
+  regs.SYSCALL_NUM_REG = -1;
+  regs.STACK_POINTER   = sp;
+
+#ifdef __aarch64__
+  if (aarch64_set_regs (tracee->pid, &regs, true))
+    return;
+#else /* !__aarch64__ */
+
+#ifdef __arm__
+  /* On ARM systems, a special request is used to update the system
+     call number as known to the kernel.  In addition, the system call
+     number must be valid, so use `tuxcall'.  Hopefully, nobody will
+     run this on a kernel with Tux.  */
+
+  if (ptrace (PTRACE_SET_SYSCALL, tracee->pid, NULL, 222))
+    return;
+#endif /* __arm__ */
+
+  if (ptrace (PTRACE_SETREGS, tracee->pid, NULL, &regs))
+    return;
+#endif /* __aarch64__ */
+
+  /* Do this invalid system call.  */
+  if (ptrace (PTRACE_SYSCALL, tracee->pid, NULL, NULL))
+    return;
+
+ again1:
+  rc = waitpid (tracee->pid, &wstatus, __WALL);
+  if (rc == -1 && errno == EINTR)
+    goto again1;
+
+  /* Return if waitpid fails.  */
+
+  if (rc == -1)
+    return;
+
+  /* If the process received a signal, see if the signal is SIGSYS and
+     from seccomp.  If so, discard it.  */
+
+  if (WIFSTOPPED (wstatus))
+    {
+      rc = check_signal (tracee, wstatus);
+
+      if (rc == -1)
+       return;
+      else if (rc)
+       goto again1;
+    }
+
+  if (!WIFSTOPPED (wstatus))
+    /* The process has been killed in response to a signal.  In this
+       case, simply unlink the tracee and return.  */
+    remove_tracee (tracee);
+  else if (reporting_error)
+    {
+#ifdef __mips__
+      /* MIPS systems place errno in v0 and set a3 to 1.  */
+      regs.gregs[2] = save_errno;
+      regs.gregs[7] = 1;
+#else /* !__mips__ */
+      regs.SYSCALL_RET_REG = -save_errno;
+#endif /* __mips__ */
+
+      /* Report errno.  */
+#ifdef __aarch64__
+      aarch64_set_regs (tracee->pid, &regs, false);
+#else /* !__aarch64__ */
+      ptrace (PTRACE_SETREGS, tracee->pid, NULL, &regs);
+#endif /* __aarch64__ */
+
+      /* Now wait for the next system call to happen.  */
+      ptrace (PTRACE_SYSCALL, tracee->pid, NULL, NULL);
+    }
+  else
+    {
+      /* No error is being reported.  Return the result in the
+        appropriate registers.  */
+
+#ifdef __mips__
+      /* MIPS systems place errno in v0 and set a3 to 1.  */
+      regs.gregs[2] = result;
+      regs.gregs[7] = 0;
+#else /* !__mips__ */
+      regs.SYSCALL_RET_REG = result;
+#endif /* __mips__ */
+
+      /* Report errno.  */
+#ifdef __aarch64__
+      aarch64_set_regs (tracee->pid, &regs, false);
+#else /* !__aarch64__ */
+      ptrace (PTRACE_SETREGS, tracee->pid, NULL, &regs);
+#endif /* __aarch64__ */
+
+      /* Now wait for the next system call to happen.  */
+      ptrace (PTRACE_SYSCALL, tracee->pid, NULL, NULL);
+    }
+}
+
+
+
+/* Like `execve', but asks the parent to begin tracing this thread.
+   Fail if tracing is unsuccessful.  */
+
+int
+tracing_execve (const char *file, char *const *argv,
+               char *const *envp)
+{
+  int rc;
+
+  /* Start tracing self.  */
+  rc = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+  if (rc)
+    return rc;
+
+  /* Notify the parent to enter signal-delivery-stop.  */
+  raise (SIGSTOP);
+  return execve (file, argv, envp);
+}
+
+/* Wait for PID to trace itself, and make a record of that process.
+   Value is 1 or 2 upon failure, 0 otherwise.  Make sure that SIGCHLD
+   is blocked around calls to this function.
+
+   If failure occurs because PID exited, value is 2; upon any other
+   kind of failure, value is 1.  */
+
+int
+after_fork (pid_t pid)
+{
+  int wstatus, rc, flags;
+  struct exec_tracee *tracee;
+
+  /* First, wait for something to happen to PID.  */
+ again:
+  rc = waitpid (pid, &wstatus, __WALL);
+  if (rc != pid && errno == EINTR)
+    goto again;
+
+  if (rc != pid)
+    return 1;
+
+  /* If the child exited (or in general wasn't traced), return 2.  */
+
+  if (!WIFSTOPPED (wstatus))
+    return 2;
+
+  /* Apply required options to the child, so that the kernel
+     automatically traces children and makes it easy to differentiate
+     between system call traps and other kinds of traps.  */
+
+  flags  = PTRACE_O_TRACECLONE;
+  flags |= PTRACE_O_TRACEVFORK;
+  flags |= PTRACE_O_TRACEFORK;
+  flags |= PTRACE_O_TRACESYSGOOD;
+  flags |= PTRACE_O_TRACEEXIT;
+
+  rc = ptrace (PTRACE_SETOPTIONS, pid, 0, flags);
+
+  if (rc)
+    {
+      /* If the kernel can't trace child processes upon creation and
+        exit, then it can't work reliably.  */
+      ptrace (PTRACE_DETACH, pid, 0, 0);
+      return 1;
+    }
+
+  /* Request that the child stop upon the next system call.  */
+  rc = ptrace (PTRACE_SYSCALL, pid, 0, 0);
+  if (rc)
+    return 1;
+
+  /* Enter the child in `tracing_processes'.  */
+
+  if (free_tracees)
+    {
+      tracee = free_tracees;
+      free_tracees = free_tracees->next;
+    }
+  else
+    tracee = malloc (sizeof *tracee);
+
+  if (!tracee)
+    return 1;
+
+  tracee->pid = pid;
+  tracee->next = tracing_processes;
+  tracee->waiting_for_syscall = false;
+  tracee->new_child = false;
+#ifndef REENTRANT
+  tracee->exec_file = NULL;
+#endif /* REENTRANT */
+  tracing_processes = tracee;
+  return 0;
+}
+
+/* Wait for a child process to exit, like `waitpid'.  However, if a
+   child stops to perform a system call, send it on its way and return
+   -1.  OPTIONS must not contain WUNTRACED.  */
+
+pid_t
+exec_waitpid (pid_t pid, int *wstatus, int options)
+{
+  int status;
+  struct exec_tracee *tracee;
+  siginfo_t siginfo;
+
+  pid = waitpid (pid, &status, options | __WALL);
+  if (pid < 0)
+    return pid;
+
+  /* Copy status into *WSTATUS if specified.  */
+  if (wstatus)
+    *wstatus = status;
+
+  /* WIFSTOPPED (status) means that the process has been stopped in
+     response to a system call.  Find its tracee and process the
+     system call.  */
+
+  if (WIFSTOPPED (status))
+    {
+      tracee = find_tracee (pid);
+
+      if (!tracee || tracee->new_child)
+       {
+         if (WSTOPSIG (status) == SIGSTOP)
+           /* A new process has been created and stopped.  Record
+              it now.  */
+           handle_clone (tracee, pid);
+
+         return -1;
+       }
+
+      /* Now extract the stop signal, including ptrace event bits.  */
+      status &= 0xfff00;
+      status = status >> 8;
+
+      switch (status)
+       {
+       case SIGTRAP:
+         /* Now, use PTRACE_GETSIGINFO to determine whether or not the
+            signal was delivered in response to a system call.  */
+
+         if (ptrace (PTRACE_GETSIGINFO, pid, 0, &siginfo))
+           return -1;
+
+         if (!syscall_trap_p (&siginfo))
+           {
+             if (siginfo.si_code < 0)
+               /* SIGTRAP delivered from userspace.  Pass it on.  */
+               ptrace (PTRACE_SYSCALL, pid, 0, SIGTRAP);
+             else
+               ptrace (PTRACE_SYSCALL, pid, 0, 0);
+
+             return -1;
+           }
+
+       case SIGTRAP | 0x80: /* SIGTRAP | 0x80 specifically refers to
+                               system call traps.  */
+         /* Otherwise, process the system call and continue waiting.  */
+         process_system_call (tracee);
+         return -1;
+
+       case SIGTRAP | (PTRACE_EVENT_EXIT << 8):
+         /* The tracee has exited.  Make it finish correctly.  */
+         ptrace (PTRACE_SYSCALL, pid, 0, 0);
+         remove_tracee (tracee);
+         return -1;
+
+       case SIGTRAP | (PTRACE_EVENT_FORK << 8):
+       case SIGTRAP | (PTRACE_EVENT_VFORK << 8):
+       case SIGTRAP | (PTRACE_EVENT_CLONE << 8):
+
+         /* Both PTRACE_EVENT_CLONE and SIGSTOP must arrive before a
+            process is continued.  Otherwise, its parent's cmdline
+            cannot be obtained and propagated.
+
+            If the PID of the new process is currently not being
+            traced, create a new tracee.  Set `new_child' to true,
+            and copy over the old command line in preparation for a
+            SIGSTOP signal being delivered to it.
+
+            Otherwise, start the tracee running until the next
+            syscall.  */
+
+         handle_clone_prepare (tracee);
+
+         /* These events are handled by tracing SIGSTOP signals sent
+            to unknown tracees.  Make sure not to pass through
+            status, as there's no signal really being delivered.  */
+         ptrace (PTRACE_SYSCALL, pid, 0, 0);
+         return -1;
+
+#ifdef SIGSYS
+       case SIGSYS:
+         if (ptrace (PTRACE_GETSIGINFO, pid, 0, &siginfo))
+           return -1;
+
+         /* Continue the process until the next syscall, but don't
+            pass through the signal if an emulated syscall led to
+            it.  */
+#ifdef HAVE_SIGINFO_T_SI_SYSCALL
+#ifndef __arm__
+         ptrace (PTRACE_SYSCALL, pid, 0, ((siginfo.si_code == SYS_SECCOMP
+                                           && siginfo.si_syscall == -1)
+                                          ? 0 : status));
+#else /* __arm__ */
+         ptrace (PTRACE_SYSCALL, pid, 0, ((siginfo.si_code == SYS_SECCOMP
+                                           && siginfo.si_syscall == 222)
+                                          ? 0 : status));
+#endif /* !__arm__ */
+#else /* !HAVE_SIGINFO_T_SI_SYSCALL */
+         /* Drop this signal, since what caused it is unknown.  */
+         ptrace (PTRACE_SYSCALL, pid, 0, 0);
+#endif /* HAVE_SIGINFO_T_SI_SYSCALL */
+         return -1;
+#endif /* SIGSYS */
+
+       default:
+         /* Continue the process until the next syscall.  */
+         ptrace (PTRACE_SYSCALL, pid, 0, status);
+         return -1;
+       }
+    }
+  else
+    {
+      /* The process has exited.  Unlink the associated tracee.  */
+      tracee = find_tracee (pid);
+
+      if (tracee)
+       remove_tracee (tracee);
+
+      return pid;
+    }
+}
+
+
+
+/* Initialize the exec library.  LOADER should be the file name of the
+   loader binary; it is not copied.  */
+
+void
+exec_init (const char *loader)
+{
+  loader_name = loader;
+}
diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in
new file mode 100644
index 00000000000..2cbcdbc3e5b
--- /dev/null
+++ b/java/AndroidManifest.xml.in
@@ -0,0 +1,230 @@
+<!-- @configure_input@
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<!-- targetSandboxVersion must be 1.  Otherwise, fascist security
+     restrictions prevent Emacs from making HTTP connections.  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android";
+         package="org.gnu.emacs"
+         android:targetSandboxVersion="1"
+         android:installLocation="auto"
+         android:requestLegacyExternalStorage="true"
+         @ANDROID_SHARED_USER_ID@
+         @ANDROID_SHARED_USER_NAME@
+         android:versionCode="@emacs_major_version@"
+         android:versionName="@version@">
+
+  <!-- Paste in every permission in existence so Emacs can do
+       anything.  -->
+
+  <uses-permission android:name="android.permission.READ_CONTACTS" />
+  <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+  <uses-permission android:name="android.permission.VIBRATE" />
+  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+  <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.SET_WALLPAPER" />
+  <!-- Despite the claim that WRITE_EXTERNAL_STORAGE also covers
+       reading from external storage, specifying READ_EXTERNAL_STORAGE
+       seems to still be necessary on some versions of Android.
+       (bug#64445) -->
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+  <uses-permission android:name="android.permission.SEND_SMS" />
+  <uses-permission android:name="android.permission.RECEIVE_SMS" />
+  <uses-permission android:name="android.permission.RECEIVE_MMS"/>
+  <uses-permission android:name="android.permission.WRITE_SMS"/>
+  <uses-permission android:name="android.permission.READ_SMS"/>
+  <uses-permission android:name="android.permission.NFC" />
+  <uses-permission android:name="android.permission.TRANSMIT_IR" />
+  <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+  <uses-permission android:name="android.permission.WAKE_LOCK"/>
+  <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+  <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+  <uses-permission android:name="android.permission.RECORD_AUDIO" />
+  <uses-permission android:name="android.permission.CAMERA" />
+
+  <!-- This is required on Android 11 or later to access /sdcard.  -->
+
+  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+  <!-- And under Android 13 or later to post desktop
+       notifications.  -->
+
+  <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+
+  <uses-sdk android:minSdkVersion="@ANDROID_MIN_SDK@"
+           android:targetSdkVersion="33"/>
+
+  <application android:name="org.gnu.emacs.EmacsApplication"
+              android:label="Emacs"
+              android:icon="@mipmap/emacs_icon"
+              android:hardwareAccelerated="true"
+              android:supportsRtl="true"
+              android:theme="@style/EmacsStyle"
+              android:debuggable="@ANDROID_DEBUGGABLE@"
+              android:allowBackup="true"
+              android:extractNativeLibs="true">
+
+    <activity android:name="org.gnu.emacs.EmacsActivity"
+             android:launchMode="singleInstance"
+             android:windowSoftInputMode="adjustResize"
+             android:exported="true"
+             
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+
+    <activity android:name="org.gnu.emacs.EmacsOpenActivity"
+             android:taskAffinity="open.dialog"
+             android:excludeFromRecents="true"
+             android:exported="true">
+
+      <!-- Allow Emacs to open all kinds of files known to Android.  -->
+
+      <intent-filter>
+        <action android:name="android.intent.action.VIEW"/>
+       <action android:name="android.intent.action.EDIT"/>
+       <action android:name="android.intent.action.PICK"/>
+
+        <category android:name="android.intent.category.DEFAULT"/>
+
+       <data android:mimeType="image/aces"/>
+       <data android:mimeType="image/avci"/>
+       <data android:mimeType="image/avcs"/>
+       <data android:mimeType="image/avif"/>
+       <data android:mimeType="image/bmp"/>
+       <data android:mimeType="image/cgm"/>
+       <data android:mimeType="image/dicom-rle"/>
+       <data android:mimeType="image/dpx"/>
+       <data android:mimeType="image/emf"/>
+       <data android:mimeType="image/example"/>
+       <data android:mimeType="image/fits"/>
+       <data android:mimeType="image/g3fax"/>
+       <data android:mimeType="image/heic"/>
+       <data android:mimeType="image/heic-sequence"/>
+       <data android:mimeType="image/heif"/>
+       <data android:mimeType="image/heif-sequence"/>
+       <data android:mimeType="image/hej2k"/>
+       <data android:mimeType="image/hsj2"/>
+       <data android:mimeType="image/jls"/>
+       <data android:mimeType="image/jp2"/>
+       <data android:mimeType="image/jph"/>
+       <data android:mimeType="image/jphc"/>
+       <data android:mimeType="image/jpm"/>
+       <data android:mimeType="image/jpx"/>
+       <data android:mimeType="image/jxr"/>
+       <data android:mimeType="image/jxrA"/>
+       <data android:mimeType="image/jxrS"/>
+       <data android:mimeType="image/jxs"/>
+       <data android:mimeType="image/jxsc"/>
+       <data android:mimeType="image/jxsi"/>
+       <data android:mimeType="image/jxss"/>
+       <data android:mimeType="image/ktx"/>
+       <data android:mimeType="image/ktx2"/>
+       <data android:mimeType="image/naplps"/>
+       <data android:mimeType="image/png"/>
+       <data android:mimeType="image/prs.btif"/>
+       <data android:mimeType="image/prs.pti"/>
+       <data android:mimeType="image/pwg-raster"/>
+       <data android:mimeType="image/svg+xml"/>
+       <data android:mimeType="image/t38"/>
+       <data android:mimeType="image/tiff"/>
+       <data android:mimeType="image/tiff-fx"/>
+       <data android:mimeType="image/xpm"/>
+       <data android:mimeType="text/*"/>
+        <data android:mimeType="application/*xml"/>
+        <data android:mimeType="application/atom+xml"/>
+        <data android:mimeType="application/dxf"/>
+        <data android:mimeType="application/ecmascript"/>
+        <data android:mimeType="application/javascript"/>
+        <data android:mimeType="application/json"/>
+        <data android:mimeType="application/*log*"/>
+        <data android:mimeType="application/octet-stream"/>
+        <data android:mimeType="application/soap+xm"/>
+        <data android:mimeType="application/x-caramel"/>
+        <data android:mimeType="application/x-klaunch"/>
+        <data android:mimeType="application/x-latex"/>
+        <data android:mimeType="application/x-sh"/>
+        <data android:mimeType="application/x-tcl"/>
+        <data android:mimeType="application/x-tex*"/>
+        <data android:mimeType="application/x-troff*"/>
+        <data android:mimeType="application/xhtml+xml"/>
+        <data android:mimeType="application/xml*"/>
+        <data android:mimeType="application/zip"/>
+        <data android:mimeType="application/x-zip-compressed"/>
+      </intent-filter>
+    </activity>
+
+    <activity android:name="org.gnu.emacs.EmacsMultitaskActivity"
+             android:windowSoftInputMode="adjustResize"
+             android:exported="true"
+             
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"/>
+
+    <activity android:autoRemoveFromRecents="true"
+              android:label="Emacs options"
+             android:exported="true"
+              android:name=".EmacsPreferencesActivity">
+      <intent-filter>
+        <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
+        <category android:name="android.intent.category.DEFAULT" />
+      </intent-filter>
+    </activity>
+
+    <!-- Android 6 and earlier don't display ``application
+         preferences'' activities in Settings, so display the
+         preferences activity as a launcher icon instead.  -->
+
+    <activity android:autoRemoveFromRecents="true"
+              android:label="Emacs options"
+             android:enabled="@bool/isBeforeNougat"
+             android:exported="@bool/isBeforeNougat"
+             android:icon="@drawable/emacs_wrench"
+              android:name=".EmacsLauncherPreferencesActivity">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+
+    <provider android:name="org.gnu.emacs.EmacsDocumentsProvider"
+             android:authorities="org.gnu.emacs"
+             android:exported="true"
+             android:grantUriPermissions="true"
+             android:permission="android.permission.MANAGE_DOCUMENTS"
+             android:enabled="@bool/isAtLeastKitKat">
+      <intent-filter>
+       <action
+           android:name="android.content.action.DOCUMENTS_PROVIDER"/>
+      </intent-filter>
+    </provider>
+
+    <service android:name="org.gnu.emacs.EmacsService"
+            android:directBootAware="false"
+            android:enabled="true"
+            android:exported="false"
+            android:label="GNU Emacs service"/>
+  </application>
+</manifest>
diff --git a/java/INSTALL b/java/INSTALL
new file mode 100644
index 00000000000..fb235af1346
--- /dev/null
+++ b/java/INSTALL
@@ -0,0 +1,1028 @@
+Installation instructions for Android
+Copyright (C) 2023 Free Software Foundation, Inc.
+See the end of the file for license conditions.
+
+Please read the entirety of this file before attempting to build Emacs
+as an application package which can run on Android devices.
+
+When building from the source repository (as opposed to from a release
+tarball), make sure to read INSTALL.REPO in the top-level directory as
+well.
+
+
+
+Android is an unusual operating system in that program binaries cannot
+be produced on computers running Android themselves.  Instead, they
+must be built on some other computer using a set of tools known as the
+``Android SDK'' (Software Development Kit) and the ``Android NDK''
+(Native Development Kit.)  Appropriate versions of both must be
+obtained to build GNU Emacs; after being built, the generated binaries
+will work on almost all Android devices.  This document does not
+elaborate on how both sets of tools can be obtained.  However, for
+your freedom's sake, you should use the Android SDK provided by the
+Debian project.
+
+In addition to the Android SDK and Android NDK, Emacs also requires
+the Java compiler from OpenJDK 1.7.0 to be installed on your system,
+along with a working `m4' macro processor.  Building on GNU systems is
+all that is officially supported.  We are told that Mac OS works too,
+and other Unix systems will likely work as well, but MS Windows and
+Cygwin will not.
+
+Once all of those tools are obtained, you may invoke the `configure'
+script like so:
+
+  ./configure --with-android=/path/to/android.jar \
+             ANDROID_CC=/path/to/android/ndk/cc  \
+             SDK_BUILD_TOOLS=/path/to/sdk/build/tools
+
+Replacing the paths in the command line above with:
+
+  - the path to the `android.jar' headers which come with the Android
+    SDK.  They must correspond to Android version 13 (API level 33).
+
+  - the path to the C compiler in the Android NDK, for the kind of CPU
+    you are building Emacs to run on.
+
+  - the path to the directory in the Android SDK containing binaries
+    such as `aapt', `apksigner', and `d8'.  These are used to build
+    the application package.
+
+Where the type of CPU can either be `armeabi', `armv7*', `i686',
+`x86_64', `mips', or `mips64'.
+
+After the configuration process completes, you may run:
+
+  make all
+
+Once `make' finishes, there should be a file in the `java' directory
+named along the lines of:
+
+  emacs-<version>-<api-version>-<abi>.apk
+
+where <api-version> is the oldest version of Android that the package
+will run on, and <abi> is the type of Android machine the package was
+built for.
+
+The generated package can be uploaded onto an SD card (or similar
+medium) and installed on-device.
+
+
+LOCATING NECESSARY FILES
+
+As illustrated above, building Emacs for Android requires the presence
+three separate components of the Android SDK and NDK.  Subsequent to
+their installation, the contents of the Android development tools are
+organized into several directories, of which those pertinent to the
+Emacs compilation process are:
+
+  platforms
+  ndk
+  build-tools
+
+The platforms directory contains one subdirectory for each API level
+whose headers have been installed.  Each of these directories in turn
+includes the android.jar archive for that version of Android, also
+necessary for compiling Emacs.
+
+It is imperative that Emacs is compiled using the headers for the
+exact API level that it is written for.  This is currently API level
+33, so the correct android.jar archive is located within a directory
+whose name begins with `android-33'.  Minor revisions to the headers
+are inconsequential towards the Emacs compilation process; if there is
+a directory named `android-33-extN' (where N represents a revision to
+the Android SDK), whether you provide `configure' with that
+directory's android.jar or the android.jar contained within the
+directory named `android-33' is of no special importance.
+
+The ndk directory contains one subdirectory for each version of the
+Android NDK installed.  This directory in turn contains the C and C++
+compilation system.  In contrast to the Java headers mentioned within
+the previous paragraph, the version of the NDK used does not affect
+Emacs to the extent the version of android.jar does.  Having said
+that, each version of the NDK only supports a limited range of API
+levels; your choice of C compiler binary (or __ANDROID_API__) bears
+upon the earliest version of Android the compiled package will
+support.
+
+In most cases, each subdirectory contains a folder named `toolchains',
+holding an `llvm' directory and one directory for each GCC toolchain
+supplied by the NDK.  The C compiler is then positioned within
+`prebuilt/*/bin' inside that directory.
+
+The build-tools directory holds subdirectories containing the utility
+programs used to convert class files output by the Java compiler to
+the DEX format employed by Android.  There is one subdirectory for
+each version of the build tools, but the version you opt for is not of
+paramount significance: if your version does not work, configure will
+protest, so install a newer one.  We anticipate that most recent
+releases will work, such as those from the 33.0.x and 34.0.x series.
+
+
+BUILDING WITH OLD NDK VERSIONS
+
+Building Emacs with an old version of the Android NDK requires special
+setup. This is because there is no separate C compiler binary for
+each version of Android in those versions of the NDK.
+
+Before running `configure', you must identify three variables:
+
+  - What kind of Android system you are building Emacs for.
+
+  - The minimum API version of Android you want to build Emacs for.
+
+  - The locations of the system root and include files for that
+    version of Android in the NDK.
+
+That information must then be specified as arguments to the NDK C
+compiler.  For example:
+
+  ./configure [...] \
+     ANDROID_CC="i686-linux-android-gcc \
+                --sysroot=/path/to/ndk/platforms/android-14/arch-x86/"
+     ANDROID_CFLAGS="-isystem /path/to/ndk/sysroot/usr/include \
+                    -isystem 
/path/to/ndk/sysroot/usr/include/i686-linux-android \
+                    -D__ANDROID_API__=14"
+
+Where __ANDROID_API__ and the version identifier in
+"platforms/android-14" defines the version of Android you are building
+for, and the include directories specify the paths to the relevant
+Android headers.  In addition, it may be necessary to specify
+"-gdwarf-2", due to a bug in the Android NDK.
+
+Even older versions of the Android SDK do not require the extra
+`-isystem' directives.
+
+Emacs is known to run on Android 2.2 (API version 8) or later, with
+the NDK r10b or later.  We wanted to make Emacs work on even older
+versions of Android, but they are missing the required JNI graphics
+library that allows Emacs to display text from C code.
+
+Due to an extremely nasty bug in the Android 2.2 system, the generated
+Emacs package cannot be compressed in builds for Android 2.2.  As a
+result, the Emacs package will be approximately 100 megabytes larger
+than a compressed package for a newer version of Android.
+
+
+BUILDING C++ DEPENDENCIES
+
+With a new version of the NDK, dependencies containing C++ code should
+build without any futher configuration.  However, older versions
+require that you use the ``make_standalone_toolchain.py'' script in
+the NDK distribution to create a ``standalone toolchain'', and use
+that instead, in order for C++ headers to be found.
+
+See https://developer.android.com/ndk/guides/standalone_toolchain for
+more details; when a ``standalone toolchain'' is specified, the
+configure script will try to determine the location of the C++
+compiler based on the C compiler specified.  If that automatic
+detection does not work, you can specify a C++ compiler yourself, like
+so:
+
+  ./configure --with-ndk-cxx=/path/to/toolchain/bin/i686-linux-android-g++
+
+Some versions of the NDK have a bug, where GCC fails to locate
+``stddef.h'' after being copied to a standalone toolchain.  To work
+around this problem (which normally exhibits itself when building C++
+code), add:
+
+   -isystem /path/to/toolchain/include/c++/4.9.x
+
+to ANDROID_CFLAGS.
+
+
+DEBUG AND RELEASE BUILDS
+
+Android makes a distinction between ``debug'' and ``release'' builds
+of applications.  With ``release'' builds, the system will apply
+stronger optimizations to the application at the cost of being unable
+to debug them with the steps in etc/DEBUG.
+
+Emacs is built as a debuggable package by default, but:
+
+      ./configure --without-android-debug
+
+will create a release build of Emacs instead.  This may be useful when
+running Emacs on resource constrained machines.
+
+If you are building an Emacs package for redistribution, we urge you
+to provide both debug and release versions.
+
+
+BUILDING WITH A SHARED USER ID
+
+Sometimes it may be desirable to build Emacs so that it is able to
+access executables and application data from another program.  To
+achieve this, that other program must have a ``shared user ID'', and
+be signed with the same signing key used to sign Emacs (normally
+`emacs.keystore'.)
+
+Once you have both that signing key and its ``shared user ID'', you
+can give it to configure:
+
+    ./configure --with-shared-user-id=MY.SHARED.USER.ID
+
+For instance,
+
+    ./configure --with-shared-user-id=com.termux
+
+will result in Termux (https://termux.dev)'s application data being
+accessible to Emacs, within its own application data directory located
+at `/data/data/com.termux/files'.
+
+Don't do this if you already have Emacs installed with a different
+shared user ID, as the system does not allow programs to change their
+user IDs after being installed.
+
+
+BUILDING WITH THIRD PARTY LIBRARIES
+
+The Android NDK does not support the usual ways of locating third
+party libraries, especially not via `pkg-config'.  Instead, it uses
+its own system called `ndk-build'.  The one exception to this rule is
+zlib, which is considered a part of the Android OS itself and is
+available on all devices running Android.
+
+Android also requires that each application include its own
+dependencies, as the system makes no guarantee about the existence of
+any particular library.
+
+Emacs is not built with the `ndk-build' system.         Instead, it is built
+with Autoconf and Make.
+
+However, it supports building and including dependencies which use the
+similarly Make-based `ndk-build' system.
+
+To use dependencies built through `ndk-build', you must specify a list
+of directories within which Emacs will search for ``Android.mk''
+files, like so:
+
+  ./configure "--with-ndk-path=directory1 directory2"
+
+If `configure' complains about not being able to find
+``libc++_shared.so'', then you must locate that file in your copy of
+the NDK, and specify it like so:
+
+  ./configure --with-ndk-cxx-shared=/path/to/sysroot/libc++_shared.so
+
+Emacs will then read the ``Android.mk'' file in each directory, and
+automatically build and use those modules.
+
+When building for Intel systems, some ``ndk-build'' modules require
+the Netwide Assembler, usually installed under ``nasm'', to be present
+on the system that is building Emacs.
+
+Google, Inc. has adapted many common Emacs dependencies to use the
+`ndk-build' system.  Here is a non-exhaustive list of what is known to
+work, along with what has to be patched to make them work:
+
+  libpng       - https://android.googlesource.com/platform/external/libpng
+  libwebp      - https://android.googlesource.com/platform/external/webp
+     (You must apply the patch at the end of this file for the resulting
+      binary to work on armv7 devices.)
+  giflib       - https://android.googlesource.com/platform/external/giflib
+     (You must add LOCAL_EXPORT_CFLAGS := -I$(LOCAL_PATH) before
+      its Android.mk includes $(BUILD_STATIC_LIBRARY))
+  libjpeg-turbo - 
https://android.googlesource.com/platform/external/libjpeg-turbo
+     (You must add LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) before
+      its Android.mk includes $(BUILD_SHARED_LIBRARY))
+  libxml2      - https://android.googlesource.com/platform/external/libxml2/
+     (You must also place the dependency icu4c in ``--with-ndk-path'',
+      and apply the patch at the end of this file.)
+  icu4c                - 
https://android.googlesource.com/platform/external/icu/
+     (You must apply the patch at the end of this file.)
+  sqlite3      - https://android.googlesource.com/platform/external/sqlite/
+     (You must apply the patch at the end of this file, and add the `dist'
+      directory to ``--with-ndk-path''.)
+  libselinux   - https://android.googlesource.com/platform/external/libselinux
+     (You must apply the patches at the end of the file, and obtain
+      the following three dependencies.)
+  libpackagelistparser
+    
https://android.googlesource.com/platform/system/core/+/refs/heads/nougat-mr1-dev/libpackagelistparser/
+  libpcre      - https://android.googlesource.com/platform/external/pcre
+  libcrypto    - https://android.googlesource.com/platform/external/boringssl
+     (You must apply the patch at the end of this file when building for
+      ARM systems.)
+
+Many of these dependencies have been migrated over to the
+``Android.bp'' build system now used to build Android itself.
+However, the old ``Android.mk'' Makefiles are still present in older
+branches, and can be easily adapte to newer versions.
+
+In addition, some Emacs dependencies provide `ndk-build' support
+themselves:
+
+  libjansson   - https://github.com/akheron/jansson
+    (You must add LOCAL_EXPORT_INCLUDES := $(LOCAL_C_INCLUDES) before
+     its Android.mk includes $(BUILD_SHARED_LIBRARY), then copy
+     android/jansson_config.h to android/jansson_private_config.h.)
+
+Emacs developers have ported the following dependencies to ARM Android
+systems:
+
+  gnutls, gmp  - https://sourceforge.net/projects/android-ports-for-gnu-emacs
+    (Please see the section GNUTLS near the end of this file.)
+  libtiff      - https://sourceforge.net/projects/android-ports-for-gnu-emacs
+    (Extract and point ``--with-ndk-path'' to tiff-4.5.0-emacs.tar.gz.)
+  tree-sitter  - https://sourceforge.net/projects/android-ports-for-gnu-emacs
+    (Please see the section TREE-SITTER near the end of this file.)
+  harfbuzz     - https://sourceforge.net/projects/android-ports-for-gnu-emacs
+    (Please see the section HARFBUZZ near the end of this file.)
+
+And other developers have ported the following dependencies to Android
+systems:
+
+  ImageMagick, lcms2 - https://github.com/MolotovCherry/Android-ImageMagick7
+    (Please see the section IMAGEMAGICK near the end of this file.)
+
+We anticipate that most untested non-trivial ndk-build dependencies
+will need adjustments in Emacs to work, as the Emacs build system
+which emulates ndk-build is in an extremely early state.
+
+
+GNUTLS
+
+Modified copies of GnuTLS and its dependencies (such as libgmp,
+libtasn1, p11-kit) which can be built with the ndk-build system can be
+found at https://sourceforge.net/projects/android-ports-for-gnu-emacs.
+
+They have only been tested on arm64 Android systems running Android
+5.0 or later, and armv7l systems running Android 13 or later, so your
+mileage may vary, especially if you are trying to build Emacs for
+another kind of machine.
+
+To build Emacs with GnuTLS, you must unpack each of the following tar
+archives in that site:
+
+  gmp-6.2.1-emacs.tgz
+  gnutls-3.7.8-emacs.tar.gz
+  libtasn1-4.19.0-emacs.tar.gz
+  p11-kit-0.24.1-emacs.tar.gz
+  nettle-3.8-emacs.tar.gz
+
+and add the resulting folders to ``--with-ndk-path''.  Note that you
+should not try to build these packages separately using any
+`configure' script or Makefiles inside.
+
+
+TREE-SITTER
+
+A copy of tree-sitter modified to build with the ndk-build system can
+also be found that URL.  To build Emacs with tree-sitter, you must
+unpack the following tar archive in that site:
+
+  tree-sitter-0.20.7-emacs.tar.gz
+
+and add the resulting folder to ``--with-ndk-build''.
+
+
+HARFBUZZ
+
+A copy of HarfBuzz modified to build with the ndk-build system can
+also be found at that URL.  To build Emacs with HarfBuzz, you must
+unpack the following tar archive in that site:
+
+  harfbuzz-7.1.0-emacs.tar.gz
+
+and add the resulting folder to ``--with-ndk-build''.
+
+
+IMAGEMAGICK
+
+There is a third party port of ImageMagick to Android.  Unfortunately,
+the port also uses its own patched versions of libpng, libjpeg,
+libtiff and libwebp, which conflict with those used by Emacs.  Its
+Makefiles were also written for MS Windows, so you must also apply the
+patch at the end of this file.
+
+
+
+PATCH FOR LIBXML2
+
+This patch must be applied to the Android.mk in Google's version of
+libxml2 before it can be built for Emacs.  In addition, you must also
+revert the commit `edb5870767fed8712a9b77ef34097209b61ab2db'.
+
+diff --git a/Android.mk b/Android.mk
+index 07c7b372..24f67e49 100644
+--- a/Android.mk
++++ b/Android.mk
+@@ -80,6 +80,7 @@ LOCAL_SHARED_LIBRARIES := libicuuc
+ LOCAL_MODULE:= libxml2
+ LOCAL_CLANG := true
+ LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
++LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)
+ include $(BUILD_SHARED_LIBRARY)
+ 
+ # For the host
+@@ -94,3 +95,5 @@ LOCAL_MODULE := libxml2
+ LOCAL_CLANG := true
+ LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
+ include $(BUILD_HOST_STATIC_LIBRARY)
++
++$(call import-module,libicuuc)
+
+PATCH FOR ICU
+
+This patch must be applied to icu4j/Android.mk in Google's version of
+icu before it can be built for Emacs.
+
+diff --git a/icu4j/Android.mk b/icu4j/Android.mk
+index d1ab3d5..69eff81 100644
+--- a/icu4j/Android.mk
++++ b/icu4j/Android.mk
+@@ -69,7 +69,7 @@ include $(BUILD_STATIC_JAVA_LIBRARY)
+ # Path to the ICU4C data files in the Android device file system:
+ icu4c_data := /system/usr/icu
+ icu4j_config_root := $(LOCAL_PATH)/main/classes/core/src
+-include external/icu/icu4j/adjust_icudt_path.mk
++include $(LOCAL_PATH)/adjust_icudt_path.mk
+ 
+ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES := $(icu4j_src_files)
+
+diff --git a/icu4c/source/common/Android.mk b/icu4c/source/common/Android.mk
+index 8e5f757..44bb130 100644
+--- a/icu4c/source/common/Android.mk
++++ b/icu4c/source/common/Android.mk
+@@ -231,7 +231,7 @@ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES += $(src_files)
+ LOCAL_C_INCLUDES += $(c_includes) $(optional_android_logging_includes)
+ LOCAL_CFLAGS += $(local_cflags) -DPIC -fPIC
+-LOCAL_SHARED_LIBRARIES += libdl $(optional_android_logging_libraries)
++LOCAL_SHARED_LIBRARIES += libdl libstdc++ 
$(optional_android_logging_libraries)
+ LOCAL_MODULE_TAGS := optional
+ LOCAL_MODULE := libicuuc
+ LOCAL_RTTI_FLAG := -frtti
+
+PATCH FOR SQLITE3
+
+diff --git a/dist/Android.mk b/dist/Android.mk
+index bf277d2..36734d9 100644
+--- a/dist/Android.mk
++++ b/dist/Android.mk
+@@ -141,6 +141,7 @@ include $(BUILD_HOST_EXECUTABLE)
+ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES := $(common_src_files)
+ LOCAL_CFLAGS += $(minimal_sqlite_flags)
++LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)
+ LOCAL_MODULE:= libsqlite_static_minimal
+ LOCAL_SDK_VERSION := 23
+ include $(BUILD_STATIC_LIBRARY)
+
+diff --git a/dist/sqlite3.c b/dist/sqlite3.c
+index b0536a4..8fa1ee9 100644
+--- a/dist/sqlite3.c
++++ b/dist/sqlite3.c
+@@ -26474,7 +26474,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
+ */
+ #if !defined(HAVE_POSIX_FALLOCATE) \
+       && (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L)
+-# define HAVE_POSIX_FALLOCATE 1
++/* # define HAVE_POSIX_FALLOCATE 1 */
+ #endif
+ 
+ /*
+
+PATCH FOR WEBP
+
+diff --git a/Android.mk b/Android.mk
+index c7bcb0f5..d4da1704 100644
+--- a/Android.mk
++++ b/Android.mk
+@@ -28,9 +28,10 @@ ifneq ($(findstring armeabi-v7a, $(TARGET_ARCH_ABI)),)
+   # Setting LOCAL_ARM_NEON will enable -mfpu=neon which may cause illegal
+   # instructions to be generated for armv7a code. Instead target the neon code
+   # specifically.
+-  NEON := c.neon
+-  USE_CPUFEATURES := yes
+-  WEBP_CFLAGS += -DHAVE_CPU_FEATURES_H
++  # NEON := c.neon
++  # USE_CPUFEATURES := yes
++  # WEBP_CFLAGS += -DHAVE_CPU_FEATURES_H
++  NEON := c
+ else
+   NEON := c
+ endif
+
+PATCHES FOR SELINUX
+
+diff --git a/Android.mk b/Android.mk
+index 659232e..1e64fd6 100644
+--- a/Android.mk
++++ b/Android.mk
+@@ -116,3 +116,7 @@ LOCAL_STATIC_LIBRARIES := libselinux
+ LOCAL_WHOLE_STATIC_LIBRARIES := libpcre
+ LOCAL_C_INCLUDES := external/pcre
+ include $(BUILD_HOST_EXECUTABLE)
++
++$(call import-module,libpcre)
++$(call import-module,libpackagelistparser)
++$(call import-module,libcrypto)
+
+diff --git a/src/android.c b/src/android.c
+index 5206a9f..b351ffc 100644
+--- a/src/android.c
++++ b/src/android.c
+@@ -21,8 +21,7 @@
+ #include <selinux/label.h>
+ #include <selinux/avc.h>
+ #include <openssl/sha.h>
+-#include <private/android_filesystem_config.h>
+-#include <log/log.h>
++#include <android/log.h>
+ #include "policy.h"
+ #include "callbacks.h"
+ #include "selinux_internal.h"
+@@ -686,6 +685,7 @@ static int seapp_context_lookup(enum seapp_kind kind,
+               seinfo = parsedseinfo;
+       }
+ 
++#if 0
+       userid = uid / AID_USER;
+       isOwner = (userid == 0);
+       appid = uid % AID_USER;
+@@ -702,9 +702,13 @@ static int seapp_context_lookup(enum seapp_kind kind,
+               username = "_app";
+               appid -= AID_APP;
+       } else {
++#endif
+               username = "_isolated";
++              appid = 0;
++#if 0
+               appid -= AID_ISOLATED_START;
+       }
++#endif
+ 
+       if (appid >= CAT_MAPPING_MAX_ID || userid >= CAT_MAPPING_MAX_ID)
+               goto err;
+@@ -1662,8 +1666,10 @@ int selinux_log_callback(int type, const char *fmt, ...)
+ 
+     va_start(ap, fmt);
+     if (vasprintf(&strp, fmt, ap) != -1) {
++#if 0
+         LOG_PRI(priority, "SELinux", "%s", strp);
+         LOG_EVENT_STRING(AUDITD_LOG_TAG, strp);
++#endif
+         free(strp);
+     }
+     va_end(ap);
+
+PATCH FOR BORINGSSL
+
+diff --git a/Android.mk b/Android.mk
+index 3e3ef2a..277d4a9 100644
+--- a/Android.mk
++++ b/Android.mk
+@@ -27,7 +27,9 @@ LOCAL_MODULE := libcrypto
+ LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/src/include
+ LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk 
$(LOCAL_PATH)/crypto-sources.mk
+ LOCAL_CFLAGS += -fvisibility=hidden -DBORINGSSL_SHARED_LIBRARY 
-DBORINGSSL_IMPLEMENTATION -DOPENSSL_SMALL -Wno-unused-parameter
++LOCAL_CFLAGS_arm = -DOPENSSL_STATIC_ARMCAP -DOPENSSL_NO_ASM
+ LOCAL_SDK_VERSION := 9
++LOCAL_LDFLAGS = --no-undefined
+ # sha256-armv4.S does not compile with clang.
+ LOCAL_CLANG_ASFLAGS_arm += -no-integrated-as
+ LOCAL_CLANG_ASFLAGS_arm64 += -march=armv8-a+crypto
+diff --git a/sources.mk b/sources.mk
+index e82f3d5..be3a3c4 100644
+--- a/sources.mk
++++ b/sources.mk
+@@ -337,20 +337,20 @@ linux_aarch64_sources := \
+   linux-aarch64/crypto/sha/sha256-armv8.S\
+   linux-aarch64/crypto/sha/sha512-armv8.S\
+ 
+-linux_arm_sources := \
+-  linux-arm/crypto/aes/aes-armv4.S\
+-  linux-arm/crypto/aes/aesv8-armx32.S\
+-  linux-arm/crypto/aes/bsaes-armv7.S\
+-  linux-arm/crypto/bn/armv4-mont.S\
+-  linux-arm/crypto/modes/ghash-armv4.S\
+-  linux-arm/crypto/modes/ghashv8-armx32.S\
+-  linux-arm/crypto/sha/sha1-armv4-large.S\
+-  linux-arm/crypto/sha/sha256-armv4.S\
+-  linux-arm/crypto/sha/sha512-armv4.S\
+-  src/crypto/chacha/chacha_vec_arm.S\
+-  src/crypto/cpu-arm-asm.S\
+-  src/crypto/curve25519/asm/x25519-asm-arm.S\
+-  src/crypto/poly1305/poly1305_arm_asm.S\
++# linux_arm_sources := \
++#   linux-arm/crypto/aes/aes-armv4.S\
++#   linux-arm/crypto/aes/aesv8-armx32.S\
++#   linux-arm/crypto/aes/bsaes-armv7.S\
++#   linux-arm/crypto/bn/armv4-mont.S\
++#   linux-arm/crypto/modes/ghash-armv4.S\
++#   linux-arm/crypto/modes/ghashv8-armx32.S\
++#   linux-arm/crypto/sha/sha1-armv4-large.S\
++#   linux-arm/crypto/sha/sha256-armv4.S\
++#   linux-arm/crypto/sha/sha512-armv4.S\
++#   src/crypto/chacha/chacha_vec_arm.S\
++#   src/crypto/cpu-arm-asm.S\
++#   src/crypto/curve25519/asm/x25519-asm-arm.S\
++#   src/crypto/poly1305/poly1305_arm_asm.S\
+ 
+ linux_x86_sources := \
+   linux-x86/crypto/aes/aes-586.S\
+
+PATCH FOR IMAGEMAGICK
+
+diff --git a/Android.mk b/Android.mk
+index 5ab6699..4441417 100644
+--- a/Android.mk
++++ b/Android.mk
+@@ -52,6 +52,20 @@ LZMA_LIB_PATH                   := $(LOCAL_PATH)/xz-5.2.4
+ BZLIB_LIB_PATH                  := $(LOCAL_PATH)/bzip-1.0.8
+ LCMS_LIB_PATH                   := $(LOCAL_PATH)/liblcms2-2.9
+ 
++LIBBZ2_ENABLED        := true
++LIBFFTW_ENABLED       := true
++LIBFREETYPE2_ENABLED  := true
++LIBJPEG_TURBO_ENABLED := true
++LIBLZMA_ENABLED       := true
++LIBOPENJPEG_ENABLED   := true
++LIBPNG_ENABLED        := true
++LIBTIFF_ENABLED       := true
++LIBWEBP_ENABLED       := true
++LIBXML2_ENABLED       := true
++LIBZLIB_ENABLED       := true
++LIBLCMS2_ENABLED      := true
++BUILD_MAGICKWAND      := true
++
+ #-------------------------------------------------------------
+ # Include all modules
+ #-------------------------------------------------------------
+@@ -68,6 +82,9 @@ include $(MAKE_PATH)/libjpeg-turbo.mk
+ # libopenjpeg
+ include $(MAKE_PATH)/libopenjpeg.mk
+ 
++# libwebp
++include $(MAKE_PATH)/libwebp.mk
++
+ # libtiff
+ include $(MAKE_PATH)/libtiff.mk
+ 
+@@ -77,9 +94,6 @@ include $(MAKE_PATH)/libpng.mk
+ # libfreetype2
+ include $(MAKE_PATH)/libfreetype2.mk
+ 
+-# libwebp
+-include $(MAKE_PATH)/libwebp.mk
+-
+ # libfftw
+ include $(MAKE_PATH)/libfftw.mk
+ 
+diff --git a/libjpeg-turbo-2.0.2/jconfig.h b/libjpeg-turbo-2.0.2/jconfig.h
+index 47d14c9..5c6f8ee 100644
+--- a/libjpeg-turbo-2.0.2/jconfig.h
++++ b/libjpeg-turbo-2.0.2/jconfig.h
+@@ -1,57 +1,43 @@
+-/* autogenerated jconfig.h based on Android.mk var JCONFIG_FLAGS */ 
++/* autogenerated jconfig.h based on Android.mk var JCONFIG_FLAGS */
+ #ifndef JPEG_LIB_VERSION
+ #define JPEG_LIB_VERSION 62
+ #endif
+-
+ #ifndef LIBJPEG_TURBO_VERSION
+ #define LIBJPEG_TURBO_VERSION 2.0.2
+ #endif
+-
+ #ifndef LIBJPEG_TURBO_VERSION_NUMBER
+ #define LIBJPEG_TURBO_VERSION_NUMBER 202
+ #endif
+-
+ #ifndef C_ARITH_CODING_SUPPORTED
+ #define C_ARITH_CODING_SUPPORTED
+ #endif
+-
+ #ifndef D_ARITH_CODING_SUPPORTED
+ #define D_ARITH_CODING_SUPPORTED
+ #endif
+-
+ #ifndef MEM_SRCDST_SUPPORTED
+ #define MEM_SRCDST_SUPPORTED
+ #endif
+-
+ #ifndef WITH_SIMD
+ #define WITH_SIMD
+ #endif
+-
+ #ifndef BITS_IN_JSAMPLE
+ #define BITS_IN_JSAMPLE 8
+ #endif
+-
+ #ifndef HAVE_LOCALE_H
+ #define HAVE_LOCALE_H
+ #endif
+-
+ #ifndef HAVE_STDDEF_H
+ #define HAVE_STDDEF_H
+ #endif
+-
+ #ifndef HAVE_STDLIB_H
+ #define HAVE_STDLIB_H
+ #endif
+-
+ #ifndef NEED_SYS_TYPES_H
+ #define NEED_SYS_TYPES_H
+ #endif
+-
+ #ifndef HAVE_UNSIGNED_CHAR
+ #define HAVE_UNSIGNED_CHAR
+ #endif
+-
+ #ifndef HAVE_UNSIGNED_SHORT
+ #define HAVE_UNSIGNED_SHORT
+ #endif
+-
+diff --git a/libxml2-2.9.9/encoding.c b/libxml2-2.9.9/encoding.c
+index a3aaf10..60f165b 100644
+--- a/libxml2-2.9.9/encoding.c
++++ b/libxml2-2.9.9/encoding.c
+@@ -2394,7 +2394,6 @@ xmlCharEncOutput(xmlOutputBufferPtr output, int init)
+ {
+     int ret;
+     size_t written;
+-    size_t writtentot = 0;
+     size_t toconv;
+     int c_in;
+     int c_out;
+@@ -2451,7 +2450,6 @@ retry:
+                             xmlBufContent(in), &c_in);
+     xmlBufShrink(in, c_in);
+     xmlBufAddLen(out, c_out);
+-    writtentot += c_out;
+     if (ret == -1) {
+         if (c_out > 0) {
+             /* Can be a limitation of iconv or uconv */
+@@ -2536,7 +2534,6 @@ retry:
+           }
+ 
+             xmlBufAddLen(out, c_out);
+-            writtentot += c_out;
+             goto retry;
+       }
+     }
+@@ -2567,9 +2564,7 @@ xmlCharEncOutFunc(xmlCharEncodingHandler *handler, 
xmlBufferPtr out,
+                   xmlBufferPtr in) {
+     int ret;
+     int written;
+-    int writtentot = 0;
+     int toconv;
+-    int output = 0;
+ 
+     if (handler == NULL) return(-1);
+     if (out == NULL) return(-1);
+@@ -2612,7 +2607,6 @@ retry:
+                             in->content, &toconv);
+     xmlBufferShrink(in, toconv);
+     out->use += written;
+-    writtentot += written;
+     out->content[out->use] = 0;
+     if (ret == -1) {
+         if (written > 0) {
+@@ -2622,8 +2616,6 @@ retry:
+         ret = -3;
+     }
+ 
+-    if (ret >= 0) output += ret;
+-
+     /*
+      * Attempt to handle error cases
+      */
+@@ -2700,7 +2692,6 @@ retry:
+           }
+ 
+             out->use += written;
+-            writtentot += written;
+             out->content[out->use] = 0;
+             goto retry;
+       }
+diff --git a/libxml2-2.9.9/xpath.c b/libxml2-2.9.9/xpath.c
+index 5e3bb9f..505ec82 100644
+--- a/libxml2-2.9.9/xpath.c
++++ b/libxml2-2.9.9/xpath.c
+@@ -10547,7 +10547,7 @@ xmlXPathCompFilterExpr(xmlXPathParserContextPtr ctxt) {
+ 
+ static xmlChar *
+ xmlXPathScanName(xmlXPathParserContextPtr ctxt) {
+-    int len = 0, l;
++    int l;
+     int c;
+     const xmlChar *cur;
+     xmlChar *ret;
+@@ -10567,7 +10567,6 @@ xmlXPathScanName(xmlXPathParserContextPtr ctxt) {
+           (c == '_') || (c == ':') ||
+           (IS_COMBINING(c)) ||
+           (IS_EXTENDER(c)))) {
+-      len += l;
+       NEXTL(l);
+       c = CUR_CHAR(l);
+     }
+diff --git a/make/libicu4c.mk b/make/libicu4c.mk
+index 21ec121..8b77865 100644
+--- a/make/libicu4c.mk
++++ b/make/libicu4c.mk
+@@ -250,7 +250,7 @@ LOCAL_MODULE    := libicuuc
+ LOCAL_SRC_FILES := $(src_files)
+ 
+ # when built in android, they require uconfig_local (because of android 
project), but we don't need this
+-$(shell > $(ICU_COMMON_PATH)/unicode/uconfig_local.h echo /* Autogenerated 
stub file to make libicuuc build happy */) \
++$(shell > $(ICU_COMMON_PATH)/unicode/uconfig_local.h echo /\* Autogenerated 
stub file to make libicuuc build happy \*/) \
+ 
+ ifeq ($(LIBXML2_ENABLED),true)
+     include $(BUILD_STATIC_LIBRARY)
+diff --git a/make/libjpeg-turbo.mk b/make/libjpeg-turbo.mk
+index d39dd41..fdebcf3 100644
+--- a/make/libjpeg-turbo.mk
++++ b/make/libjpeg-turbo.mk
+@@ -230,30 +230,30 @@ JCONFIG_FLAGS += \
+     HAVE_UNSIGNED_SHORT
+ 
+ JCONFIGINT_FLAGS += \
+-    BUILD="20190814" \
+-    PACKAGE_NAME="libjpeg-turbo" \
+-    VERSION="2.0.2"
++    BUILD=\"20190814\" \
++    PACKAGE_NAME=\"libjpeg-turbo\" \
++    VERSION=\"2.0.2\"
+ 
+ # originally defined in jconfigint.h, but the substitution has problems with 
spaces
+ LOCAL_CFLAGS := \
+     -DINLINE="inline __attribute__((always_inline))"
+ 
+ # create definition file jconfig.h, needed in order to build
+-$(shell echo /* autogenerated jconfig.h based on Android.mk var JCONFIG_FLAGS 
*/ > $(JPEG_LIB_PATH)/jconfig.h)
++$(shell echo \/\* autogenerated jconfig.h based on Android.mk var 
JCONFIG_FLAGS \*\/ > $(JPEG_LIB_PATH)/jconfig.h)
+ $(foreach name,$(JCONFIG_FLAGS), \
+     $(if $(findstring =,$(name)), \
+-        $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo #ifndef $(firstword $(subst 
=, ,$(name)))) \
++        $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo \#ifndef $(firstword 
$(subst =, ,$(name)))) \
+     , \
+-        $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo #ifndef $(name)) \
++        $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo \#ifndef $(name)) \
+     ) \
+-    $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo #define $(subst =, ,$(name))) \
+-    $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo #endif) \
++    $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo \#define $(subst =, ,$(name))) \
++    $(shell >>$(JPEG_LIB_PATH)/jconfig.h echo \#endif) \
+     $(shell >> $(JPEG_LIB_PATH)/jconfig.h echo.) \
+ )
+ 
+ # create definition file jconfigint.h, needed in order to build
+-$(shell >$(JPEG_LIB_PATH)/jconfigint.h echo /* autogenerated jconfigint.h 
based on Android.mk vars JCONFIGINT_FLAGS */)
+-$(foreach name,$(JCONFIGINT_FLAGS),$(shell >>$(JPEG_LIB_PATH)/jconfigint.h 
echo #define $(subst =, ,$(name))))
++$(shell >$(JPEG_LIB_PATH)/jconfigint.h echo /\* autogenerated jconfigint.h 
based on Android.mk vars JCONFIGINT_FLAGS \*/)
++$(foreach name,$(JCONFIGINT_FLAGS),$(shell >>$(JPEG_LIB_PATH)/jconfigint.h 
echo \#define $(subst =, ,$(name))))
+ 
+ ifeq ($(LIBJPEG_TURBO_ENABLED),true)
+     include $(BUILD_STATIC_LIBRARY)
+diff --git a/make/liblcms2.mk b/make/liblcms2.mk
+index e1fd3b9..29ca791 100644
+--- a/make/liblcms2.mk
++++ b/make/liblcms2.mk
+@@ -10,6 +10,10 @@ LOCAL_C_INCLUDES := \
+     $(LCMS_LIB_PATH)/include \
+     $(LCMS_LIB_PATH)/src
+ 
++LOCAL_EXPORT_C_INCLUDES := \
++    $(LCMS_LIB_PATH) \
++    $(LCMS_LIB_PATH)/include \
++    $(LCMS_LIB_PATH)/src
+ 
+ LOCAL_CFLAGS := \
+     -DHAVE_FUNC_ATTRIBUTE_VISIBILITY=1 \
+diff --git a/make/libmagick++-7.mk b/make/libmagick++-7.mk
+index 5352ccb..929396d 100644
+--- a/make/libmagick++-7.mk
++++ b/make/libmagick++-7.mk
+@@ -12,7 +12,7 @@ LOCAL_C_INCLUDES  :=  \
+ 
+ ifneq ($(STATIC_BUILD),true)
+     LOCAL_LDFLAGS += -fexceptions
+-    LOCAL_LDLIBS    := -L$(SYSROOT)/usr/lib -llog -lz
++    LOCAL_LDLIBS    := -llog -lz
+ endif
+ 
+ LOCAL_SRC_FILES := \
+diff --git a/make/libmagickcore-7.mk b/make/libmagickcore-7.mk
+index 81293b2..d51fced 100644
+--- a/make/libmagickcore-7.mk
++++ b/make/libmagickcore-7.mk
+@@ -25,6 +25,7 @@ else ifeq ($(TARGET_ARCH_ABI),x86_64)
+     
+ endif
+ 
++LOCAL_EXPORT_C_INCLUDES += $(IMAGE_MAGICK)
+ 
+ LOCAL_C_INCLUDES  += \
+     $(IMAGE_MAGICK) \
+@@ -45,10 +46,9 @@ LOCAL_C_INCLUDES  += \
+     $(BZLIB_LIB_PATH) \
+     $(LCMS_LIB_PATH)/include
+ 
+-
+ ifneq ($(STATIC_BUILD),true)
+ # ignored in static library builds
+-    LOCAL_LDLIBS    := -L$(SYSROOT)/usr/lib -llog -lz
++    LOCAL_LDLIBS    := -llog -lz
+ endif
+ 
+ 
+diff --git a/make/libmagickwand-7.mk b/make/libmagickwand-7.mk
+index 7be2fb6..0bbcca5 100644
+--- a/make/libmagickwand-7.mk
++++ b/make/libmagickwand-7.mk
+@@ -14,7 +14,7 @@ LOCAL_C_INCLUDES  :=  \
+ 
+ # always ignored in static builds
+ ifneq ($(STATIC_BUILD),true)
+-    LOCAL_LDLIBS    := -L$(SYSROOT)/usr/lib -llog -lz
++    LOCAL_LDLIBS    := -llog -lz
+ endif
+ 
+ LOCAL_SRC_FILES := \
+@@ -54,6 +54,29 @@ ifeq ($(OPENCL_BUILD),true)
+     LOCAL_SHARED_LIBRARIES += libopencl
+ endif
+ 
++LOCAL_SHARED_LIBRARIES += libstdc++
++
++ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
++    LOCAL_EXPORT_C_INCLUDES += $(IMAGE_MAGICK)/configs/arm64
++    LOCAL_C_INCLUDES += $(IMAGE_MAGICK)/configs/arm64
++else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)  
++    LOCAL_EXPORT_C_INCLUDES += $(IMAGE_MAGICK)/configs/arm
++    LOCAL_C_INCLUDES += $(IMAGE_MAGICK)/configs/arm
++else ifeq ($(TARGET_ARCH_ABI),x86)
++    LOCAL_EXPORT_C_INCLUDES += $(IMAGE_MAGICK)/configs/x86
++    LOCAL_C_INCLUDES += $(IMAGE_MAGICK)/configs/x86
++else ifeq ($(TARGET_ARCH_ABI),x86_64)
++    LOCAL_EXPORT_C_INCLUDES += $(IMAGE_MAGICK)/configs/x86-64
++    LOCAL_C_INCLUDES += $(IMAGE_MAGICK)/configs/x86-64
++    
++    ifneq ($(STATIC_BUILD),true)
++        LOCAL_LDFLAGS += -latomic
++    endif
++    
++endif
++
++LOCAL_EXPORT_C_INCLUDES += $(IMAGE_MAGICK)
++
+ ifeq ($(BUILD_MAGICKWAND),true)
+     ifeq ($(STATIC_BUILD),true)
+         LOCAL_STATIC_LIBRARIES := \
+diff --git a/make/libpng.mk b/make/libpng.mk
+index 24fb8ac..dda05fd 100644
+--- a/make/libpng.mk
++++ b/make/libpng.mk
+@@ -30,6 +30,7 @@ ifeq ($(TARGET_ARCH_ABI), arm64-v8a)
+ endif # TARGET_ARCH_ABI == arm64-v8a
+ 
+ 
++LOCAL_EXPORT_C_INCLUDES := $(PNG_LIB_PATH)
+ LOCAL_C_INCLUDES := $(PNG_LIB_PATH)
+ 
+ LOCAL_SRC_FILES += \
+diff --git a/make/libtiff.mk b/make/libtiff.mk
+index ca43f25..2b17508 100644
+--- a/make/libtiff.mk
++++ b/make/libtiff.mk
+@@ -12,6 +12,9 @@ LOCAL_C_INCLUDES :=  \
+     $(LZMA_LIB_PATH)/liblzma/api \
+     $(WEBP_LIB_PATH)/src
+ 
++LOCAL_EXPORT_C_INCLUDES :=  \
++    $(TIFF_LIB_PATH)
++
+ ifeq ($(LIBLZMA_ENABLED),true)
+     LOCAL_CFLAGS += -DLZMA_SUPPORT=1
+ endif
+diff --git a/make/magick.mk b/make/magick.mk
+index 3ba4b1d..5471608 100644
+--- a/make/magick.mk
++++ b/make/magick.mk
+@@ -18,7 +18,7 @@ LOCAL_C_INCLUDES  :=  \
+     $(FREETYPE_LIB_PATH)/include
+ 
+ 
+-LOCAL_LDLIBS    := -L$(SYSROOT)/usr/lib -llog -lz
++LOCAL_LDLIBS    := -llog -lz
+ LOCAL_SRC_FILES := \
+     $(IMAGE_MAGICK)/utilities/magick.c \
+ 
+
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
diff --git a/java/Makefile.in b/java/Makefile.in
new file mode 100644
index 00000000000..87683f12544
--- /dev/null
+++ b/java/Makefile.in
@@ -0,0 +1,344 @@
+### @configure_input@
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This file is part of GNU Emacs.
+
+# GNU Emacs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# GNU Emacs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+builddir = @builddir@
+version = @version@
+
+# Don't install movemail if mailutils are to be used.
+emacs_use_mailutils = @emacs_use_mailutils@
+
+# This is the host lib-src and lib, not the cross compiler's lib-src.
+libsrc = ../lib-src
+EXEEXT = @EXEEXT@
+
+-include ${top_builddir}/src/verbose.mk
+
+SHELL = @SHELL@
+JAVAC = @JAVAC@
+AAPT = @AAPT@
+D8 = @D8@
+ZIPALIGN = @ZIPALIGN@
+JARSIGNER = @JARSIGNER@
+APKSIGNER = @APKSIGNER@
+JARSIGNER_FLAGS =
+ANDROID_JAR = @ANDROID_JAR@
+ANDROID_ABI = @ANDROID_ABI@
+ANDROID_SDK_18_OR_EARLIER = @ANDROID_SDK_18_OR_EARLIER@
+ANDROID_SDK_8_OR_EARLIER = @ANDROID_SDK_8_OR_EARLIER@
+WARN_JAVAFLAGS = @WARN_JAVAFLAGS@
+JAVAFLAGS = $(WARN_JAVAFLAGS) -classpath "$(ANDROID_JAR):$(srcdir)"
+FIND_DELETE = @FIND_DELETE@
+
+# Android 4.3 and earlier require Emacs to be signed with a different
+# digital signature algorithm.
+
+ifneq (,$(ANDROID_SDK_18_OR_EARLIER))
+JARSIGNER_FLAGS = -sigalg MD5withRSA -digestalg SHA1
+else
+JARSIGNER_FLAGS =
+endif
+
+# When building Emacs for Android 2.2, assets must not be compressed.
+# Otherwise, the asset manager fails to extract files larger than 1
+# MB.
+
+ifneq (,$(ANDROID_SDK_8_OR_EARLIER))
+AAPT_ASSET_ARGS = -0 ""
+else
+AAPT_ASSET_ARGS =
+endif
+
+SIGN_EMACS = -keystore $(srcdir)/emacs.keystore -storepass     \
+       emacs1 $(JARSIGNER_FLAGS)
+SIGN_EMACS_V2 = sign --v2-signing-enabled --ks                 \
+       $(srcdir)/emacs.keystore -debuggable-apk-permitted      \
+       --ks-pass pass:emacs1
+
+JAVA_FILES := $(wildcard $(srcdir)/org/gnu/emacs/*.java)
+RESOURCE_FILES := $(foreach file,$(wildcard $(srcdir)/res/*),  \
+                   $(wildcard $(file)/*))
+
+# R.java is a file generated by the `aapt' utility containing
+# constants that can then be used to locate ``resource identifiers''.
+# It is not a regular file and should not be compiled as Java source
+# code.  Instead, it is automatically included by the Java compiler.
+RESOURCE_FILE := $(srcdir)/org/gnu/emacs/R.java
+
+# CLASS_FILES is what should actually be built and included in the
+# resulting Emacs executable.  The Java compiler might generate more
+# than one class file for each source file, so this only serves as a
+# list of dependencies for Make.
+CLASS_FILES := $(foreach file,$(JAVA_FILES),$(basename $(file)).class)
+
+# Remove RESOURCE_FILE from JAVA_FILES, if it is already present.
+JAVA_FILES := $(filter-out $(RESOURCE_FILE),$(JAVA_FILES))
+
+# Compute the name for the Emacs application package.  This should be:
+# emacs-<version>-<min-sdk>-<abi>.apk
+
+ANDROID_MIN_SDK := @ANDROID_MIN_SDK@
+APK_NAME := emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk
+
+# How this stuff works.
+
+# emacs.apk depends on emacs.apk-in, which is simply a ZIP archive
+# containing the following files:
+#  lib/$(ANDROID_ABI)/libemacs.so
+#  lib/$(ANDROID_ABI)/libandroid-emacs.so
+#  lib/$(ANDROID_ABI)/libctags.so
+#  lib/$(ANDROID_ABI)/libetags.so
+#  lib/$(ANDROID_ABI)/libhexl.so
+#  lib/$(ANDROID_ABI)/libmovemail.so
+#  lib/$(ANDROID_ABI)/librcs2log.so
+#  lib/$(ANDROID_ABI)/libebrowse.so
+#  assets/info/
+#  assets/etc/
+#  assets/lisp/
+
+.PHONY: emacs.apk-in all
+all: $(APK_NAME)
+
+# Binaries to cross-compile.
+CROSS_SRC_BINS := $(top_builddir)/cross/src/android-emacs
+CROSS_LIBSRC_BINS := $(top_builddir)/cross/lib-src/ctags       \
+                    $(top_builddir)/cross/lib-src/hexl         \
+                    $(top_builddir)/cross/lib-src/ebrowse      \
+                    $(top_builddir)/cross/lib-src/emacsclient  \
+                    $(top_builddir)/cross/lib-src/etags
+CROSS_LIBSRC_BINS_MOVEMAIL := $(top_builddir)/cross/lib-src/movemail
+CROSS_EXEC_BINS := $(top_builddir)/exec/exec1 $(top_builddir)/exec/loader
+CROSS_BINS = $(CROSS_SRC_BINS) $(CROSS_LIBSRC_BINS) $(CROSS_EXEC_BINS)
+
+ifneq ($(emacs_use_mailutils),yes)
+CROSS_LIBSRC_BINS := $(CROSS_LIBSRC_BINS) $(CROSS_LIBSRC_BINS_MOVEMAIL)
+endif
+
+# Libraries to cross-compile.
+CROSS_LIBS = $(top_builddir)/cross/src/libemacs.so
+
+# Make sure gnulib is built first!
+# If not, then the recursive invocations of make below will try to
+# build gnulib at the same time.
+CROSS_ARCHIVES = $(top_builddir)/cross/lib/libgnu.a
+
+# Third party libraries to compile.
+-include $(top_builddir)/cross/ndk-build/ndk-build.mk
+
+.PHONY: $(CROSS_BINS) $(CROSS_LIBS) $(CROSS_ARCHIVES)
+
+# There should only be a single invocation of $(MAKE) -C
+# $(top_srcdir)/cross for each directory under $(top_srcdir)/cross.
+$(CROSS_SRC_BINS) $(CROSS_LIBS) &: $(CROSS_ARCHIVES)
+       $(MAKE) -C $(top_builddir)/cross $(foreach file,        \
+                                          $(CROSS_SRC_BINS)    \
+                                          $(CROSS_LIBS),       \
+                                          src/$(notdir $(file)))
+
+$(CROSS_LIBSRC_BINS) &: $(CROSS_ARCHIVES)
+       $(MAKE) -C $(top_builddir)/cross $(foreach file,                \
+                                          $(CROSS_LIBSRC_BINS),        \
+                                          lib-src/$(notdir $(file)))
+
+$(CROSS_ARCHIVES):
+       $(MAKE) -C $(top_builddir)/cross lib/libgnu.a
+
+# These two binaries are helpers used to execute binaries on Android
+# 10 and later.
+
+$(CROSS_EXEC_BINS) &:
+       $(MAKE) -C $(top_builddir)/exec $(notdir $(CROSS_EXEC_BINS))
+
+# This is needed to generate the ``.directory-tree'' file used by the
+# Android emulations of readdir and faccessat.
+
+$(libsrc)/asset-directory-tool:
+       $(MAKE) -C $(libsrc) $(notdir $@)
+
+# install_tmp is a directory used to generate emacs.apk-in.
+# That is then packaged into $(APK_NAME).
+# There is no need to depend on NDK_BUILD_SHARED as libemacs.so
+# does already.
+
+.PHONY: install_temp install_temp/assets/directory-tree
+install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(RESOURCE_FILES)
+       $(AM_V_GEN)
+# Make the working directory for this stuff
+       $(AM_V_SILENT) rm -rf install_temp
+       $(AM_V_SILENT) mkdir -p install_temp/lib/$(ANDROID_ABI)
+       $(AM_V_SILENT) mkdir -p install_temp/assets/etc
+       $(AM_V_SILENT) mkdir -p install_temp/assets/lisp
+       $(AM_V_SILENT) mkdir -p install_temp/assets/info
+# Install architecture independents to assets/etc and assets/lisp
+       $(AM_V_SILENT) cp -r $(top_srcdir)/lisp install_temp/assets
+       $(AM_V_SILENT) cp -r $(top_srcdir)/etc install_temp/assets
+       $(AM_V_SILENT) cp -r $(top_srcdir)/info install_temp/assets
+# Replace etc/DOC generated by compiling Emacs for the build machine
+# with etc/DOC from the cross-compiled Emacs.
+       $(AM_V_SILENT) test -f $(top_builddir)/cross/etc/DOC    \
+         && cp -r $(top_builddir)/cross/etc/DOC                \
+            install_temp/assets/etc
+# Remove undesirable files from those directories.
+       $(AM_V_SILENT)                                          \
+       for subdir in `find install_temp -type d -print`; do    \
+         chmod a+rx $${subdir} ;                               \
+         rm -rf $${subdir}/.gitignore ;                        \
+         rm -rf $${subdir}/.DS_Store ;                         \
+         rm -rf $${subdir}/#* ;                                \
+         rm -rf $${subdir}/.#* ;                               \
+         rm -rf $${subdir}/*~ ;                                \
+         rm -rf $${subdir}/*.orig ;                            \
+         rm -rf $${subdir}/ChangeLog* ;                        \
+         rm -rf $${subdir}/[mM]akefile*[.-]in ;                \
+         rm -rf $${subdir}/Makefile;                           \
+       done
+# Generate the directory tree for those directories.
+# Install architecture dependents to lib/$(ANDROID_ABI).  This
+# perculiar naming scheme is required to make Android preserve these
+# binaries upon installation.
+       $(AM_V_SILENT)                                                          
\
+       for file in $(CROSS_BINS); do                                           
\
+         if [ -x $$file ]; then                                                
\
+           filename=`basename $$file`;                                         
\
+           cp -f $$file install_temp/lib/$(ANDROID_ABI)/lib$${filename}.so;    
\
+         fi                                                                    
\
+       done
+       $(AM_V_SILENT)                                          \
+       for file in $(CROSS_LIBS); do                           \
+         if [ -x $$file ]; then                                \
+           cp -f $$file install_temp/lib/$(ANDROID_ABI);       \
+         fi                                                    \
+       done
+# Next, produce a version of rcs2log befitting Android's naming
+# conventions and shell interpreter location.
+       $(AM_V_at) \
+       sed 's|/bin/sh|/system/bin/sh|'                                 \
+        $(top_srcdir)/lib-src/rcs2log >                                \
+        install_temp/lib/$(ANDROID_ABI)/librcs2log.so
+       $(AM_V_at) chmod +x install_temp/lib/$(ANDROID_ABI)/librcs2log.so
+ifneq ($(NDK_BUILD_SHARED),)
+       $(AM_V_SILENT) cp -f $(NDK_BUILD_SHARED) \
+         install_temp/lib/$(ANDROID_ABI)
+endif
+
+install_temp/assets/directory-tree: $(libsrc)/asset-directory-tool \
+  install_temp install_temp/assets/version                        \
+  install_temp/assets/build_info
+       $(AM_V_GEN) $(libsrc)/asset-directory-tool install_temp/assets \
+         install_temp/assets/directory-tree
+
+install_temp/assets/version: install_temp
+       $(AM_V_GEN) { (cd $(top_srcdir)                                 \
+                       && git rev-parse HEAD || echo "Unknown")        \
+                       && (git rev-parse --abbrev-ref HEAD             \
+                           || echo "Unknown") } 2> /dev/null > $@
+
+install_temp/assets/build_info: install_temp
+       $(AM_V_GEN) { hostname; date +%s; } > $@
+
+emacs.apk-in: install_temp install_temp/assets/directory-tree  \
+  AndroidManifest.xml install_temp/assets/version             \
+  install_temp/assets/build_info
+# Package everything.  Specifying the assets on this command line is
+# necessary for AAssetManager_getNextFileName to work on old versions
+# of Android.  Make sure not to generate R.java, as it's already been
+# generated.
+       $(AM_V_AAPT) $(AAPT) p -I "$(ANDROID_JAR)" -F $@   \
+         -f -M AndroidManifest.xml $(AAPT_ASSET_ARGS)     \
+         -A install_temp/assets                           \
+         -S $(top_srcdir)/java/res -J install_temp
+       $(AM_V_SILENT) pushd install_temp &> /dev/null; \
+         $(AAPT) add ../$@ `find lib -type f`;         \
+       popd &> /dev/null
+       $(AM_V_SILENT) rm -rf install_temp
+
+# Makefile itself.
+.PRECIOUS: $(top_srcdir)/config.status Makefile
+$(top_srcdir)/config.status: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4
+       $(MAKE) -C $(dir $@) $(notdir $@)
+Makefile: $(top_srcdir)/config.status $(top_srcdir)/java/Makefile.in
+       $(MAKE) -C .. java/$@
+
+# AndroidManifest.xml:
+AndroidManifest.xml: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 \
+  $(srcdir)/AndroidManifest.xml.in
+       pushd ..; ./config.status java/AndroidManifest.xml; popd
+
+# R.java:
+$(RESOURCE_FILE): $(RESOURCE_FILES)
+       $(AM_V_GEN) $(AAPT) p -I "$(ANDROID_JAR)" -f    \
+         -J $(dir $@) -M AndroidManifest.xml           \
+         -S $(top_srcdir)/java/res
+
+# Make all class files depend on R.java being built.
+$(CLASS_FILES): $(RESOURCE_FILE)
+
+.SUFFIXES: .java .class
+$(CLASS_FILES) &: $(JAVA_FILES)
+       $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $(JAVA_FILES)
+       $(AM_V_SILENT) touch $(CLASS_FILES)
+
+# N.B. that find must be called all over again in case javac generated
+# nested classes.
+
+classes.dex: $(CLASS_FILES)
+       $(AM_V_D8) $(D8) --classpath $(ANDROID_JAR)             \
+         $(subst $$,\$$,$(shell find $(srcdir) -type f         \
+           -name *.class)) --output $(builddir)
+
+# When emacs.keystore expires, regenerate it with:
+#
+#  keytool -genkey -v -keystore emacs.keystore -alias "Emacs keystore" \
+#   -keyalg RSA -sigalg  SHA1withRSA -keysize 2048 -validity 100000
+
+.PHONY: clean maintainer-clean
+
+$(APK_NAME): classes.dex emacs.apk-in $(srcdir)/emacs.keystore
+       $(AM_V_GEN)
+       $(AM_V_SILENT) cp -f emacs.apk-in $@.unaligned
+       $(AM_V_SILENT) $(AAPT) add $@.unaligned classes.dex
+       $(AM_V_SILENT) $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore"
+       $(AM_V_SILENT) $(ZIPALIGN) -f 4 $@.unaligned $@
+# Signing must happen after alignment!
+       $(AM_V_SILENT) $(APKSIGNER) $(SIGN_EMACS_V2) $@
+       $(AM_V_SILENT) rm -f $@.unaligned *.idsig
+
+# TAGS generation.
+
+ETAGS = $(top_builddir)/lib-src/etags
+
+$(ETAGS): FORCE
+       $(MAKE) -C ../lib-src $(notdir $@)
+
+tagsfiles = $(JAVA_FILES) $(RESOURCE_FILE)
+
+.PHONY: tags FORCE
+tags: TAGS
+TAGS: $(ETAGS) $(tagsfiles)
+       $(AM_V_GEN) $(ETAGS) $(tagsfiles)
+
+clean:
+       rm -f *.apk emacs.apk-in *.dex *.unaligned *.class *.idsig
+       rm -rf install-temp $(RESOURCE_FILE) TAGS
+       find . -name '*.class' $(FIND_DELETE)
+
+maintainer-clean distclean bootstrap-clean: clean
+       rm -f Makefile ndk-build.mk
diff --git a/java/README b/java/README
new file mode 100644
index 00000000000..e518e9fbb2f
--- /dev/null
+++ b/java/README
@@ -0,0 +1,1048 @@
+This directory holds the Java sources of the port of GNU Emacs to
+Android-like systems, along with files needed to create an application
+package out of them.  If you need to build this port, please read the
+file INSTALL in this directory.
+
+The ``org/gnu/emacs'' subdirectory contains the Java sources under the
+``org.gnu.emacs'' package identifier.
+
+``AndroidManifest.xml'' contains a manifest describing the Java
+sources to the system.
+
+The ``res'' directory contains resources, mainly the Emacs icon and
+several ``boolean resources'' which are used as a form of conditional
+evaluation for manifest entries.
+
+`emacs.keystore' is the signing key used to build Emacs.  It is kept
+here, and we encourage all people redistributing Emacs to use this
+key.  It holds no security value, and otherwise it will be impossible
+to install different builds of Emacs on top of each other.
+
+Please keep the Java code indented with tabs and formatted according
+to the rules for C code in the GNU coding standards.  Always use
+C-style comments.
+
+======================================================================
+
+OVERVIEW OF JAVA
+
+Emacs developers do not know Java, and there is no reason they should
+have to.  Thus, the code in this directory is confined to what is
+strictly necessary to support Emacs, and only uses a subset of Java
+written in a way that is easily understandable to C programmers.
+
+Java is required because the entire Android runtime is based around
+Java, and there is no way to write an Android program which runs
+without Java.
+
+This text exists to prime other Emacs developers, already familar with
+C, on the basic architecture of the Android port, and to teach them
+how to read and write the Java code found in this directory.
+
+Java is an object oriented language with automatic memory management
+compiled down to bytecode, which is then subject to interpretation by
+a Java virtual machine.
+
+What that means, is that:
+
+struct emacs_window
+{
+  int some_fields;
+  int of_emacs_window;
+};
+
+static void
+do_something_with_emacs_window (struct emacs_window *a, int n)
+{
+  a->some_fields = a->of_emacs_window + n;
+}
+
+would be written:
+
+public class EmacsWindow
+{
+  public int someFields;
+  public int ofEmacsWindow;
+
+  public void
+  doSomething (int n)
+  {
+    someFields = ofEmacsWindow + n;
+  }
+}
+
+and instead of doing:
+
+do_something_with_emacs_window (my_window, 1);
+
+you say:
+
+myWindow.doSomething (1);
+
+In addition to functions associated with an object of a given class
+(such as EmacsWindow), Java also has two other kinds of functions.
+
+The first are so-called ``static'' functions (the static means
+something entirely different from what it does in C.)
+
+A static function, while still having to be defined within a class,
+can be called without any object.  Instead of the object, you write
+the name of the Java class within which it is defined. For example,
+the following C code:
+
+int
+multiply_a_with_b_and_then_add_c (int a, int b, int c)
+{
+  return a * b + c;
+}
+
+would be:
+
+public class EmacsSomething
+{
+  public static int
+  multiplyAWithBAndThenAddC (int a, int b, int c)
+  {
+    return a * b + c;
+  }
+};
+
+Then, instead of calling:
+
+int foo;
+
+foo = multiply_a_with_b_then_add_c (1, 2, 3);
+
+you say:
+
+int foo;
+
+foo = EmacsSomething.multiplyAWithBAndThenAddC (1, 2, 3);
+
+In Java, ``static'' does not mean that the function is only used
+within its compilation unit!  Instead, the ``private'' qualifier is
+used to mean more or less the same thing:
+
+static void
+this_procedure_is_only_used_within_this_file (void)
+{
+  do_something ();
+}
+
+becomes
+
+public class EmacsSomething
+{
+  private static void
+  thisProcedureIsOnlyUsedWithinThisClass ()
+  {
+
+  }
+}
+
+the other kind are called ``constructors''.  They are functions that
+must be called to allocate memory to hold a class:
+
+public class EmacsFoo
+{
+  int bar;
+
+  public
+  EmacsFoo (int tokenA, int tokenB)
+  {
+    bar = tokenA + tokenB;
+  }
+}
+
+now, the following statement:
+
+EmacsFoo foo;
+
+foo = new EmacsFoo (1, 2);
+
+becomes more or less equivalent to the following C code:
+
+struct emacs_foo
+{
+  int bar;
+};
+
+struct emacs_foo *
+make_emacs_foo (int token_a, int token_b)
+{
+  struct emacs_foo *foo;
+
+  foo = xmalloc (sizeof *foo);
+  foo->bar = token_a + token_b;
+
+  return foo;
+}
+
+/* ... */
+
+struct emacs_foo *foo;
+
+foo = make_emacs_foo (1, 2);
+
+A class may have any number of constructors, or no constructors at
+all, in which case the compiler inserts an empty constructor.
+
+
+
+Sometimes, you will see Java code that looks like this:
+
+    allFiles = filesDirectory.listFiles (new FileFilter () {
+       @Override
+       public boolean
+       accept (File file)
+       {
+         return (!file.isDirectory ()
+                 && file.getName ().endsWith (".pdmp"));
+       }
+      });
+
+This is Java's version of GCC's nested function extension.  The major
+difference is that the nested function may still be called even after
+it goes out of scope, and always retains a reference to the class and
+local variables around where it was called.
+
+Being an object-oriented language, Java also allows defining that a
+class ``extends'' another class.  The following C code:
+
+struct a
+{
+  long thirty_two;
+};
+
+struct b
+{
+  struct a a;
+  long long sixty_four;
+};
+
+extern void do_something (struct a *);
+
+void
+my_function (struct b *b)
+{
+  do_something (&b->a);
+}
+
+is roughly equivalent to the following Java code, split into two
+files:
+
+  A.java
+
+public class A
+{
+  int thirtyTwo;
+
+  public void
+  doSomething ()
+  {
+    etcEtcEtc ();
+  }
+};
+
+  B.java
+
+public class B extends A
+{
+  long sixty_four;
+
+  public static void
+  myFunction (B b)
+  {
+    b.doSomething ();
+  }
+}
+
+the Java runtime has transformed the call to ``b.doSomething'' to
+``((A) b).doSomething''.
+
+However, Java also allows overriding this behavior, by specifying the
+@Override keyword:
+
+public class B extends A
+{
+  long sixty_four;
+
+  @Override
+  public void
+  doSomething ()
+  {
+    Something.doSomethingTwo ();
+    super.doSomething ();
+  }
+}
+
+now, any call to ``doSomething'' on a ``B'' created using ``new B ()''
+will end up calling ``Something.doSomethingTwo'', before calling back
+to ``A.doSomething''.  This override also applies in reverse; that is
+to say, even if you write:
+
+  ((A) b).doSomething ();
+
+B's version of doSomething will still be called, if ``b'' was created
+using ``new B ()''.
+
+This mechanism is used extensively throughout the Java language and
+Android windowing APIs.
+
+Elsewhere, you will encounter Java code that defines arrays:
+
+public class EmacsFrobinicator
+{
+  public static void
+  emacsFrobinicate (int something)
+  {
+    int[] primesFromSomething;
+
+    primesFromSomething = new int[numberOfPrimes];
+    /* ... */
+  }
+}
+
+Java arrays are similar to C arrays in that they can not grow.  But
+they are very much unlike C arrays in that they are always references
+(as opposed to decaying into pointers in only some situations), and
+contain information about their length.
+
+If another function named ``frobinicate1'' takes an array as an
+argument, then it need not take the length of the array.
+
+Instead, it may simply iterate over the array like so:
+
+int i, k;
+
+for (i = 0; i < array.length; ++i)
+  {
+    k = array[i];
+
+    Whatever.doSomethingWithK (k);
+  }
+
+The syntax used to define arrays is also slightly different.  As
+arrays are always references, there is no way for you to tell the
+runtime to allocate an array of size N in a structure (class.)
+
+Instead, if you need an array of that size, you must declare a field
+with the type of the array, and allocate the array inside the class's
+constructor, like so:
+
+public class EmacsArrayContainer
+{
+  public int[] myArray;
+
+  public
+  EmacsArrayContainer ()
+  {
+    myArray = new array[10];
+  }
+}
+
+while in C, you could just have written:
+
+struct emacs_array_container
+{
+  int my_array[10];
+};
+
+or, possibly even better,
+
+typedef int emacs_array_container[10];
+
+Alas, Java has no equivalent of `typedef'.
+
+Like in C, Java string literals are delimited by double quotes.
+Unlike C, however, strings are not NULL-terminated arrays of
+characters, but a distinct type named ``String''.  They store their
+own length, characters in Java's 16-bit ``char'' type, and are capable
+of holding NULL bytes.
+
+Instead of writing:
+
+wchar_t character;
+extern char *s;
+size_t s;
+
+  for (/* determine n, s in a loop.  */)
+    s += mbstowc (&character, s, n);
+
+or:
+
+const char *byte;
+
+for (byte = my_string; *byte; ++byte)
+  /* do something with *byte.  */;
+
+or perhaps even:
+
+size_t length, i;
+char foo;
+
+length = strlen (my_string);
+
+for (i = 0; i < length; ++i)
+  foo = my_string[i];
+
+you write:
+
+char foo;
+int i;
+
+for (i = 0; i < myString.length (); ++i)
+  foo = myString.charAt (0);
+
+Java also has stricter rules on what can be used as a truth value in a
+conditional.  While in C, any non-zero value is true, Java requires
+that every truth value be of the boolean type ``boolean''.
+
+What this means is that instead of simply writing:
+
+  if (foo || bar)
+
+where foo can either be 1 or 0, and bar can either be NULL or a
+pointer to something, you must explicitly write:
+
+  if (foo != 0 || bar != null)
+
+in Java.
+
+JAVA NATIVE INTERFACE
+
+Java also provides an interface for C code to interface with Java.
+
+C functions exported from a shared library become static Java
+functions within a class, like so:
+
+public class EmacsNative
+{
+  /* Obtain the fingerprint of this build of Emacs.  The fingerprint
+     can be used to determine the dump file name.  */
+  public static native String getFingerprint ();
+
+  /* Set certain parameters before initializing Emacs.
+
+     assetManager must be the asset manager associated with the
+     context that is loading Emacs.  It is saved and remains for the
+     remainder the lifetime of the Emacs process.
+
+     filesDir must be the package's data storage location for the
+     current Android user.
+
+     libDir must be the package's data storage location for native
+     libraries.         It is used as PATH.
+
+     cacheDir must be the package's cache directory.  It is used as
+     the `temporary-file-directory'.
+
+     pixelDensityX and pixelDensityY are the DPI values that will be
+     used by Emacs.
+
+     classPath must be the classpath of this app_process process, or
+     NULL.
+
+     emacsService must be the EmacsService singleton, or NULL. */
+  public static native void setEmacsParams (AssetManager assetManager,
+                                           String filesDir,
+                                           String libDir,
+                                           String cacheDir,
+                                           float pixelDensityX,
+                                           float pixelDensityY,
+                                           String classPath,
+                                           EmacsService emacsService);
+}
+
+Where the corresponding C functions are located in android.c, and
+loaded by the special invocation:
+
+  static
+  {
+    System.loadLibrary ("emacs");
+  };
+
+where ``static'' defines a section of code which will be run upon the
+object (containing class) being loaded.  This is like:
+
+  __attribute__((constructor))
+
+on systems where shared object constructors are supported.
+
+See http://docs.oracle.com/en/java/javase/19/docs/specs/jni/intro.html
+for more details.
+
+
+
+OVERVIEW OF ANDROID
+
+When the Android system starts an application, it does not actually
+call the application's ``main'' function.  It may not even start the
+application's process if one is already running.
+
+Instead, Android is organized around components.  When the user opens
+the ``Emacs'' icon, the Android system looks up and starts the
+component associated with the ``Emacs'' icon.  In this case, the
+component is called an activity, and is declared in
+the AndroidManifest.xml in this directory:
+
+    <activity android:name="org.gnu.emacs.EmacsActivity"
+             android:launchMode="singleTop"
+             android:windowSoftInputMode="adjustResize"
+             android:exported="true"
+             
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
+      <intent-filter>
+       <action android:name="android.intent.action.MAIN" />
+       <category android:name="android.intent.category.DEFAULT" />
+       <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+
+This tells Android to start the activity defined in ``EmacsActivity''
+(defined in org/gnu/emacs/EmacsActivity.java), a class extending the
+Android class ``Activity''.
+
+To do so, the Android system creates an instance of ``EmacsActivity''
+and the window system window associated with it, and eventually calls:
+
+  Activity activity;
+
+  activity.onCreate (...);
+
+But which ``onCreate'' is really called?
+It is actually the ``onCreate'' defined in EmacsActivity.java, as
+it overrides the ``onCreate'' defined in Android's own Activity class:
+
+  @Override
+  public void
+  onCreate (Bundle savedInstanceState)
+  {
+    FrameLayout.LayoutParams params;
+    Intent intent;
+
+Then, this is what happens step-by-step within the ``onCreate''
+function:
+
+    /* See if Emacs should be started with -Q. */
+    intent = getIntent ();
+    EmacsService.needDashQ
+      = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q",
+                               false);
+
+Here, Emacs obtains the intent (a request to start a component) which
+was used to start Emacs, and sets a special flag if it contains a
+request for Emacs to start with the ``-Q'' command-line argument.
+
+    /* Set the theme to one without a title bar.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+      setTheme (android.R.style.Theme_DeviceDefault_NoActionBar);
+    else
+      setTheme (android.R.style.Theme_NoTitleBar);
+
+Next, Emacs sets an appropriate theme for the activity's associated
+window decorations.
+
+    params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT,
+                                          LayoutParams.MATCH_PARENT);
+
+    /* Make the frame layout.  */
+    layout = new FrameLayout (this);
+    layout.setLayoutParams (params);
+
+    /* Set it as the content view.  */
+    setContentView (layout);
+
+Then, Emacs creates a ``FrameLayout'', a widget that holds a single
+other widget, and makes it the activity's ``content view''.
+
+The activity itself is a ``FrameLayout'', so the ``layout parameters''
+here apply to the FrameLayout itself, and not its children.
+
+    /* Maybe start the Emacs service if necessary.  */
+    EmacsService.startEmacsService (this);
+
+And after that, Emacs calls the static function ``startEmacsService'',
+defined in the class ``EmacsService''. This starts the Emacs service
+component if necessary.
+
+    /* Add this activity to the list of available activities.  */
+    EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
+
+    super.onCreate (savedInstanceState);
+
+Finally, Emacs registers that this activity is now ready to receive
+top-level frames (windows) created from Lisp.
+
+Activities come and go, but Emacs has to stay running in the mean
+time.  Thus, Emacs also defines a ``service'', which is a long-running
+component that the Android system allows to run in the background.
+
+Let us go back and review the definition of ``startEmacsService'':
+
+  public static void
+  startEmacsService (Context context)
+  {
+    if (EmacsService.SERVICE == null)
+      {
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+         /* Start the Emacs service now.  */
+         context.startService (new Intent (context,
+                                           EmacsService.class));
+       else
+         /* Display the permanant notification and start Emacs as a
+            foreground service.  */
+         context.startForegroundService (new Intent (context,
+                                                     EmacsService.class));
+      }
+  }
+
+If ``EmacsService.SERVICE'' does not yet exist, what this does is to
+tell the ``context'' (the equivalent of an Xlib Display *) to start a
+service defined by the class ``EmacsService''. Eventually, this
+results in ``EmacsService.onCreate'' being called:
+
+  @Override
+  public void
+  onCreate ()
+  {
+    AssetManager manager;
+    Context app_context;
+    String filesDir, libDir, cacheDir, classPath;
+    double pixelDensityX;
+    double pixelDensityY;
+
+Here is what this function does, step-by-step:
+
+    SERVICE = this;
+
+First, it sets the special static variable ``SERVICE'' to ``this'',
+which is a pointer to the ``EmacsService' object that was created.
+
+    handler = new Handler (Looper.getMainLooper ());
+
+Next, it creates a ``Handler'' object for the ``main looper''.
+This is a helper structure which allows executing code on the Android
+user interface thread.
+
+    manager = getAssets ();
+    app_context = getApplicationContext ();
+    metrics = getResources ().getDisplayMetrics ();
+    pixelDensityX = metrics.xdpi;
+    pixelDensityY = metrics.ydpi;
+
+Finally, it obtains:
+
+  - the asset manager, which is used to retrieve assets packaged
+    into the Emacs application package.
+
+  - the application context, used to obtain application specific
+    information.
+
+  - the display metrics, and from them, the X and Y densities in dots
+    per inch.
+
+Then, inside a ``try'' block:
+
+    try
+      {
+       /* Configure Emacs with the asset manager and other necessary
+          parameters.  */
+       filesDir = app_context.getFilesDir ().getCanonicalPath ();
+       libDir = getLibraryDirectory ();
+       cacheDir = app_context.getCacheDir ().getCanonicalPath ();
+
+It obtains the names of the Emacs home, shared library, and temporary
+file directories.
+
+       /* Now provide this application's apk file, so a recursive
+          invocation of app_process (through android-emacs) can
+          find EmacsNoninteractive.  */
+       classPath = getApkFile ();
+
+The name of the Emacs application package.
+
+       Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+              + ", libDir = " + libDir + ", and classPath = " + classPath);
+
+Prints a debug message to the Android system log with this
+information.
+
+       EmacsNative.setEmacsParams (manager, filesDir, libDir,
+                                   cacheDir, (float) pixelDensityX,
+                                   (float) pixelDensityY,
+                                   classPath, this);
+
+And calls the native function ``setEmacsParams'' (defined in
+android.c) to configure Emacs with this information.
+
+       /* Start the thread that runs Emacs.  */
+       thread = new EmacsThread (this, needDashQ);
+       thread.start ();
+
+Then, it allocates an ``EmacsThread'' object, and starts that thread.
+Inside that thread is where Emacs's C code runs.
+
+      }
+    catch (IOException exception)
+      {
+       EmacsNative.emacsAbort ();
+       return;
+
+And here is the purpose of the ``try'' block.  Functions related to
+file names in Java will signal errors of various types upon failure.
+
+This ``catch'' block means that the Java virtual machine will abort
+execution of the contents of the ``try'' block as soon as an error of
+type ``IOException'' is encountered, and begin executing the contents
+of the ``catch'' block.
+
+Any failure of that type here is a crash, and
+``EmacsNative.emacsAbort'' is called to quickly abort the process to
+get a useful backtrace.
+      }
+  }
+
+Now, let us look at the definition of the class ``EmacsThread'', found
+in org/gnu/emacs/EmacsThread.java:
+
+public class EmacsThread extends Thread
+{
+  /* Whether or not Emacs should be started -Q.         */
+  private boolean startDashQ;
+
+  public
+  EmacsThread (EmacsService service, boolean startDashQ)
+  {
+    super ("Emacs main thread");
+    this.startDashQ = startDashQ;
+  }
+
+  @Override
+  public void
+  run ()
+  {
+    String args[];
+
+    if (!startDashQ)
+      args = new String[] { "libandroid-emacs.so", };
+    else
+      args = new String[] { "libandroid-emacs.so", "-Q", };
+
+    /* Run the native code now.         */
+    EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
+  }
+};
+
+The class itself defines a single field, ``startDashQ'', a constructor
+with an unused argument of the type ``EmacsService'' (which is useful
+while debugging) and a flag ``startDashQ'', and a single function
+``run'', overriding the same function in the class ``Thread''.
+
+When ``thread.start'' is called, the Java virtual machine creates a
+new thread, and then calls the function ``run'' within that thread.
+
+This function then computes a suitable argument vector, and calls
+``EmacsNative.initEmacs'' (defined in android.c), which then calls a
+modified version of the regular Emacs ``main'' function.
+
+At that point, Emacs initialization proceeds as usual:
+Vinitial_window_system is set, loadup.el calls `normal-top-level',
+which calls `command-line', and finally
+`window-system-initialization', which initializes the `android'
+terminal interface as usual.
+
+What happens here is the same as on other platforms.  Now, here is
+what happens when the initial frame is created: Fx_create_frame calls
+`android_create_frame_window' to create a top level window:
+
+static void
+android_create_frame_window (struct frame *f)
+{
+  struct android_set_window_attributes attributes;
+  enum android_window_value_mask attribute_mask;
+
+  attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f);
+  attribute_mask = ANDROID_CW_BACK_PIXEL;
+
+  block_input ();
+  FRAME_ANDROID_WINDOW (f)
+    = android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
+                            f->left_pos,
+                            f->top_pos,
+                            FRAME_PIXEL_WIDTH (f),
+                            FRAME_PIXEL_HEIGHT (f),
+                            attribute_mask, &attributes);
+  unblock_input ();
+}
+
+This calls the function `android_create_window' with some arguments
+whose meanings are identical to the arguments to `XCreateWindow'.
+
+Here is the definition of `android_create_window', in android.c:
+
+android_window
+android_create_window (android_window parent, int x, int y,
+                      int width, int height,
+                      enum android_window_value_mask value_mask,
+                      struct android_set_window_attributes *attrs)
+{
+  static jclass class;
+  static jmethodID constructor;
+  jobject object, parent_object, old;
+  android_window window;
+  android_handle prev_max_handle;
+  bool override_redirect;
+
+What does it do? First, some context:
+
+At any time, there can be at most 65535 Java objects referred to by
+the rest of Emacs through the Java native interface.  Each such object
+is assigned a ``handle'' (similar to an XID on X) and given a unique
+type.  The function `android_resolve_handle' returns the JNI `jobject'
+associated with a given handle.
+
+  parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
+
+Here, it is being used to look up the `jobject' associated with the
+`parent' handle.
+
+  prev_max_handle = max_handle;
+  window = android_alloc_id ();
+
+Next, `max_handle' is saved, and a new handle is allocated for
+`window'.
+
+  if (!window)
+    error ("Out of window handles!");
+
+An error is signalled if Emacs runs out of available handles.
+
+  if (!class)
+    {
+      class = (*android_java_env)->FindClass (android_java_env,
+                                             "org/gnu/emacs/EmacsWindow");
+      assert (class != NULL);
+
+Then, if this initialization has not yet been completed, Emacs
+proceeds to find the Java class named ``EmacsWindow''.
+
+      constructor
+       = (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
+                                           "(SLorg/gnu/emacs/EmacsWindow;"
+                                           "IIIIZ)V");
+      assert (constructor != NULL);
+
+And it tries to look up the constructor, which should take seven
+arguments:
+
+  S                                    - a short.  (the handle ID)
+  Lorg/gnu/Emacs/EmacsWindow;          - an instance of the EmacsWindow
+                                         class.  (the parent)
+  IIII                                 - four ints.  (the window geometry.)
+  Z                                    - a boolean.  (whether or not the
+                                         window is override-redirect; see
+                                         XChangeWindowAttributes.)
+
+      old = class;
+      class = (*android_java_env)->NewGlobalRef (android_java_env, class);
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (old);
+
+Next, it saves a global reference to the class and deletes the local
+reference.  Global references will never be deallocated by the Java
+virtual machine as long as they still exist.
+
+      if (!class)
+       memory_full (0);
+    }
+
+  /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
+     creation time.  */
+  override_redirect = ((value_mask
+                       & ANDROID_CW_OVERRIDE_REDIRECT)
+                      && attrs->override_redirect);
+
+  object = (*android_java_env)->NewObject (android_java_env, class,
+                                          constructor, (jshort) window,
+                                          parent_object, (jint) x, (jint) y,
+                                          (jint) width, (jint) height,
+                                          (jboolean) override_redirect);
+
+Then, it creates an instance of the ``EmacsWindow'' class with the
+appropriate arguments and previously determined constructor.
+
+  if (!object)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      max_handle = prev_max_handle;
+      memory_full (0);
+
+If creating the object fails, Emacs clears the ``pending exception''
+and signals that it is out of memory.
+    }
+
+  android_handles[window].type = ANDROID_HANDLE_WINDOW;
+  android_handles[window].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env,
+                                        object);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+Otherwise, it associates a new global reference to the object with the
+handle, and deletes the local reference returned from the JNI
+NewObject function.
+
+  if (!android_handles[window].handle)
+    memory_full (0);
+
+If allocating the global reference fails, Emacs signals that it is out
+of memory.
+
+  android_change_window_attributes (window, value_mask, attrs);
+  return window;
+
+Otherwise, it applies the specified window attributes and returns the
+handle of the new window.
+}
+
+
+
+DRAWABLES, CURSORS AND HANDLES
+
+Each widget created by Emacs corresponds to a single ``window'', which
+has its own backing store.  This arrangement is quite similar to X.
+
+C code does not directly refer to the EmacsView widgets that implement
+the UI logic behind windows.  Instead, its handles refer to
+EmacsWindow structures, which contain the state necessary to interact
+with the widgets in an orderly and synchronized manner.
+
+Like X, both pixmaps and windows are drawable resources, and the same
+graphics operations can be applied to both.  Thus, a separate
+EmacsPixmap structure is used to wrap around Android Bitmap resources,
+and the Java-level graphics operation functions are capable of
+operating on them both.
+
+Finally, graphics contexts are maintained on both the C and Java
+levels; the C state recorded in `struct android_gc' is kept in sync
+with the Java state in the GContext handle's corresponding EmacsGC
+structure, and cursors are used through handles that refer to
+EmacsCursor structures that hold system PointerIcons.
+
+In all cases, the interfaces provided are identical to X.
+
+
+
+EVENT LOOP
+
+In a typical Android application, the event loop is managed by the
+operating system, and callbacks (implemented through overriding
+separate functions in widgets) are run by the event loop wherever
+necessary.  The thread which runs the event loop is also the only
+thread capable of creating and manipulating widgets and activities,
+and is referred to as the ``UI thread''.
+
+These callbacks are used by Emacs to write representations of X-like
+events to a separate event queue, which are then read from Emacs's own
+event loop running in a separate thread.  This is accomplished through
+replacing `select' by a function which waits for the event queue to be
+occupied, in addition to any file descriptors that `select' would
+normally wait for.
+
+Conversely, Emacs's event loop sometimes needs to send events to the
+UI thread.  These events are implemented as tiny fragments of code,
+which are run as they are received by the main thread.
+
+A typical example is `displayToast', which is implemented in
+EmacsService.java:
+
+  public void
+  displayToast (final String string)
+  {
+    runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         Toast toast;
+
+         toast = Toast.makeText (getApplicationContext (),
+                                 string, Toast.LENGTH_SHORT);
+         toast.show ();
+       }
+      });
+  }
+
+Here, the variable `string' is used by a nested function.  This nested
+function contains a copy of that variable, and is run on the main
+thread using the function `runOnUiThread', in order to display a short
+status message on the display.
+
+When Emacs needs to wait for the nested function to finish, it uses a
+mechanism implemented in `syncRunnable'.  This mechanism first calls a
+deadlock avoidance mechanism, then runs a nested function on the UI
+thread, which is expected to signal itself as a condition variable
+upon completion.  It is typically used to allocate resources that can
+only be allocated from the UI thread, or to obtain non-thread-safe
+information.  The following function is an example; it returns a new
+EmacsView widget corresponding to the provided window:
+
+  public EmacsView
+  getEmacsView (final EmacsWindow window, final int visibility,
+               final boolean isFocusedByDefault)
+  {
+    Runnable runnable;
+    final EmacsHolder<EmacsView> view;
+
+    view = new EmacsHolder<EmacsView> ();
+
+    runnable = new Runnable () {
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             view.thing = new EmacsView (window);
+             view.thing.setVisibility (visibility);
+
+             /* The following function is only present on Android 26
+                or later.  */
+             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+               view.thing.setFocusedByDefault (isFocusedByDefault);
+
+             notify ();
+           }
+       }
+      };
+
+    syncRunnable (runnable);
+    return view.thing;
+  }
+
+As no value can be directly returned from the nested function, a
+separate container object is used to hold the result after the
+function finishes execution.  Note the type name inside the angle
+brackets: this type is substituted into the class definition as it is
+used; a definition such as:
+
+public class Foo<T>
+{
+  T bar;
+};
+
+can not be used alone:
+
+  Foo holder; /* Error! */
+
+but must have a type specified:
+
+  Foo<Object> holder;
+
+in which case the effective definition is:
+
+public class Foo
+{
+  Object bar;
+};
diff --git a/java/debug.sh b/java/debug.sh
new file mode 100755
index 00000000000..d6e439bec90
--- /dev/null
+++ b/java/debug.sh
@@ -0,0 +1,368 @@
+#!/bin/bash
+### Run Emacs under GDB or JDB on Android.
+
+## Copyright (C) 2023 Free Software Foundation, Inc.
+
+## This file is part of GNU Emacs.
+
+## GNU Emacs is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+
+## GNU Emacs is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+
+## You should have received a copy of the GNU General Public License
+## along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+set -m
+oldpwd=`pwd`
+cd `dirname $0`
+
+devices=`adb devices | grep device | awk -- '/device\y/ { print $1 }' -`
+device=
+progname=$0
+package=org.gnu.emacs
+activity=org.gnu.emacs.EmacsActivity
+gdb_port=5039
+jdb_port=64013
+jdb=no
+attach_existing=no
+gdbserver=
+gdb=gdb
+
+while [ $# -gt 0 ]; do
+    case "$1" in
+       ## This option specifies the serial number of a device to use.
+       "--device" )
+           device="$2"
+           if [ -z device ]; then
+               echo "You must specify an argument to --device"
+               exit 1
+           fi
+           shift
+           ;;
+       "--help" )
+           echo "Usage: $progname [options] -- [gdb options]"
+           echo ""
+           echo "  --device DEVICE     run Emacs on the specified device"
+           echo "  --port PORT         run the GDB server on a specific port"
+           echo "  --jdb-port PORT     run the JDB server on a specific port"
+           echo "  --jdb               run JDB instead of GDB"
+           echo "  --gdb               use specified GDB binary"
+           echo "  --attach-existing   attach to an existing process"
+           echo "  --gdbserver BINARY  upload and use the specified gdbserver 
binary"
+           echo "  --help              print this message"
+           echo ""
+           echo "Available devices:"
+           for device in $devices; do
+               echo "  " $device
+           done
+           echo ""
+           exit 0
+           ;;
+       "--jdb" )
+           jdb=yes
+           ;;
+       "--gdb" )
+           shift
+           gdb=$1
+           ;;
+       "--gdbserver" )
+           shift
+           gdbserver=$1
+           ;;
+       "--port" )
+           shift
+           gdb_port=$1
+           ;;
+       "--jdb-port" )
+           shift
+           jdb_port=$1
+           ;;
+       "--attach-existing" )
+           attach_existing=yes
+           ;;
+       "--" )
+           shift
+           gdbargs=$@
+           break;
+           ;;
+       * )
+           echo "$progname: Unrecognized argument $1"
+           exit 1
+           ;;
+    esac
+    shift
+done
+
+if [ -z "$devices" ]; then
+    echo "No devices are available."
+    exit 1
+fi
+
+if [ -z $device ]; then
+    device=$devices
+fi
+
+if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z device ]; then
+    echo "Multiple devices are available.  Please pick one using"
+    echo "--device and try again."
+fi
+
+echo "Looking for $package on device $device"
+
+# Find the application data directory
+app_data_dir=`adb -s $device shell run-as $package sh -c 'pwd 2> /dev/null'`
+
+if [ -z $app_data_dir ]; then
+   echo "The data directory for the package $package was not found."
+   echo "Is it installed?"
+fi
+
+echo "Found application data directory at" "$app_data_dir"
+
+# Generate an awk script to extract PIDs from Android ps output.  It
+# is enough to run `ps' as the package user on newer versions of
+# Android, but that doesn't work on Android 2.3.
+cat << EOF > tmp.awk
+BEGIN {
+  pid = 0;
+  pid_column = 2;
+}
+
+{
+  # Remove any trailing carriage return from the input line.
+  gsub ("\r", "", \$NF)
+
+  # If this is line 1, figure out which column contains the PID.
+  if (NR == 1)
+    {
+      for (n = 1; n <= NF; ++n)
+       {
+         if (\$n == "PID")
+           pid_column=n;
+       }
+    }
+  else if (\$NF == "$package")
+   print \$pid_column
+}
+EOF
+
+# Make sure that file disappears once this script exits.
+trap "rm -f $(pwd)/tmp.awk" 0
+
+# First, run ps to fetch the list of process IDs.
+package_pids=`adb -s $device shell ps`
+
+# Next, extract the list of PIDs currently running.
+package_pids=`awk -f tmp.awk <<< $package_pids`
+
+if [ "$attach_existing" != "yes" ]; then
+    # Finally, kill each existing process.
+    for pid in $package_pids; do
+       echo "Killing existing process $pid..."
+       adb -s $device shell run-as $package kill -9 $pid &> /dev/null
+    done
+
+    # Now run the main activity.  This must be done as the adb user and
+    # not as the package user.
+    echo "Starting activity $activity and attaching debugger"
+
+    # Exit if the activity could not be started.
+    adb -s $device shell am start -D -n "$package/$activity"
+    if [ ! $? ]; then
+       exit 1;
+    fi
+
+    # Sleep for a bit.  Otherwise, the process may not have started
+    # yet.
+    sleep 1
+
+    # Now look for processes matching the package again.
+    package_pids=`adb -s $device shell ps`
+
+    # Next, remove lines matching "ps" itself.
+    package_pids=`awk -f tmp.awk <<< $package_pids`
+fi
+
+pid=$package_pids
+num_pids=`wc -w <<< "$package_pids"`
+
+if [ $num_pids -gt 1 ]; then
+    echo "More than one process was started:"
+    echo ""
+    adb -s $device shell run-as $package ps | awk -- "{
+      if (!match (\$0, /ps/) && match (\$0, /$package/))
+        print \$0
+    }"
+    echo ""
+    printf "Which one do you want to attach to? "
+    read pid
+elif [ -z $package_pids ]; then
+    echo "No processes were found to attach to."
+    exit 1
+fi
+
+# If either --jdb was specified or debug.sh is not connecting to an
+# existing process, then store a suitable JDB invocation in
+# jdb_command.  GDB will then run JDB to unblock the application from
+# the wait dialog after startup.
+
+if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then
+    adb -s $device forward --remove-all
+    adb -s $device forward "tcp:$jdb_port" "jdwp:$pid"
+
+    if [ ! $? ]; then
+       echo "Failed to forward jdwp:$pid to $jdb_port!"
+       echo "Perhaps you need to specify a different port with --port?"
+       exit 1;
+    fi
+
+    jdb_command="jdb -connect \
+                com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port"
+
+    if [ $jdb = "yes" ]; then
+       # Just start JDB and then exit
+       $jdb_command
+       exit 1
+    fi
+fi
+
+if [ -n "$jdb_command" ]; then
+    echo "Starting JDB to unblock application."
+
+    # Start JDB to unblock the application.
+    coproc JDB { $jdb_command; }
+
+    # Tell JDB to first suspend all threads.
+    echo "suspend" >&${JDB[1]}
+
+    # Tell JDB to print a magic string once the program is
+    # initialized.
+    echo "print \"__verify_jdb_has_started__\"" >&${JDB[1]}
+
+    # Now wait for JDB to give the string back.
+    line=
+    while :; do
+       read -u ${JDB[0]} line
+       if [ ! $? ]; then
+           echo "Failed to read JDB output"
+           exit 1
+       fi
+
+       case "$line" in
+           *__verify_jdb_has_started__*)
+               # Android only polls for a Java debugger every 200ms, so
+               # the debugger must be connected for at least that long.
+               echo "Pausing 1 second for the program to continue."
+               sleep 1
+               break
+               ;;
+       esac
+    done
+
+    # Note that JDB does not exit until GDB is fully attached!
+fi
+
+# See if gdbserver has to be uploaded
+gdbserver_cmd=
+is_root=
+if [ -z "$gdbserver" ]; then
+    gdbserver_bin=/system/bin/gdbserver64
+else
+    gdbserver_bin=/data/local/tmp/gdbserver
+    gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \
+                  \"tee gdbserver > /dev/null\""
+
+    # Upload the specified gdbserver binary to the device.
+    adb -s $device push "$gdbserver" "$gdbserver_bin"
+
+    if (adb -s $device shell ls /system/bin | grep -G tee); then
+       # Copy it to the user directory.
+       adb -s $device shell "$gdbserver_cat"
+       adb -s $device shell "run-as $package chmod 777 gdbserver"
+       gdbserver_cmd="./gdbserver"
+    else
+       # Hopefully this is an old version of Android which allows
+       # execution from /data/local/tmp.  Its `chmod' doesn't support
+       # `+x' either.
+       adb -s $device shell "chmod 777 $gdbserver_bin"
+       gdbserver_cmd="$gdbserver_bin"
+
+       # If the user is root, then there is no need to open any kind
+       # of TCP socket.
+       if (adb -s $device shell id | grep -G root); then
+           gdbserver=
+           is_root=yes
+       fi
+    fi
+fi
+
+# Now start gdbserver on the device asynchronously.
+
+echo "Attaching gdbserver to $pid on $device..."
+exec 5<> /tmp/file-descriptor-stamp
+rm -f /tmp/file-descriptor-stamp
+
+if [ -z "$gdbserver" ]; then
+    if [ "$is_root" = "yes" ]; then
+       adb -s $device shell $gdbserver_bin --multi \
+           "0.0.0.0:7564" --attach $pid >&5 &
+       gdb_socket="tcp:7564"
+    else
+       adb -s $device shell $gdbserver_bin --multi \
+           "0.0.0.0:7564" --attach $pid >&5 &
+       gdb_socket="tcp:7564"
+    fi
+else
+    # Normally the program cannot access $gdbserver_bin when it is
+    # placed in /data/local/tmp.
+    adb -s $device shell run-as $package $gdbserver_cmd --multi \
+       "+debug.$package.socket" --attach $pid >&5 &
+    gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket"
+fi
+
+# In order to allow adb to forward to the gdbserver socket, make the
+# app data directory a+x.
+adb -s $device shell run-as $package chmod a+x $app_data_dir
+
+# Wait until gdbserver successfully runs.
+line=
+while read -u 5 line; do
+    case "$line" in
+       *Attached* )
+           break;
+           ;;
+       *error* | *Error* | failed )
+           echo "GDB error:" $line
+           exit 1
+           ;;
+       * )
+           ;;
+    esac
+done
+
+# Now that GDB is attached, tell the Java debugger to resume execution
+# and then exit.
+
+if [ -n "$jdb_command" ]; then
+    echo "resume" >&${JDB[1]}
+    echo "exit" >&${JDB[1]}
+fi
+
+# Forward the gdb server port here.
+adb -s $device forward "tcp:$gdb_port" $gdb_socket
+if [ ! $? ]; then
+    echo "Failed to forward $app_data_dir/debug.$package.socket"
+    echo "to $gdb_port!  Perhaps you need to specify a different port"
+    echo "with --port?"
+    exit 1;
+fi
+
+# Finally, start gdb with any extra arguments needed.
+cd "$oldpwd"
+$gdb --eval-command "target remote localhost:$gdb_port" $gdbargs
diff --git a/java/emacs.keystore b/java/emacs.keystore
new file mode 100644
index 00000000000..76d80b6db65
Binary files /dev/null and b/java/emacs.keystore differ
diff --git a/java/org/gnu/emacs/EmacsActivity.java 
b/java/org/gnu/emacs/EmacsActivity.java
new file mode 100644
index 00000000000..cecd9c21d99
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsActivity.java
@@ -0,0 +1,473 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.lang.IllegalStateException;
+import java.util.List;
+import java.util.ArrayList;
+
+import android.app.Activity;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.Build;
+import android.os.Bundle;
+
+import android.util.Log;
+
+import android.net.Uri;
+
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+
+import android.widget.FrameLayout;
+
+public class EmacsActivity extends Activity
+  implements EmacsWindowAttachmentManager.WindowConsumer,
+  ViewTreeObserver.OnGlobalLayoutListener
+{
+  public static final String TAG = "EmacsActivity";
+
+  /* ID for URIs from a granted document tree.  */
+  public static final int ACCEPT_DOCUMENT_TREE = 1;
+
+  /* The currently attached EmacsWindow, or null if none.  */
+  private EmacsWindow window;
+
+  /* The frame layout associated with the activity.  */
+  private FrameLayout layout;
+
+  /* List of activities with focus.  */
+  public static final List<EmacsActivity> focusedActivities;
+
+  /* The last activity to have been focused.  */
+  public static EmacsActivity lastFocusedActivity;
+
+  /* The currently focused window.  */
+  public static EmacsWindow focusedWindow;
+
+  /* Whether or not this activity is paused.  */
+  private boolean isPaused;
+
+  /* Whether or not this activity is fullscreen.  */
+  private boolean isFullscreen;
+
+  /* The last context menu to be closed.  */
+  private static Menu lastClosedMenu;
+
+  static
+  {
+    focusedActivities = new ArrayList<EmacsActivity> ();
+  };
+
+  public static void
+  invalidateFocus1 (EmacsWindow window)
+  {
+    if (window.view.isFocused ())
+      focusedWindow = window;
+
+    for (EmacsWindow child : window.children)
+      invalidateFocus1 (child);
+  }
+
+  public static void
+  invalidateFocus ()
+  {
+    EmacsWindow oldFocus;
+
+    /* Walk through each focused activity and assign the window focus
+       to the bottom-most focused window within.  Record the old focus
+       as well.  */
+    oldFocus = focusedWindow;
+    focusedWindow = null;
+
+    for (EmacsActivity activity : focusedActivities)
+      {
+       if (activity.window != null)
+         invalidateFocus1 (activity.window);
+      }
+
+    /* Send focus in- and out- events to the previous and current
+       focus.  */
+
+    if (oldFocus != null)
+      EmacsNative.sendFocusOut (oldFocus.handle,
+                               System.currentTimeMillis ());
+
+    if (focusedWindow != null)
+      EmacsNative.sendFocusIn (focusedWindow.handle,
+                              System.currentTimeMillis ());
+  }
+
+  @Override
+  public final void
+  detachWindow ()
+  {
+    syncFullscreenWith (null);
+
+    if (window == null)
+      Log.w (TAG, "detachWindow called, but there is no window");
+    else
+      {
+       /* Clear the window's pointer to this activity and remove the
+          window's view.  */
+       window.setConsumer (null);
+
+       /* The window can't be iconified any longer.  */
+       window.noticeDeiconified ();
+       layout.removeView (window.view);
+       window = null;
+
+       invalidateFocus ();
+      }
+  }
+
+  @Override
+  public final void
+  attachWindow (EmacsWindow child)
+  {
+    if (window != null)
+      throw new IllegalStateException ("trying to attach window when one"
+                                      + " already exists");
+
+    syncFullscreenWith (child);
+
+    /* Record and attach the view.  */
+
+    window = child;
+    layout.addView (window.view);
+    child.setConsumer (this);
+
+    /* If the window isn't no-focus-on-map, focus its view.  */
+    if (!child.getDontFocusOnMap ())
+      window.view.requestFocus ();
+
+    /* If the activity is iconified, send that to the window.  */
+    if (isPaused)
+      window.noticeIconified ();
+
+    /* Invalidate the focus.  */
+    invalidateFocus ();
+  }
+
+  @Override
+  public final void
+  destroy ()
+  {
+    finish ();
+  }
+
+  @Override
+  public final EmacsWindow
+  getAttachedWindow ()
+  {
+    return window;
+  }
+
+  @Override
+  public final void
+  onCreate (Bundle savedInstanceState)
+  {
+    FrameLayout.LayoutParams params;
+    Intent intent;
+    View decorView;
+    ViewTreeObserver observer;
+    int matchParent;
+
+    /* See if Emacs should be started with any extra arguments, such
+       as `--quick'.  */
+    intent = getIntent ();
+    EmacsService.extraStartupArgument
+      = intent.getStringExtra ("org.gnu.emacs.STARTUP_ARGUMENT");
+
+    matchParent = FrameLayout.LayoutParams.MATCH_PARENT;
+    params
+      = new FrameLayout.LayoutParams (matchParent,
+                                     matchParent);
+
+    /* Make the frame layout.  */
+    layout = new FrameLayout (this);
+    layout.setLayoutParams (params);
+
+    /* Set it as the content view.  */
+    setContentView (layout);
+
+    /* Maybe start the Emacs service if necessary.  */
+    EmacsService.startEmacsService (this);
+
+    /* Add this activity to the list of available activities.  */
+    EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
+
+    /* Start observing global layout changes between Jelly Bean and Q.
+       This is required to restore the fullscreen state whenever the
+       on screen keyboard is displayed, as there is otherwise no way
+       to determine when the on screen keyboard becomes visible.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+       && Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
+      {
+       decorView = getWindow ().getDecorView ();
+       observer = decorView.getViewTreeObserver ();
+       observer.addOnGlobalLayoutListener (this);
+      }
+
+    super.onCreate (savedInstanceState);
+  }
+
+  @Override
+  public final void
+  onGlobalLayout ()
+  {
+    syncFullscreenWith (window);
+  }
+
+  @Override
+  public final void
+  onDestroy ()
+  {
+    EmacsWindowAttachmentManager manager;
+    boolean isMultitask;
+
+    manager = EmacsWindowAttachmentManager.MANAGER;
+
+    /* The activity will die shortly hereafter.  If there is a window
+       attached, close it now.  */
+    isMultitask = this instanceof EmacsMultitaskActivity;
+    manager.removeWindowConsumer (this, isMultitask || isFinishing ());
+    focusedActivities.remove (this);
+    invalidateFocus ();
+
+    /* Remove this activity from the static field, lest it leak.  */
+    if (lastFocusedActivity == this)
+      lastFocusedActivity = null;
+
+    super.onDestroy ();
+  }
+
+  @Override
+  public final void
+  onWindowFocusChanged (boolean isFocused)
+  {
+    if (isFocused && !focusedActivities.contains (this))
+      {
+       focusedActivities.add (this);
+       lastFocusedActivity = this;
+
+       /* Update the window insets as the focus change may have
+          changed the window insets as well, and the system does not
+          automatically restore visibility flags.  */
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+           && Build.VERSION.SDK_INT < Build.VERSION_CODES.R
+           && isFullscreen)
+         syncFullscreenWith (window);
+      }
+    else
+      focusedActivities.remove (this);
+
+    invalidateFocus ();
+  }
+
+  @Override
+  public final void
+  onPause ()
+  {
+    isPaused = true;
+
+    EmacsWindowAttachmentManager.MANAGER.noticeIconified (this);
+    super.onPause ();
+  }
+
+  @Override
+  public final void
+  onResume ()
+  {
+    isPaused = false;
+
+    EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this);
+    super.onResume ();
+  }
+
+  @Override
+  public final void
+  onContextMenuClosed (Menu menu)
+  {
+    int serial;
+
+    /* See the comment inside onMenuItemClick.  */
+
+    if (((EmacsContextMenu.wasSubmenuSelected == -2)
+        || (EmacsContextMenu.wasSubmenuSelected >= 0
+            && ((System.currentTimeMillis ()
+                 - EmacsContextMenu.wasSubmenuSelected)
+                <= 300)))
+       || menu == lastClosedMenu)
+      {
+       EmacsContextMenu.wasSubmenuSelected = -1;
+       lastClosedMenu = menu;
+       return;
+      }
+
+    /* lastClosedMenu is set because Android apparently calls this
+       function twice.  */
+
+    lastClosedMenu = null;
+
+    /* Send a context menu event given that no menu item has already
+       been selected.  */
+    if (!EmacsContextMenu.itemAlreadySelected)
+      {
+       serial = EmacsContextMenu.lastMenuEventSerial;
+       EmacsNative.sendContextMenu ((short) 0, 0,
+                                    serial);
+      }
+
+    super.onContextMenuClosed (menu);
+  }
+
+  @SuppressWarnings ("deprecation")
+  public final void
+  syncFullscreenWith (EmacsWindow emacsWindow)
+  {
+    WindowInsetsController controller;
+    Window window;
+    int behavior, flags;
+    View view;
+
+    if (emacsWindow != null)
+      isFullscreen = emacsWindow.fullscreen;
+    else
+      isFullscreen = false;
+
+    /* On Android 11 or later, use the window insets controller to
+       control whether or not the view is fullscreen.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+      {
+       window = getWindow ();
+
+       /* If there is no attached window, return immediately.  */
+       if (window == null)
+         return;
+
+       behavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+       controller = window.getInsetsController ();
+       controller.setSystemBarsBehavior (behavior);
+
+       if (isFullscreen)
+         controller.hide (WindowInsets.Type.statusBars ()
+                          | WindowInsets.Type.navigationBars ());
+       else
+         controller.show (WindowInsets.Type.statusBars ()
+                          | WindowInsets.Type.navigationBars ());
+
+       return;
+      }
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+      {
+       /* On Android 4.1 or later, use `setSystemUiVisibility'.  */
+
+       window = getWindow ();
+
+       if (window == null)
+         return;
+
+       view = window.getDecorView ();
+
+       if (isFullscreen)
+         {
+           flags = 0;
+           flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+
+           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+             {
+               /* These flags means that Emacs will be full screen as
+                  long as the state flag is set.  */
+               flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+               flags |= View.SYSTEM_UI_FLAG_IMMERSIVE;
+               flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+             }
+
+           /* Apply the given flags.  */
+           view.setSystemUiVisibility (flags);
+         }
+       else
+         view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_VISIBLE);
+      }
+  }
+
+  @Override
+  public final void
+  onAttachedToWindow ()
+  {
+    super.onAttachedToWindow ();
+
+    /* Update the window insets.  */
+    syncFullscreenWith (window);
+  }
+
+
+
+  @Override
+  public final void
+  onActivityResult (int requestCode, int resultCode, Intent data)
+  {
+    ContentResolver resolver;
+    Uri uri;
+    int flags;
+
+    switch (requestCode)
+      {
+      case ACCEPT_DOCUMENT_TREE:
+
+       /* A document granted through
+          EmacsService.requestDirectoryAccess.  */
+
+       if (resultCode == RESULT_OK)
+         {
+           resolver = getContentResolver ();
+           uri = data.getData ();
+           flags = (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+           try
+             {
+               if (uri != null)
+                 resolver.takePersistableUriPermission (uri, flags);
+             }
+           catch (Exception exception)
+             {
+               /* Permission to access URI might've been revoked in
+                  between selecting the file and this callback being
+                  invoked.  Don't crash in such cases.  */
+             }
+         }
+
+       break;
+      }
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsApplication.java 
b/java/org/gnu/emacs/EmacsApplication.java
new file mode 100644
index 00000000000..8afa5bcedb4
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsApplication.java
@@ -0,0 +1,92 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.io.File;
+import java.io.FileFilter;
+
+import android.content.Context;
+
+import android.app.Application;
+import android.util.Log;
+
+public final class EmacsApplication extends Application
+{
+  private static final String TAG = "EmacsApplication";
+
+  /* The name of the dump file to use.  */
+  public static String dumpFileName;
+
+  public static void
+  findDumpFile (Context context)
+  {
+    File filesDirectory;
+    File[] allFiles;
+    String wantedDumpFile;
+    int i;
+
+    wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint ()
+                     + ".pdmp");
+
+    /* Obtain a list of all files ending with ``.pdmp''.  Then, look
+       for a file named ``emacs-<fingerprint>.pdmp'' and delete the
+       rest.  */
+    filesDirectory = context.getFilesDir ();
+
+    allFiles = filesDirectory.listFiles (new FileFilter () {
+       @Override
+       public boolean
+       accept (File file)
+       {
+         return (!file.isDirectory ()
+                 && file.getName ().endsWith (".pdmp"));
+       }
+      });
+
+    if (allFiles == null)
+      return;
+
+    /* Now try to find the right dump file.  */
+    for (i = 0; i < allFiles.length; ++i)
+      {
+       if (allFiles[i].getName ().equals (wantedDumpFile))
+         dumpFileName = allFiles[i].getAbsolutePath ();
+       else
+         /* Delete this outdated dump file.  */
+         allFiles[i].delete ();
+      }
+  }
+
+  @Override
+  public void
+  onCreate ()
+  {
+    /* Block signals which don't interest the current thread and its
+       descendants created by the system.  The original signal mask
+       will be restored for the Emacs thread in `initEmacs'.  */
+    EmacsNative.setupSystemThread ();
+
+    /* Locate a suitable dump file.  */
+    findDumpFile (this);
+
+    /* Start the rest of the application.  */
+    super.onCreate ();
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsClipboard.java 
b/java/org/gnu/emacs/EmacsClipboard.java
new file mode 100644
index 00000000000..5cd48af6e3a
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsClipboard.java
@@ -0,0 +1,47 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.os.Build;
+
+/* This class provides helper code for accessing the clipboard,
+   abstracting between the different interfaces on API 8 and 11.  */
+
+public abstract class EmacsClipboard
+{
+  public abstract void setClipboard (byte[] bytes);
+  public abstract int ownsClipboard ();
+  public abstract boolean clipboardExists ();
+  public abstract byte[] getClipboard ();
+
+  public abstract byte[][] getClipboardTargets ();
+  public abstract long[] getClipboardData (byte[] target);
+
+  /* Create the correct kind of clipboard for this system.  */
+
+  public static EmacsClipboard
+  makeClipboard ()
+  {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
+      return new EmacsSdk11Clipboard ();
+    else
+      return new EmacsSdk8Clipboard ();
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsContextMenu.java 
b/java/org/gnu/emacs/EmacsContextMenu.java
new file mode 100644
index 00000000000..c5b87aa804a
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsContextMenu.java
@@ -0,0 +1,389 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.Build;
+
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.SubMenu;
+
+import android.util.Log;
+
+/* Context menu implementation.  This object is built from JNI and
+   describes a menu hiearchy.  Then, `inflate' can turn it into an
+   Android menu, which can be turned into a popup (or other kind of)
+   menu.  */
+
+public final class EmacsContextMenu
+{
+  private static final String TAG = "EmacsContextMenu";
+
+  /* Whether or not an item was selected.  */
+  public static boolean itemAlreadySelected;
+
+  /* Whether or not a submenu was selected.
+     Value is -1 if no; value is -2 if yes, and a context menu
+     close event will definitely be sent.  Any other value is
+     the timestamp when the submenu was selected.  */
+  public static long wasSubmenuSelected;
+
+  /* The serial ID of the last context menu to be displayed.  */
+  public static int lastMenuEventSerial;
+
+  /* The last group ID used for a menu item.  */
+  public int lastGroupId;
+
+  private static final class Item implements MenuItem.OnMenuItemClickListener
+  {
+    public int itemID;
+    public String itemName, tooltip;
+    public EmacsContextMenu subMenu;
+    public boolean isEnabled, isCheckable, isChecked;
+    public EmacsView inflatedView;
+    public boolean isRadio;
+
+    @Override
+    public boolean
+    onMenuItemClick (MenuItem item)
+    {
+      if (subMenu != null)
+       {
+         /* Android 6.0 and earlier don't support nested submenus
+            properly, so display the submenu popup by hand.  */
+
+         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+           {
+             /* Still set wasSubmenuSelected -- if not set, the
+                dismissal of this context menu will result in a
+                context menu event being sent.  */
+             wasSubmenuSelected = -2;
+
+             /* Running a popup menu from inside a click handler
+                doesn't work, so make sure it is displayed
+                outside.  */
+
+             inflatedView.post (new Runnable () {
+                 @Override
+                 public void
+                 run ()
+                 {
+                   inflatedView.popupMenu (subMenu, 0, 0, true);
+                 }
+               });
+
+             return true;
+           }
+
+         /* After opening a submenu within a submenu, Android will
+            send onContextMenuClosed for a ContextMenuBuilder.  This
+            will normally confuse Emacs into thinking that the
+            context menu has been dismissed.  Wrong!
+
+            Setting this flag makes EmacsActivity to only handle
+            SubMenuBuilder being closed, which always means the menu
+            has actually been dismissed.
+
+            However, these extraneous events aren't sent on devices
+            where submenus display without dismissing their parents.
+            Thus, only ignore the close event if it happens within
+            300 milliseconds of the submenu being selected.  */
+         wasSubmenuSelected = System.currentTimeMillis ();
+         return false;
+       }
+
+      /* Send a context menu event.  */
+      EmacsNative.sendContextMenu ((short) 0, itemID,
+                                  lastMenuEventSerial);
+
+      /* Say that an item has already been selected.  */
+      itemAlreadySelected = true;
+      return true;
+    }
+  };
+
+  /* List of menu items contained in this menu.  */
+  public List<Item> menuItems;
+
+  /* The parent context menu, or NULL if none.  */
+  private EmacsContextMenu parent;
+
+  /* The title of this context menu, or NULL if none.  */
+  private String title;
+
+
+
+  /* Create a context menu with no items inside and the title TITLE,
+     which may be NULL.  */
+
+  public static EmacsContextMenu
+  createContextMenu (String title)
+  {
+    EmacsContextMenu menu;
+
+    menu = new EmacsContextMenu ();
+    menu.title = title;
+    menu.menuItems = new ArrayList<Item> ();
+
+    return menu;
+  }
+
+  /* Add a normal menu item to the context menu with the id ITEMID and
+     the name ITEMNAME.  Enable it if ISENABLED, else keep it
+     disabled.
+
+     If this is not a submenu and ISCHECKABLE is set, make the item
+     checkable.  Likewise, if ISCHECKED is set, make the item
+     checked.
+
+     If TOOLTIP is non-NULL, set the menu item tooltip to TOOLTIP.
+
+     If ISRADIO, then display the check mark as a radio button.  */
+
+  public void
+  addItem (int itemID, String itemName, boolean isEnabled,
+          boolean isCheckable, boolean isChecked,
+          String tooltip, boolean isRadio)
+  {
+    Item item;
+
+    item = new Item ();
+    item.itemID = itemID;
+    item.itemName = itemName;
+    item.isEnabled = isEnabled;
+    item.isCheckable = isCheckable;
+    item.isChecked = isChecked;
+    item.tooltip = tooltip;
+    item.isRadio = isRadio;
+
+    menuItems.add (item);
+  }
+
+  /* Create a disabled menu item with the name ITEMNAME.  */
+
+  public void
+  addPane (String itemName)
+  {
+    Item item;
+
+    item = new Item ();
+    item.itemName = itemName;
+
+    menuItems.add (item);
+  }
+
+  /* Add a submenu to the context menu with the specified title and
+     item name.  */
+
+  public EmacsContextMenu
+  addSubmenu (String itemName, String tooltip)
+  {
+    EmacsContextMenu submenu;
+    Item item;
+
+    item = new Item ();
+    item.itemID = 0;
+    item.itemName = itemName;
+    item.tooltip = tooltip;
+    item.subMenu = createContextMenu (itemName);
+    item.subMenu.parent = this;
+
+    menuItems.add (item);
+    return item.subMenu;
+  }
+
+  /* Add the contents of this menu to MENU.  Assume MENU will be
+     displayed in INFLATEDVIEW.  */
+
+  private void
+  inflateMenuItems (Menu menu, EmacsView inflatedView)
+  {
+    Intent intent;
+    MenuItem menuItem;
+    SubMenu submenu;
+
+    for (Item item : menuItems)
+      {
+       if (item.subMenu != null)
+         {
+           /* This is a submenu.  On versions of Android which
+              support doing so, create the submenu and add the
+              contents of the menu to it.
+
+              Note that Android 4.0 and later technically supports
+              having multiple layers of nested submenus, but if they
+              are used, onContextMenuClosed becomes unreliable.  */
+
+           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+             {
+               submenu = menu.addSubMenu (item.itemName);
+               item.subMenu.inflateMenuItems (submenu, inflatedView);
+
+               /* This is still needed to set wasSubmenuSelected.  */
+               menuItem = submenu.getItem ();
+             }
+           else
+             menuItem = menu.add (item.itemName);
+
+           item.inflatedView = inflatedView;
+           menuItem.setOnMenuItemClickListener (item);
+         }
+       else
+         {
+           if (item.isRadio)
+             menuItem = menu.add (++lastGroupId, Menu.NONE, Menu.NONE,
+                                  item.itemName);
+           else
+             menuItem = menu.add (item.itemName);
+           menuItem.setOnMenuItemClickListener (item);
+
+           /* If the item ID is zero, then disable the item.  */
+           if (item.itemID == 0 || !item.isEnabled)
+             menuItem.setEnabled (false);
+
+           /* Now make the menu item display a checkmark as
+              appropriate.  */
+
+           if (item.isCheckable)
+             menuItem.setCheckable (true);
+
+           if (item.isChecked)
+             menuItem.setChecked (true);
+
+           /* Define an exclusively checkable group if the item is a
+              radio button.  */
+
+           if (item.isRadio)
+             menu.setGroupCheckable (lastGroupId, true, true);
+
+           /* If the tooltip text is set and the system is new enough
+              to support menu item tooltips, set it on the item.  */
+
+           if (item.tooltip != null
+               && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+             menuItem.setTooltipText (item.tooltip);
+         }
+      }
+  }
+
+  /* Enter the items in this context menu to MENU.
+     Assume that MENU will be displayed in VIEW; this may lead to
+     popupMenu being called on VIEW if a submenu is selected.
+
+     If MENU is a ContextMenu, set its header title to the one
+     contained in this object.  */
+
+  public void
+  expandTo (Menu menu, EmacsView view)
+  {
+    inflateMenuItems (menu, view);
+
+    /* See if menu is a ContextMenu and a title is set.  */
+    if (title == null || !(menu instanceof ContextMenu))
+      return;
+
+    /* Set its title to this.title.  */
+    ((ContextMenu) menu).setHeaderTitle (title);
+  }
+
+  /* Return the parent or NULL.  */
+
+  public EmacsContextMenu
+  parent ()
+  {
+    return this.parent;
+  }
+
+  /* Like display, but does the actual work and runs in the main
+     thread.  */
+
+  private boolean
+  display1 (EmacsWindow window, int xPosition, int yPosition)
+  {
+    /* Set this flag to false.  It is used to decide whether or not to
+       send 0 in response to the context menu being closed.  */
+    itemAlreadySelected = false;
+
+    /* No submenu has been selected yet.  */
+    wasSubmenuSelected = -1;
+
+    return window.view.popupMenu (this, xPosition, yPosition,
+                                 false);
+  }
+
+  /* Display this context menu on WINDOW, at xPosition and yPosition.
+     SERIAL is a number that will be returned in any menu event
+     generated to identify this context menu.  */
+
+  public boolean
+  display (final EmacsWindow window, final int xPosition,
+          final int yPosition, final int serial)
+  {
+    Runnable runnable;
+    final EmacsHolder<Boolean> rc;
+
+    rc = new EmacsHolder<Boolean> ();
+    rc.thing = false;
+
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             lastMenuEventSerial = serial;
+             rc.thing = display1 (window, xPosition, yPosition);
+             notify ();
+           }
+       }
+      };
+
+    EmacsService.syncRunnable (runnable);
+    return rc.thing;
+  }
+
+  /* Dismiss this context menu.  WINDOW is the window where the
+     context menu is being displayed.  */
+
+  public void
+  dismiss (final EmacsWindow window)
+  {
+    Runnable runnable;
+
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         window.view.cancelPopupMenu ();
+         itemAlreadySelected = false;
+       }
+      });
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsCursor.java 
b/java/org/gnu/emacs/EmacsCursor.java
new file mode 100644
index 00000000000..c14c6f2a11b
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsCursor.java
@@ -0,0 +1,47 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.view.PointerIcon;
+import android.os.Build;
+
+/* Cursor wrapper.  Note that pointer icons are not supported prior to
+   Android 24.  */
+
+public final class EmacsCursor extends EmacsHandleObject
+{
+  /* The pointer icon associated with this cursor.  */
+  public final PointerIcon icon;
+
+  public
+  EmacsCursor (short handle, int glyph)
+  {
+    super (handle);
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+      {
+       icon = null;
+       return;
+      }
+
+    icon = PointerIcon.getSystemIcon (EmacsService.SERVICE,
+                                     glyph);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsDesktopNotification.java 
b/java/org/gnu/emacs/EmacsDesktopNotification.java
new file mode 100644
index 00000000000..121d8f481a2
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDesktopNotification.java
@@ -0,0 +1,175 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.Build;
+
+import android.widget.RemoteViews;
+
+
+
+/* Structure designating a single desktop notification.
+
+   New versions of Android also organize notifications into individual
+   ``channels'', which are used to implement groups.  Unlike on other
+   systems, notification importance is set for each group, not for
+   each individual notification.  */
+
+
+
+public final class EmacsDesktopNotification
+{
+  /* The content of this desktop notification.  */
+  public final String content;
+
+  /* The title of this desktop notification.  */
+  public final String title;
+
+  /* The notification group.  */
+  public final String group;
+
+  /* String identifying this notification for future replacement.
+     Typically a string resembling ``XXXX.NNNN.YYYY'', where XXXX is
+     the system boot time, NNNN is the PID of this Emacs instance, and
+     YYYY is the counter value returned by the notifications display
+     function.  */
+  public final String tag;
+
+  /* The identifier of this notification's icon.  */
+  public final int icon;
+
+  /* The importance of this notification's group.  */
+  public final int importance;
+
+  public
+  EmacsDesktopNotification (String title, String content,
+                           String group, String tag, int icon,
+                           int importance)
+  {
+    this.content    = content;
+    this.title     = title;
+    this.group     = group;
+    this.tag        = tag;
+    this.icon       = icon;
+    this.importance = importance;
+  }
+
+
+
+  /* Functions for displaying desktop notifications.  */
+
+  /* Internal helper for `display' executed on the main thread.  */
+
+  @SuppressWarnings ("deprecation") /* Notification.Builder (Context).  */
+  private void
+  display1 (Context context)
+  {
+    NotificationManager manager;
+    NotificationChannel channel;
+    Notification notification;
+    Object tem;
+    RemoteViews contentView;
+    Intent intent;
+    PendingIntent pending;
+
+    tem = context.getSystemService (Context.NOTIFICATION_SERVICE);
+    manager = (NotificationManager) tem;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+      {
+       /* Create the notification channel for this group.  If a group
+          already exists with the same name, its linked attributes
+          (such as its importance) will be overridden.  */
+        channel = new NotificationChannel (group, group, importance);
+       manager.createNotificationChannel (channel);
+
+       /* Create a notification object and display it.  */
+       notification = (new Notification.Builder (context, group)
+                       .setContentTitle (title)
+                       .setContentText (content)
+                       .setSmallIcon (icon)
+                       .build ());
+      }
+    else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
+      notification = (new Notification.Builder (context)
+                     .setContentTitle (title)
+                     .setContentText (content)
+                     .setSmallIcon (icon)
+                     .build ());
+    else
+      {
+       notification = new Notification ();
+       notification.icon = icon;
+
+       /* This remote widget tree is defined in
+          java/res/layout/sdk8_notifications_view.xml.  */
+       notification.contentView
+         = contentView
+         = new RemoteViews ("org.gnu.emacs",
+                            R.layout.sdk8_notifications_view);
+       contentView.setTextViewText (R.id.sdk8_notifications_title,
+                                    title);
+       contentView.setTextViewText (R.id.sdk8_notifications_content,
+                                    content);
+      }
+
+    /* Provide a content intent which starts Emacs when the
+       notification is clicked.  */
+
+    intent = new Intent (context, EmacsActivity.class);
+    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+      pending = PendingIntent.getActivity (context, 0, intent,
+                                          PendingIntent.FLAG_IMMUTABLE);
+    else
+      pending = PendingIntent.getActivity (context, 0, intent, 0);
+
+    notification.contentIntent = pending;
+
+    manager.notify (tag, 2, notification);
+  }
+
+  /* Display this desktop notification.
+
+     Create a notification channel named GROUP or update its
+     importance if such a channel is already defined.  */
+
+  public void
+  display ()
+  {
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         display1 (EmacsService.SERVICE);
+       }
+      });
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsDialog.java 
b/java/org/gnu/emacs/EmacsDialog.java
new file mode 100644
index 00000000000..af3bf538410
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDialog.java
@@ -0,0 +1,416 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import android.app.AlertDialog;
+
+import android.content.Context;
+import android.content.DialogInterface;
+
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+
+import android.os.Build;
+
+import android.provider.Settings;
+
+import android.util.Log;
+
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.FrameLayout;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+/* Toolkit dialog implementation.  This object is built from JNI and
+   describes a single alert dialog.  Then, `inflate' turns it into
+   AlertDialog.  */
+
+public final class EmacsDialog implements DialogInterface.OnDismissListener
+{
+  private static final String TAG = "EmacsDialog";
+
+  /* List of buttons in this dialog.  */
+  private List<EmacsButton> buttons;
+
+  /* Dialog title.  */
+  private String title;
+
+  /* Dialog text.  */
+  private String text;
+
+  /* Whether or not a selection has already been made.  */
+  private boolean wasButtonClicked;
+
+  /* Dialog to dismiss after click.  */
+  private AlertDialog dismissDialog;
+
+  /* The menu serial associated with this dialog box.  */
+  private int menuEventSerial;
+
+  private final class EmacsButton implements View.OnClickListener,
+                                 DialogInterface.OnClickListener
+  {
+    /* Name of this button.  */
+    public String name;
+
+    /* ID of this button.  */
+    public int id;
+
+    /* Whether or not the button is enabled.  */
+    public boolean enabled;
+
+    @Override
+    public void
+    onClick (View view)
+    {
+      wasButtonClicked = true;
+      EmacsNative.sendContextMenu ((short) 0, id, menuEventSerial);
+      dismissDialog.dismiss ();
+    }
+
+    @Override
+    public void
+    onClick (DialogInterface dialog, int which)
+    {
+      wasButtonClicked = true;
+      EmacsNative.sendContextMenu ((short) 0, id, menuEventSerial);
+    }
+  };
+
+  /* Create a popup dialog with the title TITLE and the text TEXT.
+     TITLE may be NULL.  MENUEVENTSERIAL is a number which will
+     identify this popup dialog inside events it sends.  */
+
+  public static EmacsDialog
+  createDialog (String title, String text, int menuEventSerial)
+  {
+    EmacsDialog dialog;
+
+    dialog = new EmacsDialog ();
+    dialog.buttons = new ArrayList<EmacsButton> ();
+    dialog.title = title;
+    dialog.text = text;
+    dialog.menuEventSerial = menuEventSerial;
+
+    return dialog;
+  }
+
+  /* Add a button named NAME, with the identifier ID.  If DISABLE,
+     disable the button.  */
+
+  public void
+  addButton (String name, int id, boolean disable)
+  {
+    EmacsButton button;
+
+    button = new EmacsButton ();
+    button.name = name;
+    button.id = id;
+    button.enabled = !disable;
+    buttons.add (button);
+  }
+
+  /* Turn this dialog into an AlertDialog for the specified
+     CONTEXT.
+
+     Upon a button being selected, the dialog will send an
+     ANDROID_CONTEXT_MENU event with the id of that button.
+
+     Upon the dialog being dismissed, an ANDROID_CONTEXT_MENU event
+     will be sent with an id of 0.  */
+
+  public AlertDialog
+  toAlertDialog (Context context)
+  {
+    AlertDialog dialog;
+    int size, styleId, flag;
+    int[] attrs;
+    EmacsButton button;
+    EmacsDialogButtonLayout layout;
+    Button buttonView;
+    ViewGroup.LayoutParams layoutParams;
+    Theme theme;
+    TypedArray attributes;
+    Window window;
+
+    size = buttons.size ();
+    styleId = -1;
+
+    if (size <= 3)
+      {
+       dialog = new AlertDialog.Builder (context).create ();
+       dialog.setMessage (text);
+       dialog.setCancelable (true);
+       dialog.setOnDismissListener (this);
+
+       if (title != null)
+         dialog.setTitle (title);
+
+       /* There are less than 4 buttons.  Add the buttons the way
+          Android intends them to be added.  */
+
+       if (size >= 1)
+         {
+           button = buttons.get (0);
+           dialog.setButton (DialogInterface.BUTTON_POSITIVE,
+                             button.name, button);
+         }
+
+       if (size >= 2)
+         {
+           button = buttons.get (1);
+           dialog.setButton (DialogInterface.BUTTON_NEGATIVE,
+                             button.name, button);
+         }
+
+       if (size >= 3)
+         {
+           button = buttons.get (2);
+           dialog.setButton (DialogInterface.BUTTON_NEUTRAL,
+                             button.name, button);
+         }
+      }
+    else
+      {
+       /* There are more than 3 buttons.  Add them all to a special
+          container widget that handles wrapping.  First, create the
+          layout.  */
+
+       layout = new EmacsDialogButtonLayout (context);
+       layoutParams
+         = new FrameLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT,
+                                         ViewGroup.LayoutParams.WRAP_CONTENT);
+       layout.setLayoutParams (layoutParams);
+
+       /* Add that layout to the dialog's custom view.
+
+          android.R.id.custom is documented to work.  But looking it
+          up returns NULL, so setView must be used instead.  */
+
+       dialog = new AlertDialog.Builder (context).setView (layout).create ();
+       dialog.setMessage (text);
+       dialog.setCancelable (true);
+       dialog.setOnDismissListener (this);
+
+       if (title != null)
+         dialog.setTitle (title);
+
+       /* Now that the dialog has been created, set the style of each
+          custom button to match the usual dialog buttons found on
+          Android 5 and later.  */
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+         {
+           /* Obtain the Theme associated with the dialog.  */
+           theme = dialog.getContext ().getTheme ();
+
+           /* Resolve the dialog button style.  */
+           attrs
+             = new int [] { android.R.attr.buttonBarNeutralButtonStyle, };
+
+           try
+             {
+               attributes = theme.obtainStyledAttributes (attrs);
+
+               /* Look for the style ID.  Default to -1 if it could
+                  not be found.  */
+               styleId = attributes.getResourceId (0, -1);
+
+               /* Now clean up the TypedAttributes object.  */
+               attributes.recycle ();
+             }
+           catch (NotFoundException e)
+             {
+               /* Nothing to do here.  */
+             }
+         }
+
+       /* Create each button and add it to the layout.  Set the style
+          if necessary.  */
+
+       for (EmacsButton emacsButton : buttons)
+         {
+           if (styleId == -1)
+             /* No specific style... */
+             buttonView = new Button (context);
+           else
+             /* Use the given styleId.  */
+             buttonView = new Button (context, null, 0, styleId);
+
+           /* Set the text and on click handler.  */
+           buttonView.setText (emacsButton.name);
+           buttonView.setOnClickListener (emacsButton);
+           buttonView.setEnabled (emacsButton.enabled);
+           layout.addView (buttonView);
+         }
+      }
+
+    return dialog;
+  }
+
+  /* Internal helper for display run on the main thread.  */
+
+  @SuppressWarnings("deprecation")
+  private boolean
+  display1 ()
+  {
+    Context context;
+    int size, type;
+    Button buttonView;
+    EmacsButton button;
+    AlertDialog dialog;
+    Window window;
+
+    if (EmacsActivity.focusedActivities.isEmpty ())
+      {
+       /* If focusedActivities is empty then this dialog may have
+          been displayed immediately after another popup dialog was
+          dismissed.  Or Emacs might legitimately be in the
+          background, possibly displaying this popup in response to
+          an Emacsclient request.  Try the service context if it will
+          work, then any focused EmacsOpenActivity, and finally the
+          last EmacsActivity to be focused.  */
+
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+           || Settings.canDrawOverlays (EmacsService.SERVICE))
+         context = EmacsService.SERVICE;
+       else if (EmacsOpenActivity.currentActivity != null)
+         context = EmacsOpenActivity.currentActivity;
+       else
+         context = EmacsActivity.lastFocusedActivity;
+
+       if (context == null)
+         return false;
+      }
+    else
+      /* Display using the activity context when Emacs is in the
+        foreground, as this allows the dialog to be dismissed more
+        consistently.  */
+      context = EmacsActivity.focusedActivities.get (0);
+
+    dialog = dismissDialog = toAlertDialog (context);
+
+    try
+      {
+       if (context == EmacsService.SERVICE)
+         {
+           /* Apply the system alert window type to make sure this
+              dialog can be displayed.  */
+
+           window = dialog.getWindow ();
+           type = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                   ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+                   : WindowManager.LayoutParams.TYPE_PHONE);
+           window.setType (type);
+         }
+
+       dismissDialog.show ();
+      }
+    catch (Exception exception)
+      {
+       /* This can happen when the system decides Emacs is not in the
+          foreground any longer.  */
+       return false;
+      }
+
+    /* If there are less than four buttons, then they must be
+       individually enabled or disabled after the dialog is
+       displayed.  */
+    size = buttons.size ();
+
+    if (size <= 3)
+      {
+       if (size >= 1)
+         {
+           button = buttons.get (0);
+           buttonView
+             = dialog.getButton (DialogInterface.BUTTON_POSITIVE);
+           buttonView.setEnabled (button.enabled);
+         }
+
+       if (size >= 2)
+         {
+           button = buttons.get (1);
+           buttonView
+             = dialog.getButton (DialogInterface.BUTTON_NEGATIVE);
+           buttonView.setEnabled (button.enabled);
+         }
+
+       if (size >= 3)
+         {
+           button = buttons.get (2);
+           buttonView
+             = dialog.getButton (DialogInterface.BUTTON_NEUTRAL);
+           buttonView.setEnabled (button.enabled);
+         }
+      }
+
+    return true;
+  }
+
+  /* Display this dialog for a suitable activity.
+     Value is false if the dialog could not be displayed,
+     and true otherwise.  */
+
+  public boolean
+  display ()
+  {
+    Runnable runnable;
+    final EmacsHolder<Boolean> rc;
+
+    rc = new EmacsHolder<Boolean> ();
+    rc.thing = false;
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             rc.thing = display1 ();
+             notify ();
+           }
+       }
+      };
+
+    EmacsService.syncRunnable (runnable);
+    return rc.thing;
+  }
+
+
+
+  @Override
+  public void
+  onDismiss (DialogInterface dialog)
+  {
+    if (wasButtonClicked)
+      return;
+
+    EmacsNative.sendContextMenu ((short) 0, 0, menuEventSerial);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsDialogButtonLayout.java 
b/java/org/gnu/emacs/EmacsDialogButtonLayout.java
new file mode 100644
index 00000000000..fd8d63d81d3
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDialogButtonLayout.java
@@ -0,0 +1,152 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+
+
+import android.content.Context;
+
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+
+
+
+/* This ``view group'' implements a container widget for multiple
+   buttons of the type found in pop-up dialogs.  It is used when
+   displaying a dialog box that contains more than three buttons, as
+   the default dialog box widget is not capable of holding more than
+   that many.  */
+
+
+
+public final class EmacsDialogButtonLayout extends ViewGroup
+{
+  public
+  EmacsDialogButtonLayout (Context context)
+  {
+    super (context);
+  }
+
+  @Override
+  protected void
+  onMeasure (int widthMeasureSpec, int heightMeasureSpec)
+  {
+    int width, count, i, x, y, height, spec, tempSpec;
+    View view;
+
+    /* Obtain the width of this widget and create the measure
+       specification used to measure children.  */
+
+    width = MeasureSpec.getSize (widthMeasureSpec);
+    spec = MeasureSpec.makeMeasureSpec (0, MeasureSpec.UNSPECIFIED);
+    tempSpec
+      = MeasureSpec.makeMeasureSpec (width, MeasureSpec.AT_MOST);
+    x = y = height = 0;
+
+    /* Run through each widget.  */
+
+    count = getChildCount ();
+
+    for (i = 0; i < count; ++i)
+      {
+       view = getChildAt (i);
+
+       /* Measure this view.  */
+       view.measure (spec, spec);
+
+       if (width - x < view.getMeasuredWidth ())
+         {
+           /* Move onto the next line, unless this line is empty.  */
+
+           if (x != 0)
+             {
+               y += height;
+               height = x = 0;
+             }
+
+           if (view.getMeasuredWidth () > width)
+             /* Measure the view again, this time forcing it to be at
+                most width wide, if it is not already.  */
+             view.measure (tempSpec, spec);
+         }
+
+       height = Math.max (height, view.getMeasuredHeight ());
+       x += view.getMeasuredWidth ();
+      }
+
+    /* Now set the measured size of this widget.  */
+    setMeasuredDimension (width, y + height);
+  }
+
+  @Override
+  protected void
+  onLayout (boolean changed, int left, int top, int right,
+           int bottom)
+  {
+    int width, count, i, x, y, height, spec, tempSpec;
+    View view;
+
+    /* Obtain the width of this widget and create the measure
+       specification used to measure children.  */
+
+    width = getMeasuredWidth ();
+    spec = MeasureSpec.makeMeasureSpec (0, MeasureSpec.UNSPECIFIED);
+    tempSpec
+      = MeasureSpec.makeMeasureSpec (width, MeasureSpec.AT_MOST);
+    x = y = height = 0;
+
+    /* Run through each widget.  */
+
+    count = getChildCount ();
+
+    for (i = 0; i < count; ++i)
+      {
+       view = getChildAt (i);
+
+       /* Measure this view.  */
+       view.measure (spec, spec);
+
+       if (width - x < view.getMeasuredWidth ())
+         {
+           /* Move onto the next line, unless this line is empty.  */
+
+           if (x != 0)
+             {
+               y += height;
+               height = x = 0;
+             }
+
+           if (view.getMeasuredWidth () > width)
+             /* Measure the view again, this time forcing it to be at
+                most width wide, if it is not already.  */
+             view.measure (tempSpec, spec);
+         }
+
+       /* Now assign this view its position.  */
+       view.layout (x, y, x + view.getMeasuredWidth (),
+                    y + view.getMeasuredHeight ());
+
+       /* And move on to the next widget.  */
+       height = Math.max (height, view.getMeasuredHeight ());
+       x += view.getMeasuredWidth ();
+      }
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsDirectoryEntry.java 
b/java/org/gnu/emacs/EmacsDirectoryEntry.java
new file mode 100644
index 00000000000..75c52e48002
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDirectoryEntry.java
@@ -0,0 +1,33 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+/* Structure holding a single ``directory entry'' from a document
+   provider.  */
+
+public final class EmacsDirectoryEntry
+{
+  /* The type of this directory entry.  0 means a regular file and 1
+     means a directory.  */
+  public int d_type;
+
+  /* The display name of the file represented.  */
+  public String d_name;
+};
diff --git a/java/org/gnu/emacs/EmacsDocumentsProvider.java 
b/java/org/gnu/emacs/EmacsDocumentsProvider.java
new file mode 100644
index 00000000000..96dc2bc6e14
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDocumentsProvider.java
@@ -0,0 +1,578 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.content.Context;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import static android.provider.DocumentsContract.buildChildDocumentsUri;
+import android.provider.DocumentsProvider;
+
+import android.webkit.MimeTypeMap;
+
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/* ``Documents provider''.  This allows Emacs's home directory to be
+   modified by other programs holding permissions to manage system
+   storage, which is useful to (for example) correct misconfigurations
+   which prevent Emacs from starting up.
+
+   This functionality is only available on Android 19 and later.  */
+
+public final class EmacsDocumentsProvider extends DocumentsProvider
+{
+  /* Home directory.  This is the directory whose contents are
+     initially returned to requesting applications.  */
+  private File baseDir;
+
+  /* The default projection for requests for the root directory.  */
+  private static final String[] DEFAULT_ROOT_PROJECTION;
+
+  /* The default projection for requests for a file.  */
+  private static final String[] DEFAULT_DOCUMENT_PROJECTION;
+
+  static
+  {
+    DEFAULT_ROOT_PROJECTION = new String[] {
+      Root.COLUMN_ROOT_ID,
+      Root.COLUMN_MIME_TYPES,
+      Root.COLUMN_FLAGS,
+      Root.COLUMN_ICON,
+      Root.COLUMN_TITLE,
+      Root.COLUMN_SUMMARY,
+      Root.COLUMN_DOCUMENT_ID,
+      Root.COLUMN_AVAILABLE_BYTES,
+    };
+
+    DEFAULT_DOCUMENT_PROJECTION = new String[] {
+      Document.COLUMN_DOCUMENT_ID,
+      Document.COLUMN_MIME_TYPE,
+      Document.COLUMN_DISPLAY_NAME,
+      Document.COLUMN_LAST_MODIFIED,
+      Document.COLUMN_FLAGS,
+      Document.COLUMN_SIZE,
+    };
+  }
+
+  @Override
+  public boolean
+  onCreate ()
+  {
+    /* Set the base directory to Emacs's files directory.  */
+    baseDir = getContext ().getFilesDir ();
+    return true;
+  }
+
+  @Override
+  public Cursor
+  queryRoots (String[] projection)
+  {
+    MatrixCursor result;
+    MatrixCursor.RowBuilder row;
+
+    /* If the requestor asked for nothing at all, then it wants some
+       data by default.  */
+
+    if (projection == null)
+      projection = DEFAULT_ROOT_PROJECTION;
+
+    result = new MatrixCursor (projection);
+    row = result.newRow ();
+
+    /* Now create and add a row for each file in the base
+       directory.  */
+    row.add (Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath ());
+    row.add (Root.COLUMN_SUMMARY, "Emacs home directory");
+
+    /* Add the appropriate flags.  */
+
+    row.add (Root.COLUMN_FLAGS, (Root.FLAG_SUPPORTS_CREATE
+                                | Root.FLAG_SUPPORTS_IS_CHILD));
+    row.add (Root.COLUMN_ICON, R.drawable.emacs);
+    row.add (Root.FLAG_LOCAL_ONLY);
+    row.add (Root.COLUMN_TITLE, "Emacs");
+    row.add (Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath ());
+
+    return result;
+  }
+
+  private Uri
+  getNotificationUri (File file)
+  {
+    Uri updatedUri;
+
+    updatedUri
+      = buildChildDocumentsUri ("org.gnu.emacs",
+                               file.getAbsolutePath ());
+
+    return updatedUri;
+  }
+
+  /* Inform the system that FILE's contents (or FILE itself) has
+     changed.  */
+
+  private void
+  notifyChange (File file)
+  {
+    Uri updatedUri;
+    Context context;
+
+    context = getContext ();
+    updatedUri
+      = buildChildDocumentsUri ("org.gnu.emacs",
+                               file.getAbsolutePath ());
+    context.getContentResolver ().notifyChange (updatedUri, null);
+  }
+
+  /* Inform the system that FILE's contents (or FILE itself) has
+     changed.  FILE is a string describing containing the file name of
+     a directory as opposed to a File.  */
+
+  private void
+  notifyChangeByName (String file)
+  {
+    Uri updatedUri;
+    Context context;
+
+    context = getContext ();
+    updatedUri
+      = buildChildDocumentsUri ("org.gnu.emacs", file);
+    context.getContentResolver ().notifyChange (updatedUri, null);
+  }
+
+  /* Return the MIME type of a file FILE.  */
+
+  private String
+  getMimeType (File file)
+  {
+    String name, extension, mime;
+    int extensionSeparator;
+    MimeTypeMap singleton;
+
+    if (file.isDirectory ())
+      return Document.MIME_TYPE_DIR;
+
+    /* Abuse WebView stuff to get the file's MIME type.  */
+    name = file.getName ();
+    extensionSeparator = name.lastIndexOf ('.');
+
+    if (extensionSeparator > 0)
+      {
+       singleton = MimeTypeMap.getSingleton ();
+       extension = name.substring (extensionSeparator + 1);
+       mime = singleton.getMimeTypeFromExtension (extension);
+
+       if (mime != null)
+         return mime;
+      }
+
+    return "application/octet-stream";
+  }
+
+  /* Append the specified FILE to the query result RESULT.
+     Handle both directories and ordinary files.  */
+
+  private void
+  queryDocument1 (MatrixCursor result, File file)
+  {
+    MatrixCursor.RowBuilder row;
+    String fileName, displayName, mimeType;
+    int flags;
+
+    row = result.newRow ();
+    flags = 0;
+
+    /* fileName is a string that the system will ask for some time in
+       the future.  Here, it is just the absolute name of the file.  */
+    fileName = file.getAbsolutePath ();
+
+    /* If file is a directory, add the right flags for that.  */
+
+    if (file.isDirectory ())
+      {
+       if (file.canWrite ())
+         {
+           flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
+           flags |= Document.FLAG_SUPPORTS_DELETE;
+
+           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+             flags |= Document.FLAG_SUPPORTS_RENAME;
+
+           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+             flags |= Document.FLAG_SUPPORTS_MOVE;
+         }
+      }
+    else if (file.canWrite ())
+      {
+       /* Apply the correct flags for a writable file.  */
+       flags |= Document.FLAG_SUPPORTS_WRITE;
+       flags |= Document.FLAG_SUPPORTS_DELETE;
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+         flags |= Document.FLAG_SUPPORTS_RENAME;
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+         {
+           flags |= Document.FLAG_SUPPORTS_REMOVE;
+           flags |= Document.FLAG_SUPPORTS_MOVE;
+         }
+      }
+
+    displayName = file.getName ();
+    mimeType = getMimeType (file);
+
+    row.add (Document.COLUMN_DOCUMENT_ID, fileName);
+    row.add (Document.COLUMN_DISPLAY_NAME, displayName);
+    row.add (Document.COLUMN_SIZE, file.length ());
+    row.add (Document.COLUMN_MIME_TYPE, mimeType);
+    row.add (Document.COLUMN_LAST_MODIFIED, file.lastModified ());
+    row.add (Document.COLUMN_FLAGS, flags);
+  }
+
+  @Override
+  public Cursor
+  queryDocument (String documentId, String[] projection)
+    throws FileNotFoundException
+  {
+    MatrixCursor result;
+    File file;
+    Context context;
+
+    file = new File (documentId);
+    context = getContext ();
+
+    if (projection == null)
+      projection = DEFAULT_DOCUMENT_PROJECTION;
+
+    result = new MatrixCursor (projection);
+    queryDocument1 (result, file);
+
+    /* Now allow interested applications to detect changes.  */
+    result.setNotificationUri (context.getContentResolver (),
+                              getNotificationUri (file));
+
+    return result;
+  }
+
+  @Override
+  public Cursor
+  queryChildDocuments (String parentDocumentId, String[] projection,
+                      String sortOrder) throws FileNotFoundException
+  {
+    MatrixCursor result;
+    File directory;
+    File[] files;
+    Context context;
+
+    if (projection == null)
+      projection = DEFAULT_DOCUMENT_PROJECTION;
+
+    result = new MatrixCursor (projection);
+
+    /* Try to open the file corresponding to the location being
+       requested.  */
+    directory = new File (parentDocumentId);
+
+    /* Look up each child.  */
+    files = directory.listFiles ();
+
+    if (files != null)
+      {
+       /* Now add each child.  */
+       for (File child : files)
+         queryDocument1 (result, child);
+      }
+
+    context = getContext ();
+
+    /* Now allow interested applications to detect changes.  */
+    result.setNotificationUri (context.getContentResolver (),
+                              getNotificationUri (directory));
+
+    return result;
+  }
+
+  @Override
+  public ParcelFileDescriptor
+  openDocument (String documentId, String mode,
+               CancellationSignal signal) throws FileNotFoundException
+  {
+    return ParcelFileDescriptor.open (new File (documentId),
+                                     ParcelFileDescriptor.parseMode (mode));
+  }
+
+  @Override
+  public String
+  createDocument (String documentId, String mimeType,
+                 String displayName) throws FileNotFoundException
+  {
+    File file, parentFile;
+    boolean rc;
+
+    file = new File (documentId, displayName);
+
+    try
+      {
+       rc = false;
+
+       if (Document.MIME_TYPE_DIR.equals (mimeType))
+         {
+           file.mkdirs ();
+
+           if (file.isDirectory ())
+             rc = true;
+         }
+       else
+         {
+           file.createNewFile ();
+
+           if (file.isFile ()
+               && file.setWritable (true)
+               && file.setReadable (true))
+             rc = true;
+         }
+
+       if (!rc)
+         throw new FileNotFoundException ("rc != 1");
+      }
+    catch (IOException e)
+      {
+       throw new FileNotFoundException (e.toString ());
+      }
+
+    parentFile = file.getParentFile ();
+
+    if (parentFile != null)
+      notifyChange (parentFile);
+
+    return file.getAbsolutePath ();
+  }
+
+  private void
+  deleteDocument1 (File child)
+  {
+    File[] children;
+
+    /* Don't delete symlinks recursively.
+
+       Calling readlink or stat is problematic due to file name
+       encoding problems, so try to delete the file first, and only
+       try to delete files recursively afterword.  */
+
+    if (child.delete ())
+      return;
+
+    children = child.listFiles ();
+
+    if (children != null)
+      {
+       for (File file : children)
+         deleteDocument1 (file);
+      }
+
+    child.delete ();
+  }
+
+  @Override
+  public void
+  deleteDocument (String documentId)
+    throws FileNotFoundException
+  {
+    File file, parent;
+    File[] children;
+
+    /* Java makes recursively deleting a file hard.  File name
+       encoding issues also prevent easily calling into C...  */
+
+    file = new File (documentId);
+    parent = file.getParentFile ();
+
+    if (parent == null)
+      throw new RuntimeException ("trying to delete file without"
+                                 + " parent!");
+
+    if (file.delete ())
+      {
+       /* Tell the system about the change.  */
+       notifyChange (parent);
+       return;
+      }
+
+    children = file.listFiles ();
+
+    if (children != null)
+      {
+       for (File child : children)
+         deleteDocument1 (child);
+      }
+
+    if (file.delete ())
+      /* Tell the system about the change.  */
+      notifyChange (parent);
+  }
+
+  @Override
+  public void
+  removeDocument (String documentId, String parentDocumentId)
+    throws FileNotFoundException
+  {
+    deleteDocument (documentId);
+  }
+
+  @Override
+  public String
+  getDocumentType (String documentId)
+  {
+    return getMimeType (new File (documentId));
+  }
+
+  @Override
+  public String
+  renameDocument (String documentId, String displayName)
+    throws FileNotFoundException
+  {
+    File file, newName;
+    File parent;
+
+    file = new File (documentId);
+    parent = file.getParentFile ();
+    newName = new File (parent, displayName);
+
+    if (parent == null)
+      throw new FileNotFoundException ("parent is null");
+
+    file = new File (documentId);
+
+    if (!file.renameTo (newName))
+      return null;
+
+    notifyChange (parent);
+    return newName.getAbsolutePath ();
+  }
+
+  @Override
+  public boolean
+  isChildDocument (String parentDocumentId, String documentId)
+  {
+    return documentId.startsWith (parentDocumentId);
+  }
+
+  @Override
+  public String
+  moveDocument (String sourceDocumentId,
+               String sourceParentDocumentId,
+               String targetParentDocumentId)
+    throws FileNotFoundException
+  {
+    File file, newName;
+    FileInputStream inputStream;
+    FileOutputStream outputStream;
+    byte buffer[];
+    int length;
+
+    file = new File (sourceDocumentId);
+
+    /* Now, create the file name of the parent document.  */
+    newName = new File (targetParentDocumentId,
+                       file.getName ());
+
+    /* Try to perform a simple rename, before falling back to
+       copying.  */
+
+    if (file.renameTo (newName))
+      {
+       notifyChangeByName (file.getParent ());
+       notifyChangeByName (targetParentDocumentId);
+       return newName.getAbsolutePath ();
+      }
+
+    /* If that doesn't work, create the new file and copy over the old
+       file's contents.  */
+
+    inputStream = null;
+    outputStream = null;
+
+    try
+      {
+       if (!newName.createNewFile ()
+           || !newName.setWritable (true)
+           || !newName.setReadable (true))
+         throw new FileNotFoundException ("failed to create new file");
+
+       /* Open the file in preparation for a copy.  */
+
+       inputStream = new FileInputStream (file);
+       outputStream = new FileOutputStream (newName);
+
+       /* Allocate the buffer used to hold data.  */
+
+       buffer = new byte[4096];
+
+       while ((length = inputStream.read (buffer)) > 0)
+         outputStream.write (buffer, 0, length);
+      }
+    catch (IOException e)
+      {
+       throw new FileNotFoundException ("IOException: " + e);
+      }
+    finally
+      {
+       try
+         {
+           if (inputStream != null)
+             inputStream.close ();
+         }
+       catch (IOException e)
+         {
+
+         }
+
+       try
+         {
+           if (outputStream != null)
+             outputStream.close ();
+         }
+       catch (IOException e)
+         {
+
+         }
+      }
+
+    file.delete ();
+    notifyChangeByName (file.getParent ());
+    notifyChangeByName (targetParentDocumentId);
+
+    return newName.getAbsolutePath ();
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsDrawLine.java 
b/java/org/gnu/emacs/EmacsDrawLine.java
new file mode 100644
index 00000000000..d367ccff9c4
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDrawLine.java
@@ -0,0 +1,79 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public final class EmacsDrawLine
+{
+  public static void
+  perform (EmacsDrawable drawable, EmacsGC gc,
+          int x, int y, int x2, int y2)
+  {
+    Rect rect;
+    Canvas canvas;
+    Paint paint;
+    int x0, x1, y0, y1;
+
+    /* TODO implement stippling.  */
+    if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
+      return;
+
+    /* Calculate the leftmost and rightmost points.  */
+
+    x0 = Math.min (x, x2 + 1);
+    x1 = Math.max (x, x2 + 1);
+    y0 = Math.min (y, y2 + 1);
+    y1 = Math.max (y, y2 + 1);
+
+    /* And the clip rectangle.  */
+
+    paint = gc.gcPaint;
+    rect = new Rect (x0, y0, x1, y1);
+    canvas = drawable.lockCanvas (gc);
+
+    if (canvas == null)
+      return;
+
+    paint.setStyle (Paint.Style.FILL);
+
+    /* Since drawLine has PostScript style behavior, adjust the
+       coordinates appropriately.
+
+       The left most pixel of a straight line is always partially
+       filled.  Patch it in manually.  */
+
+    if (gc.clip_mask == null)
+      {
+       canvas.drawLine ((float) x + 0.5f, (float) y + 0.5f,
+                        (float) x2 + 0.5f, (float) y2 + 0.5f,
+                        paint);
+
+       if (x2 > x)
+         canvas.drawRect (new Rect (x, y, x + 1, y + 1), paint);
+      }
+
+    /* DrawLine with clip mask not implemented; it is not used by
+       Emacs.  */
+    drawable.damageRect (rect);
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java 
b/java/org/gnu/emacs/EmacsDrawPoint.java
new file mode 100644
index 00000000000..6a1cb744d60
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDrawPoint.java
@@ -0,0 +1,34 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+public final class EmacsDrawPoint
+{
+  public static void
+  perform (EmacsDrawable drawable,
+          EmacsGC immutableGC, int x, int y)
+  {
+    /* Use EmacsFillRectangle instead of EmacsDrawRectangle, as the
+       latter actually draws a rectangle one pixel wider than
+       specified.  */
+    EmacsFillRectangle.perform (drawable, immutableGC,
+                               x, y, 1, 1);
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java 
b/java/org/gnu/emacs/EmacsDrawRectangle.java
new file mode 100644
index 00000000000..e1261b4a2d2
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDrawRectangle.java
@@ -0,0 +1,120 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import android.util.Log;
+
+public final class EmacsDrawRectangle
+{
+  public static void
+  perform (EmacsDrawable drawable, EmacsGC gc,
+          int x, int y, int width, int height)
+  {
+    Paint maskPaint, paint;
+    Canvas maskCanvas;
+    Bitmap maskBitmap;
+    Rect maskRect, dstRect;
+    Canvas canvas;
+    Bitmap clipBitmap;
+
+    /* TODO implement stippling.  */
+    if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
+      return;
+
+    canvas = drawable.lockCanvas (gc);
+
+    if (canvas == null)
+      return;
+
+    paint = gc.gcPaint;
+    paint.setStyle (Paint.Style.STROKE);
+
+    if (gc.clip_mask == null)
+      /* Use canvas.drawRect with a RectF.  That seems to reliably
+        get PostScript behavior.  */
+      canvas.drawRect (new RectF (x + 0.5f, y + 0.5f,
+                                 x + width + 0.5f,
+                                 y + height + 0.5f),
+                      paint);
+    else
+      {
+       /* Drawing with a clip mask involves calculating the
+          intersection of the clip mask with the dst rect, and
+          extrapolating the corresponding part of the src rect.  */
+       clipBitmap = gc.clip_mask.bitmap;
+       dstRect = new Rect (x, y, x + width, y + height);
+       maskRect = new Rect (gc.clip_x_origin,
+                            gc.clip_y_origin,
+                            (gc.clip_x_origin
+                             + clipBitmap.getWidth ()),
+                            (gc.clip_y_origin
+                             + clipBitmap.getHeight ()));
+
+       if (!maskRect.setIntersect (dstRect, maskRect))
+         /* There is no intersection between the clip mask and the
+            dest rect.  */
+         return;
+
+       /* Finally, create a temporary bitmap that is the size of
+          maskRect.  */
+
+       maskBitmap
+         = Bitmap.createBitmap (maskRect.width (), maskRect.height (),
+                                Bitmap.Config.ARGB_8888);
+
+       /* Draw the mask onto the maskBitmap.  */
+       maskCanvas = new Canvas (maskBitmap);
+       maskRect.offset (-gc.clip_x_origin,
+                        -gc.clip_y_origin);
+       maskCanvas.drawBitmap (gc.clip_mask.bitmap,
+                              maskRect, new Rect (0, 0,
+                                                  maskRect.width (),
+                                                  maskRect.height ()),
+                              paint);
+       maskRect.offset (gc.clip_x_origin,
+                        gc.clip_y_origin);
+
+       /* Set the transfer mode to SRC_IN to preserve only the parts
+          of the source that overlap with the mask.  */
+       maskPaint = new Paint ();
+       maskPaint.setXfermode (EmacsGC.srcInAlu);
+       maskPaint.setStyle (Paint.Style.STROKE);
+
+       /* Draw the source.  */
+       maskCanvas.drawRect (maskRect, maskPaint);
+
+       /* Finally, draw the mask bitmap to the destination.  */
+       paint.setXfermode (null);
+       canvas.drawBitmap (maskBitmap, null, maskRect, paint);
+
+       /* Recycle this unused bitmap.  */
+       maskBitmap.recycle ();
+      }
+
+    drawable.damageRect (new Rect (x, y, x + width + 1,
+                                  y + height + 1));
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsDrawable.java 
b/java/org/gnu/emacs/EmacsDrawable.java
new file mode 100644
index 00000000000..f2f8885e976
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDrawable.java
@@ -0,0 +1,32 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Rect;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+
+public interface EmacsDrawable
+{
+  public Canvas lockCanvas (EmacsGC gc);
+  public void damageRect (Rect damageRect);
+  public Bitmap getBitmap ();
+  public boolean isDestroyed ();
+};
diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java 
b/java/org/gnu/emacs/EmacsFillPolygon.java
new file mode 100644
index 00000000000..4ae3882cab4
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsFillPolygon.java
@@ -0,0 +1,80 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+public final class EmacsFillPolygon
+{
+  public static void
+  perform (EmacsDrawable drawable, EmacsGC gc, Point points[])
+  {
+    Canvas canvas;
+    Path path;
+    Paint paint;
+    Rect rect;
+    RectF rectF;
+    int i;
+
+    canvas = drawable.lockCanvas (gc);
+
+    if (canvas == null)
+      return;
+
+    paint = gc.gcPaint;
+
+    /* Build the path from the given array of points.  */
+    path = new Path ();
+
+    if (points.length >= 1)
+      {
+       path.moveTo (points[0].x, points[0].y);
+
+       for (i = 1; i < points.length; ++i)
+         path.lineTo (points[i].x, points[i].y);
+
+       path.close ();
+      }
+
+    /* Compute the damage rectangle.  */
+    rectF = new RectF (0, 0, 0, 0);
+    path.computeBounds (rectF, true);
+
+    rect = new Rect ((int) Math.floor (rectF.left),
+                    (int) Math.floor (rectF.top),
+                    (int) Math.ceil (rectF.right),
+                    (int) Math.ceil (rectF.bottom));
+
+    paint.setStyle (Paint.Style.FILL);
+
+    if (gc.clip_mask == null)
+      canvas.drawPath (path, paint);
+
+    drawable.damageRect (rect);
+
+    /* FillPolygon with clip mask not implemented; it is not used by
+       Emacs.  */
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java 
b/java/org/gnu/emacs/EmacsFillRectangle.java
new file mode 100644
index 00000000000..461fd3c639c
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsFillRectangle.java
@@ -0,0 +1,116 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import android.util.Log;
+
+public final class EmacsFillRectangle
+{
+  public static void
+  perform (EmacsDrawable drawable, EmacsGC gc,
+          int x, int y, int width, int height)
+  {
+    Paint maskPaint, paint;
+    Canvas maskCanvas;
+    Bitmap maskBitmap;
+    Rect rect;
+    Rect maskRect, dstRect;
+    Canvas canvas;
+    Bitmap clipBitmap;
+
+    /* TODO implement stippling.  */
+    if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
+      return;
+
+    canvas = drawable.lockCanvas (gc);
+
+    if (canvas == null)
+      return;
+
+    paint = gc.gcPaint;
+    rect = new Rect (x, y, x + width, y + height);
+
+    paint.setStyle (Paint.Style.FILL);
+
+    if (gc.clip_mask == null)
+      canvas.drawRect (rect, paint);
+    else
+      {
+       /* Drawing with a clip mask involves calculating the
+          intersection of the clip mask with the dst rect, and
+          extrapolating the corresponding part of the src rect.  */
+
+       clipBitmap = gc.clip_mask.bitmap;
+       dstRect = new Rect (x, y, x + width, y + height);
+       maskRect = new Rect (gc.clip_x_origin,
+                            gc.clip_y_origin,
+                            (gc.clip_x_origin
+                             + clipBitmap.getWidth ()),
+                            (gc.clip_y_origin
+                             + clipBitmap.getHeight ()));
+
+       if (!maskRect.setIntersect (dstRect, maskRect))
+         /* There is no intersection between the clip mask and the
+            dest rect.  */
+         return;
+
+       /* Finally, create a temporary bitmap that is the size of
+          maskRect.  */
+
+       maskBitmap
+         = Bitmap.createBitmap (maskRect.width (), maskRect.height (),
+                                Bitmap.Config.ARGB_8888);
+
+       /* Draw the mask onto the maskBitmap.  */
+       maskCanvas = new Canvas (maskBitmap);
+       maskRect.offset (-gc.clip_x_origin,
+                        -gc.clip_y_origin);
+       maskCanvas.drawBitmap (gc.clip_mask.bitmap,
+                              maskRect, new Rect (0, 0,
+                                                  maskRect.width (),
+                                                  maskRect.height ()),
+                              paint);
+       maskRect.offset (gc.clip_x_origin,
+                        gc.clip_y_origin);
+
+       /* Set the transfer mode to SRC_IN to preserve only the parts
+          of the source that overlap with the mask.  */
+       maskPaint = new Paint ();
+       maskPaint.setXfermode (EmacsGC.srcInAlu);
+
+       /* Draw the source.  */
+       maskCanvas.drawRect (maskRect, maskPaint);
+
+       /* Finally, draw the mask bitmap to the destination.  */
+       paint.setXfermode (null);
+       canvas.drawBitmap (maskBitmap, null, maskRect, paint);
+
+       /* Recycle this unused bitmap.  */
+       maskBitmap.recycle ();
+      }
+
+    drawable.damageRect (rect);
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsFontDriver.java 
b/java/org/gnu/emacs/EmacsFontDriver.java
new file mode 100644
index 00000000000..ff52899a897
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsFontDriver.java
@@ -0,0 +1,173 @@
+/* Font backend for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.os.Build;
+
+/* This code is mostly unused.  See sfntfont-android.c for the code
+   that is actually used.  */
+
+public abstract class EmacsFontDriver
+{
+  /* Font weights.  */
+  public static final int THIN         = 0;
+  public static final int ULTRA_LIGHT  = 40;
+  public static final int LIGHT                = 50;
+  public static final int SEMI_LIGHT   = 55;
+  public static final int REGULAR      = 80;
+  public static final int MEDIUM       = 100;
+  public static final int SEMI_BOLD    = 180;
+  public static final int BOLD         = 200;
+  public static final int EXTRA_BOLD   = 205;
+  public static final int BLACK                = 210;
+  public static final int ULTRA_HEAVY  = 250;
+
+  /* Font slants.  */
+  public static final int REVERSE_OBLIQUE      = 0;
+  public static final int REVERSE_ITALIC       = 10;
+  public static final int NORMAL               = 100;
+  public static final int ITALIC               = 200;
+  public static final int OBLIQUE              = 210;
+
+  /* Font widths.  */
+  public static final int ULTRA_CONDENSED      = 50;
+  public static final int EXTRA_CONDENSED      = 63;
+  public static final int CONDENSED            = 75;
+  public static final int SEMI_CONDENSED       = 87;
+  public static final int UNSPECIFIED          = 100;
+  public static final int SEMI_EXPANDED                = 113;
+  public static final int EXPANDED             = 125;
+  public static final int EXTRA_EXPANDED       = 150;
+  public static final int ULTRA_EXPANDED       = 200;
+
+  /* Font spacings.  */
+  public static final int PROPORTIONAL = 0;
+  public static final int DUAL         = 90;
+  public static final int MONO         = 100;
+  public static final int CHARCELL     = 110;
+
+  public static class FontSpec
+  {
+    /* The fields below mean the same as they do in enum
+       font_property_index in font.h.  */
+
+    public String foundry;
+    public String family;
+    public String adstyle;
+    public String registry;
+    public Integer width;
+    public Integer weight;
+    public Integer slant;
+    public Integer size;
+    public Integer spacing;
+    public Integer avgwidth;
+    public Integer dpi;
+
+    @Override
+    public String
+    toString ()
+    {
+      return ("foundry: " + foundry
+             + " family: " + family
+             + " adstyle: " + adstyle
+             + " registry: " + registry
+             + " width: " + width
+             + " weight: " + weight
+             + " slant: " + slant
+             + " spacing: " + spacing
+             + " avgwidth: " + avgwidth
+             + " dpi: " + dpi);
+    }
+  };
+
+  public static class FontMetrics
+  {
+    public short lbearing;
+    public short rbearing;
+    public short width;
+    public short ascent;
+    public short descent;
+
+    @Override
+    public String
+    toString ()
+    {
+      return ("lbearing " + lbearing
+             + " rbearing " + rbearing
+             + " width " + width
+             + " ascent " + ascent
+             + " descent " + descent);
+    }
+  }
+
+  public static class FontEntity extends FontSpec
+  {
+    /* No extra fields here.  */
+  };
+
+  public abstract class FontObject extends FontSpec
+  {
+    public int minWidth;
+    public int maxWidth;
+    public int pixelSize;
+    public int height;
+    public int spaceWidth;
+    public int averageWidth;
+    public int ascent;
+    public int descent;
+    public int underlineThickness;
+    public int underlinePosition;
+    public int baselineOffset;
+    public int relativeCompose;
+    public int defaultAscent;
+    public int encodingCharset;
+    public int repertoryCharset;
+
+    public
+    FontObject ()
+    {
+      encodingCharset = -1;
+      repertoryCharset = -1;
+    }
+  };
+
+  /* These mean the same as they do in struct font_driver.  */
+  public abstract FontEntity[] list (FontSpec fontSpec);
+  public abstract FontEntity match (FontSpec fontSpec);
+  public abstract String[] listFamilies ();
+  public abstract FontObject openFont (FontEntity fontEntity, int pixelSize);
+  public abstract int hasChar (FontSpec font, char charCode);
+  public abstract void textExtents (FontObject font, int code[],
+                                   FontMetrics fontMetrics);
+  public abstract int encodeChar (FontObject fontObject, char charCode);
+  public abstract int draw (FontObject fontObject, EmacsGC gc,
+                           EmacsDrawable drawable, int[] chars,
+                           int x, int y, int backgroundWidth,
+                           boolean withBackground);
+
+  public static EmacsFontDriver
+  createFontDriver ()
+  {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+      return new EmacsSdk23FontDriver ();
+
+    return new EmacsSdk7FontDriver ();
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java
new file mode 100644
index 00000000000..a7467cb9bd0
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsGC.java
@@ -0,0 +1,121 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Rect;
+import android.graphics.Paint;
+
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+
+/* X like graphics context structures.  Keep the enums in synch with
+   androidgui.h! */
+
+public final class EmacsGC extends EmacsHandleObject
+{
+  public static final int GC_COPY = 0;
+  public static final int GC_XOR  = 1;
+
+  public static final int GC_FILL_SOLID                        = 0;
+  public static final int GC_FILL_OPAQUE_STIPPLED      = 1;
+
+  public static final Xfermode xorAlu, srcInAlu;
+
+  public int function, fill_style;
+  public int foreground, background;
+  public int clip_x_origin, clip_y_origin;
+  public int ts_origin_x, ts_origin_y;
+  public Rect clip_rects[], real_clip_rects[];
+  public EmacsPixmap clip_mask, stipple;
+  public Paint gcPaint;
+
+  /* ID incremented every time the clipping rectangles of any GC
+     changes.  */
+  private static long clip_serial;
+
+  /* The value of clipRectID after the last time this GCs clip
+     rectangles changed.  0 if there are no clip rectangles.  */
+  public long clipRectID;
+
+  static
+  {
+    xorAlu = new PorterDuffXfermode (Mode.XOR);
+    srcInAlu = new PorterDuffXfermode (Mode.SRC_IN);
+  }
+
+  /* The following fields are only set on immutable GCs.  */
+
+  public
+  EmacsGC (short handle)
+  {
+    /* For historical reasons the C code has an extra layer of
+       indirection above this GC handle.  struct android_gc is the GC
+       used by Emacs code, while android_gcontext is the type of the
+       handle.  */
+    super (handle);
+
+    fill_style = GC_FILL_SOLID;
+    function = GC_COPY;
+    foreground = 0;
+    background = 0xffffff;
+    gcPaint = new Paint ();
+  }
+
+  /* Mark this GC as dirty.  Apply parameters to the paint and
+     recompute real_clip_rects.  */
+
+  public void
+  markDirty (boolean clipRectsChanged)
+  {
+    int i;
+
+    if (clipRectsChanged)
+      {
+       if ((ts_origin_x != 0 || ts_origin_y != 0)
+           && clip_rects != null)
+         {
+           real_clip_rects = new Rect[clip_rects.length];
+
+           for (i = 0; i < clip_rects.length; ++i)
+             {
+               real_clip_rects[i] = new Rect (clip_rects[i]);
+               real_clip_rects[i].offset (ts_origin_x, ts_origin_y);
+             }
+         }
+       else
+         real_clip_rects = clip_rects;
+
+       clipRectID = ++clip_serial;
+      }
+
+    gcPaint.setStrokeWidth (1f);
+    gcPaint.setColor (foreground | 0xff000000);
+    gcPaint.setXfermode (function == GC_XOR
+                        ? xorAlu : srcInAlu);
+  }
+
+  public void
+  resetXfermode ()
+  {
+    gcPaint.setXfermode (function == GC_XOR
+                        ? xorAlu : srcInAlu);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsHandleObject.java 
b/java/org/gnu/emacs/EmacsHandleObject.java
new file mode 100644
index 00000000000..5b889895337
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsHandleObject.java
@@ -0,0 +1,59 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.lang.IllegalStateException;
+
+/* This defines something that is a so-called ``handle''.  Handles
+   must be created by C code, and will remain existing until
+   destroyHandle is called.  C code then refers to the handle by a
+   number which maps into the Java object representing the handle.
+
+   All handle operations must be done from the Emacs thread.  */
+
+public abstract class EmacsHandleObject
+{
+  /* Whether or not this handle has been destroyed.  */
+  volatile boolean destroyed;
+
+  /* The handle associated with this object.  */
+  public short handle;
+
+  public
+  EmacsHandleObject (short handle)
+  {
+    this.handle = handle;
+  }
+
+  public void
+  destroyHandle () throws IllegalStateException
+  {
+    synchronized (this)
+      {
+       destroyed = true;
+      }
+  }
+
+  public boolean
+  isDestroyed ()
+  {
+    return destroyed;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsHolder.java 
b/java/org/gnu/emacs/EmacsHolder.java
new file mode 100644
index 00000000000..6cd48ba57ce
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsHolder.java
@@ -0,0 +1,30 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+
+
+/* This class serves as a simple reference to an object of type T.
+   Nothing could be found inside the standard library.  */
+
+public final class EmacsHolder<T>
+{
+  T thing;
+};
diff --git a/java/org/gnu/emacs/EmacsInputConnection.java 
b/java/org/gnu/emacs/EmacsInputConnection.java
new file mode 100644
index 00000000000..c3764a7b29f
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsInputConnection.java
@@ -0,0 +1,698 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.view.KeyEvent;
+
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextSnapshot;
+
+import android.util.Log;
+
+/* Android input methods, take number six.  See textconv.c for more
+   details; this is more-or-less a thin wrapper around that file.  */
+
+public final class EmacsInputConnection implements InputConnection
+{
+  private static final String TAG = "EmacsInputConnection";
+
+  /* View associated with this input connection.  */
+  private EmacsView view;
+
+  /* The handle ID associated with that view's window.  */
+  private short windowHandle;
+
+  /* Number of batch edits currently underway.  Used to avoid
+     synchronizing with the Emacs thread after each
+     `endBatchEdit'.  */
+  private int batchEditCount;
+
+  /* Whether or not to synchronize and call `updateIC' with the
+     selection position after committing text.
+
+     This helps with on screen keyboard programs found in some vendor
+     versions of Android, which rely on immediate updates to the point
+     position after text is commited in order to place the cursor
+     within that text.  */
+
+  private static boolean syncAfterCommit;
+
+  /* Whether or not to return empty text with the offset set to zero
+     if a request arrives that has no flags set and has requested no
+     characters at all.
+
+     This is necessary with on screen keyboard programs found in some
+     vendor versions of Android which don't rely on the documented
+     meaning of `ExtractedText.startOffset', and instead take the
+     selection offset inside at face value.  */
+
+  private static boolean extractAbsoluteOffsets;
+
+  static
+  {
+    if (Build.MANUFACTURER.equalsIgnoreCase ("Huawei")
+       || Build.MANUFACTURER.equalsIgnoreCase ("Honor"))
+      extractAbsoluteOffsets = syncAfterCommit = true;
+
+    /* The Samsung and Vivo keyboards take `selectionStart' at face
+       value if some text is returned, and also searches for words
+       solely within that text.  However, when no text is returned, it
+       falls back to getTextAfterCursor and getTextBeforeCursor.  */
+    if (Build.MANUFACTURER.equalsIgnoreCase ("Samsung")
+       || Build.MANUFACTURER.equalsIgnoreCase ("Vivo"))
+      extractAbsoluteOffsets = true;
+  };
+
+
+  public
+  EmacsInputConnection (EmacsView view)
+  {
+    this.view = view;
+    this.windowHandle = view.window.handle;
+  }
+
+
+  /* The functions below are called by input methods whenever they
+     need to perform an edit.  */
+
+  @Override
+  public boolean
+  beginBatchEdit ()
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "beginBatchEdit");
+
+    EmacsNative.beginBatchEdit (windowHandle);
+
+    /* Keep a record of the number of outstanding batch edits here as
+       well.  */
+    batchEditCount++;
+    return true;
+  }
+
+  @Override
+  public boolean
+  endBatchEdit ()
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "endBatchEdit");
+
+    EmacsNative.endBatchEdit (windowHandle);
+
+    /* Subtract one from the UI thread record of the number of batch
+       edits currently under way.  */
+
+    if (batchEditCount > 0)
+      batchEditCount -= 1;
+
+    return batchEditCount > 0;
+  }
+
+  public boolean
+  commitCompletion (CompletionInfo info)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "commitCompletion: " + info);
+
+    EmacsNative.commitCompletion (windowHandle,
+                                 info.getText ().toString (),
+                                 info.getPosition ());
+    return true;
+  }
+
+  @Override
+  public boolean
+  commitCorrection (CorrectionInfo info)
+  {
+    /* The input method calls this function not to commit text, but to
+       indicate that a subsequent edit will consist of a correction.
+       Emacs has no use for this information.
+
+       Of course this completely contradicts the provided
+       documentation, but this is how Android actually behaves.  */
+    return false;
+  }
+
+  @Override
+  public boolean
+  commitText (CharSequence text, int newCursorPosition)
+  {
+    int[] selection;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
+
+    EmacsNative.commitText (windowHandle, text.toString (),
+                           newCursorPosition);
+
+    if (syncAfterCommit)
+      {
+       /* Synchronize with the Emacs thread, obtain the new
+          selection, and report it immediately.  */
+
+       selection = EmacsNative.getSelection (windowHandle);
+
+       if (EmacsService.DEBUG_IC && selection != null)
+         Log.d (TAG, "commitText: new selection is " + selection[0]
+                + ", by " + selection[1]);
+
+       if (selection != null)
+         /* N.B. that the composing region is removed after text is
+            committed.  */
+         view.imManager.updateSelection (view, selection[0],
+                                         selection[1], -1, -1);
+      }
+
+    return true;
+  }
+
+  @Override
+  public boolean
+  commitText (CharSequence text, int newCursorPosition,
+             TextAttribute textAttribute)
+  {
+    return commitText (text, newCursorPosition);
+  }
+
+  @Override
+  public boolean
+  deleteSurroundingText (int leftLength, int rightLength)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, ("deleteSurroundingText: "
+                  + leftLength + " " + rightLength));
+
+    EmacsNative.deleteSurroundingText (windowHandle, leftLength,
+                                      rightLength);
+    return true;
+  }
+
+  @Override
+  public boolean
+  deleteSurroundingTextInCodePoints (int leftLength, int rightLength)
+  {
+    /* Emacs returns characters which cannot be represented in a Java
+       `char' as NULL characters, so code points always reflect
+       characters themselves.  */
+    return deleteSurroundingText (leftLength, rightLength);
+  }
+
+  @Override
+  public boolean
+  finishComposingText ()
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "finishComposingText");
+
+    EmacsNative.finishComposingText (windowHandle);
+    return true;
+  }
+
+  @Override
+  public String
+  getSelectedText (int flags)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return null;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "getSelectedText: " + flags);
+
+    return EmacsNative.getSelectedText (windowHandle, flags);
+  }
+
+  @Override
+  public String
+  getTextAfterCursor (int length, int flags)
+  {
+    String string;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return null;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
+
+    string = EmacsNative.getTextAfterCursor (windowHandle, length,
+                                            flags);
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "   --> " + string);
+
+    return string;
+  }
+
+  @Override
+  public String
+  getTextBeforeCursor (int length, int flags)
+  {
+    String string;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return null;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
+
+    string = EmacsNative.getTextBeforeCursor (windowHandle, length,
+                                             flags);
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "   --> " + string);
+
+    return string;
+  }
+
+  @Override
+  public boolean
+  setComposingText (CharSequence text, int newCursorPosition)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, ("setComposingText: "
+                  + text + " ## " + newCursorPosition));
+
+    EmacsNative.setComposingText (windowHandle, text.toString (),
+                                 newCursorPosition);
+    return true;
+  }
+
+  @Override
+  public boolean
+  setComposingText (CharSequence text, int newCursorPosition,
+                   TextAttribute textAttribute)
+  {
+    return setComposingText (text, newCursorPosition);
+  }
+
+  @Override
+  public boolean
+  setComposingRegion (int start, int end)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "setComposingRegion: " + start + " " + end);
+
+    EmacsNative.setComposingRegion (windowHandle, start, end);
+    return true;
+  }
+
+  @Override
+  public boolean
+  setComposingRegion (int start, int end, TextAttribute textAttribute)
+  {
+    return setComposingRegion (start, end);
+  }
+
+  @Override
+  public boolean
+  performEditorAction (int editorAction)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "performEditorAction: " + editorAction);
+
+    EmacsNative.performEditorAction (windowHandle, editorAction);
+    return true;
+  }
+
+  @Override
+  public boolean
+  performContextMenuAction (int contextMenuAction)
+  {
+    int action;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "performContextMenuAction: " + contextMenuAction);
+
+    /* Translate the action in Java code.  That way, a great deal of
+       JNI boilerplate can be avoided.  */
+
+    switch (contextMenuAction)
+      {
+      case android.R.id.selectAll:
+       action = 0;
+       break;
+
+      case android.R.id.startSelectingText:
+       action = 1;
+       break;
+
+      case android.R.id.stopSelectingText:
+       action = 2;
+       break;
+
+      case android.R.id.cut:
+       action = 3;
+       break;
+
+      case android.R.id.copy:
+       action = 4;
+       break;
+
+      case android.R.id.paste:
+       action = 5;
+       break;
+
+      default:
+       return true;
+      }
+
+    EmacsNative.performContextMenuAction (windowHandle, action);
+    return true;
+  }
+
+  @Override
+  public ExtractedText
+  getExtractedText (ExtractedTextRequest request, int flags)
+  {
+    ExtractedText text;
+    int[] selection;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return null;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "getExtractedText: " + request.hintMaxChars + ", "
+            + request.hintMaxLines + " " + flags);
+
+    /* If a request arrives with hintMaxChars, hintMaxLines and flags
+       set to 0, and the system is known to be buggy, return an empty
+       extracted text object with the absolute selection positions.  */
+
+    if (extractAbsoluteOffsets
+       && request.hintMaxChars == 0
+       && request.hintMaxLines == 0
+       && flags == 0)
+      {
+       /* Obtain the selection.  */
+       selection = EmacsNative.getSelection (windowHandle);
+       if (selection == null)
+         return null;
+
+       /* Create the workaround extracted text.  */
+       text = new ExtractedText ();
+       text.partialStartOffset = -1;
+       text.partialEndOffset = -1;
+       text.text = "";
+       text.selectionStart = selection[0];
+       text.selectionEnd = selection[1];
+      }
+    else
+      text = EmacsNative.getExtractedText (windowHandle, request,
+                                          flags);
+
+    if (text == null)
+      {
+       if (EmacsService.DEBUG_IC)
+         Log.d (TAG, "getExtractedText: text is NULL");
+
+       return null;
+      }
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "getExtractedText: " + text.text + " @"
+            + text.startOffset + ":" + text.selectionStart
+            + ", " + text.selectionEnd);
+
+    return text;
+  }
+
+  @Override
+  public boolean
+  setSelection (int start, int end)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "setSelection: " + start + " " + end);
+
+    EmacsNative.setSelection (windowHandle, start, end);
+    return true;
+  }
+
+  @Override
+  /* ACTION_MULTIPLE is apparently obsolete.  */
+  @SuppressWarnings ("deprecation")
+  public boolean
+  sendKeyEvent (KeyEvent key)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "sendKeyEvent: " + key);
+
+    /* Use the standard API if possible.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+      view.imManager.dispatchKeyEventFromInputMethod (view, key);
+    else
+      {
+       /* Fall back to dispatching the event manually if not.  */
+
+       switch (key.getAction ())
+         {
+         case KeyEvent.ACTION_DOWN:
+           view.onKeyDown (key.getKeyCode (), key);
+           break;
+
+         case KeyEvent.ACTION_UP:
+           view.onKeyUp (key.getKeyCode (), key);
+           break;
+
+         case KeyEvent.ACTION_MULTIPLE:
+           view.onKeyMultiple (key.getKeyCode (),
+                               key.getRepeatCount (),
+                               key);
+           break;
+         }
+      }
+
+    return true;
+  }
+
+  @Override
+  public boolean
+  requestCursorUpdates (int cursorUpdateMode)
+  {
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return false;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, "requestCursorUpdates: " + cursorUpdateMode);
+
+    EmacsNative.requestCursorUpdates (windowHandle, cursorUpdateMode);
+    return true;
+  }
+
+  @Override
+  public boolean
+  requestCursorUpdates (int cursorUpdateMode, int filter)
+  {
+    if (filter != 0)
+      return false;
+
+    return requestCursorUpdates (cursorUpdateMode);
+  }
+
+  @Override
+  public SurroundingText
+  getSurroundingText (int beforeLength, int afterLength,
+                     int flags)
+  {
+    SurroundingText text;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return null;
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, ("getSurroundingText: " + beforeLength + ", "
+                  + afterLength));
+
+    text = EmacsNative.getSurroundingText (windowHandle, beforeLength,
+                                          afterLength, flags);
+
+    if (EmacsService.DEBUG_IC && text != null)
+      Log.d (TAG, ("getSurroundingText: "
+                  + text.getSelectionStart ()
+                  + ","
+                  + text.getSelectionEnd ()
+                  + "+"
+                  + text.getOffset ()
+                  + ": "
+                  + text.getText ()));
+
+    return text;
+  }
+
+  @Override
+  public TextSnapshot
+  takeSnapshot ()
+  {
+    TextSnapshot snapshot;
+
+    /* Return if the input connection is out of date.  */
+    if (view.icSerial < view.icGeneration)
+      return null;
+
+    snapshot = EmacsNative.takeSnapshot (windowHandle);
+
+    if (EmacsService.DEBUG_IC)
+      Log.d (TAG, ("takeSnapshot: "
+                  + snapshot.getSurroundingText ().getText ()
+                  + " @ " + snapshot.getCompositionEnd ()
+                  + ", " + snapshot.getCompositionStart ()));
+
+    return snapshot;
+  }
+
+  @Override
+  public void
+  closeConnection ()
+  {
+    batchEditCount = 0;
+  }
+
+
+
+  public void
+  reset ()
+  {
+    batchEditCount = 0;
+  }
+
+
+  /* Override functions which are not implemented.  */
+
+  @Override
+  public Handler
+  getHandler ()
+  {
+    return null;
+  }
+
+  @Override
+  public boolean
+  commitContent (InputContentInfo inputContentInfo, int flags,
+                Bundle opts)
+  {
+    return false;
+  }
+
+  @Override
+  public boolean
+  setImeConsumesInput (boolean imeConsumesInput)
+  {
+    return false;
+  }
+
+  @Override
+  public boolean
+  clearMetaKeyStates (int states)
+  {
+    return false;
+  }
+
+  @Override
+  public boolean
+  reportFullscreenMode (boolean enabled)
+  {
+    return false;
+  }
+
+  @Override
+  public boolean
+  performSpellCheck ()
+  {
+    return false;
+  }
+
+  @Override
+  public boolean
+  performPrivateCommand (String action, Bundle data)
+  {
+    return false;
+  }
+
+  @Override
+  public int
+  getCursorCapsMode (int reqModes)
+  {
+    return 0;
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java 
b/java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java
new file mode 100644
index 00000000000..1e1e5d97631
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsLauncherPreferencesActivity.java
@@ -0,0 +1,31 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+/* This class only exists because EmacsPreferencesActivity is already
+   defined as an activity, the system wants a new class in order to
+   define a new activity, and only activities can be enabled or
+   disabled per the API level of the host.  */
+
+public final class EmacsLauncherPreferencesActivity
+  extends EmacsPreferencesActivity
+{
+
+}
diff --git a/java/org/gnu/emacs/EmacsMultitaskActivity.java 
b/java/org/gnu/emacs/EmacsMultitaskActivity.java
new file mode 100644
index 00000000000..b1c48f03fba
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsMultitaskActivity.java
@@ -0,0 +1,29 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+/* This class only exists because EmacsActivity is already defined as
+   an activity, and the system wants a new class in order to define a
+   new activity.  */
+
+public final class EmacsMultitaskActivity extends EmacsActivity
+{
+
+}
diff --git a/java/org/gnu/emacs/EmacsNative.java 
b/java/org/gnu/emacs/EmacsNative.java
new file mode 100644
index 00000000000..fae0ba98f86
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -0,0 +1,316 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.content.res.AssetManager;
+
+import android.graphics.Bitmap;
+
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextSnapshot;
+
+public final class EmacsNative
+{
+  /* List of native libraries that must be loaded during class
+     initialization.  */
+  private static final String[] libraryDeps;
+
+
+  /* Like `dup' in C.  */
+  public static native int dup (int fd);
+
+  /* Obtain the fingerprint of this build of Emacs.  The fingerprint
+     can be used to determine the dump file name.  */
+  public static native String getFingerprint ();
+
+  /* Set certain parameters before initializing Emacs.
+
+     assetManager must be the asset manager associated with the
+     context that is loading Emacs.  It is saved and remains for the
+     remainder the lifetime of the Emacs process.
+
+     filesDir must be the package's data storage location for the
+     current Android user.
+
+     libDir must be the package's data storage location for native
+     libraries.  It is used as PATH.
+
+     cacheDir must be the package's cache directory.  It is used as
+     the `temporary-file-directory'.
+
+     pixelDensityX and pixelDensityY are the DPI values that will be
+     used by Emacs.
+
+     scaledDensity is the DPI value used to translate point sizes to
+     pixel sizes when loading fonts.
+
+     classPath must be the classpath of this app_process process, or
+     NULL.
+
+     emacsService must be the EmacsService singleton, or NULL.
+
+     apiLevel is the version of Android being run.  */
+  public static native void setEmacsParams (AssetManager assetManager,
+                                           String filesDir,
+                                           String libDir,
+                                           String cacheDir,
+                                           float pixelDensityX,
+                                           float pixelDensityY,
+                                           float scaledDensity,
+                                           String classPath,
+                                           EmacsService emacsService,
+                                           int apiLevel);
+
+  /* Initialize Emacs with the argument array ARGV.  Each argument
+     must contain a NULL terminated string, or else the behavior is
+     undefined.
+
+     DUMPFILE is the dump file to use, or NULL if Emacs is to load
+     loadup.el itself.  */
+  public static native void initEmacs (String argv[], String dumpFile);
+
+  /* Abort and generate a native core dump.  */
+  public static native void emacsAbort ();
+
+  /* Set Vquit_flag to t, resulting in Emacs quitting as soon as
+     possible.  */
+  public static native void quit ();
+
+  /* Send an ANDROID_CONFIGURE_NOTIFY event.  The values of all the
+     functions below are the serials of the events sent.  */
+  public static native long sendConfigureNotify (short window, long time,
+                                                int x, int y, int width,
+                                                int height);
+
+  /* Send an ANDROID_KEY_PRESS event.  */
+  public static native long sendKeyPress (short window, long time, int state,
+                                         int keyCode, int unicodeChar);
+
+  /* Send an ANDROID_KEY_RELEASE event.  */
+  public static native long sendKeyRelease (short window, long time, int state,
+                                           int keyCode, int unicodeChar);
+
+  /* Send an ANDROID_FOCUS_IN event.  */
+  public static native long sendFocusIn (short window, long time);
+
+  /* Send an ANDROID_FOCUS_OUT event.  */
+  public static native long sendFocusOut (short window, long time);
+
+  /* Send an ANDROID_WINDOW_ACTION event.  */
+  public static native long sendWindowAction (short window, int action);
+
+  /* Send an ANDROID_ENTER_NOTIFY event.  */
+  public static native long sendEnterNotify (short window, int x, int y,
+                                            long time);
+
+  /* Send an ANDROID_LEAVE_NOTIFY event.  */
+  public static native long sendLeaveNotify (short window, int x, int y,
+                                            long time);
+
+  /* Send an ANDROID_MOTION_NOTIFY event.  */
+  public static native long sendMotionNotify (short window, int x, int y,
+                                             long time);
+
+  /* Send an ANDROID_BUTTON_PRESS event.  */
+  public static native long sendButtonPress (short window, int x, int y,
+                                            long time, int state,
+                                            int button);
+
+  /* Send an ANDROID_BUTTON_RELEASE event.  */
+  public static native long sendButtonRelease (short window, int x, int y,
+                                              long time, int state,
+                                              int button);
+
+  /* Send an ANDROID_TOUCH_DOWN event.  */
+  public static native long sendTouchDown (short window, int x, int y,
+                                          long time, int pointerID,
+                                          int flags);
+
+  /* Send an ANDROID_TOUCH_UP event.  */
+  public static native long sendTouchUp (short window, int x, int y,
+                                        long time, int pointerID,
+                                        int flags);
+
+  /* Send an ANDROID_TOUCH_MOVE event.  */
+  public static native long sendTouchMove (short window, int x, int y,
+                                          long time, int pointerID,
+                                          int flags);
+
+  /* Send an ANDROID_WHEEL event.  */
+  public static native long sendWheel (short window, int x, int y,
+                                      long time, int state,
+                                      float xDelta, float yDelta);
+
+  /* Send an ANDROID_ICONIFIED event.  */
+  public static native long sendIconified (short window);
+
+  /* Send an ANDROID_DEICONIFIED event.  */
+  public static native long sendDeiconified (short window);
+
+  /* Send an ANDROID_CONTEXT_MENU event.  */
+  public static native long sendContextMenu (short window, int menuEventID,
+                                            int menuEventSerial);
+
+  /* Send an ANDROID_EXPOSE event.  */
+  public static native long sendExpose (short window, int x, int y,
+                                       int width, int height);
+
+  /* Return the file name associated with the specified file
+     descriptor, or NULL if there is none.  */
+  public static native byte[] getProcName (int fd);
+
+  /* Notice that the Emacs thread will now start waiting for the main
+     thread's looper to respond.  */
+  public static native void beginSynchronous ();
+
+  /* Notice that the Emacs thread will has finished waiting for the
+     main thread's looper to respond.  */
+  public static native void endSynchronous ();
+
+  /* Prevent deadlocks while reliably allowing queries from the Emacs
+     thread to the main thread to complete by waiting for a query to
+     start from the main thread, then answer it; assume that a query
+     is certain to start shortly.  */
+  public static native void answerQuerySpin ();
+
+  /* Return whether or not KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP and
+     KEYCODE_VOLUME_MUTE should be forwarded to Emacs.  */
+  public static native boolean shouldForwardMultimediaButtons ();
+
+  /* Initialize the current thread, by blocking signals that do not
+     interest it.  */
+  public static native void setupSystemThread ();
+
+
+
+  /* Input connection functions.  These mostly correspond to their
+     counterparts in Android's InputConnection.  */
+
+  public static native void beginBatchEdit (short window);
+  public static native void endBatchEdit (short window);
+  public static native void commitCompletion (short window, String text,
+                                             int position);
+  public static native void commitText (short window, String text,
+                                       int position);
+  public static native void deleteSurroundingText (short window,
+                                                  int leftLength,
+                                                  int rightLength);
+  public static native void finishComposingText (short window);
+  public static native String getSelectedText (short window, int flags);
+  public static native String getTextAfterCursor (short window, int length,
+                                                 int flags);
+  public static native String getTextBeforeCursor (short window, int length,
+                                                  int flags);
+  public static native void setComposingText (short window, String text,
+                                             int newCursorPosition);
+  public static native void setComposingRegion (short window, int start,
+                                               int end);
+  public static native void setSelection (short window, int start, int end);
+  public static native void performEditorAction (short window,
+                                                int editorAction);
+  public static native void performContextMenuAction (short window,
+                                                     int contextMenuAction);
+  public static native ExtractedText getExtractedText (short window,
+                                                      ExtractedTextRequest req,
+                                                      int flags);
+  public static native void requestSelectionUpdate (short window);
+  public static native void requestCursorUpdates (short window, int mode);
+  public static native void clearInputFlags (short window);
+  public static native SurroundingText getSurroundingText (short window,
+                                                          int left, int right,
+                                                          int flags);
+  public static native TextSnapshot takeSnapshot (short window);
+
+
+  /* Return the current value of the selection, or -1 upon
+     failure.  */
+  public static native int[] getSelection (short window);
+
+
+  /* Graphics functions used as a replacement for potentially buggy
+     Android APIs.  */
+
+  public static native void blitRect (Bitmap src, Bitmap dest, int x1,
+                                     int y1, int x2, int y2);
+
+  /* Increment the generation ID of the specified BITMAP, forcing its
+     texture to be re-uploaded to the GPU.  */
+
+  public static native void notifyPixelsChanged (Bitmap bitmap);
+
+
+  /* Functions used to synchronize document provider access with the
+     main thread.  */
+
+  /* Wait for a call to `safPostRequest' while also reading async
+     input.
+
+     If asynchronous input arrives and sets Vquit_flag, return 1.  */
+  public static native int safSyncAndReadInput ();
+
+  /* Wait for a call to `safPostRequest'.  */
+  public static native void safSync ();
+
+  /* Post the semaphore used to await the completion of SAF
+     operations.  */
+  public static native void safPostRequest ();
+
+  /* Detect and return FD is writable.  FD may be truncated to 0 bytes
+     in the process.  */
+  public static native boolean ftruncate (int fd);
+
+  static
+  {
+    /* Older versions of Android cannot link correctly with shared
+       libraries that link with other shared libraries built along
+       Emacs unless all requisite shared libraries are explicitly
+       loaded from Java.
+
+       Every time you add a new shared library dependency to Emacs,
+       please add it here as well.  */
+
+    libraryDeps = new String[] { "png_emacs", "selinux_emacs",
+                                "crypto_emacs", "pcre_emacs",
+                                "packagelistparser_emacs",
+                                "gnutls_emacs", "gmp_emacs",
+                                "nettle_emacs", "p11-kit_emacs",
+                                "tasn1_emacs", "hogweed_emacs",
+                                "jansson_emacs", "jpeg_emacs",
+                                "tiff_emacs", "xml2_emacs",
+                                "icuuc_emacs",
+                                "tree-sitter_emacs", };
+
+    for (String dependency : libraryDeps)
+      {
+       try
+         {
+           System.loadLibrary (dependency);
+         }
+       catch (UnsatisfiedLinkError exception)
+         {
+           /* Ignore this exception.  */
+         }
+      }
+
+    System.loadLibrary ("emacs");
+  };
+};
diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java 
b/java/org/gnu/emacs/EmacsNoninteractive.java
new file mode 100644
index 00000000000..1c7513e1cc9
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsNoninteractive.java
@@ -0,0 +1,203 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.os.Looper;
+import android.os.Build;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/* Noninteractive Emacs.
+
+   This is the class that libandroid-emacs.so starts.
+   libandroid-emacs.so figures out the system classpath, then starts
+   dalvikvm with the framework jars.
+
+   At that point, dalvikvm calls main, which sets up the main looper,
+   creates an ActivityThread and attaches it to the main thread.
+
+   Then, it obtains an application context for the LoadedApk in the
+   application thread.
+
+   Finally, it obtains the necessary context specific objects and
+   initializes Emacs.  */
+
+@SuppressWarnings ("unchecked")
+public final class EmacsNoninteractive
+{
+  public static void
+  main (String[] args)
+  {
+    Object activityThread, loadedApk;
+    Class activityThreadClass, loadedApkClass, contextImplClass;
+    Class compatibilityInfoClass;
+    Method method;
+    Context context;
+    AssetManager assets;
+    String filesDir, libDir, cacheDir;
+
+    Looper.prepare ();
+    context = null;
+    assets = null;
+    filesDir = libDir = cacheDir = null;
+
+    try
+      {
+       /* Get the activity thread.  */
+       activityThreadClass = Class.forName ("android.app.ActivityThread");
+
+       /* Get the systemMain method.  */
+       method = activityThreadClass.getMethod ("systemMain");
+
+       /* Create and attach the activity thread.  */
+       activityThread = method.invoke (null);
+       context = null;
+
+       /* Now get an LoadedApk.  */
+
+       try
+         {
+           loadedApkClass = Class.forName ("android.app.LoadedApk");
+         }
+       catch (ClassNotFoundException exception)
+         {
+           /* Android 2.2 has no LoadedApk class, but fortunately it
+              does not need to be used, since contexts can be
+              directly created.  */
+
+           loadedApkClass = null;
+           contextImplClass = Class.forName ("android.app.ContextImpl");
+
+           method = activityThreadClass.getDeclaredMethod ("getSystemContext");
+           context = (Context) method.invoke (activityThread);
+           method = contextImplClass.getDeclaredMethod ("createPackageContext",
+                                                        String.class,
+                                                        int.class);
+           method.setAccessible (true);
+           context = (Context) method.invoke (context, "org.gnu.emacs",
+                                              0);
+         }
+
+       /* If the context has not already been created, then do what
+          is appropriate for newer versions of Android.  */
+
+       if (context == null)
+         {
+           /* Get a LoadedApk.  How to do this varies by Android version.
+              On Android 2.3.3 and earlier, there is no
+              ``compatibilityInfo'' argument to getPackageInfo.  */
+
+           if (Build.VERSION.SDK_INT
+               <= Build.VERSION_CODES.GINGERBREAD_MR1)
+             {
+               method
+                 = activityThreadClass.getMethod ("getPackageInfo",
+                                                  String.class,
+                                                  int.class);
+               loadedApk = method.invoke (activityThread, "org.gnu.emacs",
+                                          0);
+             }
+           else
+             {
+               compatibilityInfoClass
+                 = Class.forName ("android.content.res.CompatibilityInfo");
+
+               method
+                 = activityThreadClass.getMethod ("getPackageInfo",
+                                                  String.class,
+                                                  compatibilityInfoClass,
+                                                  int.class);
+               loadedApk = method.invoke (activityThread, "org.gnu.emacs",
+                                          null, 0);
+             }
+
+           if (loadedApk == null)
+             throw new RuntimeException ("getPackageInfo returned NULL");
+
+           /* Now, get a context.  */
+           contextImplClass = Class.forName ("android.app.ContextImpl");
+
+           try
+             {
+               method
+                 = contextImplClass.getDeclaredMethod ("createAppContext",
+                                                       activityThreadClass,
+                                                       loadedApkClass);
+               method.setAccessible (true);
+               context = (Context) method.invoke (null, activityThread,
+                                                  loadedApk);
+             }
+           catch (NoSuchMethodException exception)
+             {
+               /* Older Android versions don't have createAppContext, but
+                  instead require creating a ContextImpl, and then
+                  calling createPackageContext.  */
+               method
+                 = activityThreadClass.getDeclaredMethod ("getSystemContext");
+               context = (Context) method.invoke (activityThread);
+               method
+                 = contextImplClass.getDeclaredMethod ("createPackageContext",
+                                                       String.class,
+                                                       int.class);
+               method.setAccessible (true);
+               context = (Context) method.invoke (context, "org.gnu.emacs",
+                                                  0);
+             }
+         }
+
+       /* Don't actually start the looper or anything.  Instead, obtain
+          an AssetManager.  */
+       assets = context.getAssets ();
+
+       /* Now configure Emacs.  The class path should already be set.  */
+
+       filesDir = context.getFilesDir ().getCanonicalPath ();
+       libDir = EmacsService.getLibraryDirectory (context);
+       cacheDir = context.getCacheDir ().getCanonicalPath ();
+      }
+    catch (Exception e)
+      {
+       System.err.println ("Internal error: " + e);
+       System.err.println ("This means that the Android platform changed,");
+       System.err.println ("and that Emacs needs adjustments in order to");
+       System.err.println ("obtain required system internal resources.");
+       System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org.");
+       e.printStackTrace ();
+
+       System.exit (1);
+      }
+
+    EmacsNative.setEmacsParams (assets, filesDir,
+                               libDir, cacheDir, 0.0f,
+                               0.0f, 0.0f, null, null,
+                               Build.VERSION.SDK_INT);
+
+    /* Now find the dump file that Emacs should use, if it has already
+       been dumped.  */
+    EmacsApplication.findDumpFile (context);
+
+    /* Start Emacs.  */
+    EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java 
b/java/org/gnu/emacs/EmacsOpenActivity.java
new file mode 100644
index 00000000000..ea503ebd120
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsOpenActivity.java
@@ -0,0 +1,540 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+/* This class makes the Emacs server work reasonably on Android.
+
+   There is no way to make the Unix socket publicly available on
+   Android.
+
+   Instead, this activity tries to connect to the Emacs server, to
+   make it open files the system asks Emacs to open, and to emulate
+   some reasonable behavior when Emacs has not yet started.
+
+   First, Emacs registers itself as an application that can open text
+   and image files.
+
+   Then, when the user is asked to open a file and selects ``Emacs''
+   as the application that will open the file, the system pops up a
+   window, this activity, and calls the `onCreate' function.
+
+   `onCreate' then tries very to find the file name of the file that
+   was selected, and give it to emacsclient.
+
+   If emacsclient successfully opens the file, then this activity
+   starts EmacsActivity (to bring it on to the screen); otherwise, it
+   displays the output of emacsclient or any error message that occurs
+   and exits.  */
+
+import android.app.AlertDialog;
+import android.app.Activity;
+
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.Intent;
+
+import android.net.Uri;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+public final class EmacsOpenActivity extends Activity
+  implements DialogInterface.OnClickListener,
+  DialogInterface.OnCancelListener
+{
+  private static final String TAG = "EmacsOpenActivity";
+
+  /* The name of any file that should be opened as EmacsThread starts
+     Emacs.  This is never cleared, even if EmacsOpenActivity is
+     started a second time, as EmacsThread only starts once.  */
+  public static String fileToOpen;
+
+  /* Any currently focused EmacsOpenActivity.  Used to show pop ups
+     while the activity is active and Emacs doesn't have permission to
+     display over other programs.  */
+  public static EmacsOpenActivity currentActivity;
+
+  private class EmacsClientThread extends Thread
+  {
+    private ProcessBuilder builder;
+
+    public
+    EmacsClientThread (ProcessBuilder processBuilder)
+    {
+      builder = processBuilder;
+    }
+
+    @Override
+    public void
+    run ()
+    {
+      Process process;
+      InputStream error;
+      String errorText;
+
+      try
+       {
+         /* Start emacsclient.  */
+         process = builder.start ();
+         process.waitFor ();
+
+         /* Now figure out whether or not starting the process was
+            successful.  */
+         if (process.exitValue () == 0)
+           finishSuccess ();
+         else
+           finishFailure ("Error opening file", null);
+       }
+      catch (IOException exception)
+       {
+         finishFailure ("Internal error", exception.toString ());
+       }
+      catch (InterruptedException exception)
+       {
+         finishFailure ("Internal error", exception.toString ());
+       }
+    }
+  }
+
+  @Override
+  public void
+  onClick (DialogInterface dialog, int which)
+  {
+    finish ();
+  }
+
+  @Override
+  public void
+  onCancel (DialogInterface dialog)
+  {
+    finish ();
+  }
+
+  public String
+  readEmacsClientLog ()
+  {
+    File file, cache;
+    FileReader reader;
+    char[] buffer;
+    int rc;
+    StringBuilder builder;
+
+    /* Because the ProcessBuilder functions necessary to redirect
+       process output are not implemented on Android 7 and earlier,
+       print a generic error message.  */
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+      return ("This is likely because the Emacs server"
+             + " is not running, or because you did"
+             + " not grant Emacs permission to access"
+             + " external storage.");
+
+    cache = getCacheDir ();
+    file = new File (cache, "emacsclient.log");
+    builder = new StringBuilder ();
+    reader = null;
+
+    try
+      {
+       reader = new FileReader (file);
+       buffer = new char[2048];
+
+       while ((rc = reader.read (buffer, 0, 2048)) != -1)
+         builder.append (buffer, 0, rc);
+
+       reader.close ();
+       return builder.toString ();
+      }
+    catch (IOException exception)
+      {
+       /* Close the reader if it's already been opened.  */
+
+       try
+         {
+           if (reader != null)
+             reader.close ();
+         }
+       catch (IOException e)
+         {
+           /* Not sure what to do here.  */
+         }
+
+       return ("Couldn't read emacsclient.log: "
+               + exception.toString ());
+      }
+  }
+
+  private void
+  displayFailureDialog (String title, String text)
+  {
+    AlertDialog.Builder builder;
+    AlertDialog dialog;
+
+    builder = new AlertDialog.Builder (this);
+    dialog = builder.create ();
+    dialog.setTitle (title);
+
+    if (text == null)
+      /* Read in emacsclient.log instead.  */
+      text = readEmacsClientLog ();
+
+    dialog.setMessage (text);
+    dialog.setButton (DialogInterface.BUTTON_POSITIVE, "OK", this);
+    dialog.setOnCancelListener (this);
+    dialog.show ();
+  }
+
+  /* Check that the specified FILE is readable.  If Android 4.4 or
+     later is being used, return URI formatted into a `/content/' file
+     name.
+
+     If it is not, then copy the file in FD to a location in the
+     system cache directory and return the name of that file.  */
+
+  private String
+  checkReadableOrCopy (String file, ParcelFileDescriptor fd,
+                      Uri uri)
+    throws IOException, FileNotFoundException
+  {
+    File inFile;
+    FileOutputStream outStream;
+    InputStream stream;
+    byte buffer[];
+    int read;
+    String content;
+
+    inFile = new File (file);
+
+    if (inFile.canRead ())
+      return file;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+      {
+       content = EmacsService.buildContentName (uri);
+       return content;
+      }
+
+    /* inFile is now the file being written to.  */
+    inFile = new File (getCacheDir (), inFile.getName ());
+    buffer = new byte[4098];
+
+    /* Initialize both streams to NULL.  */
+    outStream = null;
+    stream = null;
+
+    try
+      {
+       outStream = new FileOutputStream (inFile);
+       stream = new FileInputStream (fd.getFileDescriptor ());
+
+       while ((read = stream.read (buffer)) >= 0)
+         outStream.write (buffer, 0, read);
+      }
+    finally
+      {
+       /* Note that this does not close FD.
+
+          Keep in mind that execution is transferred to ``finally''
+          even if an exception happens inside the while loop
+          above.  */
+
+       if (stream != null)
+         stream.close ();
+
+       if (outStream != null)
+         outStream.close ();
+      }
+
+    return inFile.getCanonicalPath ();
+  }
+
+  /* Finish this activity in response to emacsclient having
+     successfully opened a file.
+
+     In the main thread, close this window, and open a window
+     belonging to an Emacs frame.  */
+
+  public void
+  finishSuccess ()
+  {
+    runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         Intent intent;
+
+         intent = new Intent (EmacsOpenActivity.this,
+                              EmacsActivity.class);
+
+         /* This means only an existing frame will be displayed.  */
+         intent.addFlags (Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+         startActivity (intent);
+
+         EmacsOpenActivity.this.finish ();
+       }
+      });
+  }
+
+  /* Finish this activity after displaying a dialog associated with
+     failure to open a file.
+
+     Use TITLE as the title of the dialog.  If TEXT is non-NULL,
+     display that text in the dialog.  Otherwise, use the contents of
+     emacsclient.log in the cache directory instead, or describe why
+     that file cannot be read.  */
+
+  public void
+  finishFailure (final String title, final String text)
+  {
+    runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         displayFailureDialog (title, text);
+       }
+      });
+  }
+
+  public void
+  startEmacsClient (String fileName)
+  {
+    String libDir;
+    ProcessBuilder builder;
+    Process process;
+    EmacsClientThread 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");
+
+    /* Redirection is unfortunately not possible in Android 7 and
+       earlier.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+      {
+       file = new File (getCacheDir (), "emacsclient.log");
+
+       /* Redirect standard error to a file so that errors can be
+          meaningfully reported.  */
+
+       if (file.exists ())
+         file.delete ();
+
+       builder.redirectError (file);
+      }
+
+    /* Track process output in a new thread, since this is the UI
+       thread and doing so here can cause deadlocks when EmacsService
+       decides to wait for something.  */
+
+    thread = new EmacsClientThread (builder);
+    thread.start ();
+  }
+
+  /* Run emacsclient to open the file specified in the Intent that
+     caused this activity to start.
+
+     Determine the name of the file corresponding to the URI specified
+     in that intent; then, run emacsclient and wait for it to finish.
+
+     Finally, display any error message, transfer the focus to an
+     Emacs frame, and finish the activity.  */
+
+  @Override
+  public void
+  onCreate (Bundle savedInstanceState)
+  {
+    String action, fileName;
+    Intent intent;
+    Uri uri;
+    ContentResolver resolver;
+    ParcelFileDescriptor fd;
+    byte[] names;
+    String errorBlurb;
+
+    super.onCreate (savedInstanceState);
+
+    /* Obtain the intent that started Emacs.  */
+    intent = getIntent ();
+    action = intent.getAction ();
+
+    if (action == null)
+      {
+       finish ();
+       return;
+      }
+
+    /* Now see if the action specified is supported by Emacs.  */
+
+    if (action.equals ("android.intent.action.VIEW")
+       || action.equals ("android.intent.action.EDIT")
+       || action.equals ("android.intent.action.PICK"))
+      {
+       /* Obtain the URI of the action.  */
+       uri = intent.getData ();
+
+       if (uri == null)
+         {
+           finish ();
+           return;
+         }
+
+       /* Now, try to get the file name.  */
+
+       if (uri.getScheme ().equals ("file"))
+         fileName = uri.getPath ();
+       else
+         {
+           fileName = null;
+
+           if (uri.getScheme ().equals ("content"))
+             {
+               /* This is one of the annoying Android ``content''
+                  URIs.  Most of the time, there is actually an
+                  underlying file, but it cannot be found without
+                  opening the file and doing readlink on its file
+                  descriptor in /proc/self/fd.  */
+               resolver = getContentResolver ();
+               fd = null;
+
+               try
+                 {
+                   fd = resolver.openFileDescriptor (uri, "r");
+                   names = EmacsNative.getProcName (fd.getFd ());
+
+                   /* What is the right encoding here? */
+
+                   if (names != null)
+                     fileName = new String (names, "UTF-8");
+
+                   fileName = checkReadableOrCopy (fileName, fd, uri);
+                 }
+               catch (FileNotFoundException exception)
+                 {
+                   /* Do nothing.  */
+                 }
+               catch (IOException exception)
+                 {
+                   /* Do nothing.  */
+                 }
+
+               if (fd != null)
+                 {
+                   try
+                     {
+                       fd.close ();
+                     }
+                   catch (IOException exception)
+                     {
+                       /* Do nothing.  */
+                     }
+                 }
+             }
+
+           if (fileName == null)
+             {
+               errorBlurb = ("The URI: " + uri + " could not be opened"
+                             + ", as it does not encode file name inform"
+                             + "ation.");
+               displayFailureDialog ("Error opening file", errorBlurb);
+               return;
+             }
+         }
+
+       /* And start emacsclient.  Set `currentActivity' to this now.
+          Presumably, it will shortly become capable of displaying
+          dialogs.  */
+       currentActivity = this;
+       startEmacsClient (fileName);
+      }
+    else
+      finish ();
+  }
+
+
+
+  @Override
+  public void
+  onDestroy ()
+  {
+    /* Clear `currentActivity' if it refers to the activity being
+       destroyed.  */
+
+    if (currentActivity == this)
+      this.currentActivity = null;
+
+    super.onDestroy ();
+  }
+
+  @Override
+  public void
+  onWindowFocusChanged (boolean isFocused)
+  {
+    if (isFocused)
+      currentActivity = this;
+    else if (currentActivity == this)
+      currentActivity = null;
+
+    super.onWindowFocusChanged (isFocused);
+  }
+
+  @Override
+  public void
+  onPause ()
+  {
+    /* XXX: clear currentActivity here as well; I don't know whether
+       or not onWindowFocusChanged is always called prior to this.  */
+
+    if (currentActivity == this)
+      currentActivity = null;
+
+    super.onPause ();
+  }
+}
diff --git a/java/org/gnu/emacs/EmacsPixmap.java 
b/java/org/gnu/emacs/EmacsPixmap.java
new file mode 100644
index 00000000000..eb011bc5e65
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsPixmap.java
@@ -0,0 +1,192 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.lang.IllegalArgumentException;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+
+import android.os.Build;
+
+/* Drawable backed by bitmap.  */
+
+public final class EmacsPixmap extends EmacsHandleObject
+  implements EmacsDrawable
+{
+  /* The depth of the bitmap.  This is not actually used, just defined
+     in order to be consistent with X.  */
+  public int depth, width, height;
+
+  /* The bitmap itself.  */
+  public Bitmap bitmap;
+
+  /* The canvas used to draw to BITMAP.  */
+  public Canvas canvas;
+
+  /* Whether or not GC should be explicitly triggered upon
+     release.  */
+  private boolean needCollect;
+
+  /* ID used to determine whether or not the GC clip rects
+     changed.  */
+  private long gcClipRectID;
+
+  public
+  EmacsPixmap (short handle, int colors[], int width,
+              int height, int depth)
+  {
+    super (handle);
+
+    if (depth != 1 && depth != 24)
+      throw new IllegalArgumentException ("Invalid depth specified"
+                                         + " for pixmap: " + depth);
+
+    switch (depth)
+      {
+      case 1:
+       bitmap = Bitmap.createBitmap (colors, width, height,
+                                     Bitmap.Config.ALPHA_8);
+       break;
+
+      case 24:
+       bitmap = Bitmap.createBitmap (colors, width, height,
+                                     Bitmap.Config.ARGB_8888);
+       bitmap.setHasAlpha (false);
+       break;
+      }
+
+    this.width = width;
+    this.height = height;
+    this.depth = depth;
+  }
+
+  public
+  EmacsPixmap (short handle, int width, int height, int depth)
+  {
+    super (handle);
+
+    if (depth != 1 && depth != 24)
+      throw new IllegalArgumentException ("Invalid depth specified"
+                                         + " for pixmap: " + depth);
+
+    switch (depth)
+      {
+      case 1:
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+         bitmap = Bitmap.createBitmap (width, height,
+                                       Bitmap.Config.ALPHA_8,
+                                       false);
+       else
+         bitmap = Bitmap.createBitmap (width, height,
+                                       Bitmap.Config.ALPHA_8);
+       break;
+
+      case 24:
+
+       /* Emacs doesn't just use the first kind of `createBitmap'
+          because the latter allows specifying that the pixmap is
+          always opaque, which really increases efficiency.  */
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+         bitmap = Bitmap.createBitmap (width, height,
+                                       Bitmap.Config.ARGB_8888);
+       else
+         bitmap = Bitmap.createBitmap (width, height,
+                                       Bitmap.Config.ARGB_8888,
+                                       false);
+       break;
+      }
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1)
+      /* On these old versions of Android, Bitmap.recycle frees bitmap
+        contents immediately.  */
+      needCollect = false;
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+      needCollect = (bitmap.getByteCount ()
+                    >= 1024 * 512);
+    else
+      needCollect = (bitmap.getAllocationByteCount ()
+                    >= 1024 * 512);
+
+    bitmap.eraseColor (0xff000000);
+
+    this.width = width;
+    this.height = height;
+    this.depth = depth;
+  }
+
+  @Override
+  public Canvas
+  lockCanvas (EmacsGC gc)
+  {
+    int i;
+
+    if (canvas == null)
+      {
+       canvas = new Canvas (bitmap);
+       canvas.save ();
+      }
+
+    /* Now see if clipping has to be redone.  */
+    if (gc.clipRectID == gcClipRectID)
+      return canvas;
+
+    /* It does have to be redone.  Reapply gc.real_clip_rects.  */
+    canvas.restore ();
+    canvas.save ();
+
+    if (gc.real_clip_rects != null)
+      {
+       for (i = 0; i < gc.real_clip_rects.length; ++i)
+         canvas.clipRect (gc.real_clip_rects[i]);
+      }
+
+    /* Save the clip rect ID again.  */
+    gcClipRectID = gc.clipRectID;
+    return canvas;
+  }
+
+  @Override
+  public void
+  damageRect (Rect damageRect)
+  {
+
+  }
+
+  @Override
+  public Bitmap
+  getBitmap ()
+  {
+    return bitmap;
+  }
+
+  @Override
+  public void
+  destroyHandle ()
+  {
+    bitmap.recycle ();
+    bitmap = null;
+
+    /* Collect the bitmap storage if the bitmap is big.  */
+    if (needCollect)
+      Runtime.getRuntime ().gc ();
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java 
b/java/org/gnu/emacs/EmacsPreferencesActivity.java
new file mode 100644
index 00000000000..7e67cc3679b
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java
@@ -0,0 +1,168 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.io.File;
+
+import android.app.Activity;
+
+import android.content.Intent;
+
+import android.os.Bundle;
+import android.os.Build;
+
+import android.widget.Toast;
+
+import android.preference.*;
+
+/* This module provides a ``preferences'' display for Emacs.  It is
+   supposed to be launched from inside the Settings application to
+   perform various actions, such as starting Emacs with the ``-Q''
+   option, which would not be possible otherwise, as there is no
+   command line on Android.
+
+   Android provides a preferences activity, but it is deprecated.
+   Unfortunately, there is no alternative that looks the same way.  */
+
+@SuppressWarnings ("deprecation")
+public class EmacsPreferencesActivity extends PreferenceActivity
+{
+  /* Restart Emacs with -Q.  Call EmacsThread.exit to kill Emacs now,
+     and tell the system to start EmacsActivity with some parameters
+     later.  */
+
+  private void
+  startEmacsQ ()
+  {
+    Intent intent;
+
+    intent = new Intent (this, EmacsActivity.class);
+    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+    intent.putExtra ("org.gnu.emacs.STARTUP_ARGUMENT", "--quick");
+    startActivity (intent);
+    System.exit (0);
+  }
+
+  /* Restart Emacs with `--debug-init'.  Call EmacsThread.exit to kill
+     Emacs now, and tell the system to EmacsActivity with some
+     parameters later.  */
+
+  private void
+  startEmacsDebugInit ()
+  {
+    Intent intent;
+
+    intent = new Intent (this, EmacsActivity.class);
+    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+    intent.putExtra ("org.gnu.emacs.STARTUP_ARGUMENT", "--debug-init");
+    startActivity (intent);
+    System.exit (0);
+  }
+
+  /* Erase Emacs's dump file.  */
+
+  private void
+  eraseDumpFile ()
+  {
+    String wantedDumpFile;
+    File file;
+    Toast toast;
+
+    wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint ()
+                     + ".pdmp");
+    file = new File (getFilesDir (), wantedDumpFile);
+
+    if (file.exists ())
+      file.delete ();
+
+    /* Make sure to clear EmacsApplication.dumpFileName, or
+       starting Emacs without restarting this program will
+       make Emacs try to load a nonexistent dump file.  */
+    EmacsApplication.dumpFileName = null;
+
+    /* Display a message stating that the dump file has been
+       erased.  */
+    toast = Toast.makeText (this, "Dump file removed",
+                           Toast.LENGTH_SHORT);
+    toast.show ();
+  }
+
+  @Override
+  public final void
+  onCreate (Bundle savedInstanceState)
+  {
+    Preference tem;
+    Preference.OnPreferenceClickListener listener;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+      setTheme (android.R.style.Theme_DeviceDefault_Settings);
+    else if (Build.VERSION.SDK_INT
+            >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+      setTheme (android.R.style.Theme_DeviceDefault);
+
+    /* This must come before using any preference APIs.  */
+    super.onCreate (savedInstanceState);
+
+    /* Add preferences from the XML file where they are defined.  */
+    addPreferencesFromResource (R.xml.preferences);
+
+    /* Now, set up on click handlers for each of the preferences
+       items.  */
+
+    tem = findPreference ("start_quick");
+    listener = new Preference.OnPreferenceClickListener () {
+       @Override
+       public boolean
+       onPreferenceClick (Preference preference)
+       {
+         startEmacsQ ();
+         return true;
+       }
+      };
+
+    tem.setOnPreferenceClickListener (listener);
+    tem = findPreference ("start_debug_init");
+    listener = new Preference.OnPreferenceClickListener () {
+       @Override
+       public boolean
+       onPreferenceClick (Preference preference)
+       {
+         startEmacsDebugInit ();
+         return true;
+       }
+      };
+
+    tem.setOnPreferenceClickListener (listener);
+    tem = findPreference ("erase_dump");
+    listener = new Preference.OnPreferenceClickListener () {
+       @Override
+       public boolean
+       onPreferenceClick (Preference preference)
+       {
+         eraseDumpFile ();
+         return true;
+       }
+      };
+
+    tem.setOnPreferenceClickListener (listener);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsSafThread.java 
b/java/org/gnu/emacs/EmacsSafThread.java
new file mode 100644
index 00000000000..1b62662b4fc
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSafThread.java
@@ -0,0 +1,1711 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.OperationCanceledException;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+
+import android.util.Log;
+
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+
+
+
+/* Emacs runs long-running SAF operations on a second thread running
+   its own handler.  These operations include opening files and
+   maintaining the path to document ID cache.
+
+   Because Emacs paths are based on file display names, while Android
+   document identifiers have no discernible hierarchy of their own,
+   each file name lookup must carry out a repeated search for
+   directory documents with the names of all of the file name's
+   constituent components, where each iteration searches within the
+   directory document identified by the previous iteration.
+
+   A time limited cache tying components to document IDs is maintained
+   in order to speed up consecutive searches for file names sharing
+   the same components.  Since listening for changes to each document
+   in the cache is prohibitively expensive, Emacs instead elects to
+   periodically remove entries that are older than a predetermined
+   amount of a time.
+
+   The cache is split into two levels: the first caches the
+   relationships between display names and document IDs, while the
+   second caches individual document IDs and their contents (children,
+   type, etc.)
+
+   Long-running operations are also run on this thread for another
+   reason: Android uses special cancellation objects to terminate
+   ongoing IPC operations.  However, the functions that perform these
+   operations block instead of providing mechanisms for the caller to
+   wait for their completion while also reading async input, as a
+   consequence of which the calling thread is unable to signal the
+   cancellation objects that it provides.  Performing the blocking
+   operations in this auxiliary thread enables the main thread to wait
+   for completion itself, signaling the cancellation objects when it
+   deems necessary.  */
+
+
+
+public final class EmacsSafThread extends HandlerThread
+{
+  private static final String TAG = "EmacsSafThread";
+
+  /* The content resolver used by this thread.  */
+  private final ContentResolver resolver;
+
+  /* Map between tree URIs and the cache entry representing its
+     toplevel directory.  */
+  private final HashMap<Uri, CacheToplevel> cacheToplevels;
+
+  /* Handler for this thread's main loop.  */
+  private Handler handler;
+
+  /* File access mode constants.  See `man 7 inode'.  */
+  public static final int S_IRUSR = 0000400;
+  public static final int S_IWUSR = 0000200;
+  public static final int S_IXUSR = 0000100;
+  public static final int S_IFCHR = 0020000;
+  public static final int S_IFDIR = 0040000;
+  public static final int S_IFREG = 0100000;
+
+  /* Number of seconds in between each attempt to prune the storage
+     cache.  */
+  public static final int CACHE_PRUNE_TIME = 10;
+
+  /* Number of seconds after which an entry in the cache is to be
+     considered invalid.  */
+  public static final int CACHE_INVALID_TIME = 10;
+
+  public
+  EmacsSafThread (ContentResolver resolver)
+  {
+    super ("Document provider access thread");
+    this.resolver = resolver;
+    this.cacheToplevels = new HashMap<Uri, CacheToplevel> ();
+  }
+
+
+
+  @Override
+  public void
+  start ()
+  {
+    super.start ();
+
+    /* Set up the handler after the thread starts.  */
+    handler = new Handler (getLooper ());
+
+    /* And start periodically pruning the cache.  */
+    postPruneMessage ();
+  }
+
+
+  private static final class CacheToplevel
+  {
+    /* Map between document names and children.  */
+    HashMap<String, DocIdEntry> children;
+
+    /* Map between document names and file status.  */
+    HashMap<String, StatCacheEntry> statCache;
+
+    /* Map between document IDs and cache items.  */
+    HashMap<String, CacheEntry> idCache;
+  };
+
+  private static final class StatCacheEntry
+  {
+    /* The time at which this cache entry was created.  */
+    long time;
+
+    /* Flags, size, and modification time of this file.  */
+    long flags, size, mtime;
+
+    /* Whether or not this file is a directory.  */
+    boolean isDirectory;
+
+    public
+    StatCacheEntry ()
+    {
+      time = SystemClock.uptimeMillis ();
+    }
+
+    public boolean
+    isValid ()
+    {
+      return ((SystemClock.uptimeMillis () - time)
+             < CACHE_INVALID_TIME * 1000);
+    }
+  };
+
+  private static final class DocIdEntry
+  {
+    /* The document ID.  */
+    String documentId;
+
+    /* The time this entry was created.  */
+    long time;
+
+    public
+    DocIdEntry ()
+    {
+      time = SystemClock.uptimeMillis ();
+    }
+
+    /* Return a cache entry comprised of the state of the file
+       identified by `documentId'.  TREE is the URI of the tree
+       containing this entry, and TOPLEVEL is the toplevel
+       representing it.  SIGNAL is a cancellation signal.
+
+       RESOLVER is the content provider used to retrieve file
+       information.
+
+       Value is NULL if the file cannot be found.  */
+
+    public CacheEntry
+    getCacheEntry (ContentResolver resolver, Uri tree,
+                  CacheToplevel toplevel,
+                  CancellationSignal signal)
+    {
+      Uri uri;
+      String[] projection;
+      String type;
+      Cursor cursor;
+      int column;
+      CacheEntry entry;
+
+      /* Create a document URI representing DOCUMENTID within URI's
+        authority.  */
+
+      uri = DocumentsContract.buildDocumentUriUsingTree (tree,
+                                                        documentId);
+      projection = new String[] {
+       Document.COLUMN_MIME_TYPE,
+      };
+
+      cursor = null;
+
+      try
+       {
+         cursor = resolver.query (uri, projection, null,
+                                  null, null, signal);
+
+         if (!cursor.moveToFirst ())
+           return null;
+
+         column = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+         if (column < 0)
+           return null;
+
+         type = cursor.getString (column);
+
+         if (type == null)
+           return null;
+
+         entry = new CacheEntry ();
+         entry.type = type;
+         toplevel.idCache.put (documentId, entry);
+         return entry;
+       }
+      catch (OperationCanceledException e)
+       {
+         throw e;
+       }
+      catch (Throwable e)
+       {
+         return null;
+       }
+      finally
+       {
+         if (cursor != null)
+           cursor.close ();
+       }
+    }
+
+    public boolean
+    isValid ()
+    {
+      return ((SystemClock.uptimeMillis () - time)
+             < CACHE_INVALID_TIME * 1000);
+    }
+  };
+
+  private static final class CacheEntry
+  {
+    /* The type of this document.  */
+    String type;
+
+    /* Map between document names and children.  */
+    HashMap<String, DocIdEntry> children;
+
+    /* The time this entry was created.  */
+    long time;
+
+    public
+    CacheEntry ()
+    {
+      children = new HashMap<String, DocIdEntry> ();
+      time = SystemClock.uptimeMillis ();
+    }
+
+    public boolean
+    isValid ()
+    {
+      return ((SystemClock.uptimeMillis () - time)
+             < CACHE_INVALID_TIME * 1000);
+    }
+  };
+
+  /* Create or return a toplevel for the given tree URI.  */
+
+  private CacheToplevel
+  getCache (Uri uri)
+  {
+    CacheToplevel toplevel;
+
+    toplevel = cacheToplevels.get (uri);
+
+    if (toplevel != null)
+      return toplevel;
+
+    toplevel = new CacheToplevel ();
+    toplevel.children = new HashMap<String, DocIdEntry> ();
+    toplevel.statCache = new HashMap<String, StatCacheEntry> ();
+    toplevel.idCache = new HashMap<String, CacheEntry> ();
+    cacheToplevels.put (uri, toplevel);
+    return toplevel;
+  }
+
+  /* Remove each cache entry within COLLECTION older than
+     CACHE_INVALID_TIME.  */
+
+  private void
+  pruneCache1 (Collection<DocIdEntry> collection)
+  {
+    Iterator<DocIdEntry> iter;
+    DocIdEntry tem;
+
+    iter = collection.iterator ();
+    while (iter.hasNext ())
+      {
+       /* Get the cache entry.  */
+       tem = iter.next ();
+
+       /* If it's not valid anymore, remove it.  Iterating over a
+          collection whose contents are being removed is undefined
+          unless the removal is performed using the iterator's own
+          `remove' function, so tem.remove cannot be used here.  */
+
+       if (tem.isValid ())
+         continue;
+
+       iter.remove ();
+      }
+  }
+
+  /* Remove every entry older than CACHE_INVALID_TIME from each
+     toplevel inside `cachedToplevels'.  */
+
+  private void
+  pruneCache ()
+  {
+    Iterator<CacheEntry> iter;
+    Iterator<StatCacheEntry> statIter;
+    CacheEntry tem;
+    StatCacheEntry stat;
+
+    for (CacheToplevel toplevel : cacheToplevels.values ())
+      {
+       /* First, clean up expired cache entries.  */
+       iter = toplevel.idCache.values ().iterator ();
+
+       while (iter.hasNext ())
+         {
+           /* Get the cache entry.  */
+           tem = iter.next ();
+
+           /* If it's not valid anymore, remove it.  Iterating over a
+              collection whose contents are being removed is
+              undefined unless the removal is performed using the
+              iterator's own `remove' function, so tem.remove cannot
+              be used here.  */
+
+           if (tem.isValid ())
+             {
+               /* Otherwise, clean up expired items in its document
+                  ID cache.  */
+               pruneCache1 (tem.children.values ());
+               continue;
+             }
+
+           iter.remove ();
+         }
+
+       statIter = toplevel.statCache.values ().iterator ();
+
+       while (statIter.hasNext ())
+         {
+           /* Get the cache entry.  */
+           stat = statIter.next ();
+
+           /* If it's not valid anymore, remove it.  Iterating over a
+              collection whose contents are being removed is
+              undefined unless the removal is performed using the
+              iterator's own `remove' function, so tem.remove cannot
+              be used here.  */
+
+           if (stat.isValid ())
+             continue;
+
+           statIter.remove ();
+         }
+      }
+
+    postPruneMessage ();
+  }
+
+  /* Cache file information within TOPLEVEL, under the list of
+     children CHILDREN.
+
+     NAME, ID, and TYPE should respectively be the display name of the
+     document within its parent document (the CacheEntry whose
+     `children' field is CHILDREN), its document ID, and its MIME
+     type.
+
+     If ID_ENTRY_EXISTS, don't create a new document ID entry within
+     CHILDREN indexed by NAME.
+
+     Value is the cache entry saved for the document ID.  */
+
+  private CacheEntry
+  cacheChild (CacheToplevel toplevel,
+             HashMap<String, DocIdEntry> children,
+             String name, String id, String type,
+             boolean id_entry_exists)
+  {
+    DocIdEntry idEntry;
+    CacheEntry cacheEntry;
+
+    if (!id_entry_exists)
+      {
+       idEntry = new DocIdEntry ();
+       idEntry.documentId = id;
+       children.put (name, idEntry);
+      }
+
+    cacheEntry = new CacheEntry ();
+    cacheEntry.type = type;
+    toplevel.idCache.put (id, cacheEntry);
+    return cacheEntry;
+  }
+
+  /* Cache file status for DOCUMENTID within TOPLEVEL.  Value is the
+     new cache entry.  CURSOR is the cursor from where to retrieve the
+     file status, in the form of the columns COLUMN_FLAGS,
+     COLUMN_SIZE, COLUMN_MIME_TYPE and COLUMN_LAST_MODIFIED.
+
+     If NO_CACHE, don't cache the file status; just return the
+     entry.  */
+
+  private StatCacheEntry
+  cacheFileStatus (String documentId, CacheToplevel toplevel,
+                  Cursor cursor, boolean no_cache)
+  {
+    StatCacheEntry entry;
+    int flagsIndex, columnIndex, typeIndex;
+    int sizeIndex, mtimeIndex;
+    String type;
+
+    /* Obtain the indices for columns wanted from this cursor.  */
+    flagsIndex = cursor.getColumnIndex (Document.COLUMN_FLAGS);
+    sizeIndex = cursor.getColumnIndex (Document.COLUMN_SIZE);
+    typeIndex = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+    mtimeIndex = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED);
+
+    /* COLUMN_LAST_MODIFIED is allowed to be absent in a
+       conforming documents provider.  */
+    if (flagsIndex < 0 || sizeIndex < 0 || typeIndex < 0)
+      return null;
+
+    /* Get the file status from CURSOR.  */
+    entry = new StatCacheEntry ();
+    entry.flags = cursor.getInt (flagsIndex);
+    type = cursor.getString (typeIndex);
+
+    if (type == null)
+      return null;
+
+    entry.isDirectory = type.equals (Document.MIME_TYPE_DIR);
+
+    if (cursor.isNull (sizeIndex))
+      /* The size is unknown.  */
+      entry.size = -1;
+    else
+      entry.size = cursor.getLong (sizeIndex);
+
+    /* mtimeIndex is potentially unset, since document providers
+       aren't obligated to provide modification times.  */
+
+    if (mtimeIndex >= 0 && !cursor.isNull (mtimeIndex))
+      entry.mtime = cursor.getLong (mtimeIndex);
+
+    /* Finally, add this entry to the cache and return.  */
+    if (!no_cache)
+      toplevel.statCache.put (documentId, entry);
+    return entry;
+  }
+
+  /* Cache the type and as many of the children of the directory
+     designated by DOCUMENTID as possible into TOPLEVEL.
+
+     CURSOR should be a cursor representing an open directory stream,
+     with its projection consisting of at least the display name,
+     document ID and MIME type columns.
+
+     Rewind the position of CURSOR to before its first element after
+     completion.  */
+
+  private void
+  cacheDirectoryFromCursor (CacheToplevel toplevel, String documentId,
+                           Cursor cursor)
+  {
+    CacheEntry entry, constitutent;
+    int nameColumn, idColumn, typeColumn;
+    String id, name, type;
+    DocIdEntry idEntry;
+
+    /* Find the numbers of the columns wanted.  */
+
+    nameColumn
+      = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
+    idColumn
+      = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
+    typeColumn
+      = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+    if (nameColumn < 0 || idColumn < 0 || typeColumn < 0)
+      return;
+
+    entry = new CacheEntry ();
+
+    /* We know this is a directory already.  */
+    entry.type = Document.MIME_TYPE_DIR;
+    toplevel.idCache.put (documentId, entry);
+
+    /* Now, try to cache each of its constituents.  */
+
+    while (cursor.moveToNext ())
+      {
+       try
+         {
+           name = cursor.getString (nameColumn);
+           id = cursor.getString (idColumn);
+           type = cursor.getString (typeColumn);
+
+           if (name == null || id == null || type == null)
+             continue;
+
+           /* First, add the name and ID to ENTRY's map of
+              children.  */
+           idEntry = new DocIdEntry ();
+           idEntry.documentId = id;
+           entry.children.put (id, idEntry);
+
+           /* Cache the file status for ID within TOPELVEL too; if a
+              directory listing is being requested, it's very likely
+              that a series of calls for file status will follow.  */
+
+           cacheFileStatus (id, toplevel, cursor, false);
+
+           /* If this constituent is a directory, don't cache any
+              information about it.  It cannot be cached without
+              knowing its children.  */
+
+           if (type.equals (Document.MIME_TYPE_DIR))
+             continue;
+
+           /* Otherwise, create a new cache entry comprised of its
+              type.  */
+           constitutent = new CacheEntry ();
+           constitutent.type = type;
+           toplevel.idCache.put (documentId, entry);
+         }
+       catch (Exception e)
+         {
+           e.printStackTrace ();
+           continue;
+         }
+      }
+
+    /* Rewind cursor back to the beginning.  */
+    cursor.moveToPosition (-1);
+  }
+
+  /* Post a message to run `pruneCache' every CACHE_PRUNE_TIME
+     seconds.  */
+
+  private void
+  postPruneMessage ()
+  {
+    handler.postDelayed (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         pruneCache ();
+       }
+      }, CACHE_PRUNE_TIME * 1000);
+  }
+
+  /* Invalidate the cache entry denoted by DOCUMENT_ID, within the
+     document tree URI.
+     Call this after deleting a document or directory.
+
+     At the same time, remove the final component within the file name
+     CACHENAME from the cache if it exists.  */
+
+  public void
+  postInvalidateCache (final Uri uri, final String documentId,
+                      final String cacheName)
+  {
+    handler.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         CacheToplevel toplevel;
+         HashMap<String, DocIdEntry> children;
+         String[] components;
+         CacheEntry entry;
+         DocIdEntry idEntry;
+
+         toplevel = getCache (uri);
+         toplevel.idCache.remove (documentId);
+         toplevel.statCache.remove (documentId);
+
+         /* If the parent of CACHENAME is cached, remove it.  */
+
+         children = toplevel.children;
+         components = cacheName.split ("/");
+
+         for (String component : components)
+           {
+             /* Java `split' removes trailing empty matches but not
+                leading or intermediary ones.  */
+             if (component.isEmpty ())
+               continue;
+
+             if (component == components[components.length - 1])
+               {
+                 /* This is the last component, so remove it from
+                    children.  */
+                 children.remove (component);
+                 return;
+               }
+             else
+               {
+                 /* Search for this component within the last level
+                    of the cache.  */
+
+                 idEntry = children.get (component);
+
+                 if (idEntry == null)
+                   /* Not cached, so return.  */
+                   return;
+
+                 entry = toplevel.idCache.get (idEntry.documentId);
+
+                 if (entry == null)
+                   /* Not cached, so return.  */
+                   return;
+
+                 /* Locate the next component within this
+                    directory.  */
+                 children = entry.children;
+               }
+           }
+       }
+      });
+  }
+
+  /* Invalidate the cache entry denoted by DOCUMENT_ID, within the
+     document tree URI.
+     Call this after deleting a document or directory.
+
+     At the same time, remove the child referring to DOCUMENTID from
+     within CACHENAME's cache entry if it exists.  */
+
+  public void
+  postInvalidateCacheDir (final Uri uri, final String documentId,
+                         final String cacheName)
+  {
+    handler.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         CacheToplevel toplevel;
+         HashMap<String, DocIdEntry> children;
+         String[] components;
+         CacheEntry entry;
+         DocIdEntry idEntry;
+         Iterator<DocIdEntry> iter;
+
+         toplevel = getCache (uri);
+         toplevel.idCache.remove (documentId);
+         toplevel.statCache.remove (documentId);
+
+         /* Now remove DOCUMENTID from CACHENAME's cache entry, if
+            any.  */
+
+         children = toplevel.children;
+         components = cacheName.split ("/");
+
+         for (String component : components)
+           {
+             /* Java `split' removes trailing empty matches but not
+                leading or intermediary ones.  */
+             if (component.isEmpty ())
+               continue;
+
+             /* Search for this component within the last level
+                of the cache.  */
+
+             idEntry = children.get (component);
+
+             if (idEntry == null)
+               /* Not cached, so return.  */
+               return;
+
+             entry = toplevel.idCache.get (idEntry.documentId);
+
+             if (entry == null)
+               /* Not cached, so return.  */
+               return;
+
+             /* Locate the next component within this
+                directory.  */
+             children = entry.children;
+           }
+
+         iter = children.values ().iterator ();
+         while (iter.hasNext ())
+           {
+             idEntry = iter.next ();
+
+             if (idEntry.documentId.equals (documentId))
+               {
+                 iter.remove ();
+                 break;
+               }
+           }
+       }
+      });
+  }
+
+  /* Invalidate the file status cache entry for DOCUMENTID within URI.
+     Call this when the contents of a file (i.e. the constituents of a
+     directory file) may have changed, but the document's display name
+     has not.  */
+
+  public void
+  postInvalidateStat (final Uri uri, final String documentId)
+  {
+    handler.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         CacheToplevel toplevel;
+
+         toplevel = getCache (uri);
+         toplevel.statCache.remove (documentId);
+       }
+      });
+  }
+
+
+
+  /* ``Prototypes'' for nested functions that are run within the SAF
+     thread and accepts a cancellation signal.  They differ in their
+     return types.  */
+
+  private abstract class SafIntFunction
+  {
+    /* The ``throws Throwable'' here is a Java idiosyncracy that tells
+       the compiler to allow arbitrary error objects to be signaled
+       from within this function.
+
+       Later, runIntFunction will try to re-throw any error object
+       generated by this function in the Emacs thread, using a trick
+       to avoid the compiler requirement to expressly declare that an
+       error (and which types of errors) will be signaled.  */
+
+    public abstract int runInt (CancellationSignal signal)
+      throws Throwable;
+  };
+
+  private abstract class SafObjectFunction
+  {
+    /* The ``throws Throwable'' here is a Java idiosyncracy that tells
+       the compiler to allow arbitrary error objects to be signaled
+       from within this function.
+
+       Later, runObjectFunction will try to re-throw any error object
+       generated by this function in the Emacs thread, using a trick
+       to avoid the compiler requirement to expressly declare that an
+       error (and which types of errors) will be signaled.  */
+
+    public abstract Object runObject (CancellationSignal signal)
+      throws Throwable;
+  };
+
+
+
+  /* Functions that run cancel-able queries.  These functions are
+     internally run within the SAF thread.  */
+
+  /* Throw the specified EXCEPTION.  The type template T is erased by
+     the compiler before the object is compiled, so the compiled code
+     simply throws EXCEPTION without the cast being verified.
+
+     T should be RuntimeException to obtain the desired effect of
+     throwing an exception without a compiler check.  */
+
+  @SuppressWarnings("unchecked")
+  private static <T extends Throwable> void
+  throwException (Throwable exception)
+    throws T
+  {
+    throw (T) exception;
+  }
+
+  /* Run the given function (or rather, its `runInt' field) within the
+     SAF thread, waiting for it to complete.
+
+     If async input arrives in the meantime and sets Vquit_flag,
+     signal the cancellation signal supplied to that function.
+
+     Rethrow any exception thrown from that function, and return its
+     value otherwise.  */
+
+  private int
+  runIntFunction (final SafIntFunction function)
+  {
+    final EmacsHolder<Object> result;
+    final CancellationSignal signal;
+    Throwable throwable;
+
+    result = new EmacsHolder<Object> ();
+    signal = new CancellationSignal ();
+
+    handler.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         try
+           {
+             result.thing
+               = Integer.valueOf (function.runInt (signal));
+           }
+         catch (Throwable throwable)
+           {
+             result.thing = throwable;
+           }
+
+         EmacsNative.safPostRequest ();
+       }
+      });
+
+    if (EmacsNative.safSyncAndReadInput () != 0)
+      {
+       signal.cancel ();
+
+       /* Now wait for the function to finish.  Either the signal has
+          arrived after the query took place, in which case it will
+          finish normally, or an OperationCanceledException will be
+          thrown.  */
+
+       EmacsNative.safSync ();
+      }
+
+    if (result.thing instanceof Throwable)
+      {
+       throwable = (Throwable) result.thing;
+       EmacsSafThread.<RuntimeException>throwException (throwable);
+      }
+
+    return (Integer) result.thing;
+  }
+
+  /* Run the given function (or rather, its `runObject' field) within
+     the SAF thread, waiting for it to complete.
+
+     If async input arrives in the meantime and sets Vquit_flag,
+     signal the cancellation signal supplied to that function.
+
+     Rethrow any exception thrown from that function, and return its
+     value otherwise.  */
+
+  private Object
+  runObjectFunction (final SafObjectFunction function)
+  {
+    final EmacsHolder<Object> result;
+    final CancellationSignal signal;
+    Throwable throwable;
+
+    result = new EmacsHolder<Object> ();
+    signal = new CancellationSignal ();
+
+    handler.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         try
+           {
+             result.thing = function.runObject (signal);
+           }
+         catch (Throwable throwable)
+           {
+             result.thing = throwable;
+           }
+
+         EmacsNative.safPostRequest ();
+       }
+      });
+
+    if (EmacsNative.safSyncAndReadInput () != 0)
+      {
+       signal.cancel ();
+
+       /* Now wait for the function to finish.  Either the signal has
+          arrived after the query took place, in which case it will
+          finish normally, or an OperationCanceledException will be
+          thrown.  */
+
+       EmacsNative.safSync ();
+      }
+
+    if (result.thing instanceof Throwable)
+      {
+       throwable = (Throwable) result.thing;
+       EmacsSafThread.<RuntimeException>throwException (throwable);
+      }
+
+    return result.thing;
+  }
+
+  /* The crux of `documentIdFromName1', run within the SAF thread.
+     SIGNAL should be a cancellation signal run upon quitting.  */
+
+  private int
+  documentIdFromName1 (String tree_uri, String name,
+                      String[] id_return, CancellationSignal signal)
+  {
+    Uri uri, treeUri;
+    String id, type, newId, newType;
+    String[] components, projection;
+    Cursor cursor;
+    int nameColumn, idColumn, typeColumn;
+    CacheToplevel toplevel;
+    DocIdEntry idEntry;
+    HashMap<String, DocIdEntry> children, next;
+    CacheEntry cache;
+
+    projection = new String[] {
+      Document.COLUMN_DISPLAY_NAME,
+      Document.COLUMN_DOCUMENT_ID,
+      Document.COLUMN_MIME_TYPE,
+    };
+
+    /* Parse the URI identifying the tree first.  */
+    uri = Uri.parse (tree_uri);
+
+    /* Now, split NAME into its individual components.  */
+    components = name.split ("/");
+
+    /* Set id and type to the value at the root of the tree.  */
+    type = id = null;
+    cursor = null;
+
+    /* Obtain the top level of this cache.  */
+    toplevel = getCache (uri);
+
+    /* Set the current map of children to this top level.  */
+    children = toplevel.children;
+
+    /* For each component... */
+
+    try
+      {
+       for (String component : components)
+         {
+           /* Java split doesn't behave very much like strtok when it
+              comes to trailing and leading delimiters...  */
+           if (component.isEmpty ())
+             continue;
+
+           /* Search for component within the currently cached list
+              of children.  */
+
+           idEntry = children.get (component);
+
+           if (idEntry != null)
+             {
+               /* The document ID is known.  Now find the
+                  corresponding document ID cache.  */
+
+               cache = toplevel.idCache.get (idEntry.documentId);
+
+               /* Fetch just the information for this document.  */
+
+               if (cache == null)
+                 cache = idEntry.getCacheEntry (resolver, uri, toplevel,
+                                                signal);
+
+               if (cache == null)
+                 {
+                   /* File status matching idEntry could not be
+                      obtained.  Treat this as if the file does not
+                      exist.  */
+
+                   children.remove (component);
+
+                   if (id == null)
+                     id = DocumentsContract.getTreeDocumentId (uri);
+
+                   id_return[0] = id;
+
+                   if ((type == null
+                        || type.equals (Document.MIME_TYPE_DIR))
+                       /* ... and type and id currently represent the
+                          penultimate component.  */
+                       && component == components[components.length  - 1])
+                     return -2;
+
+                   return -1;
+                 }
+
+               /* Otherwise, use the cached information.  */
+               id = idEntry.documentId;
+               type = cache.type;
+               children = cache.children;
+               continue;
+             }
+
+           /* Create the tree URI for URI from ID if it exists, or
+              the root otherwise.  */
+
+           if (id == null)
+             id = DocumentsContract.getTreeDocumentId (uri);
+
+           treeUri
+             = DocumentsContract.buildChildDocumentsUriUsingTree (uri, id);
+
+           /* Look for a file in this directory by the name of
+              component.  */
+
+           cursor = resolver.query (treeUri, projection,
+                                    (Document.COLUMN_DISPLAY_NAME
+                                     + " = ?"),
+                                    new String[] { component, },
+                                    null, signal);
+
+           if (cursor == null)
+             return -1;
+
+           /* Find the column numbers for each of the columns that
+              are wanted.  */
+
+           nameColumn
+             = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
+           idColumn
+             = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
+           typeColumn
+             = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+           if (nameColumn < 0 || idColumn < 0 || typeColumn < 0)
+             return -1;
+
+           next = null;
+
+           while (true)
+             {
+               /* Even though the query selects for a specific
+                  display name, some content providers nevertheless
+                  return every file within the directory.  */
+
+               if (!cursor.moveToNext ())
+                 {
+                   /* If a component has been found, break out of the
+                      loop.  */
+
+                   if (next != null)
+                     break;
+
+                   /* If the last component considered is a
+                      directory... */
+                   if ((type == null
+                        || type.equals (Document.MIME_TYPE_DIR))
+                       /* ... and type and id currently represent the
+                          penultimate component.  */
+                       && component == components[components.length  - 1])
+                     {
+                       /* The cursor is empty.  In this case, return
+                          -2 and the current document ID (belonging
+                          to the previous component) in
+                          ID_RETURN.  */
+
+                       id_return[0] = id;
+
+                       /* But return -1 on the off chance that id is
+                          null.  */
+
+                       if (id == null)
+                         return -1;
+
+                       return -2;
+                     }
+
+                   /* The last component found is not a directory, so
+                      return -1.  */
+                   return -1;
+                 }
+
+               /* So move CURSOR to a row with the right display
+                  name.  */
+
+               name = cursor.getString (nameColumn);
+               newId = cursor.getString (idColumn);
+               newType = cursor.getString (typeColumn);
+
+               /* Any of the three variables above may be NULL if the
+                  column data is of the wrong type depending on how
+                  the Cursor returned is implemented.  */
+
+               if (name == null || newId == null || newType == null)
+                 return -1;
+
+               /* Cache this name, even if it isn't the document
+                  that's being searched for.  */
+
+               cache = cacheChild (toplevel, children, name,
+                                   newId, newType,
+                                   idEntry != null);
+
+               /* Record the desired component once it is located,
+                  but continue reading and caching items from the
+                  cursor.  */
+
+               if (name.equals (component))
+                 {
+                   id = newId;
+                   next = cache.children;
+                   type = newType;
+                 }
+             }
+
+           children = next;
+
+           /* Now close the cursor.  */
+           cursor.close ();
+           cursor = null;
+
+           /* ID may have become NULL if the data is in an invalid
+              format.  */
+           if (id == null)
+             return -1;
+         }
+      }
+    finally
+      {
+       /* If an error is thrown within the block above, let
+          android_saf_exception_check handle it, but make sure the
+          cursor is closed.  */
+
+       if (cursor != null)
+         cursor.close ();
+      }
+
+    /* Here, id is either NULL (meaning the same as TREE_URI), and
+       type is either NULL (in which case id should also be NULL) or
+       the MIME type of the file.  */
+
+    /* First return the ID.  */
+
+    if (id == null)
+      id_return[0] = DocumentsContract.getTreeDocumentId (uri);
+    else
+      id_return[0] = id;
+
+    /* Next, return whether or not this is a directory.  */
+    if (type == null || type.equals (Document.MIME_TYPE_DIR))
+      return 1;
+
+    return 0;
+  }
+
+  /* Find the document ID of the file within TREE_URI designated by
+     NAME.
+
+     NAME is a ``file name'' comprised of the display names of
+     individual files.  Each constituent component prior to the last
+     must name a directory file within TREE_URI.
+
+     Upon success, return 0 or 1 (contingent upon whether or not the
+     last component within NAME is a directory) and place the document
+     ID of the named file in ID_RETURN[0].
+
+     If the designated file can't be located, but each component of
+     NAME up to the last component can and is a directory, return -2
+     and the ID of the last component located in ID_RETURN[0].
+
+     If the designated file can't be located, return -1, or signal one
+     of OperationCanceledException, SecurityException,
+     FileNotFoundException, or UnsupportedOperationException.  */
+
+  public int
+  documentIdFromName (final String tree_uri, final String name,
+                     final String[] id_return)
+  {
+    return runIntFunction (new SafIntFunction () {
+       @Override
+       public int
+       runInt (CancellationSignal signal)
+       {
+         return documentIdFromName1 (tree_uri, name, id_return,
+                                     signal);
+       }
+      });
+  }
+
+  /* The bulk of `statDocument'.  SIGNAL should be a cancelation
+     signal.  */
+
+  private long[]
+  statDocument1 (String uri, String documentId,
+                CancellationSignal signal, boolean noCache)
+  {
+    Uri uriObject, tree;
+    String[] projection;
+    long[] stat;
+    Cursor cursor;
+    CacheToplevel toplevel;
+    StatCacheEntry cache;
+
+    tree = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (tree);
+
+    /* Create a document URI representing DOCUMENTID within URI's
+       authority.  */
+
+    uriObject
+      = DocumentsContract.buildDocumentUriUsingTree (tree, documentId);
+
+    /* See if the file status cache currently contains this
+       document.  */
+
+    toplevel = getCache (tree);
+    cache = toplevel.statCache.get (documentId);
+
+    if (cache == null || !cache.isValid ())
+      {
+       /* Stat this document and enter its information into the
+          cache.  */
+
+       projection = new String[] {
+         Document.COLUMN_FLAGS,
+         Document.COLUMN_LAST_MODIFIED,
+         Document.COLUMN_MIME_TYPE,
+         Document.COLUMN_SIZE,
+       };
+
+       cursor = resolver.query (uriObject, projection, null,
+                                null, null, signal);
+
+       if (cursor == null)
+         return null;
+
+       try
+         {
+           if (!cursor.moveToFirst ())
+             return null;
+
+           cache = cacheFileStatus (documentId, toplevel, cursor,
+                                    noCache);
+         }
+       finally
+         {
+           cursor.close ();
+         }
+
+       /* If cache is still null, return null.  */
+
+       if (cache == null)
+         return null;
+      }
+
+    /* Create the array of file status and populate it with the
+       information within cache.  */
+    stat = new long[3];
+
+    stat[0] |= S_IRUSR;
+    if ((cache.flags & Document.FLAG_SUPPORTS_WRITE) != 0)
+      stat[0] |= S_IWUSR;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+       && (cache.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0)
+      stat[0] |= S_IFCHR;
+
+    stat[1] = cache.size;
+
+    /* Check if this is a directory file.  */
+    if (cache.isDirectory
+       /* Files shouldn't be specials and directories at the same
+          time, but Android doesn't forbid document providers
+          from returning this information.  */
+       && (stat[0] & S_IFCHR) == 0)
+      {
+       /* Since FLAG_SUPPORTS_WRITE doesn't apply to directories,
+          just assume they're writable.  */
+       stat[0] |= S_IFDIR | S_IWUSR | S_IXUSR;
+
+       /* Directory files cannot be modified if
+          FLAG_DIR_SUPPORTS_CREATE is not set.  */
+
+       if ((cache.flags & Document.FLAG_DIR_SUPPORTS_CREATE) == 0)
+         stat[0] &= ~S_IWUSR;
+      }
+
+    /* If this file is neither a character special nor a
+       directory, indicate that it's a regular file.  */
+
+    if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0)
+      stat[0] |= S_IFREG;
+
+    stat[2] = cache.mtime;
+    return stat;
+  }
+
+  /* Return file status for the document designated by the given
+     DOCUMENTID and tree URI.  If DOCUMENTID is NULL, use the document
+     ID in URI itself.
+
+     Value is null upon failure, or an array of longs [MODE, SIZE,
+     MTIM] upon success, where MODE contains the file type and access
+     modes of the file as in `struct stat', SIZE is the size of the
+     file in BYTES or -1 if not known, and MTIM is the time of the
+     last modification to this file in milliseconds since 00:00,
+     January 1st, 1970.
+
+     If NOCACHE, refrain from placing the file status within the
+     status cache.
+
+     OperationCanceledException and other typical exceptions may be
+     signaled upon receiving async input or other errors.  */
+
+  public long[]
+  statDocument (final String uri, final String documentId,
+               final boolean noCache)
+  {
+    return (long[]) runObjectFunction (new SafObjectFunction () {
+       @Override
+       public Object
+       runObject (CancellationSignal signal)
+       {
+         return statDocument1 (uri, documentId, signal, noCache);
+       }
+      });
+  }
+
+  /* The bulk of `accessDocument'.  SIGNAL should be a cancellation
+     signal.  */
+
+  private int
+  accessDocument1 (String uri, String documentId, boolean writable,
+                  CancellationSignal signal)
+  {
+    Uri uriObject;
+    String[] projection;
+    int tem, index;
+    String tem1;
+    Cursor cursor;
+    CacheToplevel toplevel;
+    CacheEntry entry;
+
+    uriObject = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (uriObject);
+
+    /* If WRITABLE is false and the document ID is cached, use its
+       cached value instead.  This speeds up
+       `directory-files-with-attributes' a little.  */
+
+    if (!writable)
+      {
+       toplevel = getCache (uriObject);
+       entry = toplevel.idCache.get (documentId);
+
+       if (entry != null)
+         return 0;
+      }
+
+    /* Create a document URI representing DOCUMENTID within URI's
+       authority.  */
+
+    uriObject
+      = DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId);
+
+    /* Now stat this document.  */
+
+    projection = new String[] {
+      Document.COLUMN_FLAGS,
+      Document.COLUMN_MIME_TYPE,
+    };
+
+    cursor = resolver.query (uriObject, projection, null,
+                            null, null, signal);
+
+    if (cursor == null)
+      return -1;
+
+    try
+      {
+       if (!cursor.moveToFirst ())
+         return -1;
+
+       if (!writable)
+         return 0;
+
+       index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+       if (index < 0)
+         return -3;
+
+       /* Get the type of this file to check if it's a directory.  */
+       tem1 = cursor.getString (index);
+
+       /* Check if this is a directory file.  */
+       if (tem1.equals (Document.MIME_TYPE_DIR))
+         {
+           /* If so, don't check for FLAG_SUPPORTS_WRITE.
+              Check for FLAG_DIR_SUPPORTS_CREATE instead.  */
+
+           if (!writable)
+             return 0;
+
+           index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
+           if (index < 0)
+             return -3;
+
+           tem = cursor.getInt (index);
+           if ((tem & Document.FLAG_DIR_SUPPORTS_CREATE) == 0)
+             return -3;
+
+           return 0;
+         }
+
+       index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
+       if (index < 0)
+         return -3;
+
+       tem = cursor.getInt (index);
+       if (writable && (tem & Document.FLAG_SUPPORTS_WRITE) == 0)
+         return -3;
+      }
+    finally
+      {
+       /* Close the cursor if an exception occurs.  */
+       cursor.close ();
+      }
+
+    return 0;
+  }
+
+  /* Find out whether Emacs has access to the document designated by
+     the specified DOCUMENTID within the tree URI.  If DOCUMENTID is
+     NULL, use the document ID in URI itself.
+
+     If WRITABLE, also check that the file is writable, which is true
+     if it is either a directory or its flags contains
+     FLAG_SUPPORTS_WRITE.
+
+     Value is 0 if the file is accessible, and one of the following if
+     not:
+
+     -1, if the file does not exist.
+     -2, if WRITABLE and the file is not writable.
+     -3, upon any other error.
+
+     In addition, arbitrary runtime exceptions (such as
+     SecurityException or UnsupportedOperationException) may be
+     thrown.  */
+
+  public int
+  accessDocument (final String uri, final String documentId,
+                 final boolean writable)
+  {
+    return runIntFunction (new SafIntFunction () {
+       @Override
+       public int
+       runInt (CancellationSignal signal)
+       {
+         return accessDocument1 (uri, documentId, writable,
+                                 signal);
+       }
+      });
+  }
+
+  /* The crux of openDocumentDirectory.  SIGNAL must be a cancellation
+     signal.  */
+
+  private Cursor
+  openDocumentDirectory1 (String uri, String documentId,
+                         CancellationSignal signal)
+  {
+    Uri uriObject, tree;
+    Cursor cursor;
+    String projection[];
+    CacheToplevel toplevel;
+
+    tree = uriObject = Uri.parse (uri);
+
+    /* If documentId is not set, use the document ID of the tree URI
+       itself.  */
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (uriObject);
+
+    /* Build a URI representing each directory entry within
+       DOCUMENTID.  */
+
+    uriObject
+      = DocumentsContract.buildChildDocumentsUriUsingTree (uriObject,
+                                                          documentId);
+
+    projection = new String [] {
+      Document.COLUMN_DISPLAY_NAME,
+      Document.COLUMN_DOCUMENT_ID,
+      Document.COLUMN_MIME_TYPE,
+      Document.COLUMN_FLAGS,
+      Document.COLUMN_LAST_MODIFIED,
+      Document.COLUMN_SIZE,
+    };
+
+    cursor = resolver.query (uriObject, projection, null, null,
+                            null, signal);
+
+    /* Create a new cache entry tied to this document ID.  */
+
+    if (cursor != null)
+      {
+       toplevel = getCache (tree);
+       cacheDirectoryFromCursor (toplevel, documentId,
+                                 cursor);
+      }
+
+    /* Return the cursor.  */
+    return cursor;
+  }
+
+  /* Open a cursor representing each entry within the directory
+     designated by the specified DOCUMENTID within the tree URI.
+
+     If DOCUMENTID is NULL, use the document ID within URI itself.
+     Value is NULL upon failure.
+
+     In addition, arbitrary runtime exceptions (such as
+     SecurityException or UnsupportedOperationException) may be
+     thrown.  */
+
+  public Cursor
+  openDocumentDirectory (final String uri, final String documentId)
+  {
+    return (Cursor) runObjectFunction (new SafObjectFunction () {
+       @Override
+       public Object
+       runObject (CancellationSignal signal)
+       {
+         return openDocumentDirectory1 (uri, documentId, signal);
+       }
+      });
+  }
+
+  /* The crux of `openDocument'.  SIGNAL must be a cancellation
+     signal.  */
+
+  public ParcelFileDescriptor
+  openDocument1 (String uri, String documentId, boolean read,
+                boolean write, boolean truncate,
+                CancellationSignal signal)
+    throws Throwable
+  {
+    Uri treeUri, documentUri;
+    String mode;
+    ParcelFileDescriptor fileDescriptor;
+    CacheToplevel toplevel;
+
+    treeUri = Uri.parse (uri);
+
+    /* documentId must be set for this request, since it doesn't make
+       sense to ``open'' the root of the directory tree.  */
+
+    documentUri
+      = DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId);
+
+    /* Select the mode used to open the file.  */
+
+    if (write)
+      {
+       if (read)
+         {
+           if (truncate)
+             mode = "rwt";
+           else
+             mode = "rw";
+         }
+       else
+         /* Set mode to w when WRITE && !READ, disregarding TRUNCATE.
+            In contradiction with the ContentResolver documentation,
+            document providers seem to truncate files whenever w is
+            specified, at least superficially.  (But see below.)  */
+         mode = "w";
+      }
+    else
+      mode = "r";
+
+    fileDescriptor
+      = resolver.openFileDescriptor (documentUri, mode,
+                                    signal);
+
+    /* If a writable on-disk file descriptor is requested and TRUNCATE
+       is set, then probe the file descriptor to detect if it is
+       actually readable.  If not, close this file descriptor and
+       reopen it with MODE set to rw; some document providers granting
+       access to Samba shares don't implement rwt, but these document
+       providers invariably truncate the file opened even when the
+       mode is merely w.
+
+       This may be ascribed to a mix-up in Android's documentation
+       regardin DocumentsProvider: the `openDocument' function is only
+       documented to accept r or rw, whereas the default
+       implementation of the `openFile' function (which documents rwt)
+       delegates to `openDocument'.  */
+
+    if (read && write && truncate && fileDescriptor != null
+       && !EmacsNative.ftruncate (fileDescriptor.getFd ()))
+      {
+       try
+         {
+           fileDescriptor.closeWithError ("File descriptor requested"
+                                          + " is not writable");
+         }
+       catch (IOException e)
+         {
+           Log.w (TAG, "Leaking unclosed file descriptor " + e);
+         }
+
+       fileDescriptor
+         = resolver.openFileDescriptor (documentUri, "rw", signal);
+
+       /* Try to truncate fileDescriptor just to stay on the safe
+          side.  */
+       if (fileDescriptor != null)
+         EmacsNative.ftruncate (fileDescriptor.getFd ());
+      }
+    else if (!read && write && truncate && fileDescriptor != null)
+      /* Moreover, document providers that return actual seekable
+        files characteristically neglect to truncate the file
+        returned when the access mode is merely w, so attempt to
+        truncate it by hand.  */
+      EmacsNative.ftruncate (fileDescriptor.getFd ());
+
+    /* Every time a document is opened, remove it from the file status
+       cache.  */
+    toplevel = getCache (treeUri);
+    toplevel.statCache.remove (documentId);
+
+    return fileDescriptor;
+  }
+
+  /* Open a file descriptor for a file document designated by
+     DOCUMENTID within the document tree identified by URI.  If
+     TRUNCATE and the document already exists, truncate its contents
+     before returning.
+
+     If READ && WRITE, open the file under either the `rw' or `rwt'
+     access mode, which implies that the value must be a seekable
+     on-disk file.  If WRITE && !READ or TRUNC && WRITE, also truncate
+     the file after it is opened.
+
+     If only READ or WRITE is set, value may be a non-seekable FIFO or
+     one end of a socket pair.
+
+     Value is NULL upon failure or a parcel file descriptor upon
+     success.  Call `ParcelFileDescriptor.close' on this file
+     descriptor instead of using the `close' system call.
+
+     FileNotFoundException and/or SecurityException and/or
+     UnsupportedOperationException and/or OperationCanceledException
+     may be thrown upon failure.  */
+
+  public ParcelFileDescriptor
+  openDocument (final String uri, final String documentId,
+               final boolean read, final boolean write,
+               final boolean truncate)
+  {
+    Object tem;
+
+    tem = runObjectFunction (new SafObjectFunction () {
+       @Override
+       public Object
+       runObject (CancellationSignal signal)
+         throws Throwable
+       {
+         return openDocument1 (uri, documentId, read,
+                               write, truncate, signal);
+       }
+      });
+
+    return (ParcelFileDescriptor) tem;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java 
b/java/org/gnu/emacs/EmacsSdk11Clipboard.java
new file mode 100644
index 00000000000..b34753922b8
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java
@@ -0,0 +1,284 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+
+import android.content.res.AssetFileDescriptor;
+
+import android.net.Uri;
+
+import android.util.Log;
+
+import android.os.Build;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+/* This class implements EmacsClipboard for Android 3.0 and later
+   systems.  */
+
+public final class EmacsSdk11Clipboard extends EmacsClipboard
+  implements ClipboardManager.OnPrimaryClipChangedListener
+{
+  private static final String TAG = "EmacsSdk11Clipboard";
+  private ClipboardManager manager;
+  private boolean ownsClipboard;
+  private int clipboardChangedCount;
+  private int monitoredClipboardChangedCount;
+  private ContentResolver resolver;
+
+  public
+  EmacsSdk11Clipboard ()
+  {
+    manager = EmacsService.SERVICE.getClipboardManager ();
+
+    /* The system forbids Emacs from reading clipboard data in the
+       background under Android 10 or later.  */
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
+      manager.addPrimaryClipChangedListener (this);
+
+    /* Now obtain the content resolver used to open file
+       descriptors.  */
+
+    resolver = EmacsService.SERVICE.getContentResolver ();
+  }
+
+  @Override
+  public synchronized void
+  onPrimaryClipChanged ()
+  {
+    /* Increment monitoredClipboardChangeCount.  If it is now greater
+       than clipboardChangedCount, then Emacs no longer owns the
+       clipboard.  */
+    monitoredClipboardChangedCount++;
+
+    if (monitoredClipboardChangedCount > clipboardChangedCount)
+      {
+       ownsClipboard = false;
+
+       /* Reset both values back to 0.  */
+       monitoredClipboardChangedCount = 0;
+       clipboardChangedCount = 0;
+      }
+  }
+
+  /* Set the clipboard text to CLIPBOARD, a string in UTF-8
+     encoding.  */
+
+  @Override
+  public synchronized void
+  setClipboard (byte[] bytes)
+  {
+    ClipData data;
+    String string;
+
+    try
+      {
+       string = new String (bytes, "UTF-8");
+       data = ClipData.newPlainText ("Emacs", string);
+       manager.setPrimaryClip (data);
+       ownsClipboard = true;
+
+       /* onPrimaryClipChanged will be called again.  Use this
+          variable to keep track of how many times the clipboard has
+          been changed.  */
+       ++clipboardChangedCount;
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       Log.w (TAG, "setClipboard: " + exception);
+      }
+  }
+
+  /* Return whether or not Emacs owns the clipboard.  Value is 1 if
+     Emacs does, 0 if Emacs does not, and -1 if that information is
+     unavailable.  */
+
+  @Override
+  public synchronized int
+  ownsClipboard ()
+  {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+      return -1;
+
+    return ownsClipboard ? 1 : 0;
+  }
+
+  /* Return whether or not clipboard content currently exists.  */
+
+  @Override
+  public boolean
+  clipboardExists ()
+  {
+    return manager.hasPrimaryClip ();
+  }
+
+  /* Return the current content of the clipboard, as plain text, or
+     NULL if no content is available.  */
+
+  @Override
+  public byte[]
+  getClipboard ()
+  {
+    ClipData clip;
+    CharSequence text;
+    Context context;
+
+    clip = manager.getPrimaryClip ();
+
+    if (clip == null || clip.getItemCount () < 1)
+      return null;
+
+    context = EmacsService.SERVICE;
+
+    try
+      {
+       text = clip.getItemAt (0).coerceToText (context);
+       return text.toString ().getBytes ("UTF-8");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       Log.w (TAG, "getClipboard: " + exception);
+      }
+
+    return null;
+  }
+
+  /* Return an array of targets currently provided by the
+     clipboard, or NULL if there are none.  */
+
+  @Override
+  public byte[][]
+  getClipboardTargets ()
+  {
+    ClipData clip;
+    ClipDescription description;
+    byte[][] typeArray;
+    int i;
+
+    /* N.B. that Android calls the clipboard the ``primary clip''; it
+       is not related to the X primary selection.  */
+    clip = manager.getPrimaryClip ();
+
+    if (clip == null)
+      return null;
+
+    description = clip.getDescription ();
+    i = description.getMimeTypeCount ();
+    typeArray = new byte[i][i];
+
+    try
+      {
+       for (i = 0; i < description.getMimeTypeCount (); ++i)
+         typeArray[i] = description.getMimeType (i).getBytes ("UTF-8");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       return null;
+      }
+
+    return typeArray;
+  }
+
+  /* Return the clipboard data for the given target, or NULL if it
+     does not exist.
+
+     Value is normally an array of three longs: the file descriptor,
+     the start offset of the data, and its length; length may be
+     AssetFileDescriptor.UNKOWN_LENGTH, meaning that the data extends
+     from that offset to the end of the file.
+
+     Do not use this function to open text targets; use `getClipboard'
+     for that instead, as it will handle selection data consisting
+     solely of a URI.  */
+
+  @Override
+  public long[]
+  getClipboardData (byte[] target)
+  {
+    ClipData data;
+    String mimeType;
+    int fd;
+    AssetFileDescriptor assetFd;
+    Uri uri;
+    long[] value;
+
+    /* Decode the target given by Emacs.  */
+    try
+      {
+       mimeType = new String (target, "UTF-8");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       return null;
+      }
+
+    /* Now obtain the clipboard data and the data corresponding to
+       that MIME type.  */
+
+    data = manager.getPrimaryClip ();
+
+    if (data == null || data.getItemCount () < 1)
+      return null;
+
+    try
+      {
+       uri = data.getItemAt (0).getUri ();
+
+       if (uri == null)
+         return null;
+
+       /* Now open the file descriptor.  */
+       assetFd = resolver.openTypedAssetFileDescriptor (uri, mimeType,
+                                                        null);
+
+       /* Duplicate the file descriptor.  */
+       fd = assetFd.getParcelFileDescriptor ().getFd ();
+       fd = EmacsNative.dup (fd);
+
+       /* Return the relevant information.  */
+       value = new long[] { fd, assetFd.getStartOffset (),
+                            assetFd.getLength (), };
+
+       /* Close the original offset.  */
+       assetFd.close ();
+      }
+    catch (FileNotFoundException e)
+      {
+       return null;
+      }
+    catch (IOException e)
+      {
+       return null;
+      }
+
+    /* Don't return value if the file descriptor couldn't be
+       created.  */
+
+    return fd != -1 ? value : null;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java 
b/java/org/gnu/emacs/EmacsSdk23FontDriver.java
new file mode 100644
index 00000000000..aaba8dbd166
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java
@@ -0,0 +1,114 @@
+/* Font backend for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+public final class EmacsSdk23FontDriver extends EmacsSdk7FontDriver
+{
+  private void
+  textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics,
+               Paint paint, Rect bounds)
+  {
+    char[] text;
+
+    text = new char[2];
+    text[0] = (char) code;
+    text[1] = 'c';
+
+    paint.getTextBounds (text, 0, 1, bounds);
+
+    metrics.lbearing = (short) bounds.left;
+    metrics.rbearing = (short) bounds.right;
+    metrics.ascent = (short) -bounds.top;
+    metrics.descent = (short) bounds.bottom;
+    metrics.width
+      = (short) paint.getRunAdvance (text, 0, 1, 0, 1, false, 1);
+  }
+
+  @Override
+  public void
+  textExtents (FontObject font, int code[], FontMetrics fontMetrics)
+  {
+    int i;
+    Paint paintCache;
+    Rect boundsCache;
+    Sdk7FontObject fontObject;
+    char[] text;
+    float width;
+
+    fontObject = (Sdk7FontObject) font;
+    paintCache = fontObject.typeface.typefacePaint;
+    paintCache.setTextSize (fontObject.pixelSize);
+    boundsCache = new Rect ();
+
+    if (code.length == 0)
+      {
+       fontMetrics.lbearing = 0;
+       fontMetrics.rbearing = 0;
+       fontMetrics.ascent = 0;
+       fontMetrics.descent = 0;
+       fontMetrics.width = 0;
+      }
+    else if (code.length == 1)
+      textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics,
+                   paintCache, boundsCache);
+    else
+      {
+       text = new char[code.length + 1];
+
+       for (i = 0; i < code.length; ++i)
+         text[i] = (char) code[i];
+
+       text[code.length] = 'c';
+
+       paintCache.getTextBounds (text, 0, code.length,
+                                 boundsCache);
+       width = paintCache.getRunAdvance (text, 0, code.length, 0,
+                                         code.length,
+                                         false, code.length);
+
+       fontMetrics.lbearing = (short) boundsCache.left;
+       fontMetrics.rbearing = (short) boundsCache.right;
+       fontMetrics.ascent = (short) -boundsCache.top;
+       fontMetrics.descent = (short) boundsCache.bottom;
+       fontMetrics.width = (short) width;
+      }
+  }
+
+  @Override
+  public int
+  hasChar (FontSpec font, char charCode)
+  {
+    Sdk7FontObject fontObject;
+    Paint paint;
+
+    if (font instanceof Sdk7FontObject)
+      {
+       fontObject = (Sdk7FontObject) font;
+       paint = fontObject.typeface.typefacePaint;
+      }
+    else
+      paint = ((Sdk7FontEntity) font).typeface.typefacePaint;
+
+    return paint.hasGlyph (String.valueOf (charCode)) ? 1 : 0;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java 
b/java/org/gnu/emacs/EmacsSdk7FontDriver.java
new file mode 100644
index 00000000000..97969585d16
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java
@@ -0,0 +1,539 @@
+/* Font backend for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.io.File;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Canvas;
+
+import android.util.Log;
+
+public class EmacsSdk7FontDriver extends EmacsFontDriver
+{
+  private static final String TOFU_STRING = "\uDB3F\uDFFD";
+  private static final String EM_STRING   = "m";
+  private static final String TAG        = "EmacsSdk7FontDriver";
+
+  protected static final class Sdk7Typeface
+  {
+    /* The typeface and paint.  */
+    public Typeface typeface;
+    public Paint typefacePaint;
+    public String familyName;
+    public int slant, width, weight, spacing;
+
+    public
+    Sdk7Typeface (String fileName, Typeface typeface)
+    {
+      String style, testString;
+      int index, measured, i;
+      float[] widths;
+
+      slant = NORMAL;
+      weight = REGULAR;
+      width = UNSPECIFIED;
+      spacing = PROPORTIONAL;
+
+      this.typeface = typeface;
+
+      typefacePaint = new Paint ();
+      typefacePaint.setAntiAlias (true);
+      typefacePaint.setTypeface (typeface);
+
+      /* For the calls to measureText below.  */
+      typefacePaint.setTextSize (10.0f);
+
+      /* Parse the file name into some useful data.  First, strip off
+        the extension.  */
+      fileName = fileName.split ("\\.", 2)[0];
+
+      /* Next, split the file name by dashes.  Everything before the
+        last dash is part of the family name.  */
+      index = fileName.lastIndexOf ("-");
+
+      if (index > 0)
+       {
+         style = fileName.substring (index + 1, fileName.length ());
+         familyName = fileName.substring (0, index);
+
+         /* Look for something describing the weight.  */
+         if (style.contains ("Thin"))
+           weight = THIN;
+         else if (style.contains ("UltraLight"))
+           weight = ULTRA_LIGHT;
+         else if (style.contains ("SemiLight"))
+           weight = SEMI_LIGHT;
+         else if (style.contains ("Light"))
+           weight = LIGHT;
+         else if (style.contains ("Medium"))
+           weight = MEDIUM;
+         else if (style.contains ("SemiBold"))
+           weight = SEMI_BOLD;
+         else if (style.contains ("ExtraBold"))
+           weight = EXTRA_BOLD;
+         else if (style.contains ("Bold"))
+           weight = BOLD;
+         else if (style.contains ("Black"))
+           weight = BLACK;
+         else if (style.contains ("UltraHeavy"))
+           weight = ULTRA_HEAVY;
+
+         /* And the slant.  */
+         if (style.contains ("ReverseOblique"))
+           slant = OBLIQUE;
+         else if (style.contains ("ReverseItalic"))
+           slant = REVERSE_ITALIC;
+         else if (style.contains ("Italic"))
+           slant = ITALIC;
+         else if (style.contains ("Oblique"))
+           slant = OBLIQUE;
+
+         /* Finally, the width.  */
+         if (style.contains ("UltraCondensed"))
+           width = ULTRA_CONDENSED;
+         else if (style.contains ("ExtraCondensed"))
+           width = EXTRA_CONDENSED;
+         else if (style.contains ("SemiCondensed"))
+           width = SEMI_CONDENSED;
+         else if (style.contains ("Condensed"))
+           width = CONDENSED;
+         else if (style.contains ("SemiExpanded"))
+           width = SEMI_EXPANDED;
+         else if (style.contains ("ExtraExpanded"))
+           width = EXTRA_EXPANDED;
+         else if (style.contains ("UltraExpanded"))
+           width = ULTRA_EXPANDED;
+         else if (style.contains ("Expanded"))
+           width = EXPANDED;
+
+         /* Guess the spacing information.  */
+         testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+         widths = new float[testString.length ()];
+
+         measured = typefacePaint.getTextWidths (testString,
+                                                 0, testString.length (),
+                                                 widths);
+         spacing = MONO;
+         for (i = 0; i < measured; ++i)
+           {
+             if (i != 0 && widths[i - 1] != widths[i])
+               /* This isn't a monospace font.  */
+               spacing = PROPORTIONAL;
+           }
+       }
+      else
+       familyName = fileName;
+    }
+
+    @Override
+    public String
+    toString ()
+    {
+      return ("Sdk7Typeface ("
+             + String.valueOf (familyName) + ", "
+             + String.valueOf (slant) + ", "
+             + String.valueOf (width) + ", "
+             + String.valueOf (weight) + ", "
+             + String.valueOf (spacing) + ")");
+    }
+  };
+
+  protected static final class Sdk7FontEntity extends FontEntity
+  {
+    /* The typeface.  */
+    public Sdk7Typeface typeface;
+
+    public
+    Sdk7FontEntity (Sdk7Typeface typeface)
+    {
+      foundry = "Google";
+      family = typeface.familyName;
+      adstyle = null;
+      weight = typeface.weight;
+      slant = typeface.slant;
+      spacing = typeface.spacing;
+      width = typeface.width;
+      dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
+
+      this.typeface = typeface;
+    }
+  };
+
+  protected final class Sdk7FontObject extends FontObject
+  {
+    /* The typeface.  */
+    public Sdk7Typeface typeface;
+
+    public
+    Sdk7FontObject (Sdk7Typeface typeface, int pixelSize)
+    {
+      float totalWidth;
+      String testWidth, testString;
+
+      this.typeface = typeface;
+      this.pixelSize = pixelSize;
+
+      family = typeface.familyName;
+      adstyle = null;
+      weight = typeface.weight;
+      slant = typeface.slant;
+      spacing = typeface.spacing;
+      width = typeface.width;
+      dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
+
+      /* Compute the ascent and descent.  */
+      typeface.typefacePaint.setTextSize (pixelSize);
+      ascent
+       = Math.round (-typeface.typefacePaint.ascent ());
+      descent
+       = Math.round (typeface.typefacePaint.descent ());
+
+      /* Compute the average width.  */
+      testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+      totalWidth = typeface.typefacePaint.measureText (testString);
+
+      if (totalWidth > 0)
+       avgwidth = Math.round (totalWidth
+                              / testString.length ());
+
+      /* Android doesn't expose the font average width and height
+        information, so this will have to do.  */
+      minWidth = maxWidth = avgwidth;
+
+      /* This is different from avgwidth in the font spec! */
+      averageWidth = avgwidth;
+
+      /* Set the space width.  */
+      totalWidth = typeface.typefacePaint.measureText (" ");
+      spaceWidth = Math.round (totalWidth);
+
+      /* Set the height and default ascent.  */
+      height = ascent + descent;
+      defaultAscent = ascent;
+    }
+  };
+
+  private String[] fontFamilyList;
+  private Sdk7Typeface[] typefaceList;
+  private Sdk7Typeface fallbackTypeface;
+
+  public
+  EmacsSdk7FontDriver ()
+  {
+    int i;
+    File systemFontsDirectory, fontFile;
+    Typeface typeface;
+
+    systemFontsDirectory = new File ("/system/fonts");
+
+    fontFamilyList = systemFontsDirectory.list ();
+
+    /* If that returned null, replace it with an empty array.  */
+    fontFamilyList = new String[0];
+
+    typefaceList = new Sdk7Typeface[fontFamilyList.length + 3];
+
+    /* It would be nice to avoid opening each and every font upon
+       startup.  But that doesn't seem to be possible on
+       Android.  */
+
+    for (i = 0; i < fontFamilyList.length; ++i)
+      {
+       fontFile = new File (systemFontsDirectory,
+                            fontFamilyList[i]);
+       typeface = Typeface.createFromFile (fontFile);
+       typefaceList[i] = new Sdk7Typeface (fontFile.getName (),
+                                           typeface);
+      }
+
+    /* Initialize the default monospace and serif typefaces.  */
+    fallbackTypeface = new Sdk7Typeface ("monospace",
+                                        Typeface.MONOSPACE);
+    typefaceList[fontFamilyList.length] = fallbackTypeface;
+
+    fallbackTypeface = new Sdk7Typeface ("Monospace",
+                                        Typeface.MONOSPACE);
+    typefaceList[fontFamilyList.length + 1] = fallbackTypeface;
+
+    fallbackTypeface = new Sdk7Typeface ("Sans Serif",
+                                        Typeface.DEFAULT);
+    typefaceList[fontFamilyList.length + 2] = fallbackTypeface;
+  }
+
+  private boolean
+  checkMatch (Sdk7Typeface typeface, FontSpec fontSpec)
+  {
+    if (fontSpec.family != null
+       && !fontSpec.family.equals (typeface.familyName))
+      return false;
+
+    if (fontSpec.slant != null
+       && !fontSpec.weight.equals (typeface.weight))
+      return false;
+
+    if (fontSpec.spacing != null
+       && !fontSpec.spacing.equals (typeface.spacing))
+      return false;
+
+    if (fontSpec.weight != null
+       && !fontSpec.weight.equals (typeface.weight))
+      return false;
+
+    if (fontSpec.width != null
+       && !fontSpec.width.equals (typeface.width))
+      return false;
+
+    return true;
+  }
+
+  @Override
+  public FontEntity[]
+  list (FontSpec fontSpec)
+  {
+    LinkedList<FontEntity> list;
+    int i;
+
+    list = new LinkedList<FontEntity> ();
+
+    for (i = 0; i < typefaceList.length; ++i)
+      {
+       if (checkMatch (typefaceList[i], fontSpec))
+         list.add (new Sdk7FontEntity (typefaceList[i]));
+      }
+
+    return list.toArray (new FontEntity[0]);
+  }
+
+  @Override
+  public FontEntity
+  match (FontSpec fontSpec)
+  {
+    FontEntity[] entities;
+    int i;
+
+    entities = this.list (fontSpec);
+
+    if (entities.length == 0)
+      return new Sdk7FontEntity (fallbackTypeface);
+
+    return entities[0];
+  }
+
+  @Override
+  public String[]
+  listFamilies ()
+  {
+    return fontFamilyList;
+  }
+
+  @Override
+  public FontObject
+  openFont (FontEntity fontEntity, int pixelSize)
+  {
+    return new Sdk7FontObject (((Sdk7FontEntity) fontEntity).typeface,
+                              pixelSize);
+  }
+
+  @Override
+  public int
+  hasChar (FontSpec font, char charCode)
+  {
+    float missingGlyphWidth, width;
+    Rect rect1, rect2;
+    Paint paint;
+    Sdk7FontObject fontObject;
+
+    if (font instanceof Sdk7FontObject)
+      {
+       fontObject = (Sdk7FontObject) font;
+       paint = fontObject.typeface.typefacePaint;
+      }
+    else
+      paint = ((Sdk7FontEntity) font).typeface.typefacePaint;
+
+    paint.setTextSize (10);
+
+    if (Character.isWhitespace (charCode))
+      return 1;
+
+    missingGlyphWidth = paint.measureText (TOFU_STRING);
+    width = paint.measureText ("" + charCode);
+
+    if (width == 0f)
+      return 0;
+
+    if (width != missingGlyphWidth)
+      return 1;
+
+    rect1 = new Rect ();
+    rect2 = new Rect ();
+
+    paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (),
+                        rect1);
+    paint.getTextBounds ("" + charCode, 0, 1, rect2);
+    return rect1.equals (rect2) ? 0 : 1;
+  }
+
+  private void
+  textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics,
+               Paint paint, Rect bounds)
+  {
+    char[] text;
+
+    text = new char[1];
+    text[0] = (char) code;
+
+    paint.getTextBounds (text, 0, 1, bounds);
+
+    /* bounds is the bounding box of the glyph corresponding to CODE.
+       Translate these into XCharStruct values.
+
+       The origin is at 0, 0, and lbearing is the distance counting
+       rightwards from the origin to the left most pixel in the glyph
+       raster.  rbearing is the distance between the origin and the
+       rightmost pixel in the glyph raster.  ascent is the distance
+       counting upwards between the the topmost pixel in the glyph
+       raster.  descent is the distance (once again counting
+       downwards) between the origin and the bottommost pixel in the
+       glyph raster.
+
+       width is the distance between the origin and the origin of any
+       character to the right.  */
+
+    metrics.lbearing = (short) bounds.left;
+    metrics.rbearing = (short) bounds.right;
+    metrics.ascent = (short) -bounds.top;
+    metrics.descent = (short) bounds.bottom;
+    metrics.width = (short) paint.measureText ("" + text[0]);
+  }
+
+  @Override
+  public void
+  textExtents (FontObject font, int code[], FontMetrics fontMetrics)
+  {
+    int i;
+    Paint paintCache;
+    Rect boundsCache;
+    Sdk7FontObject fontObject;
+    char[] text;
+    float width;
+
+    fontObject = (Sdk7FontObject) font;
+    paintCache = fontObject.typeface.typefacePaint;
+    paintCache.setTextSize (fontObject.pixelSize);
+    boundsCache = new Rect ();
+
+    if (code.length == 0)
+      {
+       fontMetrics.lbearing = 0;
+       fontMetrics.rbearing = 0;
+       fontMetrics.ascent = 0;
+       fontMetrics.descent = 0;
+       fontMetrics.width = 0;
+      }
+    else if (code.length == 1)
+      textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics,
+                   paintCache, boundsCache);
+    else
+      {
+       text = new char[code.length];
+
+       for (i = 0; i < code.length; ++i)
+         text[i] = (char) code[i];
+
+       paintCache.getTextBounds (text, 0, code.length,
+                                 boundsCache);
+       width = paintCache.measureText (text, 0, code.length);
+
+       fontMetrics.lbearing = (short) boundsCache.left;
+       fontMetrics.rbearing = (short) boundsCache.right;
+       fontMetrics.ascent = (short) -boundsCache.top;
+       fontMetrics.descent = (short) boundsCache.bottom;
+       fontMetrics.width = (short) Math.round (width);
+      }
+  }
+
+  @Override
+  public int
+  encodeChar (FontObject fontObject, char charCode)
+  {
+    return charCode;
+  }
+
+  @Override
+  public int
+  draw (FontObject fontObject, EmacsGC gc, EmacsDrawable drawable,
+       int[] chars, int x, int y, int backgroundWidth,
+       boolean withBackground)
+  {
+    Rect backgroundRect, bounds;
+    Sdk7FontObject sdk7FontObject;
+    char[] charsArray;
+    int i;
+    Canvas canvas;
+    Paint paint;
+
+    sdk7FontObject = (Sdk7FontObject) fontObject;
+    charsArray = new char[chars.length];
+
+    for (i = 0; i < chars.length; ++i)
+      charsArray[i] = (char) chars[i];
+
+    backgroundRect = new Rect ();
+    backgroundRect.top = y - sdk7FontObject.ascent;
+    backgroundRect.left = x;
+    backgroundRect.right = x + backgroundWidth;
+    backgroundRect.bottom = y + sdk7FontObject.descent;
+
+    canvas = drawable.lockCanvas (gc);
+
+    if (canvas == null)
+      return 0;
+
+    paint = gc.gcPaint;
+    paint.setStyle (Paint.Style.FILL);
+
+    if (withBackground)
+      {
+       paint.setColor (gc.background | 0xff000000);
+       canvas.drawRect (backgroundRect, paint);
+       paint.setColor (gc.foreground | 0xff000000);
+      }
+
+    paint.setTextSize (sdk7FontObject.pixelSize);
+    paint.setTypeface (sdk7FontObject.typeface.typeface);
+    paint.setAntiAlias (true);
+    canvas.drawText (charsArray, 0, chars.length, x, y, paint);
+
+    bounds = new Rect ();
+    paint.getTextBounds (charsArray, 0, chars.length, bounds);
+    bounds.offset (x, y);
+    bounds.union (backgroundRect);
+    drawable.damageRect (bounds);
+    paint.setAntiAlias (false);
+    return 1;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java 
b/java/org/gnu/emacs/EmacsSdk8Clipboard.java
new file mode 100644
index 00000000000..9622641810f
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java
@@ -0,0 +1,147 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+/* Importing the entire package instead of just the legacy
+   ClipboardManager class avoids the deprecation warning.  */
+
+import android.text.*;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+
+/* This class implements EmacsClipboard for Android 2.2 and other
+   similarly old systems.  */
+
+@SuppressWarnings ("deprecation")
+public final class EmacsSdk8Clipboard extends EmacsClipboard
+{
+  private static final String TAG = "EmacsSdk8Clipboard";
+  private ClipboardManager manager;
+
+  public
+  EmacsSdk8Clipboard ()
+  {
+    String what;
+    Context context;
+
+    what = Context.CLIPBOARD_SERVICE;
+    context = EmacsService.SERVICE;
+    manager
+      = (ClipboardManager) context.getSystemService (what);
+  }
+
+  /* Set the clipboard text to CLIPBOARD, a string in UTF-8
+     encoding.  */
+
+  @Override
+  public void
+  setClipboard (byte[] bytes)
+  {
+    try
+      {
+       manager.setText (new String (bytes, "UTF-8"));
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       Log.w (TAG, "setClipboard: " + exception);
+      }
+  }
+
+  /* Return whether or not Emacs owns the clipboard.  Value is 1 if
+     Emacs does, 0 if Emacs does not, and -1 if that information is
+     unavailable.  */
+
+  @Override
+  public int
+  ownsClipboard ()
+  {
+    return -1;
+  }
+
+  /* Return whether or not clipboard content currently exists.  */
+
+  @Override
+  public boolean
+  clipboardExists ()
+  {
+    return manager.hasText ();
+  }
+
+  /* Return the current content of the clipboard, as plain text, or
+     NULL if no content is available.  */
+
+  @Override
+  public byte[]
+  getClipboard ()
+  {
+    String string;
+    CharSequence text;
+
+    text = manager.getText ();
+
+    if (text == null)
+      return null;
+
+    string = text.toString ();
+
+    try
+      {
+       return string.getBytes ("UTF-8");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       Log.w (TAG, "getClipboard: " + exception);
+      }
+
+    return null;
+  }
+
+  /* Return an array of targets currently provided by the
+     clipboard, or NULL if there are none.  */
+
+  @Override
+  public byte[][]
+  getClipboardTargets ()
+  {
+    return null;
+  }
+
+  /* Return the clipboard data for the given target, or NULL if it
+     does not exist.
+
+     Value is normally an array of three longs: the file descriptor,
+     the start offset of the data, and its length; length may be
+     AssetFileDescriptor.UNKOWN_LENGTH, meaning that the data extends
+     from that offset to the end of the file.
+
+     Do not use this function to open text targets; use `getClipboard'
+     for that instead, as it will handle selection data consisting
+     solely of a URI.  */
+
+  @Override
+  public long[]
+  getClipboardData (byte[] target)
+  {
+    return null;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsService.java 
b/java/org/gnu/emacs/EmacsService.java
new file mode 100644
index 00000000000..404796cd08c
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -0,0 +1,1811 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.database.Cursor;
+
+import android.graphics.Matrix;
+import android.graphics.Point;
+
+import android.webkit.MimeTypeMap;
+
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.ExtractedText;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.app.Service;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.UriPermission;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager;
+
+import android.content.res.AssetManager;
+
+import android.hardware.input.InputManager;
+
+import android.net.Uri;
+
+import android.os.BatteryManager;
+import android.os.Build;
+import android.os.Looper;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+import android.os.VibrationEffect;
+
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+
+import android.util.Log;
+import android.util.DisplayMetrics;
+
+import android.widget.Toast;
+
+/* EmacsService is the service that starts the thread running Emacs
+   and handles requests by that Emacs instance.  */
+
+public final class EmacsService extends Service
+{
+  public static final String TAG = "EmacsService";
+
+  /* The started Emacs service object.  */
+  public static EmacsService SERVICE;
+
+  /* If non-NULL, an extra argument to pass to
+     `android_emacs_init'.  */
+  public static String extraStartupArgument;
+
+  /* The thread running Emacs C code.  */
+  private EmacsThread thread;
+
+  /* Handler used to run tasks on the main thread.  */
+  private Handler handler;
+
+  /* Content resolver used to access URIs.  */
+  private ContentResolver resolver;
+
+  /* Keep this in synch with androidgui.h.  */
+  public static final int IC_MODE_NULL   = 0;
+  public static final int IC_MODE_ACTION = 1;
+  public static final int IC_MODE_TEXT   = 2;
+
+  /* Display metrics used by font backends.  */
+  public DisplayMetrics metrics;
+
+  /* Flag that says whether or not to print verbose debugging
+     information when responding to an input method.  */
+  public static final boolean DEBUG_IC = false;
+
+  /* Flag that says whether or not to stringently check that only the
+     Emacs thread is performing drawing calls.  */
+  private static final boolean DEBUG_THREADS = false;
+
+  /* Atomic integer used for synchronization between
+     icBeginSynchronous/icEndSynchronous and viewGetSelection.
+
+     Value is 0 if no query is in progress, 1 if viewGetSelection is
+     being called, and 2 if icBeginSynchronous was called.  */
+  public static final AtomicInteger servicingQuery;
+
+  /* Thread used to query document providers, or null if it hasn't
+     been created yet.  */
+  private EmacsSafThread storageThread;
+
+  static
+  {
+    servicingQuery = new AtomicInteger ();
+  };
+
+  /* Return the directory leading to the directory in which native
+     library files are stored on behalf of CONTEXT.  */
+
+  public static String
+  getLibraryDirectory (Context context)
+  {
+    int apiLevel;
+
+    apiLevel = Build.VERSION.SDK_INT;
+
+    if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
+      return context.getApplicationInfo ().nativeLibraryDir;
+
+    return context.getApplicationInfo ().dataDir + "/lib";
+  }
+
+  @Override
+  public int
+  onStartCommand (Intent intent, int flags, int startId)
+  {
+    Notification notification;
+    NotificationManager manager;
+    NotificationChannel channel;
+    String infoBlurb;
+    Object tem;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+      {
+       tem = getSystemService (Context.NOTIFICATION_SERVICE);
+       manager = (NotificationManager) tem;
+       infoBlurb = ("This notification is displayed to keep Emacs"
+                    + " running while it is in the background.  You"
+                    + " may disable it if you want;"
+                    + " see (emacs)Android Environment.");
+       channel
+         = new NotificationChannel ("emacs", "Emacs Background Service",
+                                    NotificationManager.IMPORTANCE_DEFAULT);
+       manager.createNotificationChannel (channel);
+       notification = (new Notification.Builder (this, "emacs")
+                       .setContentTitle ("Emacs")
+                       .setContentText (infoBlurb)
+                       .setSmallIcon (android.R.drawable.sym_def_app_icon)
+                       .build ());
+       manager.notify (1, notification);
+       startForeground (1, notification);
+      }
+
+    return START_NOT_STICKY;
+  }
+
+  @Override
+  public IBinder
+  onBind (Intent intent)
+  {
+    return null;
+  }
+
+  @SuppressWarnings ("deprecation")
+  private String
+  getApkFile ()
+  {
+    PackageManager manager;
+    ApplicationInfo info;
+
+    manager = getPackageManager ();
+
+    try
+      {
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
+         info = manager.getApplicationInfo ("org.gnu.emacs", 0);
+       else
+         info = manager.getApplicationInfo ("org.gnu.emacs",
+                                            ApplicationInfoFlags.of (0));
+
+       /* Return an empty string upon failure.  */
+
+       if (info.sourceDir != null)
+         return info.sourceDir;
+
+       return "";
+      }
+    catch (Exception e)
+      {
+       return "";
+      }
+  }
+
+  @Override
+  public void
+  onCreate ()
+  {
+    final AssetManager manager;
+    Context app_context;
+    final String filesDir, libDir, cacheDir, classPath;
+    final double pixelDensityX;
+    final double pixelDensityY;
+    final double scaledDensity;
+    double tempScaledDensity;
+
+    SERVICE = this;
+    handler = new Handler (Looper.getMainLooper ());
+    manager = getAssets ();
+    app_context = getApplicationContext ();
+    metrics = getResources ().getDisplayMetrics ();
+    pixelDensityX = metrics.xdpi;
+    pixelDensityY = metrics.ydpi;
+    tempScaledDensity = ((metrics.scaledDensity
+                         / metrics.density)
+                        * pixelDensityX);
+    resolver = getContentResolver ();
+
+    /* If the density used to compute the text size is lesser than
+       160, there's likely a bug with display density computation.
+       Reset it to 160 in that case.
+
+       Note that Android uses 160 ``dpi'' as the density where 1 point
+       corresponds to 1 pixel, not 72 or 96 as used elsewhere.  This
+       difference is codified in PT_PER_INCH defined in font.h.  */
+
+    if (tempScaledDensity < 160)
+      tempScaledDensity = 160;
+
+    /* scaledDensity is const as required to refer to it from within
+       the nested function below.  */
+    scaledDensity = tempScaledDensity;
+
+    try
+      {
+       /* Configure Emacs with the asset manager and other necessary
+          parameters.  */
+       filesDir = app_context.getFilesDir ().getCanonicalPath ();
+       libDir = getLibraryDirectory (this);
+       cacheDir = app_context.getCacheDir ().getCanonicalPath ();
+
+       /* Now provide this application's apk file, so a recursive
+          invocation of app_process (through android-emacs) can
+          find EmacsNoninteractive.  */
+       classPath = getApkFile ();
+
+       Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+              + ", libDir = " + libDir + ", and classPath = " + classPath
+              + "; fileToOpen = " + EmacsOpenActivity.fileToOpen
+              + "; display density: " + pixelDensityX + " by "
+              + pixelDensityY + " scaled to " + scaledDensity);
+
+       /* Start the thread that runs Emacs.  */
+       thread = new EmacsThread (this, new Runnable () {
+           @Override
+           public void
+           run ()
+           {
+             EmacsNative.setEmacsParams (manager, filesDir, libDir,
+                                         cacheDir, (float) pixelDensityX,
+                                         (float) pixelDensityY,
+                                         (float) scaledDensity,
+                                         classPath, EmacsService.this,
+                                         Build.VERSION.SDK_INT);
+           }
+         }, extraStartupArgument,
+         /* If any file needs to be opened, open it now.  */
+         EmacsOpenActivity.fileToOpen);
+       thread.start ();
+      }
+    catch (IOException exception)
+      {
+       EmacsNative.emacsAbort ();
+       return;
+      }
+  }
+
+
+
+  /* Functions from here on must only be called from the Emacs
+     thread.  */
+
+  public void
+  runOnUiThread (Runnable runnable)
+  {
+    handler.post (runnable);
+  }
+
+  public EmacsView
+  getEmacsView (final EmacsWindow window, final int visibility,
+               final boolean isFocusedByDefault)
+  {
+    Runnable runnable;
+    final EmacsHolder<EmacsView> view;
+
+    view = new EmacsHolder<EmacsView> ();
+
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             view.thing = new EmacsView (window);
+             view.thing.setVisibility (visibility);
+
+             /* The following function is only present on Android 26
+                or later.  */
+             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+               view.thing.setFocusedByDefault (isFocusedByDefault);
+
+             notify ();
+           }
+       }
+      };
+
+    syncRunnable (runnable);
+    return view.thing;
+  }
+
+  public void
+  getLocationOnScreen (final EmacsView view, final int[] coordinates)
+  {
+    Runnable runnable;
+
+    runnable = new Runnable () {
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             view.getLocationOnScreen (coordinates);
+             notify ();
+           }
+       }
+      };
+
+    syncRunnable (runnable);
+  }
+
+
+
+  public static void
+  checkEmacsThread ()
+  {
+    if (DEBUG_THREADS)
+      {
+       if (Thread.currentThread () instanceof EmacsThread)
+         return;
+
+       throw new RuntimeException ("Emacs thread function"
+                                   + " called from other thread!");
+      }
+  }
+
+  /* These drawing functions must only be called from the Emacs
+     thread.  */
+
+  public void
+  fillRectangle (EmacsDrawable drawable, EmacsGC gc,
+                int x, int y, int width, int height)
+  {
+    checkEmacsThread ();
+    EmacsFillRectangle.perform (drawable, gc, x, y,
+                               width, height);
+  }
+
+  public void
+  fillPolygon (EmacsDrawable drawable, EmacsGC gc,
+              Point points[])
+  {
+    checkEmacsThread ();
+    EmacsFillPolygon.perform (drawable, gc, points);
+  }
+
+  public void
+  drawRectangle (EmacsDrawable drawable, EmacsGC gc,
+                int x, int y, int width, int height)
+  {
+    checkEmacsThread ();
+    EmacsDrawRectangle.perform (drawable, gc, x, y,
+                               width, height);
+  }
+
+  public void
+  drawLine (EmacsDrawable drawable, EmacsGC gc,
+           int x, int y, int x2, int y2)
+  {
+    checkEmacsThread ();
+    EmacsDrawLine.perform (drawable, gc, x, y,
+                          x2, y2);
+  }
+
+  public void
+  drawPoint (EmacsDrawable drawable, EmacsGC gc,
+            int x, int y)
+  {
+    checkEmacsThread ();
+    EmacsDrawPoint.perform (drawable, gc, x, y);
+  }
+
+  public void
+  clearWindow (EmacsWindow window)
+  {
+    checkEmacsThread ();
+    window.clearWindow ();
+  }
+
+  public void
+  clearArea (EmacsWindow window, int x, int y, int width,
+            int height)
+  {
+    checkEmacsThread ();
+    window.clearArea (x, y, width, height);
+  }
+
+  @SuppressWarnings ("deprecation")
+  public void
+  ringBell ()
+  {
+    Vibrator vibrator;
+    VibrationEffect effect;
+    VibratorManager vibratorManager;
+    Object tem;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+      {
+       tem = getSystemService (Context.VIBRATOR_MANAGER_SERVICE);
+       vibratorManager = (VibratorManager) tem;
+        vibrator = vibratorManager.getDefaultVibrator ();
+      }
+    else
+      vibrator
+       = (Vibrator) getSystemService (Context.VIBRATOR_SERVICE);
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+      {
+       effect
+         = VibrationEffect.createOneShot (50,
+                                          VibrationEffect.DEFAULT_AMPLITUDE);
+       vibrator.vibrate (effect);
+      }
+    else
+      vibrator.vibrate (50);
+  }
+
+  public short[]
+  queryTree (EmacsWindow window)
+  {
+    short[] array;
+    List<EmacsWindow> windowList;
+    int i;
+
+    if (window == null)
+      /* Just return all the windows without a parent.  */
+      windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows ();
+    else
+      windowList = window.children;
+
+    array = new short[windowList.size () + 1];
+    i = 1;
+
+    array[0] = (window == null
+               ? 0 : (window.parent != null
+                      ? window.parent.handle : 0));
+
+    for (EmacsWindow treeWindow : windowList)
+      array[i++] = treeWindow.handle;
+
+    return array;
+  }
+
+  public int
+  getScreenWidth (boolean mmWise)
+  {
+    DisplayMetrics metrics;
+
+    metrics = getResources ().getDisplayMetrics ();
+
+    if (!mmWise)
+      return metrics.widthPixels;
+    else
+      return (int) ((metrics.widthPixels / metrics.xdpi) * 2540.0);
+  }
+
+  public int
+  getScreenHeight (boolean mmWise)
+  {
+    DisplayMetrics metrics;
+
+    metrics = getResources ().getDisplayMetrics ();
+
+    if (!mmWise)
+      return metrics.heightPixels;
+    else
+      return (int) ((metrics.heightPixels / metrics.ydpi) * 2540.0);
+  }
+
+  public boolean
+  detectMouse ()
+  {
+    InputManager manager;
+    InputDevice device;
+    int[] ids;
+    int i;
+
+    if (Build.VERSION.SDK_INT
+       /* Android 4.0 and earlier don't support mouse input events at
+          all.  */
+       < Build.VERSION_CODES.JELLY_BEAN)
+      return false;
+
+    manager = (InputManager) getSystemService (Context.INPUT_SERVICE);
+    ids = manager.getInputDeviceIds ();
+
+    for (i = 0; i < ids.length; ++i)
+      {
+       device = manager.getInputDevice (ids[i]);
+
+       if (device == null)
+         continue;
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+         {
+           if (device.supportsSource (InputDevice.SOURCE_MOUSE))
+             return true;
+         }
+       else
+         {
+           /* `supportsSource' is only present on API level 21 and
+              later, but earlier versions provide a bit mask
+              containing each supported source.  */
+
+           if ((device.getSources () & InputDevice.SOURCE_MOUSE) != 0)
+             return true;
+         }
+      }
+
+    return false;
+  }
+
+  public String
+  nameKeysym (int keysym)
+  {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
+      return KeyEvent.keyCodeToString (keysym);
+
+    return String.valueOf (keysym);
+  }
+
+
+
+  /* Start the Emacs service if necessary.  On Android 26 and up,
+     start Emacs as a foreground service with a notification, to avoid
+     it being killed by the system.
+
+     On older systems, simply start it as a normal background
+     service.  */
+
+  public static void
+  startEmacsService (Context context)
+  {
+    if (EmacsService.SERVICE == null)
+      {
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+         /* Start the Emacs service now.  */
+         context.startService (new Intent (context,
+                                           EmacsService.class));
+       else
+         /* Display the permanant notification and start Emacs as a
+            foreground service.  */
+         context.startForegroundService (new Intent (context,
+                                                     EmacsService.class));
+      }
+  }
+
+  /* Ask the system to open the specified URL in an application that
+     understands how to open it.
+
+     If SEND, tell the system to also open applications that can
+     ``send'' the URL (through mail, for example), instead of only
+     those that can view the URL.
+
+     Value is NULL upon success, or a string describing the error
+     upon failure.  */
+
+  public String
+  browseUrl (String url, boolean send)
+  {
+    Intent intent;
+    Uri uri;
+
+    try
+      {
+       /* Parse the URI.  */
+       if (!send)
+         {
+           uri = Uri.parse (url);
+
+           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+             {
+               /* On Android 4.4 and later, check if URI is actually
+                  a file name.  If so, rewrite it into a content
+                  provider URI, so that it can be accessed by other
+                  programs.  */
+
+               if (uri.getScheme ().equals ("file")
+                   && uri.getPath () != null)
+                 uri
+                   = DocumentsContract.buildDocumentUri ("org.gnu.emacs",
+                                                         uri.getPath ());
+             }
+
+           Log.d (TAG, ("browseUri: browsing " + url
+                        + " --> " + uri.getPath ()
+                        + " --> " + uri));
+
+           intent = new Intent (Intent.ACTION_VIEW, uri);
+           intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+         }
+       else
+         {
+           intent = new Intent (Intent.ACTION_SEND);
+           intent.setType ("text/plain");
+           intent.putExtra (Intent.EXTRA_SUBJECT, "Sharing link");
+           intent.putExtra (Intent.EXTRA_TEXT, url);
+
+           /* Display a list of programs able to send this URL.  */
+           intent = Intent.createChooser (intent, "Send");
+
+           /* Apparently flags need to be set after a choser is
+              created.  */
+           intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
+         }
+
+       startActivity (intent);
+      }
+    catch (Exception e)
+      {
+       return e.toString ();
+      }
+
+    return null;
+  }
+
+  /* Get a SDK 11 ClipboardManager.
+
+     Android 4.0.x requires that this be called from the main
+     thread.  */
+
+  public ClipboardManager
+  getClipboardManager ()
+  {
+    final EmacsHolder<ClipboardManager> manager;
+    Runnable runnable;
+
+    manager = new EmacsHolder<ClipboardManager> ();
+
+    runnable = new Runnable () {
+       public void
+       run ()
+       {
+         Object tem;
+
+         synchronized (this)
+           {
+             tem = getSystemService (Context.CLIPBOARD_SERVICE);
+             manager.thing = (ClipboardManager) tem;
+             notify ();
+           }
+       }
+      };
+
+    syncRunnable (runnable);
+    return manager.thing;
+  }
+
+  public void
+  restartEmacs ()
+  {
+    Intent intent;
+
+    intent = new Intent (this, EmacsActivity.class);
+    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+    startActivity (intent);
+    System.exit (0);
+  }
+
+  /* Wait synchronously for the specified RUNNABLE to complete in the
+     UI thread.  Must be called from the Emacs thread.  */
+
+  public static void
+  syncRunnable (Runnable runnable)
+  {
+    EmacsNative.beginSynchronous ();
+
+    synchronized (runnable)
+      {
+       SERVICE.runOnUiThread (runnable);
+
+       while (true)
+         {
+           try
+             {
+               runnable.wait ();
+               break;
+             }
+           catch (InterruptedException e)
+             {
+               continue;
+             }
+         }
+      }
+
+    EmacsNative.endSynchronous ();
+  }
+
+
+
+  /* IMM functions such as `updateSelection' holds an internal lock
+     that is also taken before `onCreateInputConnection' (in
+     EmacsView.java) is called; when that then asks the UI thread for
+     the current selection, a dead lock results.  To remedy this,
+     reply to any synchronous queries now -- and prohibit more queries
+     for the duration of `updateSelection' -- if EmacsView may have
+     been asking for the value of the region.  */
+
+  public static void
+  icBeginSynchronous ()
+  {
+    /* Set servicingQuery to 2, so viewGetSelection knows it shouldn't
+       proceed.  */
+
+    if (servicingQuery.getAndSet (2) == 1)
+      /* But if viewGetSelection is already in progress, answer it
+        first.  */
+      EmacsNative.answerQuerySpin ();
+  }
+
+  public static void
+  icEndSynchronous ()
+  {
+    if (servicingQuery.getAndSet (0) != 2)
+      throw new RuntimeException ("incorrect value of `servicingQuery': "
+                                 + "likely 1");
+  }
+
+  public static int[]
+  viewGetSelection (short window)
+  {
+    int[] selection;
+
+    /* See if a query is already in progress from the other
+       direction.  */
+    if (!servicingQuery.compareAndSet (0, 1))
+      return null;
+
+    /* Now call the regular getSelection.  Note that this can't race
+       with answerQuerySpin, as `android_servicing_query' can never be
+       2 when icBeginSynchronous is called, so a query will always be
+       started.  */
+    selection = EmacsNative.getSelection (window);
+
+    /* Finally, clear servicingQuery if its value is still 1.  If a
+       query has started from the other side, it ought to be 2.  */
+
+    servicingQuery.compareAndSet (1, 0);
+    return selection;
+  }
+
+
+
+  public void
+  updateIC (EmacsWindow window, int newSelectionStart,
+           int newSelectionEnd, int composingRegionStart,
+           int composingRegionEnd)
+  {
+    if (DEBUG_IC)
+      Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
+                  + " " + newSelectionEnd + " "
+                  + composingRegionStart + " "
+                  + composingRegionEnd));
+
+    icBeginSynchronous ();
+    window.view.imManager.updateSelection (window.view,
+                                          newSelectionStart,
+                                          newSelectionEnd,
+                                          composingRegionStart,
+                                          composingRegionEnd);
+    icEndSynchronous ();
+  }
+
+  public void
+  resetIC (EmacsWindow window, int icMode)
+  {
+    int oldMode;
+
+    if (DEBUG_IC)
+      Log.d (TAG, "resetIC: " + window + ", " + icMode);
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+       && (oldMode = window.view.getICMode ()) == icMode
+       /* Don't do this if there is currently no input
+          connection.  */
+       && oldMode != IC_MODE_NULL)
+      {
+       if (DEBUG_IC)
+         Log.d (TAG, "resetIC: calling invalidateInput");
+
+       /* Android 33 and later allow the IM reset to be optimized out
+          and replaced by a call to `invalidateInput', which is much
+          faster, as it does not involve resetting the input
+          connection.  */
+
+       icBeginSynchronous ();
+       window.view.imManager.invalidateInput (window.view);
+       icEndSynchronous ();
+
+       return;
+      }
+
+    window.view.setICMode (icMode);
+
+    icBeginSynchronous ();
+    window.view.icGeneration++;
+    window.view.imManager.restartInput (window.view);
+    icEndSynchronous ();
+  }
+
+  public void
+  updateCursorAnchorInfo (EmacsWindow window, float x,
+                         float y, float yBaseline,
+                         float yBottom)
+  {
+    CursorAnchorInfo info;
+    CursorAnchorInfo.Builder builder;
+    Matrix matrix;
+    int[] offsets;
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+      return;
+
+    offsets = new int[2];
+    builder = new CursorAnchorInfo.Builder ();
+    matrix = new Matrix (window.view.getMatrix ());
+    window.view.getLocationOnScreen (offsets);
+    matrix.postTranslate (offsets[0], offsets[1]);
+    builder.setMatrix (matrix);
+    builder.setInsertionMarkerLocation (x, y, yBaseline, yBottom,
+                                       0);
+    info = builder.build ();
+
+
+
+    if (DEBUG_IC)
+      Log.d (TAG, ("updateCursorAnchorInfo: " + x + " " + y
+                  + " " + yBaseline + "-" + yBottom));
+
+    icBeginSynchronous ();
+    window.view.imManager.updateCursorAnchorInfo (window.view, info);
+    icEndSynchronous ();
+  }
+
+
+
+  /* Content provider functions.  */
+
+  /* Open a content URI described by the bytes BYTES, a non-terminated
+     string; make it writable if WRITABLE, and readable if READABLE.
+     Truncate the file if TRUNCATE.
+
+     Value is the resulting file descriptor or -1 upon failure.  */
+
+  public int
+  openContentUri (byte[] bytes, boolean writable, boolean readable,
+                 boolean truncate)
+  {
+    String name, mode;
+    ParcelFileDescriptor fd;
+    int i;
+
+    /* Figure out the file access mode.  */
+
+    mode = "";
+
+    if (readable)
+      mode += "r";
+
+    if (writable)
+      mode += "w";
+
+    if (truncate)
+      mode += "t";
+
+    /* Try to open an associated ParcelFileDescriptor.  */
+
+    try
+      {
+       /* The usual file name encoding question rears its ugly head
+          again.  */
+
+       name = new String (bytes, "UTF-8");
+       fd = resolver.openFileDescriptor (Uri.parse (name), mode);
+
+       /* Use detachFd on newer versions of Android or plain old
+          dup.  */
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
+         {
+           i = fd.detachFd ();
+           fd.close ();
+
+           return i;
+         }
+       else
+         {
+           i = EmacsNative.dup (fd.getFd ());
+           fd.close ();
+
+           return i;
+         }
+      }
+    catch (Exception exception)
+      {
+       return -1;
+      }
+  }
+
+  /* Return whether Emacs is directly permitted to access the
+     content:// URI NAME.  This is not a suitable test for files which
+     Emacs can access by virtue of their containing document
+     trees.  */
+
+  public boolean
+  checkContentUri (String name, boolean readable, boolean writable)
+  {
+    String mode;
+    ParcelFileDescriptor fd;
+    Uri uri;
+    int rc, flags;
+
+    uri = Uri.parse (name);
+    flags = 0;
+
+    if (readable)
+      flags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+    if (writable)
+      flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
+    rc = checkCallingUriPermission (uri, flags);
+    return rc == PackageManager.PERMISSION_GRANTED;
+  }
+
+  /* Build a content file name for URI.
+
+     Return a file name within the /contents/by-authority
+     pseudo-directory that `android_get_content_name' can then
+     transform back into an encoded URI.
+
+     A content name consists of any number of unencoded path segments
+     separated by `/' characters, possibly followed by a question mark
+     and an encoded query string.  */
+
+  public static String
+  buildContentName (Uri uri)
+  {
+    StringBuilder builder;
+
+    builder = new StringBuilder ("/content/by-authority/");
+    builder.append (uri.getAuthority ());
+
+    /* First, append each path segment.  */
+
+    for (String segment : uri.getPathSegments ())
+      {
+       /* FIXME: what if segment contains a slash character? */
+       builder.append ('/');
+       builder.append (uri.encode (segment));
+      }
+
+    /* Now, append the query string if necessary.  */
+
+    if (uri.getEncodedQuery () != null)
+      builder.append ('?').append (uri.getEncodedQuery ());
+
+    return builder.toString ();
+  }
+
+
+
+  private long[]
+  queryBattery19 ()
+  {
+    IntentFilter filter;
+    Intent battery;
+    long capacity, chargeCounter, currentAvg, currentNow;
+    long status, remaining, plugged, temp;
+
+    filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
+    battery = registerReceiver (null, filter);
+
+    if (battery == null)
+      return null;
+
+    capacity = battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 0);
+    chargeCounter
+      = (battery.getIntExtra (BatteryManager.EXTRA_SCALE, 0)
+        / battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 100) * 100);
+    currentAvg = 0;
+    currentNow = 0;
+    status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
+    remaining = -1;
+    plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
+    temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
+
+    return new long[] { capacity, chargeCounter, currentAvg,
+                       currentNow, remaining, status, plugged,
+                       temp, };
+  }
+
+  /* Return the status of the battery.  See struct
+     android_battery_status for the order of the elements
+     returned.
+
+     Value may be null upon failure.  */
+
+  public long[]
+  queryBattery ()
+  {
+    Object tem;
+    BatteryManager manager;
+    long capacity, chargeCounter, currentAvg, currentNow;
+    long status, remaining, plugged, temp;
+    int prop;
+    IntentFilter filter;
+    Intent battery;
+
+    /* Android 4.4 or earlier require applications to use a different
+       API to query the battery status.  */
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+      return queryBattery19 ();
+
+    tem = getSystemService (Context.BATTERY_SERVICE);
+    manager = (BatteryManager) tem;
+    remaining = -1;
+
+    prop = BatteryManager.BATTERY_PROPERTY_CAPACITY;
+    capacity = manager.getLongProperty (prop);
+    prop = BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER;
+    chargeCounter = manager.getLongProperty (prop);
+    prop = BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE;
+    currentAvg = manager.getLongProperty (prop);
+    prop = BatteryManager.BATTERY_PROPERTY_CURRENT_NOW;
+    currentNow = manager.getLongProperty (prop);
+
+    /* Return the battery status.  N.B. that Android 7.1 and earlier
+       only return ``charging'' or ``discharging''.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+      status
+       = manager.getIntProperty (BatteryManager.BATTERY_PROPERTY_STATUS);
+    else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+      status = (manager.isCharging ()
+               ? BatteryManager.BATTERY_STATUS_CHARGING
+               : BatteryManager.BATTERY_STATUS_DISCHARGING);
+    else
+      status = (currentNow > 0
+               ? BatteryManager.BATTERY_STATUS_CHARGING
+               : BatteryManager.BATTERY_STATUS_DISCHARGING);
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+      remaining = manager.computeChargeTimeRemaining ();
+
+    plugged = -1;
+    temp = -1;
+
+    /* Now obtain additional information from the battery manager.  */
+
+    filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
+    battery = registerReceiver (null, filter);
+
+    if (battery != null)
+      {
+       plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
+       temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
+
+       /* Make status more reliable.  */
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
+         status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
+      }
+
+    return new long[] { capacity, chargeCounter, currentAvg,
+                       currentNow, remaining, status, plugged,
+                       temp, };
+  }
+
+  public void
+  updateExtractedText (EmacsWindow window, ExtractedText text,
+                      int token)
+  {
+    if (DEBUG_IC)
+      Log.d (TAG, "updateExtractedText: @" + token + ", " + text);
+
+    window.view.imManager.updateExtractedText (window.view,
+                                              token, text);
+  }
+
+
+
+  /* Document tree management functions.  These functions shouldn't be
+     called before Android 5.0.  */
+
+  /* Return an array of each document authority providing at least one
+     tree URI that Emacs holds the rights to persistently access.  */
+
+  public String[]
+  getDocumentAuthorities ()
+  {
+    List<UriPermission> permissions;
+    HashSet<String> allProviders;
+    Uri uri;
+
+    permissions = resolver.getPersistedUriPermissions ();
+    allProviders = new HashSet<String> ();
+
+    for (UriPermission permission : permissions)
+      {
+       uri = permission.getUri ();
+
+       if (DocumentsContract.isTreeUri (uri)
+           && permission.isReadPermission ())
+         allProviders.add (uri.getAuthority ());
+      }
+
+    return allProviders.toArray (new String[0]);
+  }
+
+  /* Start a file chooser activity to request access to a directory
+     tree.
+
+     Value is 1 if the activity couldn't be started for some reason,
+     and 0 in any other case.  */
+
+  public int
+  requestDirectoryAccess ()
+  {
+    Runnable runnable;
+    final EmacsHolder<Integer> rc;
+
+    /* Return 1 if Android is too old to support this feature.  */
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+      return 1;
+
+    rc = new EmacsHolder<Integer> ();
+    rc.thing = Integer.valueOf (1);
+
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         EmacsActivity activity;
+         Intent intent;
+         int id;
+
+         synchronized (this)
+           {
+             /* Try to obtain an activity that will receive the
+                response from the file chooser dialog.  */
+
+             if (EmacsActivity.focusedActivities.isEmpty ())
+               {
+                 /* If focusedActivities is empty then this dialog
+                    may have been displayed immediately after another
+                    popup dialog was dismissed.  Try the
+                    EmacsActivity to be focused.  */
+
+                 activity = EmacsActivity.lastFocusedActivity;
+
+                 if (activity == null)
+                   {
+                     /* Still no luck.  Return failure.  */
+                     notify ();
+                     return;
+                   }
+               }
+             else
+               activity = EmacsActivity.focusedActivities.get (0);
+
+             /* Now create the intent.  */
+             intent = new Intent (Intent.ACTION_OPEN_DOCUMENT_TREE);
+
+             try
+               {
+                 id = EmacsActivity.ACCEPT_DOCUMENT_TREE;
+                 activity.startActivityForResult (intent, id, null);
+                 rc.thing = Integer.valueOf (0);
+               }
+             catch (Exception e)
+               {
+                 e.printStackTrace ();
+               }
+
+             notify ();
+           }
+       }
+      };
+
+    syncRunnable (runnable);
+    return rc.thing;
+  }
+
+  /* Return an array of each tree provided by the document PROVIDER
+     that Emacs has permission to access.
+
+     Value is an array if the provider really does exist, NULL
+     otherwise.  */
+
+  public String[]
+  getDocumentTrees (byte provider[])
+  {
+    String providerName;
+    List<String> treeList;
+    List<UriPermission> permissions;
+    Uri uri;
+
+    try
+      {
+       providerName = new String (provider, "US-ASCII");
+      }
+    catch (UnsupportedEncodingException exception)
+      {
+       return null;
+      }
+
+    permissions = resolver.getPersistedUriPermissions ();
+    treeList = new ArrayList<String> ();
+
+    for (UriPermission permission : permissions)
+      {
+       uri = permission.getUri ();
+
+       if (DocumentsContract.isTreeUri (uri)
+           && uri.getAuthority ().equals (providerName)
+           && permission.isReadPermission ())
+         /* Make sure the tree document ID is encoded.  Refrain from
+            encoding characters such as +:&?#, since they don't
+            conflict with file name separators or other special
+            characters.  */
+         treeList.add (Uri.encode (DocumentsContract.getTreeDocumentId (uri),
+                                   " +:&?#"));
+      }
+
+    return treeList.toArray (new String[0]);
+  }
+
+  /* Find the document ID of the file within TREE_URI designated by
+     NAME.
+
+     NAME is a ``file name'' comprised of the display names of
+     individual files.  Each constituent component prior to the last
+     must name a directory file within TREE_URI.
+
+     Upon success, return 0 or 1 (contingent upon whether or not the
+     last component within NAME is a directory) and place the document
+     ID of the named file in ID_RETURN[0].
+
+     If the designated file can't be located, but each component of
+     NAME up to the last component can and is a directory, return -2
+     and the ID of the last component located in ID_RETURN[0].
+
+     If the designated file can't be located, return -1, or signal one
+     of OperationCanceledException, SecurityException,
+     FileNotFoundException, or UnsupportedOperationException.  */
+
+  private int
+  documentIdFromName (String tree_uri, String name, String[] id_return)
+  {
+    /* Start the thread used to run SAF requests if it isn't already
+       running.  */
+
+    if (storageThread == null)
+      {
+       storageThread = new EmacsSafThread (resolver);
+       storageThread.start ();
+      }
+
+    return storageThread.documentIdFromName (tree_uri, name,
+                                            id_return);
+  }
+
+  /* Return an encoded document URI representing a tree with the
+     specified IDENTIFIER supplied by the authority AUTHORITY.
+
+     Return null instead if Emacs does not have permanent access
+     to the specified document tree recorded on disk.  */
+
+  public String
+  getTreeUri (String tree, String authority)
+  {
+    Uri uri, grantedUri;
+    List<UriPermission> permissions;
+
+    /* First, build the URI.  */
+    tree = Uri.decode (tree);
+    uri = DocumentsContract.buildTreeDocumentUri (authority, tree);
+
+    /* Now, search for it within the list of persisted URI
+       permissions.  */
+    permissions = resolver.getPersistedUriPermissions ();
+
+    for (UriPermission permission : permissions)
+      {
+       /* If the permission doesn't entitle Emacs to read access,
+          skip it.  */
+
+       if (!permission.isReadPermission ())
+         continue;
+
+        grantedUri = permission.getUri ();
+
+       if (grantedUri.equals (uri))
+         return uri.toString ();
+      }
+
+    /* Emacs doesn't have permission to access this tree URI.  */
+    return null;
+  }
+
+  /* Return file status for the document designated by the given
+     DOCUMENTID and tree URI.  If DOCUMENTID is NULL, use the document
+     ID in URI itself.
+
+     Value is null upon failure, or an array of longs [MODE, SIZE,
+     MTIM] upon success, where MODE contains the file type and access
+     modes of the file as in `struct stat', SIZE is the size of the
+     file in BYTES or -1 if not known, and MTIM is the time of the
+     last modification to this file in milliseconds since 00:00,
+     January 1st, 1970.
+
+     If NOCACHE, refrain from placing the file status within the
+     status cache.
+
+     OperationCanceledException and other typical exceptions may be
+     signaled upon receiving async input or other errors.  */
+
+  public long[]
+  statDocument (String uri, String documentId, boolean noCache)
+  {
+    /* Start the thread used to run SAF requests if it isn't already
+       running.  */
+
+    if (storageThread == null)
+      {
+       storageThread = new EmacsSafThread (resolver);
+       storageThread.start ();
+      }
+
+    return storageThread.statDocument (uri, documentId, noCache);
+  }
+
+  /* Find out whether Emacs has access to the document designated by
+     the specified DOCUMENTID within the tree URI.  If DOCUMENTID is
+     NULL, use the document ID in URI itself.
+
+     If WRITABLE, also check that the file is writable, which is true
+     if it is either a directory or its flags contains
+     FLAG_SUPPORTS_WRITE.
+
+     Value is 0 if the file is accessible, and one of the following if
+     not:
+
+       -1, if the file does not exist.
+       -2, if WRITABLE and the file is not writable.
+       -3, upon any other error.
+
+     In addition, arbitrary runtime exceptions (such as
+     SecurityException or UnsupportedOperationException) may be
+     thrown.  */
+
+  public int
+  accessDocument (String uri, String documentId, boolean writable)
+  {
+    /* Start the thread used to run SAF requests if it isn't already
+       running.  */
+
+    if (storageThread == null)
+      {
+       storageThread = new EmacsSafThread (resolver);
+       storageThread.start ();
+      }
+
+    return storageThread.accessDocument (uri, documentId, writable);
+  }
+
+  /* Open a cursor representing each entry within the directory
+     designated by the specified DOCUMENTID within the tree URI.
+
+     If DOCUMENTID is NULL, use the document ID within URI itself.
+     Value is NULL upon failure.
+
+     In addition, arbitrary runtime exceptions (such as
+     SecurityException or UnsupportedOperationException) may be
+     thrown.  */
+
+  public Cursor
+  openDocumentDirectory (String uri, String documentId)
+  {
+    /* Start the thread used to run SAF requests if it isn't already
+       running.  */
+
+    if (storageThread == null)
+      {
+       storageThread = new EmacsSafThread (resolver);
+       storageThread.start ();
+      }
+
+    return storageThread.openDocumentDirectory (uri, documentId);
+  }
+
+  /* Read a single directory entry from the specified CURSOR.  Return
+     NULL if at the end of the directory stream, and a directory entry
+     with `d_name' set to NULL if an error occurs.  */
+
+  public EmacsDirectoryEntry
+  readDirectoryEntry (Cursor cursor)
+  {
+    EmacsDirectoryEntry entry;
+    int index;
+    String name, type;
+
+    entry = new EmacsDirectoryEntry ();
+
+    while (true)
+      {
+       if (!cursor.moveToNext ())
+         return null;
+
+       /* First, retrieve the display name.  */
+       index = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
+
+       if (index < 0)
+         /* Return an invalid directory entry upon failure.  */
+         return entry;
+
+       try
+         {
+           name = cursor.getString (index);
+         }
+       catch (Exception exception)
+         {
+           return entry;
+         }
+
+       /* Skip this entry if its name cannot be represented.  NAME
+          can still be null here, since some Cursors are permitted to
+          return NULL if INDEX is not a string.  */
+
+       if (name == null || name.equals ("..")
+           || name.equals (".") || name.contains ("/")
+           || name.contains ("\0"))
+         continue;
+
+       /* Now, look for its type.  */
+
+       index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
+
+       if (index < 0)
+         /* Return an invalid directory entry upon failure.  */
+         return entry;
+
+       try
+         {
+           type = cursor.getString (index);
+         }
+       catch (Exception exception)
+         {
+           return entry;
+         }
+
+       if (type != null
+           && type.equals (Document.MIME_TYPE_DIR))
+         entry.d_type = 1;
+       entry.d_name = name;
+       return entry;
+      }
+
+    /* Not reached.  */
+  }
+
+  /* Open a file descriptor for a file document designated by
+     DOCUMENTID within the document tree identified by URI.  If
+     TRUNCATE and the document already exists, truncate its contents
+     before returning.
+
+     If READ && WRITE, open the file under either the `rw' or `rwt'
+     access mode, which implies that the value must be a seekable
+     on-disk file.  If TRUNC && WRITE, also truncate the file after it
+     is opened.
+
+     If only READ or WRITE is set, value may be a non-seekable FIFO or
+     one end of a socket pair.
+
+     Value is NULL upon failure or a parcel file descriptor upon
+     success.  Call `ParcelFileDescriptor.close' on this file
+     descriptor instead of using the `close' system call.
+
+     FileNotFoundException and/or SecurityException and
+     UnsupportedOperationException may be thrown upon failure.  */
+
+  public ParcelFileDescriptor
+  openDocument (String uri, String documentId,
+               boolean read, boolean write, boolean truncate)
+  {
+    /* Start the thread used to run SAF requests if it isn't already
+       running.  */
+
+    if (storageThread == null)
+      {
+       storageThread = new EmacsSafThread (resolver);
+       storageThread.start ();
+      }
+
+    return storageThread.openDocument (uri, documentId, read, write,
+                                      truncate);
+  }
+
+  /* Create a new document with the given display NAME within the
+     directory identified by DOCUMENTID inside the document tree
+     designated by URI.
+
+     If DOCUMENTID is NULL, create the document inside the root of
+     that tree.
+
+     Either FileNotFoundException, SecurityException or
+     UnsupportedOperationException may be thrown upon failure.
+
+     Return the document ID of the new file upon success, NULL
+     otherwise.  */
+
+  public String
+  createDocument (String uri, String documentId, String name)
+    throws FileNotFoundException
+  {
+    String mimeType, separator, mime, extension;
+    int index;
+    MimeTypeMap singleton;
+    Uri treeUri, directoryUri, docUri;
+
+    /* Try to get the MIME type for this document.
+       Default to ``application/octet-stream''.  */
+
+    mimeType = "application/octet-stream";
+
+    /* Abuse WebView stuff to get the file's MIME type.  */
+
+    index = name.lastIndexOf ('.');
+
+    if (index > 0)
+      {
+       singleton = MimeTypeMap.getSingleton ();
+       extension = name.substring (index + 1);
+       mime = singleton.getMimeTypeFromExtension (extension);
+
+       if (mime != null)
+         mimeType = mime;
+      }
+
+    /* Now parse URI.  */
+    treeUri = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (treeUri);
+
+    /* And build a file URI referring to the directory.  */
+
+    directoryUri
+      = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
+                                                          documentId);
+
+    docUri = DocumentsContract.createDocument (resolver,
+                                              directoryUri,
+                                              mimeType, name);
+
+    if (docUri == null)
+      return null;
+
+    /* Invalidate the file status of the containing directory.  */
+
+    if (storageThread != null)
+      storageThread.postInvalidateStat (treeUri, documentId);
+
+    /* Return the ID of the new document.  */
+    return DocumentsContract.getDocumentId (docUri);
+  }
+
+  /* Like `createDocument', but create a directory instead of an
+     ordinary document.  */
+
+  public String
+  createDirectory (String uri, String documentId, String name)
+    throws FileNotFoundException
+  {
+    int index;
+    Uri treeUri, directoryUri, docUri;
+
+    /* Now parse URI.  */
+    treeUri = Uri.parse (uri);
+
+    if (documentId == null)
+      documentId = DocumentsContract.getTreeDocumentId (treeUri);
+
+    /* And build a file URI referring to the directory.  */
+
+    directoryUri
+      = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
+                                                          documentId);
+
+    /* If name ends with a directory separator character, delete
+       it.  */
+
+    if (name.endsWith ("/"))
+      name = name.substring (0, name.length () - 1);
+
+    /* From Android's perspective, directories are just ordinary
+       documents with the `MIME_TYPE_DIR' type.  */
+
+    docUri = DocumentsContract.createDocument (resolver,
+                                              directoryUri,
+                                              Document.MIME_TYPE_DIR,
+                                              name);
+
+    if (docUri == null)
+      return null;
+
+    /* Return the ID of the new document, but first invalidate the
+       state of the containing directory.  */
+
+    if (storageThread != null)
+      storageThread.postInvalidateStat (treeUri, documentId);
+
+    return DocumentsContract.getDocumentId (docUri);
+  }
+
+  /* Delete the document identified by ID from the document tree
+     identified by URI.  Return 0 upon success and -1 upon
+     failure.
+
+     NAME should be the name of the document being deleted, and is
+     used to invalidate the cache.  */
+
+  public int
+  deleteDocument (String uri, String id, String name)
+    throws FileNotFoundException
+  {
+    Uri uriObject, tree;
+
+    tree = Uri.parse (uri);
+    uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, id);
+
+    if (DocumentsContract.deleteDocument (resolver, uriObject))
+      {
+       if (storageThread != null)
+         storageThread.postInvalidateCache (tree, id, name);
+
+       return 0;
+      }
+
+    return -1;
+  }
+
+  /* Rename the document designated by DOCID inside the directory tree
+     identified by URI, which should be within the directory
+     designated by DIR, to NAME.  If the file can't be renamed because
+     it doesn't support renaming, return -1, 0 otherwise.  */
+
+  public int
+  renameDocument (String uri, String docId, String dir, String name)
+    throws FileNotFoundException
+  {
+    Uri tree, uriObject;
+
+    tree = Uri.parse (uri);
+    uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, docId);
+
+    if (DocumentsContract.renameDocument (resolver, uriObject,
+                                         name)
+       != null)
+      {
+       /* Invalidate the cache.  */
+       if (storageThread != null)
+         storageThread.postInvalidateCacheDir (tree, docId,
+                                               name);
+       return 0;
+      }
+
+    /* Handle errors specially, so `android_saf_rename_document' can
+       return ENXDEV.  */
+    return -1;
+  }
+
+  /* Move the document designated by DOCID from the directory under
+     DIR_NAME designated by SRCID to the directory designated by
+     DSTID.  If the ID of the document being moved changes as a
+     consequence of the movement, return the new ID, else NULL.
+
+     URI is the document tree containing all three documents.  */
+
+  public String
+  moveDocument (String uri, String docId, String dirName,
+               String dstId, String srcId)
+    throws FileNotFoundException
+  {
+    Uri uri1, docId1, dstId1, srcId1;
+    Uri name;
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+      throw new UnsupportedOperationException ("Documents aren't capable"
+                                              + " of being moved on Android"
+                                              + " versions before 7.0.");
+
+    uri1 = Uri.parse (uri);
+    docId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, docId);
+    dstId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, dstId);
+    srcId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, srcId);
+
+    /* Move the document; this function returns the new ID of the
+       document should it change.  */
+    name = DocumentsContract.moveDocument (resolver, docId1,
+                                          srcId1, dstId1);
+
+    /* Now invalidate the caches for both DIRNAME and DOCID.  */
+
+    if (storageThread != null)
+      {
+       storageThread.postInvalidateCacheDir (uri1, docId, dirName);
+
+       /* Invalidate the stat cache entries for both the source and
+          destination directories, since their contents have
+          changed.  */
+       storageThread.postInvalidateStat (uri1, dstId);
+       storageThread.postInvalidateStat (uri1, srcId);
+      }
+
+    return (name != null
+           ? DocumentsContract.getDocumentId (name)
+           : null);
+  }
+
+  /* Return if there is a content provider by the name of AUTHORITY
+     supplying at least one tree URI Emacs retains persistent rights
+     to access.  */
+
+  public boolean
+  validAuthority (String authority)
+  {
+    List<UriPermission> permissions;
+    Uri uri;
+
+    permissions = resolver.getPersistedUriPermissions ();
+
+    for (UriPermission permission : permissions)
+      {
+       uri = permission.getUri ();
+
+       if (DocumentsContract.isTreeUri (uri)
+           && permission.isReadPermission ()
+           && uri.getAuthority ().equals (authority))
+         return true;
+      }
+
+    return false;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java 
b/java/org/gnu/emacs/EmacsSurfaceView.java
new file mode 100644
index 00000000000..c47696b35c0
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsSurfaceView.java
@@ -0,0 +1,223 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.view.View;
+
+import android.os.Build;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Paint;
+
+import java.lang.ref.WeakReference;
+
+/* This originally extended SurfaceView.  However, doing so proved to
+   be too slow, and Android's surface view keeps up to three of its
+   own back buffers, which use too much memory (up to 96 MB for a
+   single frame.) */
+
+public final class EmacsSurfaceView extends View
+{
+  private static final String TAG = "EmacsSurfaceView";
+
+  /* The complete buffer contents at the time of the last draw.  */
+  private Bitmap frontBuffer;
+
+  /* Whether frontBuffer has been updated since the last call to
+     `onDraw'.  */
+  private boolean bitmapChanged;
+
+  /* Canvas representing the front buffer.  */
+  private Canvas bitmapCanvas;
+
+  /* Reference to the last bitmap copied to the front buffer.  */
+  private WeakReference<Bitmap> bitmap;
+
+  /* Paint objects used on the main and UI threads, respectively.  */
+  private static final Paint bitmapPaint, uiThreadPaint;
+
+  static
+  {
+    /* Create two different Paint objects; one is used on the main
+       thread for buffer swaps, while the other is used from the UI
+       thread in `onDraw'.  This is necessary because Paint objects
+       are not thread-safe, even if their uses are interlocked.  */
+
+    bitmapPaint = new Paint ();
+    uiThreadPaint = new Paint ();
+  };
+
+  public
+  EmacsSurfaceView (EmacsView view)
+  {
+    super (view.getContext ());
+
+    this.bitmap = new WeakReference<Bitmap> (null);
+  }
+
+  private void
+  copyToFrontBuffer (Bitmap bitmap, Rect damageRect)
+  {
+    EmacsService.checkEmacsThread ();
+
+    if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O
+       && Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1
+       && Build.VERSION.SDK_INT != Build.VERSION_CODES.N_MR1
+       && Build.VERSION.SDK_INT != Build.VERSION_CODES.N)
+      {
+       /* If `drawBitmap' can safely be used while a bitmap is locked
+          by another thread, continue here... */
+
+       if (damageRect != null)
+         bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect,
+                                  bitmapPaint);
+       else
+         bitmapCanvas.drawBitmap (bitmap, 0f, 0f, bitmapPaint);
+      }
+    else
+      {
+       /* But if it can not, as on Android 7.0 through 8.1, then use
+          a replacement function.  */
+
+       if (damageRect != null)
+         EmacsNative.blitRect (bitmap, frontBuffer,
+                               damageRect.left,
+                               damageRect.top,
+                               damageRect.right,
+                               damageRect.bottom);
+       else
+         EmacsNative.blitRect (bitmap, frontBuffer, 0, 0,
+                               bitmap.getWidth (),
+                               bitmap.getHeight ());
+      }
+
+    /* See the large comment inside `onDraw'.  */
+    bitmapChanged = true;
+  }
+
+  private void
+  reconfigureFrontBuffer (Bitmap bitmap)
+  {
+    /* First, remove the old front buffer.  */
+
+    if (frontBuffer != null)
+      {
+       frontBuffer.recycle ();
+       frontBuffer = null;
+       bitmapCanvas = null;
+      }
+
+    this.bitmap = new WeakReference<Bitmap> (bitmap);
+
+    /* Next, create the new front buffer if necessary.  */
+
+    if (bitmap != null && frontBuffer == null)
+      {
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+         frontBuffer = Bitmap.createBitmap (bitmap.getWidth (),
+                                            bitmap.getHeight (),
+                                            Bitmap.Config.ARGB_8888,
+                                            false);
+       else
+         frontBuffer = Bitmap.createBitmap (bitmap.getWidth (),
+                                            bitmap.getHeight (),
+                                            Bitmap.Config.ARGB_8888);
+
+       bitmapCanvas = new Canvas (frontBuffer);
+
+       /* And copy over the bitmap contents.  */
+       copyToFrontBuffer (bitmap, null);
+      }
+    else if (bitmap != null)
+      /* Just copy over the bitmap contents.  */
+      copyToFrontBuffer (bitmap, null);
+  }
+
+  public synchronized void
+  setBitmap (Bitmap bitmap, Rect damageRect)
+  {
+    if (bitmap != this.bitmap.get ())
+      reconfigureFrontBuffer (bitmap);
+    else if (bitmap != null)
+      copyToFrontBuffer (bitmap, damageRect);
+
+    if (bitmap != null)
+      {
+       /* In newer versions of Android, the invalid rectangle is
+          supposedly internally calculated by the system.  How that
+          is done is unknown, but calling `invalidateRect' is now
+          deprecated.
+
+          Fortunately, nobody has deprecated the version of
+          `postInvalidate' that accepts a dirty rectangle.  */
+
+       if (damageRect != null)
+         postInvalidate (damageRect.left, damageRect.top,
+                         damageRect.right, damageRect.bottom);
+       else
+         postInvalidate ();
+      }
+  }
+
+  @Override
+  public synchronized void
+  onDraw (Canvas canvas)
+  {
+    /* Paint the view's bitmap; the bitmap might be recycled right
+       now.  */
+
+    if (frontBuffer != null)
+      {
+       /* The first time the bitmap is drawn after a buffer swap,
+          mark its contents as having changed.  This increments the
+          ``generation ID'' used by Android to avoid uploading buffer
+          textures for unchanged bitmaps.
+
+          When a buffer swap takes place, the bitmap is initially
+          updated from the Emacs thread, resulting in the generation
+          ID being increased.  If the render thread is texturizing
+          the bitmap while the swap takes place, it might record the
+          generation ID after the update for a texture containing the
+          contents of the bitmap prior to the swap, leaving the
+          texture tied to the bitmap partially updated.
+
+          Android never calls `onDraw' if the render thread is still
+          processing the bitmap.  Update the generation ID here to
+          ensure that a new texture will be uploaded if the bitmap
+          has changed.
+
+          Uploading the bitmap contents to the GPU uses an excessive
+          amount of memory, as the entire bitmap is placed into the
+          graphics command queue, but this memory is actually shared
+          among all other applications and reclaimed by the system
+          when necessary.  */
+
+       if (bitmapChanged)
+         {
+           EmacsNative.notifyPixelsChanged (frontBuffer);
+           bitmapChanged = false;
+         }
+
+       canvas.drawBitmap (frontBuffer, 0f, 0f, uiThreadPaint);
+      }
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsThread.java 
b/java/org/gnu/emacs/EmacsThread.java
new file mode 100644
index 00000000000..5307015b46f
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsThread.java
@@ -0,0 +1,82 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.lang.Thread;
+import java.util.Arrays;
+
+import android.util.Log;
+
+public final class EmacsThread extends Thread
+{
+  private static final String TAG = "EmacsThread";
+
+  /* Whether or not Emacs should be started with an additional
+     argument, and that additional argument if non-NULL.  */
+  private String extraStartupArgument;
+
+  /* Runnable run to initialize Emacs.  */
+  private Runnable paramsClosure;
+
+  /* Whether or not to open a file after starting Emacs.  */
+  private String fileToOpen;
+
+  public
+  EmacsThread (EmacsService service, Runnable paramsClosure,
+              String extraStartupArgument, String fileToOpen)
+  {
+    super ("Emacs main thread");
+    this.extraStartupArgument = extraStartupArgument;
+    this.paramsClosure = paramsClosure;
+    this.fileToOpen = fileToOpen;
+  }
+
+  @Override
+  public void
+  run ()
+  {
+    String args[];
+
+    if (fileToOpen == null)
+      {
+       if (extraStartupArgument == null)
+         args = new String[] { "libandroid-emacs.so", };
+       else
+         args = new String[] { "libandroid-emacs.so",
+                               extraStartupArgument, };
+      }
+    else
+      {
+       if (extraStartupArgument == null)
+         args = new String[] { "libandroid-emacs.so",
+                               fileToOpen, };
+       else
+         args = new String[] { "libandroid-emacs.so",
+                               extraStartupArgument,
+                               fileToOpen, };
+      }
+
+    paramsClosure.run ();
+
+    /* Run the native code now.  */
+    Log.d (TAG, "run: " + Arrays.toString (args));
+    EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsView.java 
b/java/org/gnu/emacs/EmacsView.java
new file mode 100644
index 00000000000..5a4bcbaa005
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -0,0 +1,846 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.content.Context;
+
+import android.text.InputType;
+
+import android.view.ContextMenu;
+import android.view.View;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Paint;
+
+import android.os.Build;
+import android.util.Log;
+
+/* This is an Android view which has a back and front buffer.  When
+   swapBuffers is called, the back buffer is swapped to the front
+   buffer, and any damage is invalidated.  frontBitmap and backBitmap
+   are modified and used both from the UI and the Emacs thread.  As a
+   result, there is a lock held during all drawing operations.
+
+   It is also a ViewGroup, as it also lays out children.  */
+
+public final class EmacsView extends ViewGroup
+  implements ViewTreeObserver.OnGlobalLayoutListener
+{
+  public static final String TAG = "EmacsView";
+
+  /* The associated EmacsWindow.  */
+  public EmacsWindow window;
+
+  /* The buffer bitmap.  */
+  public Bitmap bitmap;
+
+  /* The associated canvases.  */
+  public Canvas canvas;
+
+  /* The damage region.  */
+  public Region damageRegion;
+
+  /* The associated surface view.  */
+  private EmacsSurfaceView surfaceView;
+
+  /* Whether or not a configure event must be sent for the next layout
+     event regardless of what changed.  */
+  public boolean mustReportLayout;
+
+  /* Whether or not bitmaps must be recreated upon the next call to
+     getBitmap.  */
+  private boolean bitmapDirty;
+
+  /* Whether or not a popup is active.  */
+  private boolean popupActive;
+
+  /* The current context menu.  */
+  private EmacsContextMenu contextMenu;
+
+  /* The last measured width and height.  */
+  private int measuredWidth, measuredHeight;
+
+  /* Object acting as a lock for those values.  */
+  private Object dimensionsLock;
+
+  /* The serial of the last clip rectangle change.  */
+  private long lastClipSerial;
+
+  /* The InputMethodManager for this view's context.  */
+  public InputMethodManager imManager;
+
+  /* Whether or not this view is attached to a window.  */
+  public boolean isAttachedToWindow;
+
+  /* Whether or not this view should have the on screen keyboard
+     displayed whenever possible.  */
+  public boolean isCurrentlyTextEditor;
+
+  /* The associated input connection.  */
+  private EmacsInputConnection inputConnection;
+
+  /* The current IC mode.  See `android_reset_ic' for more
+     details.  */
+  private int icMode;
+
+  /* The number of calls to `resetIC' to have taken place the last
+     time an InputConnection was created.  */
+  public long icSerial;
+
+  /* The number of calls to `recetIC' that have taken place.  */
+  public volatile long icGeneration;
+
+  public
+  EmacsView (EmacsWindow window)
+  {
+    super (EmacsService.SERVICE);
+
+    Object tem;
+    Context context;
+
+    this.window = window;
+    this.damageRegion = new Region ();
+
+    setFocusable (true);
+    setFocusableInTouchMode (true);
+
+    /* Create the surface view.  */
+    this.surfaceView = new EmacsSurfaceView (this);
+    addView (this.surfaceView);
+
+    /* Get rid of the default focus highlight.  */
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
+      setDefaultFocusHighlightEnabled (false);
+
+    /* Obtain the input method manager.  */
+    context = getContext ();
+    tem = context.getSystemService (Context.INPUT_METHOD_SERVICE);
+    imManager = (InputMethodManager) tem;
+
+    /* Add this view as its own global layout listener.  */
+    getViewTreeObserver ().addOnGlobalLayoutListener (this);
+
+    /* Create an object used as a lock.  */
+    this.dimensionsLock = new Object ();
+  }
+
+  private void
+  handleDirtyBitmap ()
+  {
+    Bitmap oldBitmap;
+    int measuredWidth, measuredHeight;
+
+    synchronized (dimensionsLock)
+      {
+       /* Load measuredWidth and measuredHeight.  */
+       measuredWidth = this.measuredWidth;
+       measuredHeight = this.measuredHeight;
+      }
+
+    if (measuredWidth == 0 || measuredHeight == 0)
+      return;
+
+    if (!isAttachedToWindow)
+      return;
+
+    /* If bitmap is the same width and height as the measured width
+       and height, there is no need to do anything.  Avoid allocating
+       the extra bitmap.  */
+    if (bitmap != null
+       && (bitmap.getWidth () == measuredWidth
+           && bitmap.getHeight () == measuredHeight))
+      {
+       bitmapDirty = false;
+       return;
+      }
+
+    /* Save the old bitmap.  */
+    oldBitmap = bitmap;
+
+    /* Recreate the back buffer bitmap.  */
+    bitmap
+      = Bitmap.createBitmap (measuredWidth,
+                            measuredHeight,
+                            Bitmap.Config.ARGB_8888);
+    bitmap.eraseColor (window.background | 0xff000000);
+
+    /* And canvases.  */
+    canvas = new Canvas (bitmap);
+    canvas.save ();
+
+    /* Since the clip rectangles have been cleared, clear the clip
+       rectangle ID.  */
+    lastClipSerial = 0;
+
+    /* Copy over the contents of the old bitmap.  */
+    if (oldBitmap != null)
+      canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ());
+
+    bitmapDirty = false;
+
+    /* Explicitly free the old bitmap's memory.  */
+
+    if (oldBitmap != null)
+      oldBitmap.recycle ();
+
+    /* Some Android versions still don't free the bitmap until the
+       next GC.  */
+    Runtime.getRuntime ().gc ();
+  }
+
+  public synchronized void
+  explicitlyDirtyBitmap ()
+  {
+    bitmapDirty = true;
+  }
+
+  public synchronized Bitmap
+  getBitmap ()
+  {
+    if (bitmapDirty || bitmap == null)
+      handleDirtyBitmap ();
+
+    return bitmap;
+  }
+
+  public synchronized Canvas
+  getCanvas (EmacsGC gc)
+  {
+    int i;
+
+    if (bitmapDirty || bitmap == null)
+      handleDirtyBitmap ();
+
+    if (canvas == null)
+      return null;
+
+    /* Update clip rectangles if necessary.  */
+    if (gc.clipRectID != lastClipSerial)
+      {
+       canvas.restore ();
+       canvas.save ();
+
+       if (gc.real_clip_rects != null)
+         {
+           for (i = 0; i < gc.real_clip_rects.length; ++i)
+             canvas.clipRect (gc.real_clip_rects[i]);
+         }
+
+       lastClipSerial = gc.clipRectID;
+      }
+
+    return canvas;
+  }
+
+  public void
+  prepareForLayout (int wantedWidth, int wantedHeight)
+  {
+    synchronized (dimensionsLock)
+      {
+       measuredWidth = wantedWidth;
+       measuredHeight = wantedWidth;
+      }
+  }
+
+  @Override
+  protected void
+  onMeasure (int widthMeasureSpec, int heightMeasureSpec)
+  {
+    Rect measurements;
+    int width, height;
+
+    /* Return the width and height of the window regardless of what
+       the parent says.  */
+    measurements = window.getGeometry ();
+
+    width = measurements.width ();
+    height = measurements.height ();
+
+    /* Now apply any extra requirements in widthMeasureSpec and
+       heightMeasureSpec.  */
+
+    if (MeasureSpec.getMode (widthMeasureSpec) == MeasureSpec.EXACTLY)
+      width = MeasureSpec.getSize (widthMeasureSpec);
+    else if (MeasureSpec.getMode (widthMeasureSpec) == MeasureSpec.AT_MOST
+            && width > MeasureSpec.getSize (widthMeasureSpec))
+      width = MeasureSpec.getSize (widthMeasureSpec);
+
+    if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.EXACTLY)
+      height = MeasureSpec.getSize (heightMeasureSpec);
+    else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST
+            && height > MeasureSpec.getSize (heightMeasureSpec))
+      height = MeasureSpec.getSize (heightMeasureSpec);
+
+    super.setMeasuredDimension (width, height);
+  }
+
+  /* Return whether this view's window is focused.  This is made
+     necessary by Android 11's unreliable dispatch of
+     onWindowFocusChanged prior to gesture navigation away from a
+     frame.  */
+
+  public boolean
+  checkWindowFocus ()
+  {
+    EmacsActivity activity;
+    Object consumer;
+
+    consumer = window.getAttachedConsumer ();
+
+    if (!(consumer instanceof EmacsActivity))
+      return false;
+
+    activity = (EmacsActivity) consumer;
+    return activity.hasWindowFocus ();
+  }
+
+  /* Note that the monitor lock for the window must never be held from
+     within the lock for the view, because the window also locks the
+     other way around.  */
+
+  @Override
+  protected void
+  onLayout (boolean changed, int left, int top, int right,
+           int bottom)
+  {
+    int count, i, oldMeasuredWidth, oldMeasuredHeight;
+    View child;
+    Rect windowRect;
+    boolean needExpose;
+    WindowInsets rootWindowInsets;
+
+    count = getChildCount ();
+    needExpose = false;
+
+    synchronized (dimensionsLock)
+      {
+       /* Load measuredWidth and measuredHeight.  */
+       oldMeasuredWidth = measuredWidth;
+       oldMeasuredHeight = measuredHeight;
+
+       /* Set measuredWidth and measuredHeight.  */
+       measuredWidth = right - left;
+       measuredHeight = bottom - top;
+      }
+
+    /* If oldMeasuredHeight or oldMeasuredWidth are wrong, set changed
+       to true as well.  */
+
+    if (right - left != oldMeasuredWidth
+       || bottom - top != oldMeasuredHeight)
+      changed = true;
+
+    /* Dirty the back buffer if the layout change resulted in the view
+       being resized.  */
+
+    if (changed)
+      {
+       explicitlyDirtyBitmap ();
+
+       /* Expose the window upon a change in the view's size.  */
+
+       if (right - left > oldMeasuredWidth
+           || bottom - top > oldMeasuredHeight)
+         needExpose = true;
+
+       /* This might return NULL if this view is not attached.  */
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+         {
+           /* If a toplevel view is focused and isCurrentlyTextEditor
+              is enabled when the IME is hidden, clear
+              isCurrentlyTextEditor so it isn't shown again if the
+              user dismisses Emacs before returning.  */
+           rootWindowInsets = getRootWindowInsets ();
+
+           if (isCurrentlyTextEditor
+               && rootWindowInsets != null
+               && isAttachedToWindow
+               && !rootWindowInsets.isVisible (WindowInsets.Type.ime ())
+               /* N.B. that the keyboard is dismissed during gesture
+                  navigation under Android 30, but the system is
+                  quite tempermental regarding whether the window is
+                  focused at that point.  Ideally
+                  isCurrentlyTextEditor shouldn't be reset in that
+                  case, but detecting that situation appears to be
+                  impossible.  Sigh.  */
+               && (window == EmacsActivity.focusedWindow
+                   && hasWindowFocus ()))
+             isCurrentlyTextEditor = false;
+         }
+      }
+
+    for (i = 0; i < count; ++i)
+      {
+       child = getChildAt (i);
+
+       if (child == surfaceView)
+         child.layout (0, 0, right - left, bottom - top);
+       else if (child.getVisibility () != GONE)
+         {
+           if (!(child instanceof EmacsView))
+             continue;
+
+           /* What to do: lay out the view precisely according to its
+              window rect.  */
+           windowRect = ((EmacsView) child).window.getGeometry ();
+           child.layout (windowRect.left, windowRect.top,
+                         windowRect.right, windowRect.bottom);
+         }
+      }
+
+    /* Now report the layout change to the window.  */
+
+    if (changed || mustReportLayout)
+      {
+       mustReportLayout = false;
+       window.viewLayout (left, top, right, bottom);
+      }
+
+    if (needExpose)
+      EmacsNative.sendExpose (this.window.handle, 0, 0,
+                             right - left, bottom - top);
+  }
+
+  public void
+  damageRect (Rect damageRect)
+  {
+    EmacsService.checkEmacsThread ();
+    damageRegion.union (damageRect);
+  }
+
+  /* This method is called from both the UI thread and the Emacs
+     thread.  */
+
+  public void
+  swapBuffers ()
+  {
+    Canvas canvas;
+    Rect damageRect;
+    Bitmap bitmap;
+
+    /* Make sure this function is called only from the Emacs
+       thread.  */
+    EmacsService.checkEmacsThread ();
+
+    damageRect = null;
+
+    /* Now see if there is a damage region.  */
+
+    if (damageRegion.isEmpty ())
+      return;
+
+    /* And extract and clear the damage region.  */
+
+    damageRect = damageRegion.getBounds ();
+    damageRegion.setEmpty ();
+
+    bitmap = getBitmap ();
+
+    /* Transfer the bitmap to the surface view, then invalidate
+       it.  */
+    surfaceView.setBitmap (bitmap, damageRect);
+  }
+
+  @Override
+  public boolean
+  onKeyDown (int keyCode, KeyEvent event)
+  {
+    if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
+        || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+        || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE)
+       && !EmacsNative.shouldForwardMultimediaButtons ())
+      return false;
+
+    window.onKeyDown (keyCode, event);
+    return true;
+  }
+
+  @Override
+  public boolean
+  onKeyMultiple (int keyCode, int repeatCount, KeyEvent event)
+  {
+    if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
+        || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+        || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE)
+       && !EmacsNative.shouldForwardMultimediaButtons ())
+      return false;
+
+    window.onKeyDown (keyCode, event);
+    return true;
+  }
+
+  @Override
+  public boolean
+  onKeyUp (int keyCode, KeyEvent event)
+  {
+    if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
+        || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+        || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE)
+       && !EmacsNative.shouldForwardMultimediaButtons ())
+      return false;
+
+    window.onKeyUp (keyCode, event);
+    return true;
+  }
+
+  @Override
+  public void
+  onFocusChanged (boolean gainFocus, int direction,
+                 Rect previouslyFocusedRect)
+  {
+    window.onFocusChanged (gainFocus);
+    super.onFocusChanged (gainFocus, direction,
+                         previouslyFocusedRect);
+  }
+
+  @Override
+  public boolean
+  onGenericMotionEvent (MotionEvent motion)
+  {
+    return window.onGenericMotionEvent (motion);
+  }
+
+  @Override
+  public boolean
+  onTouchEvent (MotionEvent motion)
+  {
+    return window.onTouchEvent (motion);
+  }
+
+
+
+  private void
+  moveChildToBack (View child)
+  {
+    int index;
+
+    index = indexOfChild (child);
+
+    if (index > 0)
+      {
+       detachViewFromParent (index);
+
+       /* The view at 0 is the surface view.  */
+       attachViewToParent (child, 1,
+                           child.getLayoutParams());
+      }
+  }
+
+  /* The following two functions must not be called if the view has no
+     parent, or is parented to an activity.  */
+
+  public void
+  raise ()
+  {
+    EmacsView parent;
+
+    parent = (EmacsView) getParent ();
+
+    if (parent.indexOfChild (this)
+       == parent.getChildCount () - 1)
+      return;
+
+    parent.bringChildToFront (this);
+  }
+
+  public void
+  lower ()
+  {
+    EmacsView parent;
+
+    parent = (EmacsView) getParent ();
+
+    if (parent.indexOfChild (this) == 1)
+      return;
+
+    parent.moveChildToBack (this);
+  }
+
+  @Override
+  protected void
+  onCreateContextMenu (ContextMenu menu)
+  {
+    if (contextMenu == null)
+      return;
+
+    contextMenu.expandTo (menu, this);
+  }
+
+  public boolean
+  popupMenu (EmacsContextMenu menu, int xPosition,
+            int yPosition, boolean force)
+  {
+    if (popupActive && !force)
+      return false;
+
+    contextMenu = menu;
+    popupActive = true;
+
+    /* Use showContextMenu (float, float) on N to get actual popup
+       behavior.  */
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+      return showContextMenu ((float) xPosition, (float) yPosition);
+    else
+      return showContextMenu ();
+  }
+
+  public void
+  cancelPopupMenu ()
+  {
+    if (!popupActive)
+      throw new IllegalStateException ("cancelPopupMenu called without"
+                                      + " popupActive set");
+
+    contextMenu = null;
+    popupActive = false;
+
+    /* It is not possible to know with 100% certainty which activity
+       is currently displaying the context menu.  Loop through each
+       activity and call `closeContextMenu' instead.  */
+
+    for (EmacsWindowAttachmentManager.WindowConsumer consumer
+          : EmacsWindowAttachmentManager.MANAGER.consumers)
+      {
+       if (consumer instanceof EmacsActivity)
+         ((EmacsActivity) consumer).closeContextMenu ();
+      }
+  }
+
+  @Override
+  public synchronized void
+  onDetachedFromWindow ()
+  {
+    isAttachedToWindow = false;
+
+    /* Recycle the bitmap and call GC.  */
+
+    if (bitmap != null)
+      bitmap.recycle ();
+
+    bitmap = null;
+    canvas = null;
+    surfaceView.setBitmap (null, null);
+
+    /* Collect the bitmap storage; it could be large.  */
+    Runtime.getRuntime ().gc ();
+
+    super.onDetachedFromWindow ();
+  }
+
+  @Override
+  public synchronized void
+  onAttachedToWindow ()
+  {
+    isAttachedToWindow = true;
+
+    /* Dirty the bitmap, as it was destroyed when onDetachedFromWindow
+       was called.  */
+    bitmapDirty = true;
+
+    synchronized (dimensionsLock)
+      {
+       /* Now expose the view contents again.  */
+       EmacsNative.sendExpose (this.window.handle, 0, 0,
+                               measuredWidth, measuredHeight);
+      }
+
+    super.onAttachedToWindow ();
+  }
+
+  public void
+  showOnScreenKeyboard ()
+  {
+    /* Specifying no flags at all tells the system the user asked for
+       the input method to be displayed.  */
+
+    imManager.showSoftInput (this, 0);
+    isCurrentlyTextEditor = true;
+  }
+
+  public void
+  hideOnScreenKeyboard ()
+  {
+    imManager.hideSoftInputFromWindow (this.getWindowToken (),
+                                      0);
+    isCurrentlyTextEditor = false;
+  }
+
+  @Override
+  public InputConnection
+  onCreateInputConnection (EditorInfo info)
+  {
+    int mode;
+    int[] selection;
+
+    /* Figure out what kind of IME behavior Emacs wants.  */
+    mode = getICMode ();
+
+    /* Make sure the input method never displays a full screen input
+       box that obscures Emacs.  */
+    info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
+    info.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
+
+    /* Set a reasonable inputType.  */
+    info.inputType = InputType.TYPE_CLASS_TEXT;
+
+    /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't
+       initialize the input connection.  */
+
+    if (mode == EmacsService.IC_MODE_NULL)
+      {
+       info.inputType = InputType.TYPE_NULL;
+       return null;
+      }
+
+    /* Set icSerial.  If icSerial < icGeneration, the input connection
+       has been reset, and future input should be ignored until a new
+       connection is created.  */
+
+    icSerial = icGeneration;
+
+    /* Reset flags set by the previous input method.  */
+
+    EmacsNative.clearInputFlags (window.handle);
+
+    /* Obtain the current position of point and set it as the
+       selection.  Don't do this under one specific situation: if
+       `android_update_ic' is being called in the main thread, trying
+       to synchronize with it can cause a dead lock in the IM manager.
+       See icBeginSynchronous in EmacsService.java for more
+       details.  */
+
+    selection = EmacsService.viewGetSelection (window.handle);
+
+    if (selection == null)
+      {
+       /* If the selection could not be obtained, return 0 by 0.
+          However, ask for the selection position to be updated as
+          soon as possible.  */
+
+       selection = new int[] { 0, 0, };
+       EmacsNative.requestSelectionUpdate (window.handle);
+      }
+
+    if (mode == EmacsService.IC_MODE_ACTION)
+      info.imeOptions |= EditorInfo.IME_ACTION_DONE;
+
+    /* Set the initial selection fields.  */
+    info.initialSelStart = selection[0];
+    info.initialSelEnd = selection[1];
+
+    /* Create the input connection if necessary.  */
+
+    if (inputConnection == null)
+      inputConnection = new EmacsInputConnection (this);
+    else
+      /* Clear several pieces of state in the input connection.  */
+      inputConnection.reset ();
+
+    /* Return the input connection.  */
+    return inputConnection;
+  }
+
+  @Override
+  public synchronized boolean
+  onCheckIsTextEditor ()
+  {
+    /* If value is true, then the system will display the on screen
+       keyboard.  */
+    return isCurrentlyTextEditor;
+  }
+
+  @Override
+  public boolean
+  isOpaque ()
+  {
+    /* Returning true here allows the system to not draw the contents
+       of windows underneath this view, thereby improving
+       performance.  */
+    return true;
+  }
+
+  public synchronized void
+  setICMode (int icMode)
+  {
+    this.icMode = icMode;
+  }
+
+  public synchronized int
+  getICMode ()
+  {
+    return icMode;
+  }
+
+  @Override
+  public void
+  onGlobalLayout ()
+  {
+    int[] locations;
+
+    /* Get the absolute offset of this view and specify its left and
+       top position in subsequent ConfigureNotify events.  */
+
+    locations = new int[2];
+    getLocationInWindow (locations);
+    window.notifyContentRectPosition (locations[0],
+                                     locations[1]);
+  }
+
+  @Override
+  public WindowInsets
+  onApplyWindowInsets (WindowInsets insets)
+  {
+    WindowInsets rootWindowInsets;
+
+    /* This function is called when window insets change, which
+       encompasses input method visibility changes under Android 30
+       and later.  If a toplevel view is focused and
+       isCurrentlyTextEditor is enabled when the IME is hidden, clear
+       isCurrentlyTextEditor so it isn't shown again if the user
+       dismisses Emacs before returning.  */
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
+      return super.onApplyWindowInsets (insets);
+
+    /* This might return NULL if this view is not attached.  */
+    rootWindowInsets = getRootWindowInsets ();
+
+    if (isCurrentlyTextEditor
+       && rootWindowInsets != null
+       && isAttachedToWindow
+       && !rootWindowInsets.isVisible (WindowInsets.Type.ime ())
+       && window == EmacsActivity.focusedWindow)
+      isCurrentlyTextEditor = false;
+
+    return super.onApplyWindowInsets (insets);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsWindow.java 
b/java/org/gnu/emacs/EmacsWindow.java
new file mode 100644
index 00000000000..aff5046b22e
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -0,0 +1,1445 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.lang.IllegalStateException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import android.content.Context;
+
+import android.graphics.Rect;
+import android.graphics.Canvas;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+
+import android.view.View;
+import android.view.ViewManager;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.InputDevice;
+import android.view.WindowManager;
+
+import android.util.Log;
+
+import android.os.Build;
+
+/* This defines a window, which is a handle.  Windows represent a
+   rectangular subset of the screen with their own contents.
+
+   Windows either have a parent window, in which case their views are
+   attached to the parent's view, or are "floating", in which case
+   their views are attached to the parent activity (if any), else
+   nothing.
+
+   Views are also drawables, meaning they can accept drawing
+   requests.  */
+
+public final class EmacsWindow extends EmacsHandleObject
+  implements EmacsDrawable
+{
+  private static final String TAG = "EmacsWindow";
+
+  private static class Coordinate
+  {
+    /* Integral coordinate.  */
+    int x, y;
+
+    /* Button associated with the coordinate, or 0 if it is a touch
+       event.  */
+    int button;
+
+    /* Pointer ID associated with the coordinate.  */
+    int id;
+
+    public
+    Coordinate (int x, int y, int button, int id)
+    {
+      this.x = x;
+      this.y = y;
+      this.button = button;
+      this.id = id;
+    }
+  };
+
+  /* The view associated with the window.  */
+  public EmacsView view;
+
+  /* The geometry of the window.  */
+  private Rect rect;
+
+  /* The parent window, or null if it is the root window.  */
+  public EmacsWindow parent;
+
+  /* List of all children in stacking order.  This must be kept
+     consistent with their Z order!  */
+  public ArrayList<EmacsWindow> children;
+
+  /* Map between pointer identifiers and last known position.  Used to
+     compute which pointer changed upon a touch event.  */
+  private HashMap<Integer, Coordinate> pointerMap;
+
+  /* The window consumer currently attached, if it exists.  */
+  private EmacsWindowAttachmentManager.WindowConsumer attached;
+
+  /* The window background scratch GC.  foreground is always the
+     window background.  */
+  private EmacsGC scratchGC;
+
+  /* The button state and keyboard modifier mask at the time of the
+     last button press or release event.  */
+  public int lastButtonState;
+
+  /* Whether or not the window is mapped.  */
+  private volatile boolean isMapped;
+
+  /* Whether or not to ask for focus upon being mapped.  */
+  private boolean dontFocusOnMap;
+
+  /* Whether or not the window is override-redirect.  An
+     override-redirect window always has its own system window.  */
+  private boolean overrideRedirect;
+
+  /* The window manager that is the parent of this window.  NULL if
+     there is no such window manager.  */
+  private WindowManager windowManager;
+
+  /* The time of the last KEYCODE_VOLUME_DOWN release.  This is used
+     to quit Emacs upon two rapid clicks of the volume down
+     button.  */
+  private long lastVolumeButtonRelease;
+
+  /* Linked list of character strings which were recently sent as
+     events.  */
+  public LinkedHashMap<Integer, String> eventStrings;
+
+  /* Whether or not this window is fullscreen.  */
+  public boolean fullscreen;
+
+  /* The window background pixel.  This is used by EmacsView when
+     creating new bitmaps.  */
+  public volatile int background;
+
+  /* The position of this window relative to the root window.  */
+  public int xPosition, yPosition;
+
+  public
+  EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
+              int width, int height, boolean overrideRedirect)
+  {
+    super (handle);
+
+    rect = new Rect (x, y, x + width, y + height);
+    pointerMap = new HashMap<Integer, Coordinate> ();
+
+    /* Create the view from the context's UI thread.  The window is
+       unmapped, so the view is GONE.  */
+    view = EmacsService.SERVICE.getEmacsView (this, View.GONE,
+                                             parent == null);
+    this.parent = parent;
+    this.overrideRedirect = overrideRedirect;
+
+    /* Create the list of children.  */
+    children = new ArrayList<EmacsWindow> ();
+
+    if (parent != null)
+      {
+       parent.children.add (this);
+        EmacsService.SERVICE.runOnUiThread (new Runnable () {
+           @Override
+           public void
+           run ()
+           {
+             parent.view.addView (view);
+           }
+         });
+      }
+
+    scratchGC = new EmacsGC ((short) 0);
+
+    /* Create the map of input method-committed strings.  Keep at most
+       ten strings in the map.  */
+
+    eventStrings
+      = new LinkedHashMap<Integer, String> () {
+         @Override
+         protected boolean
+         removeEldestEntry (Map.Entry<Integer, String> entry)
+         {
+           return size () > 10;
+         }
+       };
+  }
+
+  public void
+  changeWindowBackground (int pixel)
+  {
+    /* scratchGC is used as the argument to a FillRectangles req.  */
+    scratchGC.foreground = pixel;
+    scratchGC.markDirty (false);
+
+    /* Make the background known to the view as well.  */
+    background = pixel;
+  }
+
+  public synchronized Rect
+  getGeometry ()
+  {
+    return new Rect (rect);
+  }
+
+  @Override
+  public synchronized void
+  destroyHandle () throws IllegalStateException
+  {
+    if (parent != null)
+      parent.children.remove (this);
+
+    EmacsActivity.invalidateFocus ();
+
+    if (!children.isEmpty ())
+      throw new IllegalStateException ("Trying to destroy window with "
+                                      + "children!");
+
+    /* Remove the view from its parent and make it invisible.  */
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       public void
+       run ()
+       {
+         ViewManager parent;
+         EmacsWindowAttachmentManager manager;
+
+         if (EmacsActivity.focusedWindow == EmacsWindow.this)
+           EmacsActivity.focusedWindow = null;
+
+         manager = EmacsWindowAttachmentManager.MANAGER;
+         view.setVisibility (View.GONE);
+
+         /* If the window manager is set, use that instead.  */
+         if (windowManager != null)
+           parent = windowManager;
+         else
+           parent = (ViewManager) view.getParent ();
+         windowManager = null;
+
+         if (parent != null)
+           parent.removeView (view);
+
+         manager.detachWindow (EmacsWindow.this);
+       }
+      });
+
+    super.destroyHandle ();
+  }
+
+  public void
+  setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer)
+  {
+    attached = consumer;
+  }
+
+  public EmacsWindowAttachmentManager.WindowConsumer
+  getAttachedConsumer ()
+  {
+    return attached;
+  }
+
+  public synchronized long
+  viewLayout (int left, int top, int right, int bottom)
+  {
+    int rectWidth, rectHeight;
+
+    rect.left = left;
+    rect.top = top;
+    rect.right = right;
+    rect.bottom = bottom;
+
+    rectWidth = right - left;
+    rectHeight = bottom - top;
+
+    /* If parent is null, use xPosition and yPosition instead of the
+       geometry rectangle positions.  */
+
+    if (parent == null)
+      {
+       left = xPosition;
+       top = yPosition;
+      }
+
+    return EmacsNative.sendConfigureNotify (this.handle,
+                                           System.currentTimeMillis (),
+                                           left, top, rectWidth,
+                                           rectHeight);
+  }
+
+  public void
+  requestViewLayout ()
+  {
+    view.explicitlyDirtyBitmap ();
+
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         if (overrideRedirect)
+           /* Set the layout parameters again.  */
+           view.setLayoutParams (getWindowLayoutParams ());
+
+         view.mustReportLayout = true;
+         view.requestLayout ();
+       }
+      });
+  }
+
+  public synchronized void
+  resizeWindow (int width, int height)
+  {
+    rect.right = rect.left + width;
+    rect.bottom = rect.top + height;
+
+    requestViewLayout ();
+  }
+
+  public synchronized void
+  moveWindow (int x, int y)
+  {
+    int width, height;
+
+    width = rect.width ();
+    height = rect.height ();
+
+    rect.left = x;
+    rect.top = y;
+    rect.right = x + width;
+    rect.bottom = y + height;
+
+    requestViewLayout ();
+  }
+
+  private WindowManager.LayoutParams
+  getWindowLayoutParams ()
+  {
+    WindowManager.LayoutParams params;
+    int flags, type;
+    Rect rect;
+
+    flags = 0;
+    rect = getGeometry ();
+    flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+    flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+    type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+
+    params
+      = new WindowManager.LayoutParams (rect.width (), rect.height (),
+                                       rect.left, rect.top,
+                                       type, flags,
+                                       PixelFormat.RGBA_8888);
+    params.gravity = Gravity.TOP | Gravity.LEFT;
+    return params;
+  }
+
+  private Context
+  findSuitableActivityContext ()
+  {
+    /* Find a recently focused activity.  */
+    if (!EmacsActivity.focusedActivities.isEmpty ())
+      return EmacsActivity.focusedActivities.get (0);
+
+    /* Return the service context, which probably won't work.  */
+    return EmacsService.SERVICE;
+  }
+
+  public synchronized void
+  mapWindow ()
+  {
+    final int width, height;
+
+    if (isMapped)
+      return;
+
+    isMapped = true;
+    width = rect.width ();
+    height = rect.height ();
+
+    if (parent == null)
+      {
+        EmacsService.SERVICE.runOnUiThread (new Runnable () {
+           @Override
+           public void
+           run ()
+           {
+             EmacsWindowAttachmentManager manager;
+             WindowManager windowManager;
+             Context ctx;
+             Object tem;
+             WindowManager.LayoutParams params;
+
+             /* Make the view visible, first of all.  */
+             view.setVisibility (View.VISIBLE);
+
+             if (!overrideRedirect)
+               {
+                 manager = EmacsWindowAttachmentManager.MANAGER;
+
+                 /* If parent is the root window, notice that there are new
+                    children available for interested activites to pick
+                    up.  */
+                 manager.registerWindow (EmacsWindow.this);
+
+                 if (!getDontFocusOnMap ())
+                   /* Eventually this should check no-focus-on-map.  */
+                   view.requestFocus ();
+               }
+             else
+               {
+                 /* But if the window is an override-redirect window,
+                    then:
+
+                    - Find an activity that is currently active.
+
+                    - Map the window as a panel on top of that
+                       activity using the system window manager.  */
+
+                 ctx = findSuitableActivityContext ();
+                 tem = ctx.getSystemService (Context.WINDOW_SERVICE);
+                 windowManager = (WindowManager) tem;
+
+                 /* Calculate layout parameters.  */
+                 params = getWindowLayoutParams ();
+                 view.setLayoutParams (params);
+
+                 /* Attach the view.  */
+                 try
+                   {
+                     view.prepareForLayout (width, height);
+                     windowManager.addView (view, params);
+
+                     /* Record the window manager being used in the
+                        EmacsWindow object.  */
+                     EmacsWindow.this.windowManager = windowManager;
+                   }
+                 catch (Exception e)
+                   {
+                     Log.w (TAG,
+                            "failed to attach override-redirect window, " + e);
+                   }
+               }
+           }
+         });
+      }
+    else
+      {
+       /* Do the same thing as above, but don't register this
+          window.  */
+        EmacsService.SERVICE.runOnUiThread (new Runnable () {
+           @Override
+           public void
+           run ()
+           {
+             /* Prior to mapping the view, set its measuredWidth and
+                measuredHeight to some reasonable value, in order to
+                avoid excessive bitmap dirtying.  */
+
+             view.prepareForLayout (width, height);
+             view.setVisibility (View.VISIBLE);
+
+             if (!getDontFocusOnMap ())
+               view.requestFocus ();
+           }
+         });
+      }
+  }
+
+  public synchronized void
+  unmapWindow ()
+  {
+    if (!isMapped)
+      return;
+
+    isMapped = false;
+
+    view.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         EmacsWindowAttachmentManager manager;
+
+         manager = EmacsWindowAttachmentManager.MANAGER;
+
+         view.setVisibility (View.GONE);
+
+         /* Detach the view from the window manager if possible.  */
+         if (windowManager != null)
+           windowManager.removeView (view);
+         windowManager = null;
+
+         /* Now that the window is unmapped, unregister it as
+            well.  */
+         manager.detachWindow (EmacsWindow.this);
+       }
+      });
+  }
+
+  @Override
+  public Canvas
+  lockCanvas (EmacsGC gc)
+  {
+    return view.getCanvas (gc);
+  }
+
+  @Override
+  public void
+  damageRect (Rect damageRect)
+  {
+    view.damageRect (damageRect);
+  }
+
+  public void
+  swapBuffers ()
+  {
+    view.swapBuffers ();
+  }
+
+  public void
+  clearWindow ()
+  {
+    EmacsService.SERVICE.fillRectangle (this, scratchGC,
+                                       0, 0, rect.width (),
+                                       rect.height ());
+  }
+
+  public void
+  clearArea (int x, int y, int width, int height)
+  {
+    EmacsService.SERVICE.fillRectangle (this, scratchGC,
+                                       x, y, width, height);
+  }
+
+  @Override
+  public Bitmap
+  getBitmap ()
+  {
+    return view.getBitmap ();
+  }
+
+  /* event.getCharacters is used because older input methods still
+     require it.  */
+  @SuppressWarnings ("deprecation")
+  public int
+  getEventUnicodeChar (KeyEvent event, int state)
+  {
+    String characters;
+
+    if (event.getUnicodeChar (state) != 0)
+      return event.getUnicodeChar (state);
+
+    characters = event.getCharacters ();
+
+    if (characters != null && characters.length () == 1)
+      return characters.charAt (0);
+
+    return characters == null ? 0 : -1;
+  }
+
+  public void
+  saveUnicodeString (int serial, String string)
+  {
+    eventStrings.put (serial, string);
+  }
+
+
+
+  /* Return the modifier mask associated with the specified keyboard
+     input EVENT.  Replace bits corresponding to Left or Right keys
+     with their corresponding general modifier bits.  */
+
+  private int
+  eventModifiers (KeyEvent event)
+  {
+    int state;
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
+      state = event.getModifiers ();
+    else
+      {
+       /* Replace this with getMetaState and manual
+          normalization.  */
+       state = event.getMetaState ();
+
+       /* Normalize the state by setting the generic modifier bit if
+          either a left or right modifier is pressed.  */
+
+       if ((state & KeyEvent.META_ALT_LEFT_ON) != 0
+           || (state & KeyEvent.META_ALT_RIGHT_ON) != 0)
+         state |= KeyEvent.META_ALT_MASK;
+
+       if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0
+           || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0)
+         state |= KeyEvent.META_CTRL_MASK;
+      }
+
+    return state;
+  }
+
+  /* event.getCharacters is used because older input methods still
+     require it.  */
+  @SuppressWarnings ("deprecation")
+  public void
+  onKeyDown (int keyCode, KeyEvent event)
+  {
+    int state, state_1;
+    long serial;
+    String characters;
+
+    state = eventModifiers (event);
+
+    /* Ignore meta-state understood by Emacs for now, or key presses
+       such as Ctrl+C and Meta+C will not be recognized as an ASCII
+       key press event.  */
+
+    state_1
+      = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK
+                 | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK);
+
+    synchronized (eventStrings)
+      {
+       serial
+         = EmacsNative.sendKeyPress (this.handle,
+                                     event.getEventTime (),
+                                     state, keyCode,
+                                     getEventUnicodeChar (event,
+                                                          state_1));
+
+       characters = event.getCharacters ();
+
+       if (characters != null && characters.length () > 1)
+         saveUnicodeString ((int) serial, characters);
+      }
+  }
+
+  public void
+  onKeyUp (int keyCode, KeyEvent event)
+  {
+    int state, state_1;
+    long time;
+
+    /* Compute the event's modifier mask.  */
+    state = eventModifiers (event);
+
+    /* Ignore meta-state understood by Emacs for now, or key presses
+       such as Ctrl+C and Meta+C will not be recognized as an ASCII
+       key press event.  */
+
+    state_1
+      = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK
+                 | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK);
+
+    EmacsNative.sendKeyRelease (this.handle,
+                               event.getEventTime (),
+                               state, keyCode,
+                               getEventUnicodeChar (event,
+                                                    state_1));
+
+    if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
+      {
+       /* Check if this volume down press should quit Emacs.
+          Most Android devices have no physical keyboard, so it
+          is unreasonably hard to press C-g.  */
+
+       time = event.getEventTime ();
+
+       if (time - lastVolumeButtonRelease < 350)
+         EmacsNative.quit ();
+
+       lastVolumeButtonRelease = time;
+      }
+  }
+
+  public void
+  onFocusChanged (boolean gainFocus)
+  {
+    EmacsActivity.invalidateFocus ();
+  }
+
+  /* Notice that the activity has been detached or destroyed.
+
+     ISFINISHING is set if the activity is not the main activity, or
+     if the activity was not destroyed in response to explicit user
+     action.  */
+
+  public void
+  onActivityDetached (boolean isFinishing)
+  {
+    /* Destroy the associated frame when the activity is detached in
+       response to explicit user action.  */
+
+    if (isFinishing)
+      EmacsNative.sendWindowAction (this.handle, 0);
+  }
+
+
+
+  /* Mouse and touch event handling.
+
+     Android does not conceptually distinguish between mouse events
+     (those coming from a device whose movement affects the on-screen
+     pointer image) and touch screen events.  Each click or touch
+     starts a single pointer gesture sequence, and subsequent motion
+     of the device will result in updates being reported relative to
+     that sequence until the mouse button or touch is released.
+
+     When a touch, click, or pointer motion takes place, several kinds
+     of event can be sent:
+
+     ACTION_DOWN or ACTION_POINTER_DOWN is sent with a new coordinate
+     and an associated ``pointer ID'' identifying the event and its
+     gesture sequence when a click or touch takes place.  Emacs is
+     responsible for recording both the position and pointer ID of
+     this click for the purpose of determining future changes to its
+     position.
+
+     ACTION_UP or ACTION_POINTER_UP is sent with a pointer ID when the
+     click associated with a previous ACTION_DOWN event is released.
+
+     ACTION_CANCEL (or ACTION_POINTER_UP with FLAG_CANCELED) is sent
+     if a similar situation transpires: the window system has chosen
+     to grab the click, and future changes to its position will no
+     longer be reported to Emacs.
+
+     ACTION_MOVE is sent if a coordinate tied to a click that has not
+     been released changes.  Emacs processes this event by comparing
+     each of the coordinates within the event with its recollection of
+     those contained within prior ACTION_DOWN and ACTION_MOVE events;
+     the pointer ID of the differing coordinate is then reported
+     within a touch or pointer motion event along with its new
+     position.
+
+     The events described above are all sent for both touch and mouse
+     click events.  Determining whether an ACTION_DOWN event is
+     associated with a button event is performed by inspecting the
+     mouse button state associated with that event.  If it contains
+     any mouse buttons that were not contained in the button state at
+     the time of the last ACTION_DOWN or ACTION_UP event, the
+     coordinate contained within is assumed to be a mouse click,
+     leading to it and associated motion or ACTION_UP events being
+     reported as mouse button or motion events.  Otherwise, those
+     events are reported as touch screen events, with the touch ID set
+     to the pointer ID.
+
+     In addition to the events illustrated above, Android also sends
+     several other types of event upon select types of activity from a
+     mouse device:
+
+     ACTION_HOVER_MOVE is sent with the coordinate of the mouse
+     pointer if it moves above a frame prior to any click taking
+     place.  Emacs sends a mouse motion event containing the
+     coordinate.
+
+     ACTION_HOVER_ENTER and ACTION_HOVER_LEAVE are respectively sent
+     when the mouse pointer enters and leaves a frame.  Moreover,
+     ACTION_HOVER_LEAVE events are sent immediately before an
+     ACTION_DOWN event associated with a mouse click.  These
+     extraneous events are distinct in that their button states always
+     contain an additional button compared to the button state
+     recorded at the time of the last ACTION_UP event.
+
+     On Android 6.0 and later, ACTION_BUTTON_PRESS is sent with the
+     coordinate of the mouse pointer if a mouse click occurs,
+     alongside a ACTION_DOWN event.  ACTION_BUTTON_RELEASE is sent
+     with the same information upon a mouse click being released, also
+     accompanying an ACTION_UP event.
+
+     However, both types of button events are implemented in a buggy
+     fashion and cannot be used to report button events.  */
+
+  /* Look through the button state to determine what button EVENT was
+     generated from.  DOWN is true if EVENT is a button press event,
+     false otherwise.  Value is the X number of the button.  */
+
+  private int
+  whatButtonWasIt (MotionEvent event, boolean down)
+  {
+    int eventState, notIn;
+
+    /* Obtain the new button state.  */
+    eventState = event.getButtonState ();
+
+    /* Compute which button is now set or no longer set.  */
+
+    notIn = (down ? eventState & ~lastButtonState
+            : lastButtonState & ~eventState);
+
+    if ((notIn & (MotionEvent.BUTTON_PRIMARY
+                 | MotionEvent.BUTTON_SECONDARY
+                 | MotionEvent.BUTTON_TERTIARY)) == 0)
+      /* No buttons have been pressed, so this is a touch event.  */
+      return 0;
+
+    if ((notIn & MotionEvent.BUTTON_PRIMARY) != 0)
+      return 1;
+
+    if ((notIn & MotionEvent.BUTTON_SECONDARY) != 0)
+      return 3;
+
+    if ((notIn & MotionEvent.BUTTON_TERTIARY) != 0)
+      return 2;
+
+    /* Buttons 4, 5, 6 and 7 are actually scroll wheels under X.
+       Thus, report additional buttons starting at 8.  */
+
+    if ((notIn & MotionEvent.BUTTON_BACK) != 0)
+      return 8;
+
+    if ((notIn & MotionEvent.BUTTON_FORWARD) != 0)
+      return 9;
+
+    /* Report stylus events as touch screen events.  */
+
+    if ((notIn & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0)
+      return 0;
+
+    if ((notIn & MotionEvent.BUTTON_STYLUS_SECONDARY) != 0)
+      return 0;
+
+    /* Not a real value.  */
+    return 11;
+  }
+
+  /* Return the mouse button associated with the specified ACTION_DOWN
+     or ACTION_POINTER_DOWN EVENT.
+
+     Value is 0 if no mouse button was pressed, or the X number of
+     that mouse button.  */
+
+  private int
+  buttonForEvent (MotionEvent event)
+  {
+    /* ICS and earlier don't support true mouse button events, so
+       treat all down events as touch screen events.  */
+
+    if (Build.VERSION.SDK_INT
+       < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+      return 0;
+
+    return whatButtonWasIt (event, true);
+  }
+
+  /* Return the coordinate object associated with the specified
+     EVENT, or null if it is not known.  */
+
+  private Coordinate
+  figureChange (MotionEvent event)
+  {
+    int i, truncatedX, truncatedY, pointerIndex, pointerID, count;
+    Coordinate coordinate;
+
+    /* Initialize this variable now.  */
+    coordinate = null;
+
+    switch (event.getActionMasked ())
+      {
+      case MotionEvent.ACTION_DOWN:
+       /* Primary pointer pressed with index 0.  */
+
+       pointerID = event.getPointerId (0);
+       coordinate = new Coordinate ((int) event.getX (0),
+                                    (int) event.getY (0),
+                                    buttonForEvent (event),
+                                    pointerID);
+       pointerMap.put (pointerID, coordinate);
+       break;
+
+      case MotionEvent.ACTION_UP:
+      case MotionEvent.ACTION_CANCEL:
+       /* Primary pointer released with index 0.  */
+       pointerID = event.getPointerId (0);
+       coordinate = pointerMap.remove (pointerID);
+       break;
+
+      case MotionEvent.ACTION_POINTER_DOWN:
+       /* New pointer.  Find the pointer ID from the index and place
+          it in the map.  */
+       pointerIndex = event.getActionIndex ();
+       pointerID = event.getPointerId (pointerIndex);
+       coordinate = new Coordinate ((int) event.getX (0),
+                                    (int) event.getY (0),
+                                    buttonForEvent (event),
+                                    pointerID);
+       pointerMap.put (pointerID, coordinate);
+       break;
+
+      case MotionEvent.ACTION_POINTER_UP:
+       /* Pointer removed.  Remove it from the map.  */
+       pointerIndex = event.getActionIndex ();
+       pointerID = event.getPointerId (pointerIndex);
+       coordinate = pointerMap.remove (pointerID);
+       break;
+
+      default:
+
+       /* Loop through each pointer in the event.  */
+
+       count = event.getPointerCount ();
+       for (i = 0; i < count; ++i)
+         {
+           pointerID = event.getPointerId (i);
+
+           /* Look up that pointer in the map.  */
+           coordinate = pointerMap.get (pointerID);
+
+           if (coordinate != null)
+             {
+               /* See if coordinates have changed.  */
+               truncatedX = (int) event.getX (i);
+               truncatedY = (int) event.getY (i);
+
+               if (truncatedX != coordinate.x
+                   || truncatedY != coordinate.y)
+                 {
+                   /* The pointer changed.  Update the coordinate and
+                      break out of the loop.  */
+                   coordinate.x = truncatedX;
+                   coordinate.y = truncatedY;
+
+                   break;
+                 }
+             }
+         }
+
+       /* Set coordinate to NULL if the loop failed to find any
+          matching pointer.  */
+
+       if (i == count)
+         coordinate = null;
+      }
+
+    /* Return the pointer ID.  */
+    return coordinate;
+  }
+
+  /* Return the modifier mask associated with the specified motion
+     EVENT.  Replace bits corresponding to Left or Right keys with
+     their corresponding general modifier bits.  */
+
+  private int
+  motionEventModifiers (MotionEvent event)
+  {
+    int state;
+
+    state = event.getMetaState ();
+
+    /* Normalize the state by setting the generic modifier bit if
+       either a left or right modifier is pressed.  */
+
+    if ((state & KeyEvent.META_ALT_LEFT_ON) != 0
+       || (state & KeyEvent.META_ALT_RIGHT_ON) != 0)
+      state |= KeyEvent.META_ALT_MASK;
+
+    if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0
+       || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0)
+      state |= KeyEvent.META_CTRL_MASK;
+
+    return state;
+  }
+
+  /* Process a single ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_UP,
+     ACTION_POINTER_UP, ACTION_CANCEL, or ACTION_MOVE event.
+
+     Ascertain which coordinate changed and send an appropriate mouse
+     or touch screen event.  */
+
+  private void
+  motionEvent (MotionEvent event)
+  {
+    Coordinate coordinate;
+    int modifiers;
+    long time;
+
+    /* Find data associated with this event's pointer.  Namely, its
+       current location, whether or not a change has taken place, and
+       whether or not it is a button event.  */
+
+    coordinate = figureChange (event);
+
+    if (coordinate == null)
+      return;
+
+    time = event.getEventTime ();
+
+    if (coordinate.button != 0)
+      {
+       /* This event is tied to a mouse click, so report mouse motion
+          and button events.  */
+
+       modifiers = motionEventModifiers (event);
+
+       switch (event.getAction ())
+         {
+         case MotionEvent.ACTION_POINTER_DOWN:
+         case MotionEvent.ACTION_DOWN:
+           EmacsNative.sendButtonPress (this.handle, coordinate.x,
+                                        coordinate.y, time, modifiers,
+                                        coordinate.button);
+           break;
+
+         case MotionEvent.ACTION_POINTER_UP:
+         case MotionEvent.ACTION_UP:
+         case MotionEvent.ACTION_CANCEL:
+           EmacsNative.sendButtonRelease (this.handle, coordinate.x,
+                                          coordinate.y, time, modifiers,
+                                          coordinate.button);
+           break;
+
+         case MotionEvent.ACTION_MOVE:
+           EmacsNative.sendMotionNotify (this.handle, coordinate.x,
+                                         coordinate.y, time);
+           break;
+         }
+      }
+    else
+      {
+       /* This event is a touch event, and the touch ID is the
+          pointer ID.  */
+
+       switch (event.getActionMasked ())
+         {
+         case MotionEvent.ACTION_DOWN:
+         case MotionEvent.ACTION_POINTER_DOWN:
+           /* Touch down event.  */
+           EmacsNative.sendTouchDown (this.handle, coordinate.x,
+                                      coordinate.y, time,
+                                      coordinate.id, 0);
+           break;
+
+         case MotionEvent.ACTION_UP:
+         case MotionEvent.ACTION_POINTER_UP:
+           /* Touch up event.  */
+           EmacsNative.sendTouchUp (this.handle, coordinate.x,
+                                    coordinate.y, time,
+                                    coordinate.id, 0);
+           break;
+
+         case MotionEvent.ACTION_CANCEL:
+           /* Touch sequence cancellation event.  */
+           EmacsNative.sendTouchUp (this.handle, coordinate.x,
+                                    coordinate.y, time,
+                                    coordinate.id,
+                                    1 /* ANDROID_TOUCH_SEQUENCE_CANCELED */);
+           break;
+
+         case MotionEvent.ACTION_MOVE:
+           /* Pointer motion event.  */
+           EmacsNative.sendTouchMove (this.handle, coordinate.x,
+                                      coordinate.y, time,
+                                      coordinate.id, 0);
+           break;
+         }
+      }
+
+    if (Build.VERSION.SDK_INT
+       < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+      return;
+
+    /* Now update the button state.  */
+    lastButtonState = event.getButtonState ();
+    return;
+  }
+
+  public boolean
+  onTouchEvent (MotionEvent event)
+  {
+    switch (event.getActionMasked ())
+      {
+      case MotionEvent.ACTION_DOWN:
+      case MotionEvent.ACTION_POINTER_DOWN:
+      case MotionEvent.ACTION_UP:
+      case MotionEvent.ACTION_POINTER_UP:
+      case MotionEvent.ACTION_CANCEL:
+      case MotionEvent.ACTION_MOVE:
+       motionEvent (event);
+       return true;
+      }
+
+    return false;
+  }
+
+  public boolean
+  onGenericMotionEvent (MotionEvent event)
+  {
+    switch (event.getAction ())
+      {
+      case MotionEvent.ACTION_HOVER_ENTER:
+       EmacsNative.sendEnterNotify (this.handle, (int) event.getX (),
+                                    (int) event.getY (),
+                                    event.getEventTime ());
+       return true;
+
+      case MotionEvent.ACTION_HOVER_MOVE:
+       EmacsNative.sendMotionNotify (this.handle, (int) event.getX (),
+                                     (int) event.getY (),
+                                     event.getEventTime ());
+       return true;
+
+      case MotionEvent.ACTION_HOVER_EXIT:
+
+       /* If the exit event comes from a button press, its button
+          state will have extra bits compared to the last known
+          button state.  Since the exit event will interfere with
+          tool bar button presses, ignore such splurious events.  */
+
+       if ((event.getButtonState () & ~lastButtonState) == 0)
+         EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (),
+                                      (int) event.getY (),
+                                      event.getEventTime ());
+
+       return true;
+
+      case MotionEvent.ACTION_DOWN:
+      case MotionEvent.ACTION_POINTER_DOWN:
+      case MotionEvent.ACTION_UP:
+      case MotionEvent.ACTION_POINTER_UP:
+      case MotionEvent.ACTION_CANCEL:
+      case MotionEvent.ACTION_MOVE:
+       /* MotionEvents may either be sent to onGenericMotionEvent or
+          onTouchEvent depending on if Android thinks it is a mouse
+          event or not, but we detect them ourselves.  */
+       motionEvent (event);
+       return true;
+
+      case MotionEvent.ACTION_SCROLL:
+       /* Send a scroll event with the specified deltas.  */
+       EmacsNative.sendWheel (this.handle, (int) event.getX (),
+                              (int) event.getY (),
+                              event.getEventTime (),
+                              motionEventModifiers (event),
+                              event.getAxisValue (MotionEvent.AXIS_HSCROLL),
+                              event.getAxisValue (MotionEvent.AXIS_VSCROLL));
+       return true;
+      }
+
+    return false;
+  }
+
+
+
+  public synchronized void
+  reparentTo (final EmacsWindow otherWindow, int x, int y)
+  {
+    int width, height;
+
+    /* Reparent this window to the other window.  */
+
+    if (parent != null)
+      parent.children.remove (this);
+
+    if (otherWindow != null)
+      otherWindow.children.add (this);
+
+    parent = otherWindow;
+
+    /* Move this window to the new location.  */
+    width = rect.width ();
+    height = rect.height ();
+    rect.left = x;
+    rect.top = y;
+    rect.right = x + width;
+    rect.bottom = y + height;
+
+    /* Now do the work necessary on the UI thread to reparent the
+       window.  */
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         EmacsWindowAttachmentManager manager;
+         ViewManager parent;
+
+         /* First, detach this window if necessary.  */
+         manager = EmacsWindowAttachmentManager.MANAGER;
+         manager.detachWindow (EmacsWindow.this);
+
+         /* Also unparent this view.  */
+
+         /* If the window manager is set, use that instead.  */
+         if (windowManager != null)
+           parent = windowManager;
+         else
+           parent = (ViewManager) view.getParent ();
+         windowManager = null;
+
+         if (parent != null)
+           parent.removeView (view);
+
+         /* Next, either add this window as a child of the new
+            parent's view, or make it available again.  */
+         if (otherWindow != null)
+           otherWindow.view.addView (view);
+         else if (EmacsWindow.this.isMapped)
+           manager.registerWindow (EmacsWindow.this);
+
+         /* Request relayout.  */
+         view.requestLayout ();
+       }
+      });
+  }
+
+  public void
+  makeInputFocus (long time)
+  {
+    /* TIME is currently ignored.  Request the input focus now.  */
+
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         view.requestFocus ();
+       }
+      });
+  }
+
+  public synchronized void
+  raise ()
+  {
+    /* This does nothing here.  */
+    if (parent == null)
+      return;
+
+    /* Remove and add this view again.  */
+    parent.children.remove (this);
+    parent.children.add (this);
+
+    /* Request a relayout.  */
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         view.raise ();
+       }
+      });
+  }
+
+  public synchronized void
+  lower ()
+  {
+    /* This does nothing here.  */
+    if (parent == null)
+      return;
+
+    /* Remove and add this view again.  */
+    parent.children.remove (this);
+    parent.children.add (this);
+
+    /* Request a relayout.  */
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         view.lower ();
+       }
+      });
+  }
+
+  public synchronized int[]
+  getWindowGeometry ()
+  {
+    int[] array;
+
+    array = new int[4];
+
+    array[0] = parent != null ? rect.left : xPosition;
+    array[1] = parent != null ? rect.top : yPosition;
+    array[2] = rect.width ();
+    array[3] = rect.height ();
+
+    return array;
+  }
+
+  public void
+  noticeIconified ()
+  {
+    EmacsNative.sendIconified (this.handle);
+  }
+
+  public void
+  noticeDeiconified ()
+  {
+    EmacsNative.sendDeiconified (this.handle);
+  }
+
+  public synchronized void
+  setDontAcceptFocus (final boolean dontAcceptFocus)
+  {
+    /* Update the view's focus state.  */
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         view.setFocusable (!dontAcceptFocus);
+         view.setFocusableInTouchMode (!dontAcceptFocus);
+       }
+      });
+  }
+
+  public synchronized void
+  setDontFocusOnMap (final boolean dontFocusOnMap)
+  {
+    this.dontFocusOnMap = dontFocusOnMap;
+  }
+
+  public synchronized boolean
+  getDontFocusOnMap ()
+  {
+    return dontFocusOnMap;
+  }
+
+  public int[]
+  translateCoordinates (int x, int y)
+  {
+    int[] array;
+
+    /* This is supposed to translate coordinates to the root
+       window.  */
+    array = new int[2];
+    EmacsService.SERVICE.getLocationOnScreen (view, array);
+
+    /* Now, the coordinates of the view should be in array.  Offset X
+       and Y by them.  */
+    array[0] += x;
+    array[1] += y;
+
+    /* Return the resulting coordinates.  */
+    return array;
+  }
+
+  public void
+  toggleOnScreenKeyboard (final boolean on)
+  {
+    /* Even though InputMethodManager functions are thread safe,
+       `showOnScreenKeyboard' etc must be called from the UI thread in
+       order to avoid deadlocks if the calls happen in tandem with a
+       call to a synchronizing function within
+       `onCreateInputConnection'.  */
+
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         if (on)
+           view.showOnScreenKeyboard ();
+         else
+           view.hideOnScreenKeyboard ();
+       }
+      });
+  }
+
+  public String
+  lookupString (int eventSerial)
+  {
+    String any;
+
+    synchronized (eventStrings)
+      {
+       any = eventStrings.remove (eventSerial);
+      }
+
+    return any;
+  }
+
+  public void
+  setFullscreen (final boolean isFullscreen)
+  {
+    EmacsService.SERVICE.runOnUiThread (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         EmacsActivity activity;
+         Object tem;
+
+         fullscreen = isFullscreen;
+         tem = getAttachedConsumer ();
+
+         if (tem != null)
+           {
+             activity = (EmacsActivity) tem;
+             activity.syncFullscreenWith (EmacsWindow.this);
+           }
+       }
+      });
+  }
+
+  public void
+  defineCursor (final EmacsCursor cursor)
+  {
+    /* Don't post this message if pointer icons aren't supported.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+      view.post (new Runnable () {
+         @Override
+         public void
+         run ()
+         {
+           if (cursor != null)
+             view.setPointerIcon (cursor.icon);
+           else
+             view.setPointerIcon (null);
+         }
+       });
+  }
+
+  public synchronized void
+  notifyContentRectPosition (int xPosition, int yPosition)
+  {
+    Rect geometry;
+
+    /* Ignore these notifications if not a child of the root
+       window.  */
+    if (parent != null)
+      return;
+
+    /* xPosition and yPosition are the position of this window
+       relative to the screen.  Set them and request a ConfigureNotify
+       event.  */
+
+    if (this.xPosition != xPosition
+       || this.yPosition != yPosition)
+      {
+       this.xPosition = xPosition;
+       this.yPosition = yPosition;
+
+       EmacsNative.sendConfigureNotify (this.handle,
+                                        System.currentTimeMillis (),
+                                        xPosition, yPosition,
+                                        rect.width (), rect.height ());
+      }
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java 
b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
new file mode 100644
index 00000000000..4ba5b35aacf
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
@@ -0,0 +1,206 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+/* Code to paper over the differences in lifecycles between
+   "activities" and windows.  There are four interfaces to an instance
+   of this class:
+
+     registerWindowConsumer (WindowConsumer)
+     registerWindow (EmacsWindow)
+     removeWindowConsumer (WindowConsumer)
+     removeWindow (EmacsWindow)
+
+   A WindowConsumer is expected to allow an EmacsWindow to be attached
+   to it, and be created or destroyed.
+
+   Every time a window is created, registerWindow checks the list of
+   window consumers.  If a consumer exists and does not currently have
+   a window of its own attached, it gets the new window.  Otherwise,
+   the window attachment manager starts a new consumer.
+
+   Every time a consumer is registered, registerWindowConsumer checks
+   the list of available windows.  If a window exists and is not
+   currently attached to a consumer, then the consumer gets it.
+
+   Finally, every time a window is removed, the consumer is
+   destroyed.  */
+
+public final class EmacsWindowAttachmentManager
+{
+  private final static String TAG = "EmacsWindowAttachmentManager";
+
+  /* The single window attachment manager ``object''.  */
+  public static final EmacsWindowAttachmentManager MANAGER;
+
+  static
+  {
+    MANAGER = new EmacsWindowAttachmentManager ();
+  };
+
+  public interface WindowConsumer
+  {
+    public void attachWindow (EmacsWindow window);
+    public EmacsWindow getAttachedWindow ();
+    public void detachWindow ();
+    public void destroy ();
+  };
+
+  /* List of currently attached window consumers.  */
+  public List<WindowConsumer> consumers;
+
+  /* List of currently attached windows.  */
+  public List<EmacsWindow> windows;
+
+  public
+  EmacsWindowAttachmentManager ()
+  {
+    consumers = new ArrayList<WindowConsumer> ();
+    windows = new ArrayList<EmacsWindow> ();
+  }
+
+  public void
+  registerWindowConsumer (WindowConsumer consumer)
+  {
+    consumers.add (consumer);
+
+    for (EmacsWindow window : windows)
+      {
+       if (window.getAttachedConsumer () == null)
+         {
+           consumer.attachWindow (window);
+           return;
+         }
+      }
+
+    EmacsNative.sendWindowAction ((short) 0, 0);
+  }
+
+  public synchronized void
+  registerWindow (EmacsWindow window)
+  {
+    Intent intent;
+    ActivityOptions options;
+
+    if (windows.contains (window))
+      /* The window is already registered.  */
+      return;
+
+    windows.add (window);
+
+    for (WindowConsumer consumer : consumers)
+      {
+       if (consumer.getAttachedWindow () == null)
+         {
+           consumer.attachWindow (window);
+           return;
+         }
+      }
+
+    intent = new Intent (EmacsService.SERVICE,
+                        EmacsMultitaskActivity.class);
+    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                    | Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+      EmacsService.SERVICE.startActivity (intent);
+    else
+      {
+       /* Specify the desired window size.  */
+       options = ActivityOptions.makeBasic ();
+       options.setLaunchBounds (window.getGeometry ());
+       EmacsService.SERVICE.startActivity (intent,
+                                           options.toBundle ());
+      }
+  }
+
+  public void
+  removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
+  {
+    EmacsWindow window;
+
+    window = consumer.getAttachedWindow ();
+
+    if (window != null)
+      {
+       consumer.detachWindow ();
+       window.onActivityDetached (isFinishing);
+      }
+
+    consumers.remove (consumer);
+  }
+
+  public synchronized void
+  detachWindow (EmacsWindow window)
+  {
+    WindowConsumer consumer;
+
+    if (window.getAttachedConsumer () != null)
+      {
+       consumer = window.getAttachedConsumer ();
+
+       consumers.remove (consumer);
+       consumer.destroy ();
+      }
+
+    windows.remove (window);
+  }
+
+  public void
+  noticeIconified (WindowConsumer consumer)
+  {
+    EmacsWindow window;
+
+    /* If a window is attached, send the appropriate iconification
+       events.  */
+    window = consumer.getAttachedWindow ();
+
+    if (window != null)
+      window.noticeIconified ();
+  }
+
+  public void
+  noticeDeiconified (WindowConsumer consumer)
+  {
+    EmacsWindow window;
+
+    /* If a window is attached, send the appropriate iconification
+       events.  */
+    window = consumer.getAttachedWindow ();
+
+    if (window != null)
+      window.noticeDeiconified ();
+  }
+
+  public synchronized List<EmacsWindow>
+  copyWindows ()
+  {
+    return new ArrayList<EmacsWindow> (windows);
+  }
+};
diff --git a/java/res/drawable/emacs.png b/java/res/drawable/emacs.png
new file mode 100644
index 00000000000..9ab43d704be
Binary files /dev/null and b/java/res/drawable/emacs.png differ
diff --git a/java/res/drawable/emacs_background.xml 
b/java/res/drawable/emacs_background.xml
new file mode 100644
index 00000000000..c29e0635f7d
--- /dev/null
+++ b/java/res/drawable/emacs_background.xml
@@ -0,0 +1,42 @@
+<!-- Adaptive icon for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+       xmlns:aapt="http://schemas.android.com/aapt";
+       android:width="108dp"
+       android:height="108dp"
+       android:viewportWidth="512"
+       android:viewportHeight="512">
+  <path
+      android:pathData="M-4.99,-5.79h521.12v526.76h-521.12z"
+      android:strokeWidth="10.6667">
+    <aapt:attr name="android:fillColor">
+      <gradient
+          android:startX="0"
+          android:startY="0"
+          android:endX="512"
+          android:endY="512"
+          android:type="linear">
+        <item android:offset="0" android:color="#FF8381C5"/>
+        <item android:offset="0.64" android:color="#FE806BBC"/>
+        <item android:offset="1" android:color="#FDA52ECB"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+</vector>
diff --git a/java/res/drawable/emacs_foreground.xml 
b/java/res/drawable/emacs_foreground.xml
new file mode 100644
index 00000000000..68a4631f17b
--- /dev/null
+++ b/java/res/drawable/emacs_foreground.xml
@@ -0,0 +1,39 @@
+<!-- Adaptive icon for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+       xmlns:aapt="http://schemas.android.com/aapt";
+       android:width="108dp"
+       android:height="108dp"
+       android:viewportWidth="512"
+       android:viewportHeight="512">
+  <group android:scaleX="0.6"
+        android:scaleY="0.6"
+        android:translateX="102.4"
+        android:translateY="102.4">
+    <path
+        android:pathData="m174.83,422.11c0,0 19.74,1.4 45.13,-0.84 10.28,-0.91 
49.33,-4.74 78.52,-11.14 0,0 35.59,-7.62 54.63,-14.63 19.92,-7.34 30.76,-13.57 
35.64,-22.4 -0.21,-1.81 1.5,-8.22 -7.68,-12.08 -23.49,-9.85 -50.73,-8.07 
-104.63,-9.21 -59.78,-2.05 -79.66,-12.06 -90.26,-20.12 -10.16,-8.18 
-5.05,-30.79 38.47,-50.71 21.92,-10.61 107.87,-30.19 107.87,-30.19 
-28.95,-14.31 -82.92,-39.46 -94.01,-44.89 -9.73,-4.76 -25.3,-11.94 
-28.68,-20.61 -3.83,-8.33 9.04,-15.51 16.22,-17.56 23.14,-6 [...]
+        android:strokeLineJoin="miter"
+        android:strokeWidth="0"
+        android:fillColor="#ffffff"
+        android:strokeColor="#a0000000"
+        android:fillType="evenOdd"
+        android:strokeLineCap="butt"/>
+  </group>
+</vector>
diff --git a/java/res/drawable/emacs_wrench.png 
b/java/res/drawable/emacs_wrench.png
new file mode 100644
index 00000000000..50572d3bed1
Binary files /dev/null and b/java/res/drawable/emacs_wrench.png differ
diff --git a/java/res/layout/sdk8_notifications_view.xml 
b/java/res/layout/sdk8_notifications_view.xml
new file mode 100644
index 00000000000..0572f350cff
--- /dev/null
+++ b/java/res/layout/sdk8_notifications_view.xml
@@ -0,0 +1,33 @@
+<!-- Notification content widget tree for GNU Emacs on Android 2.3.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
+             android:orientation="vertical"
+             android:layout_width="match_parent"
+             android:layout_height="wrap_content"
+             android:padding="8dp">
+  <TextView android:id="@+id/sdk8_notifications_title"
+           android:textColor="#000000"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"/>
+  <TextView android:id="@+id/sdk8_notifications_content"
+           android:textColor="#000000"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/java/res/mipmap-v26/emacs_icon.xml 
b/java/res/mipmap-v26/emacs_icon.xml
new file mode 100644
index 00000000000..9f070e3f3d2
--- /dev/null
+++ b/java/res/mipmap-v26/emacs_icon.xml
@@ -0,0 +1,23 @@
+<!-- Adaptive icon for Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android";>
+  <background android:drawable="@drawable/emacs_background"/>
+  <foreground android:drawable="@drawable/emacs_foreground"/>
+</adaptive-icon>
diff --git a/java/res/mipmap/emacs_icon.png b/java/res/mipmap/emacs_icon.png
new file mode 100644
index 00000000000..9ab43d704be
Binary files /dev/null and b/java/res/mipmap/emacs_icon.png differ
diff --git a/java/res/values-v11/style.xml b/java/res/values-v11/style.xml
new file mode 100644
index 00000000000..b114758bf0d
--- /dev/null
+++ b/java/res/values-v11/style.xml
@@ -0,0 +1,24 @@
+<!-- Style resources for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <!-- Style used for popup menus and relatives on Android 3.x.  -->
+  <style name="EmacsStyle" parent="@android:style/Theme.Holo.NoActionBar"/>
+  <style name="EmacsStyleOpen" parent="@android:style/Theme.Holo"/>
+</resources>
diff --git a/java/res/values-v14/style.xml b/java/res/values-v14/style.xml
new file mode 100644
index 00000000000..2cb54dc301b
--- /dev/null
+++ b/java/res/values-v14/style.xml
@@ -0,0 +1,25 @@
+<!-- Style resources for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <!-- Style used for popup menus and relatives between Android 4.0
+       and Android 10.  -->
+  <style name="EmacsStyle" 
parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
+  <style name="EmacsStyleOpen" parent="@android:style/Theme.DeviceDefault"/>
+</resources>
diff --git a/java/res/values-v19/bool.xml b/java/res/values-v19/bool.xml
new file mode 100644
index 00000000000..a4e3a87ae71
--- /dev/null
+++ b/java/res/values-v19/bool.xml
@@ -0,0 +1,22 @@
+<!-- Boolean resources for GNU Emacs on Android 4.4 or later.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <bool name="isAtLeastKitKat">true</bool>
+</resources>
diff --git a/java/res/values-v24/bool.xml b/java/res/values-v24/bool.xml
new file mode 100644
index 00000000000..37f07992995
--- /dev/null
+++ b/java/res/values-v24/bool.xml
@@ -0,0 +1,22 @@
+<!-- Boolean resources for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <bool name="isBeforeNougat">false</bool>
+</resources>
diff --git a/java/res/values-v29/style.xml b/java/res/values-v29/style.xml
new file mode 100644
index 00000000000..ec7b8d14554
--- /dev/null
+++ b/java/res/values-v29/style.xml
@@ -0,0 +1,32 @@
+<!-- Style resources for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <!-- Style used for popup menus and relatives from Android 10.0
+       onwards-->
+  <style name="EmacsStyle" 
parent="@android:style/Theme.DeviceDefault.DayNight">
+    <item name="android:windowActionBar">false</item>
+    <item name="android:windowNoTitle">true</item>
+
+    <!-- Required to make sure the status bar text remains legible.  -->
+    <item name="android:statusBarColor">@android:color/black</item>
+  </style>
+  <style name="EmacsStyleOpen"
+        parent="@android:style/Theme.DeviceDefault.DayNight"/>
+</resources>
diff --git a/java/res/values/bool.xml b/java/res/values/bool.xml
new file mode 100644
index 00000000000..2b253824e29
--- /dev/null
+++ b/java/res/values/bool.xml
@@ -0,0 +1,23 @@
+<!-- Boolean resources for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <bool name="isAtLeastKitKat">false</bool>
+  <bool name="isBeforeNougat">true</bool>
+</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
new file mode 100644
index 00000000000..0bf1ef0ac9b
--- /dev/null
+++ b/java/res/values/strings.xml
@@ -0,0 +1,45 @@
+<!-- String resources used by GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <string name="start_quick_title">
+    Restart Emacs with -Q
+  </string>
+  <string name="start_quick_caption">
+    Restart Emacs, but do not load site lisp or init files.
+  </string>
+  <string name="start_debug_init_title">
+    Restart Emacs with --debug-init
+  </string>
+  <string name="start_debug_init_caption">
+    Restart Emacs, and display the debugger should an error occur while 
loading initialization files.
+  </string>
+  <string name="erase_dump_title">
+    Delete dump file
+  </string>
+  <string name="erase_dump_caption">
+    Remove the dumped state created when Emacs was installed.
+  </string>
+
+  <!-- This resource describes the purpose of any `sharedUserId'
+       specified at configure-time.  -->
+  <string name="shared_user_name">
+    Emacs shared user
+  </string>
+</resources>
diff --git a/java/res/values/style.xml b/java/res/values/style.xml
new file mode 100644
index 00000000000..498e844fda0
--- /dev/null
+++ b/java/res/values/style.xml
@@ -0,0 +1,26 @@
+<!-- Style resources for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<resources>
+  <!-- Style used for popup menus and relatives on Android 2.2 and
+       2.3.  Styles used for newer Android versions are defined in
+       the res/values- directories for their respective API levels. -->
+  <style name="EmacsStyle" parent="@android:style/Theme.NoTitleBar"/>
+  <style name="EmacsStyleOpen" parent="@android:style/Theme"/>
+</resources>
diff --git a/java/res/xml/preferences.xml b/java/res/xml/preferences.xml
new file mode 100644
index 00000000000..d52d28816e5
--- /dev/null
+++ b/java/res/xml/preferences.xml
@@ -0,0 +1,30 @@
+<!-- Descriptions for the preferences screen for GNU Emacs on Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android";>
+    <Preference android:key="start_quick"
+               android:title="@string/start_quick_title"
+               android:summary="@string/start_quick_caption"/>
+    <Preference android:key="start_debug_init"
+               android:title="@string/start_debug_init_title"
+               android:summary="@string/start_debug_init_caption"/>
+    <Preference android:key="erase_dump"
+               android:title="@string/erase_dump_title"
+               android:summary="@string/erase_dump_caption"/>
+</PreferenceScreen>
diff --git a/lib-src/ChangeLog.1 b/lib-src/ChangeLog.1
index 84f566262c4..136e8917d50 100644
--- a/lib-src/ChangeLog.1
+++ b/lib-src/ChangeLog.1
@@ -8281,7 +8281,7 @@
        * etags.c (C_entries): Save the definedef status even when a
        newline is met inside a string.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * Makefile.in (EXECUTABLES): Add rcs-checkin.
 
@@ -8482,7 +8482,7 @@
 
 1992-08-07  Jim Blandy  (jimb@pogo.cs.oberlin.edu)
 
-       * timer.c: Installed new version from Eric Raymond; this is more
+       * timer.c: Installed new version from Eric S. Raymond; this is more
        portable, since it doesn't try to use SIGIO.
 
 1992-07-17  Jim Blandy  (jimb@wookumz.gnu.ai.mit.edu)
diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in
index 5b82cf1151c..8a5ce019ca2 100644
--- a/lib-src/Makefile.in
+++ b/lib-src/Makefile.in
@@ -96,6 +96,14 @@ localstatedir=@localstatedir@
 srcdir=@srcdir@
 VPATH=@srcdir@
 
+# Cross-compilation setup
+
+XCONFIGURE=@XCONFIGURE@
+
+ifneq ($(XCONFIGURE),)
+vpath $(srcdir)
+endif
+
 # The top-level source directory, also set by configure.
 top_srcdir=@top_srcdir@
 # MinGW CPPFLAGS may use this.
@@ -140,6 +148,9 @@ HAVE_BE_APP=@HAVE_BE_APP@
 HAIKU_LIBS=@HAIKU_LIBS@
 HAIKU_CFLAGS=@HAIKU_CFLAGS@
 
+## Android build-time support
+ANDROID=@ANDROID@
+
 # emacsclientw.exe for MinGW, empty otherwise
 CLIENTW = @CLIENTW@
 
@@ -156,8 +167,13 @@ UTILITIES = hexl${EXEEXT}                           \
 ifeq ($(HAVE_BE_APP),yes)
 DONT_INSTALL= make-docfile${EXEEXT} make-fingerprint${EXEEXT} be-resources
 else
+ifeq ($(XCONFIGURE)$(HAVE_ANDROID),yes)
+DONT_INSTALL = make-docfile${EXEEXT} make-fingerprint${EXEEXT} \
+       asset-directory-tool${EXEEXT}
+else
 DONT_INSTALL= make-docfile${EXEEXT} make-fingerprint${EXEEXT}
 endif
+endif
 
 # Like UTILITIES, but they're not system-dependent, and should not be
 #  deleted by the distclean target.
@@ -374,7 +390,7 @@ clean: mostlyclean
        rm -f ${EXE_FILES}
 
 distclean: clean
-       rm -f TAGS Makefile blessmail
+       rm -f TAGS Makefile blessmail Makefile.android
 
 bootstrap-clean maintainer-clean: distclean
 
@@ -406,6 +422,9 @@ etags${EXEEXT}: ${etags_deps}
 ctags${EXEEXT}: ${srcdir}/ctags.c ${etags_deps}
        $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} -o $@ $< $(etags_libs)
 
+asset-directory-tool${EXEEXT}: ${srcdir}/asset-directory-tool.c $(config_h)
+       $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} $< $(LOADLIBES) -o $@
+
 ebrowse${EXEEXT}: ${srcdir}/ebrowse.c ${srcdir}/../lib/min-max.h $(NTLIB) \
                    $(config_h)
        $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} -o $@ $< $(NTLIB) $(LOADLIBES)
@@ -462,7 +481,7 @@ emacsclient.res: ../nt/emacsclient.rc 
$(NTINC)/../icons/emacs.ico
 ifeq ($(SECCOMP_FILTER),1)
 seccomp-filter$(EXEEXT): $(srcdir)/seccomp-filter.c $(config_h)
        $(AM_V_CCLD)$(CC) $(ALL_CFLAGS) $(LIBSECCOMP_CFLAGS) $< \
-         $(LIBSECCOMP_LIBS) -o $@
+         $(LIBSECCOMP_LIBS) $(LOADLIBES) -o $@
 
 seccomp-filter.bpf seccomp-filter.pfc seccomp-filter-exec.bpf 
seccomp-filter-exec.pfc: seccomp-filter$(EXEEXT)
        $(AM_V_GEN)./seccomp-filter$(EXEEXT) \
diff --git a/lib-src/asset-directory-tool.c b/lib-src/asset-directory-tool.c
new file mode 100644
index 00000000000..99c954a3922
--- /dev/null
+++ b/lib-src/asset-directory-tool.c
@@ -0,0 +1,289 @@
+/* Android asset directory tool.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+
+/* This program takes a directory as input, and generates a
+   ``directory-tree'' file suitable for inclusion in an Android
+   application package.
+
+   Such a file records the layout of the `assets' directory in the
+   package.  Emacs records this information itself and uses it in the
+   Android emulation of readdir, because the system asset manager APIs
+   are routinely buggy, and are often unable to locate directories or
+   files.
+
+   The file is packed, with no data alignment guarantees made.  The
+   file starts with the bytes "EMACS", following which is the name of
+   the first file or directory, a NULL byte and an unsigned int
+   indicating the offset from the start of the file to the start of
+   the next sibling.  Following that is a list of subdirectories or
+   files in the same format.  The long is stored LSB first.
+
+   Directories can be distinguished from ordinary files through the
+   last bytes of their file names (immediately previous to their
+   terminating NULL bytes), which are set to the directory separator
+   character `/'.  */
+
+
+
+struct directory_tree
+{
+  /* The offset to the next sibling.  */
+  size_t offset;
+
+  /* The name of this directory or file.  */
+  char *name;
+
+  /* Subdirectories and files inside this directory.  */
+  struct directory_tree *children, *next;
+};
+
+/* Exit with EXIT_FAILURE, after printing a description of a failing
+   function WHAT along with the details of the error.  */
+
+static _Noreturn void
+croak (const char *what)
+{
+  perror (what);
+  exit (EXIT_FAILURE);
+}
+
+/* Like malloc, but aborts on failure.  */
+
+static void *
+xmalloc (size_t size)
+{
+  void *ptr;
+
+  ptr = malloc (size);
+
+  if (!ptr)
+    croak ("malloc");
+
+  return ptr;
+}
+
+/* Recursively build a struct directory_tree structure for each
+   subdirectory or file in DIR, in preparation for writing it out to
+   disk.  PARENT should be the directory tree associated with the
+   parent directory, or else PARENT->offset must be initialized to
+   5.  */
+
+static void
+main_1 (DIR *dir, struct directory_tree *parent)
+{
+  struct dirent *dirent;
+  int dir_fd, fd;
+  struct stat statb;
+  struct directory_tree *this, **last;
+  size_t length;
+  DIR *otherdir;
+
+  dir_fd = dirfd (dir);
+  last = &parent->children;
+
+  while ((dirent = readdir (dir)))
+    {
+      /* Determine what kind of file DIRENT is.  */
+
+      if (fstatat (dir_fd, dirent->d_name, &statb,
+                  AT_SYMLINK_NOFOLLOW) == -1)
+       croak ("fstatat");
+
+      /* Ignore . and ...  */
+
+      if (!strcmp (dirent->d_name, ".")
+         || !strcmp (dirent->d_name, ".."))
+       continue;
+
+      length = strlen (dirent->d_name);
+
+      if (statb.st_mode & S_IFDIR)
+       {
+         /* This is a directory.  Write its name followed by a
+            trailing slash, then a NULL byte, and the offset to the
+            next sibling.  */
+         this = xmalloc (sizeof *this);
+         this->children = NULL;
+         this->next = NULL;
+         *last = this;
+         last = &this->next;
+         this->name = xmalloc (length + 2);
+         strcpy (this->name, dirent->d_name);
+
+         /* Now record the offset to the end of this directory.  This
+            is length + 1, for the file name, and 5 more bytes for
+            the trailing NULL and long.  */
+         this->offset = parent->offset + length + 6;
+
+         /* Terminate that with a slash and trailing NULL byte.  */
+         this->name[length] = '/';
+         this->name[length + 1] = '\0';
+
+         /* Open and build that directory recursively.  */
+
+         fd = openat (dir_fd, dirent->d_name, O_DIRECTORY,
+                      O_RDONLY);
+         if (fd < 0)
+           croak ("openat");
+         otherdir = fdopendir (fd);
+         if (!otherdir)
+           croak ("fdopendir");
+
+         main_1 (otherdir, this);
+
+         /* Close this directory.  */
+         closedir (otherdir);
+
+         /* Finally, set parent->offset to this->offset as well.  */
+         parent->offset = this->offset;
+       }
+      else if (statb.st_mode & S_IFREG)
+       {
+         /* This is a regular file.  */
+         this = xmalloc (sizeof *this);
+         this->children = NULL;
+         this->next = NULL;
+         *last = this;
+         last = &this->next;
+         this->name = xmalloc (length + 1);
+         strcpy (this->name, dirent->d_name);
+
+         /* This is one byte shorter because there is no trailing
+            slash.  */
+         this->offset = parent->offset + length + 5;
+         parent->offset = this->offset;
+       }
+    }
+}
+
+/* Write the struct directory_tree TREE and all of is children to the
+   file descriptor FD.  OFFSET is the offset of TREE and may be
+   modified; it is only used for checking purposes.  */
+
+static void
+main_2 (int fd, struct directory_tree *tree, size_t *offset)
+{
+  ssize_t size;
+  struct directory_tree *child;
+  unsigned int output;
+
+  /* Write tree->name with the trailing NULL byte.  */
+  size = strlen (tree->name) + 1;
+  if (write (fd, tree->name, size) < size)
+    croak ("write");
+
+  /* Write the offset.  */
+#ifdef WORDS_BIGENDIAN
+  output = bswap_32 (tree->offset);
+#else
+  output = tree->offset;
+#endif
+  if (write (fd, &output, 4) < 1)
+    croak ("write");
+  size += 4;
+
+  /* Now update offset.  */
+  *offset += size;
+
+  /* Write out each child.  */
+  for (child = tree->children; child; child = child->next)
+    main_2 (fd, child, offset);
+
+  /* Verify the offset is correct.  */
+  if (tree->offset != *offset)
+    {
+      fprintf (stderr,
+              "asset-directory-tool: invalid offset: expected %tu, "
+              "got %tu.\n"
+              "Please report this bug to bug-gnu-emacs@gnu.org, along\n"
+              "with an archive containing the contents of the java/inst"
+              "all_temp directory.\n",
+              tree->offset, *offset);
+      abort ();
+    }
+}
+
+int
+main (int argc, char **argv)
+{
+  int fd;
+  DIR *indir;
+  struct directory_tree tree;
+  size_t offset;
+
+  if (argc != 3)
+    {
+      fprintf (stderr, "usage: %s directory output-file\n",
+              argv[0]);
+      return EXIT_FAILURE;
+    }
+
+  fd = open (argv[2], O_CREAT | O_TRUNC | O_RDWR,
+            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  if (fd < 0)
+    {
+      perror ("open");
+      return EXIT_FAILURE;
+    }
+
+  indir = opendir (argv[1]);
+
+  if (!indir)
+    {
+      perror ("opendir");
+      return EXIT_FAILURE;
+    }
+
+  /* Write the first 5 byte header to FD.  */
+
+  if (write (fd, "EMACS", 5) < 5)
+    {
+      perror ("write");
+      return EXIT_FAILURE;
+    }
+
+  /* Now iterate through children of INDIR, building the directory
+     tree.  */
+  tree.offset = 5;
+  tree.children = NULL;
+
+  main_1 (indir, &tree);
+  closedir (indir);
+
+  /* Finally, write the directory tree to the output file.  */
+  offset = 5;
+  for (; tree.children; tree.children = tree.children->next)
+    main_2 (fd, tree.children, &offset);
+
+  return 0;
+}
diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index 698bf9b50ae..a72fced1bf2 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -626,6 +626,8 @@ decode_options (int argc, char **argv)
       alt_display = "w32";
 #elif defined (HAVE_HAIKU)
       alt_display = "be";
+#elif defined (HAVE_ANDROID)
+      alt_display = "android";
 #endif
 
 #ifdef HAVE_PGTK
diff --git a/lib/Makefile.in b/lib/Makefile.in
index 71199c32277..6752f68c50e 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -20,6 +20,15 @@
 srcdir = @srcdir@
 VPATH = @srcdir@
 
+# This is not empty if this is a Makefile that will be copied to
+# cross/lib.
+XCONFIGURE = @XCONFIGURE@
+
+# This is required to make sure symbol visibility is correct and
+# functions like readlinkat do not end up replacing their OS
+# counterparts.
+ANDROID_BUILD_CFLAGS = @ANDROID_BUILD_CFLAGS@
+
 # Variables substituted by 'configure', and not autogenerated in gnulib.mk,
 # or needed before gnulib.mk is included.
 abs_top_srcdir = @abs_top_srcdir@
@@ -33,11 +42,11 @@ all:
 
 HAVE_NATIVE_COMP = @HAVE_NATIVE_COMP@
 
-ALL_CFLAGS= \
+ALL_CFLAGS = \
   $(C_SWITCH_SYSTEM) $(C_SWITCH_MACHINE) $(DEPFLAGS) \
   $(GNULIB_WARN_CFLAGS) $(WERROR_CFLAGS) $(PROFILING_CFLAGS) $(CFLAGS) \
-  -I. -I../src -I$(srcdir) -I$(srcdir)/../src \
-  $(if $(patsubst e-%,,$(notdir $<)),,-Demacs)
+  -I. -I../src -I$(srcdir) -I$(top_srcdir)/src \
+  $(if $(patsubst e-%,,$(notdir $<)),,-Demacs) $(ANDROID_BUILD_CFLAGS)
 
 ifeq ($(HAVE_NATIVE_COMP),yes)
 ALL_CFLAGS += -DGL_COMPILE_CRYPTO_STREAM
@@ -52,6 +61,12 @@ ifneq ($(SYSTEM_TYPE),windows-nt)
   libgnu_a_SOURCES += openat-die.c save-cwd.c
 endif
 
+ifeq ($(XCONFIGURE),android)
+# The next line is necessary to override -I$(srcdir), which will end
+# up pulling in lots of headers from the host.
+ALL_CFLAGS += -I$(top_srcdir)/cross -I.
+endif
+
 DEPDIR = deps
 ifeq ($(AUTO_DEPEND),yes)
   DEPFLAGS = -MMD -MF $(DEPDIR)/$*.d -MP
@@ -60,11 +75,14 @@ else
   DEPFLAGS =
 endif
 
+# This piece of code interferes with cross compilation
+ifeq ($(XCONFIGURE),)
 .PRECIOUS: ../config.status Makefile
 ../config.status: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4
        $(MAKE) -C .. $(notdir $@)
 Makefile: ../config.status $(srcdir)/Makefile.in
        $(MAKE) -C .. lib/$@
+endif
 
 # Object modules that need not be built for Emacs.
 # Emacs does not need e-regex.o (it has its own regex-emacs.c),
@@ -111,10 +129,10 @@ clean:
 mostlyclean: clean
        rm -f $(filter-out %-t,$(MOSTLYCLEANFILES))
 distclean bootstrap-clean: mostlyclean
-       rm -f Makefile
+       rm -f Makefile Makefile.android
        rm -fr $(DEPDIR)
 maintainer-clean: distclean
-       rm -f TAGS gnulib.mk
+       rm -f TAGS gnulib.mk gnulib.mk.android
        -rmdir malloc sys 2>/dev/null || true
 
 .PHONY: mostlyclean clean distclean bootstrap-clean maintainer-clean
diff --git a/lib/boot-time-aux.h b/lib/boot-time-aux.h
new file mode 100644
index 00000000000..e59a0fd03c7
--- /dev/null
+++ b/lib/boot-time-aux.h
@@ -0,0 +1,317 @@
+/* Auxiliary functions for determining the time when the machine last booted.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation, either version 3 of the License,
+   or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>.  */
+
+#define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
+
+#if defined __linux__ || defined __ANDROID__
+
+/* Store the uptime counter, as managed by the Linux kernel, in *P_UPTIME.
+   Return 0 upon success, -1 upon failure.  */
+_GL_ATTRIBUTE_MAYBE_UNUSED
+static int
+get_linux_uptime (struct timespec *p_uptime)
+{
+  /* The clock_gettime facility returns the uptime with a resolution of 1 µsec.
+     It is available with glibc >= 2.14, Android, or musl libc.
+     In glibc < 2.17 it required linking with librt.  */
+# if !defined __GLIBC__ || 2 < __GLIBC__ + (17 <= __GLIBC_MINOR__)
+  if (clock_gettime (CLOCK_BOOTTIME, p_uptime) >= 0)
+    return 0;
+# endif
+
+  /* /proc/uptime contains the uptime with a resolution of 0.01 sec.
+     But it does not have read permissions on Android.  */
+# if !defined __ANDROID__
+  FILE *fp = fopen ("/proc/uptime", "re");
+  if (fp != NULL)
+    {
+      char buf[32 + 1];
+      size_t n = fread (buf, 1, sizeof (buf) - 1, fp);
+      fclose (fp);
+      if (n > 0)
+        {
+          buf[n] = '\0';
+          /* buf now contains two values: the uptime and the idle time.  */
+          time_t s = 0;
+          char *p;
+          for (p = buf; '0' <= *p && *p <= '9'; p++)
+            s = 10 * s + (*p - '0');
+          if (buf < p)
+            {
+              long ns = 0;
+              if (*p++ == '.')
+                for (int i = 0; i < 9; i++)
+                  ns = 10 * ns + ('0' <= *p && *p <= '9' ? *p++ - '0' : 0);
+              p_uptime->tv_sec = s;
+              p_uptime->tv_nsec = ns;
+              return 0;
+            }
+        }
+    }
+# endif
+
+# if HAVE_DECL_SYSINFO /* not available in Android API < 9 */
+  /* The sysinfo call returns the uptime with a resolution of 1 sec only.  */
+  struct sysinfo info;
+  if (sysinfo (&info) >= 0)
+    {
+      p_uptime->tv_sec = info.uptime;
+      p_uptime->tv_nsec = 0;
+      return 0;
+    }
+# endif
+
+  return -1;
+}
+
+#endif
+
+#if defined __linux__ && !defined __ANDROID__
+
+static int
+get_linux_boot_time_fallback (struct timespec *p_boot_time)
+{
+  /* On Alpine Linux, UTMP_FILE is not filled.  It is always empty.
+     So, get the time stamp of a file that gets touched only during the
+     boot process.  */
+
+  const char * const boot_touched_files[] =
+    {
+      "/var/lib/systemd/random-seed", /* seen on distros with systemd */
+      "/var/run/utmp",                /* seen on distros with OpenRC */
+      "/var/lib/random-seed"          /* seen on older distros */
+    };
+  for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
+    {
+      const char *filename = boot_touched_files[i];
+      struct stat statbuf;
+      if (stat (filename, &statbuf) >= 0)
+        {
+          *p_boot_time = get_stat_mtime (&statbuf);
+          return 0;
+        }
+    }
+  return -1;
+}
+
+/* The following approach is only usable as a fallback, because it is of
+   the form
+     boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
+   and therefore produces wrong values after the date has been bumped in the
+   running system, which happens frequently if the system is running in a
+   virtual machine and this VM has been put into "saved" or "sleep" state
+   and then resumed.  */
+static int
+get_linux_boot_time_final_fallback (struct timespec *p_boot_time)
+{
+  struct timespec uptime;
+  if (get_linux_uptime (&uptime) >= 0)
+    {
+      struct timespec result;
+# if !defined __GLIBC__ || 2 < __GLIBC__ + (16 <= __GLIBC_MINOR__)
+      /* Better than:
+      if (0 <= clock_gettime (CLOCK_REALTIME, &result))
+         because timespec_get does not need -lrt in glibc 2.16.
+      */
+      if (! timespec_get (&result, TIME_UTC))
+        return -1;
+#  else
+      /* Fall back on lower-res approach that does not need -lrt.
+         This is good enough; on these hosts UPTIME is even lower-res.  */
+      struct timeval tv;
+      int r = gettimeofday (&tv, NULL);
+      if (r < 0)
+        return r;
+      result.tv_sec = tv.tv_sec;
+      result.tv_nsec = tv.tv_usec * 1000;
+#  endif
+
+      if (result.tv_nsec < uptime.tv_nsec)
+        {
+          result.tv_nsec += 1000000000;
+          result.tv_sec -= 1;
+        }
+      result.tv_sec -= uptime.tv_sec;
+      result.tv_nsec -= uptime.tv_nsec;
+      *p_boot_time = result;
+      return 0;
+    }
+  return -1;
+}
+
+#endif
+
+#if defined __ANDROID__
+
+static int
+get_android_boot_time (struct timespec *p_boot_time)
+{
+  /* On Android, there is no /var, and normal processes don't have access
+     to system files.  Therefore use the kernel's uptime counter, although
+     it produces wrong values after the date has been bumped in the running
+     system.  */
+  struct timespec uptime;
+  if (get_linux_uptime (&uptime) >= 0)
+    {
+      struct timespec result;
+      if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
+        {
+          if (result.tv_nsec < uptime.tv_nsec)
+            {
+              result.tv_nsec += 1000000000;
+              result.tv_sec -= 1;
+            }
+          result.tv_sec -= uptime.tv_sec;
+          result.tv_nsec -= uptime.tv_nsec;
+          *p_boot_time = result;
+          return 0;
+        }
+    }
+  return -1;
+}
+
+#endif
+
+#if defined __OpenBSD__
+
+static int
+get_openbsd_boot_time (struct timespec *p_boot_time)
+{
+  /* On OpenBSD, UTMP_FILE is not filled.  It contains only dummy entries.
+     So, get the time stamp of a file that gets touched only during the
+     boot process.  */
+  const char * const boot_touched_files[] =
+    {
+      "/var/db/host.random",
+      "/var/run/utmp"
+    };
+  for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
+    {
+      const char *filename = boot_touched_files[i];
+      struct stat statbuf;
+      if (stat (filename, &statbuf) >= 0)
+        {
+          *p_boot_time = get_stat_mtime (&statbuf);
+          return 0;
+        }
+    }
+  return -1;
+}
+
+#endif
+
+#if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
+    && defined CTL_KERN && defined KERN_BOOTTIME \
+    && !defined __minix
+/* macOS, FreeBSD, GNU/kFreeBSD, NetBSD, OpenBSD */
+/* On Minix 3.3 this sysctl produces garbage results.  Therefore avoid it.  */
+
+/* The following approach is only usable as a fallback, because it produces
+   wrong values after the date has been bumped in the running system, which
+   happens frequently if the system is running in a virtual machine and this
+   VM has been put into "saved" or "sleep" state and then resumed.  */
+static int
+get_bsd_boot_time_final_fallback (struct timespec *p_boot_time)
+{
+  static int request[2] = { CTL_KERN, KERN_BOOTTIME };
+  struct timeval result;
+  size_t result_len = sizeof result;
+
+  if (sysctl (request, 2, &result, &result_len, NULL, 0) >= 0)
+    {
+      p_boot_time->tv_sec = result.tv_sec;
+      p_boot_time->tv_nsec = result.tv_usec * 1000;
+      return 0;
+    }
+  return -1;
+}
+
+#endif
+
+#if defined __HAIKU__
+
+static int
+get_haiku_boot_time (struct timespec *p_boot_time)
+{
+  /* On Haiku, /etc/utmp does not exist.  During boot,
+       1. the current time is restored, but possibly with a wrong time zone,
+          that is, with an offset of a few hours,
+       2. some symlinks and files get created,
+       3. the various devices are brought up, in particular the network device,
+       4. the correct date and time is set,
+       5. some more device nodes get created.
+     The boot time can be retrieved by looking at a directory created during
+     phase 5, such as /dev/input.  */
+  const char * const boot_touched_file = "/dev/input";
+  struct stat statbuf;
+  if (stat (boot_touched_file, &statbuf) >= 0)
+    {
+      *p_boot_time = get_stat_mtime (&statbuf);
+      return 0;
+    }
+  return -1;
+}
+
+#endif
+
+#if HAVE_OS_H /* BeOS, Haiku */
+
+/* The following approach is only usable as a fallback, because it produces
+   wrong values after the date has been bumped in the running system, which
+   happens frequently if the system is running in a virtual machine and this
+   VM has been put into "saved" or "sleep" state and then resumed.  */
+static int
+get_haiku_boot_time_final_fallback (struct timespec *p_boot_time)
+{
+  system_info si;
+
+  get_system_info (&si);
+  p_boot_time->tv_sec = si.boot_time / 1000000;
+  p_boot_time->tv_nsec = (si.boot_time % 1000000) * 1000;
+  return 0;
+}
+
+#endif
+
+#if defined __CYGWIN__ || defined _WIN32
+
+static int
+get_windows_boot_time (struct timespec *p_boot_time)
+{
+  /* On Cygwin, /var/run/utmp is empty.
+     On native Windows, <utmpx.h> and <utmp.h> don't exist.
+     Instead, on Windows, the boot time can be retrieved by looking at the
+     time stamp of a file that (normally) gets touched only during the boot
+     process, namely C:\pagefile.sys.  */
+  const char * const boot_touched_file =
+    #if defined __CYGWIN__ && !defined _WIN32
+    "/cygdrive/c/pagefile.sys"
+    #else
+    "C:\\pagefile.sys"
+    #endif
+    ;
+  struct stat statbuf;
+  if (stat (boot_touched_file, &statbuf) >= 0)
+    {
+      *p_boot_time = get_stat_mtime (&statbuf);
+      return 0;
+    }
+  return -1;
+}
+
+#endif
diff --git a/lib/boot-time.c b/lib/boot-time.c
new file mode 100644
index 00000000000..fe5b5b88c8e
--- /dev/null
+++ b/lib/boot-time.c
@@ -0,0 +1,287 @@
+/* Determine the time when the machine last booted.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation, either version 3 of the License,
+   or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "boot-time.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined __linux__ || defined __ANDROID__
+# include <sys/sysinfo.h>
+# include <time.h>
+#endif
+
+#if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) && !defined 
__minix
+# if HAVE_SYS_PARAM_H
+#  include <sys/param.h>
+# endif
+# include <sys/sysctl.h>
+#endif
+
+#if HAVE_OS_H
+# include <OS.h>
+#endif
+
+#include "idx.h"
+#include "readutmp.h"
+#include "stat-time.h"
+
+/* Each of the FILE streams in this file is only used in a single thread.  */
+#include "unlocked-io.h"
+
+/* Some helper functions.  */
+#include "boot-time-aux.h"
+
+/* The following macros describe the 'struct UTMP_STRUCT_NAME',
+   *not* 'struct gl_utmp'.  */
+#undef UT_USER
+
+/* Accessor macro for the member named ut_user or ut_name.  */
+#if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \
+     : HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME)
+# define UT_USER(UT) ((UT)->ut_name)
+#else
+# define UT_USER(UT) ((UT)->ut_user)
+#endif
+
+#if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION
+# if !HAVE_DECL_ENDUTENT /* Android */
+void endutent (void);
+# endif
+#endif
+
+#if defined __linux__ || HAVE_UTMPX_H || HAVE_UTMP_H || defined __CYGWIN__ || 
defined _WIN32
+
+static int
+get_boot_time_uncached (struct timespec *p_boot_time)
+{
+  struct timespec found_boot_time = {0};
+
+# if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+
+  /* Try to find the boot time in the /var/run/utmp file.  */
+
+#  if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, 
Minix, AIX, IRIX, Solaris, Cygwin, Android */
+
+  /* Ignore the return value for now.
+     Solaris' utmpname returns 1 upon success -- which is contrary
+     to what the GNU libc version does.  In addition, older GNU libc
+     versions are actually void.   */
+  UTMP_NAME_FUNCTION ((char *) UTMP_FILE);
+
+  SET_UTMP_ENT ();
+
+#   if (defined __linux__ && !defined __ANDROID__) || defined __minix
+  /* Timestamp of the "runlevel" entry, if any.  */
+  struct timespec runlevel_ts = {0};
+#   endif
+
+  void const *entry;
+
+  while ((entry = GET_UTMP_ENT ()) != NULL)
+    {
+      struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) 
entry;
+
+      struct timespec ts =
+        #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+        { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 };
+        #else
+        { .tv_sec = ut->ut_time, .tv_nsec = 0 };
+        #endif
+
+      if (ut->ut_type == BOOT_TIME)
+        found_boot_time = ts;
+
+#   if defined __linux__ && !defined __ANDROID__
+      if (memcmp (UT_USER (ut), "runlevel", strlen ("runlevel") + 1) == 0
+          && memcmp (ut->ut_line, "~", strlen ("~") + 1) == 0)
+        runlevel_ts = ts;
+#   endif
+#   if defined __minix
+      if (UT_USER (ut)[0] == '\0'
+          && memcmp (ut->ut_line, "run-level ", strlen ("run-level ")) == 0)
+        runlevel_ts = ts;
+#   endif
+    }
+
+  END_UTMP_ENT ();
+
+#   if defined __linux__ && !defined __ANDROID__
+  /* On Raspbian, which runs on hardware without a real-time clock, during 
boot,
+       1. the clock gets set to 1970-01-01 00:00:00,
+       2. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
+          ut_user = "reboot", ut_line = "~", time = 1970-01-01 00:00:05 or so,
+       3. the clock gets set to a correct value through NTP,
+       4. an entry gets written into /var/run/utmp, with
+          ut_user = "runlevel", ut_line = "~", time = correct value.
+     In this case, get the time from the "runlevel" entry.  */
+
+  /* Workaround for Raspbian:  */
+  if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
+    found_boot_time = runlevel_ts;
+  if (found_boot_time.tv_sec == 0)
+    {
+      /* Workaround for Alpine Linux:  */
+      get_linux_boot_time_fallback (&found_boot_time);
+    }
+#   endif
+
+#   if defined __ANDROID__
+  if (found_boot_time.tv_sec == 0)
+    {
+      /* Workaround for Android:  */
+      get_android_boot_time (&found_boot_time);
+    }
+#   endif
+
+#   if defined __minix
+  /* On Minix, during boot,
+       1. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
+          ut_user = "", ut_line = "system boot", time = 1970-01-01 00:00:00,
+       2. an entry gets written into /var/run/utmp, with
+          ut_user = "", ut_line = "run-level m", time = correct value.
+     In this case, copy the time from the "run-level m" entry to the
+     "system boot" entry.  */
+  if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
+    found_boot_time = runlevel_ts;
+#   endif
+
+#  else /* HP-UX, Haiku */
+
+  FILE *f = fopen (UTMP_FILE, "re");
+
+  if (f != NULL)
+    {
+      for (;;)
+        {
+          struct UTMP_STRUCT_NAME ut;
+
+          if (fread (&ut, sizeof ut, 1, f) == 0)
+            break;
+
+          struct timespec ts =
+            #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+            { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 };
+            #else
+            { .tv_sec = ut.ut_time, .tv_nsec = 0 };
+            #endif
+
+          if (ut.ut_type == BOOT_TIME)
+            found_boot_time = ts;
+        }
+
+      fclose (f);
+    }
+
+#  endif
+
+#  if defined __linux__ && !defined __ANDROID__
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_linux_boot_time_final_fallback (&found_boot_time);
+    }
+#  endif
+
+# else /* old FreeBSD, OpenBSD, native Windows */
+
+#  if defined __OpenBSD__
+  /* Workaround for OpenBSD:  */
+  get_openbsd_boot_time (&found_boot_time);
+#  endif
+
+# endif
+
+# if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
+     && defined CTL_KERN && defined KERN_BOOTTIME \
+     && !defined __minix
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_bsd_boot_time_final_fallback (&found_boot_time);
+    }
+# endif
+
+# if defined __HAIKU__
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_haiku_boot_time (&found_boot_time);
+    }
+# endif
+
+# if HAVE_OS_H
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_haiku_boot_time_final_fallback (&found_boot_time);
+    }
+# endif
+
+# if defined __CYGWIN__ || defined _WIN32
+  if (found_boot_time.tv_sec == 0)
+    {
+      /* Workaround for Windows:  */
+      get_windows_boot_time (&found_boot_time);
+    }
+# endif
+
+  if (found_boot_time.tv_sec != 0)
+    {
+      *p_boot_time = found_boot_time;
+      return 0;
+    }
+  else
+    return -1;
+}
+
+int
+get_boot_time (struct timespec *p_boot_time)
+{
+  /* Cache the result from get_boot_time_uncached.  */
+  static int volatile cached_result = -1;
+  static struct timespec volatile cached_boot_time;
+
+  if (cached_result < 0)
+    {
+      struct timespec boot_time;
+      int result = get_boot_time_uncached (&boot_time);
+      cached_boot_time = boot_time;
+      cached_result = result;
+    }
+
+  if (cached_result == 0)
+    {
+      *p_boot_time = cached_boot_time;
+      return 0;
+    }
+  else
+    return -1;
+}
+
+#else
+
+int
+get_boot_time (struct timespec *p_boot_time)
+{
+  return -1;
+}
+
+#endif
diff --git a/lib/boot-time.h b/lib/boot-time.h
new file mode 100644
index 00000000000..401e854adbb
--- /dev/null
+++ b/lib/boot-time.h
@@ -0,0 +1,44 @@
+/* Determine the time when the machine last booted.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation, either version 3 of the License,
+   or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>.  */
+
+#ifndef _BOOT_TIME_H
+#define _BOOT_TIME_H
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Store the approximate time when the machine last booted in *P_BOOT_TIME,
+   and return 0.  If it cannot be determined, return -1.
+
+   This function is not multithread-safe, since on many platforms it
+   invokes the functions setutxent, getutxent, endutxent.  These
+   functions are needed because they may lock FILE (so that we don't
+   read garbage when a concurrent process writes to FILE), but their
+   drawback is that they have a common global state.  */
+extern int get_boot_time (struct timespec *p_boot_time);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BOOT_TIME_H */
diff --git a/lib/diffseq.h b/lib/diffseq.h
index 06e1465bf1b..3f85ab2ec41 100644
--- a/lib/diffseq.h
+++ b/lib/diffseq.h
@@ -92,20 +92,11 @@
 # define NOTE_ORDERED false
 #endif
 
-/* Use this to suppress gcc's "...may be used before initialized" warnings.
-   Beware: The Code argument must not contain commas.  */
+/* Suppress gcc's "...may be used before initialized" warnings,
+   generated by GCC versions up to at least GCC 13.2.  */
 #if __GNUC__ + (__GNUC_MINOR__ >= 7) > 4
 # pragma GCC diagnostic push
-#endif
-#ifndef IF_LINT
-# if defined GCC_LINT || defined lint
-#  define IF_LINT(Code) Code
-# else
-#  define IF_LINT(Code) /* empty */
-#  if __GNUC__ + (__GNUC_MINOR__ >= 7) > 4
-#   pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
-#  endif
-# endif
+# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
 #endif
 
 /*
@@ -388,13 +379,8 @@ diag (OFFSET xoff, OFFSET xlim, OFFSET yoff, OFFSET ylim, 
bool find_minimal,
          and report halfway between our best results so far.  */
       if (c >= ctxt->too_expensive)
         {
-          OFFSET fxybest;
-          OFFSET fxbest IF_LINT (= 0);
-          OFFSET bxybest;
-          OFFSET bxbest IF_LINT (= 0);
-
           /* Find forward diagonal that maximizes X + Y.  */
-          fxybest = -1;
+          OFFSET fxybest = -1, fxbest;
           for (d = fmax; d >= fmin; d -= 2)
             {
               OFFSET x = MIN (fd[d], xlim);
@@ -412,7 +398,7 @@ diag (OFFSET xoff, OFFSET xlim, OFFSET yoff, OFFSET ylim, 
bool find_minimal,
             }
 
           /* Find backward diagonal that minimizes X + Y.  */
-          bxybest = OFFSET_MAX;
+          OFFSET bxybest = OFFSET_MAX, bxbest;
           for (d = bmax; d >= bmin; d -= 2)
             {
               OFFSET x = MAX (xoff, bd[d]);
diff --git a/lib/getdelim.c b/lib/getdelim.c
new file mode 100644
index 00000000000..e414df648f6
--- /dev/null
+++ b/lib/getdelim.c
@@ -0,0 +1,143 @@
+/* getdelim.c --- Implementation of replacement getdelim function.
+   Copyright (C) 1994, 1996-1998, 2001, 2003, 2005-2023 Free Software
+   Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Ported from glibc by Simon Josefsson. */
+
+/* Don't use __attribute__ __nonnull__ in this compilation unit.  Otherwise gcc
+   optimizes away the lineptr == NULL || n == NULL || fp == NULL tests below.  
*/
+#define _GL_ARG_NONNULL(params)
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#if USE_UNLOCKED_IO
+# include "unlocked-io.h"
+# define getc_maybe_unlocked(fp)        getc(fp)
+#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED
+# undef flockfile
+# undef funlockfile
+# define flockfile(x) ((void) 0)
+# define funlockfile(x) ((void) 0)
+# define getc_maybe_unlocked(fp)        getc(fp)
+#else
+# define getc_maybe_unlocked(fp)        getc_unlocked(fp)
+#endif
+
+static void
+alloc_failed (void)
+{
+#if defined _WIN32 && ! defined __CYGWIN__
+  /* Avoid errno problem without using the realloc module; see:
+     https://lists.gnu.org/r/bug-gnulib/2016-08/msg00025.html  */
+  errno = ENOMEM;
+#endif
+}
+
+/* Read up to (and including) a DELIMITER from FP into *LINEPTR (and
+   NUL-terminate it).  *LINEPTR is a pointer returned from malloc (or
+   NULL), pointing to *N characters of space.  It is realloc'ed as
+   necessary.  Returns the number of characters read (not including
+   the null terminator), or -1 on error or EOF.  */
+
+ssize_t
+getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
+{
+  ssize_t result;
+  size_t cur_len = 0;
+
+  if (lineptr == NULL || n == NULL || fp == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  flockfile (fp);
+
+  if (*lineptr == NULL || *n == 0)
+    {
+      char *new_lineptr;
+      *n = 120;
+      new_lineptr = (char *) realloc (*lineptr, *n);
+      if (new_lineptr == NULL)
+        {
+          alloc_failed ();
+          result = -1;
+          goto unlock_return;
+        }
+      *lineptr = new_lineptr;
+    }
+
+  for (;;)
+    {
+      int i;
+
+      i = getc_maybe_unlocked (fp);
+      if (i == EOF)
+        {
+          result = -1;
+          break;
+        }
+
+      /* Make enough space for len+1 (for final NUL) bytes.  */
+      if (cur_len + 1 >= *n)
+        {
+          size_t needed_max =
+            SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
+          size_t needed = 2 * *n + 1;   /* Be generous. */
+          char *new_lineptr;
+
+          if (needed_max < needed)
+            needed = needed_max;
+          if (cur_len + 1 >= needed)
+            {
+              result = -1;
+              errno = EOVERFLOW;
+              goto unlock_return;
+            }
+
+          new_lineptr = (char *) realloc (*lineptr, needed);
+          if (new_lineptr == NULL)
+            {
+              alloc_failed ();
+              result = -1;
+              goto unlock_return;
+            }
+
+          *lineptr = new_lineptr;
+          *n = needed;
+        }
+
+      (*lineptr)[cur_len] = i;
+      cur_len++;
+
+      if (i == delimiter)
+        break;
+    }
+  (*lineptr)[cur_len] = '\0';
+  result = cur_len ? cur_len : result;
+
+ unlock_return:
+  funlockfile (fp); /* doesn't set errno */
+
+  return result;
+}
diff --git a/lib/getline.c b/lib/getline.c
new file mode 100644
index 00000000000..85f16ab8bac
--- /dev/null
+++ b/lib/getline.c
@@ -0,0 +1,27 @@
+/* getline.c --- Implementation of replacement getline function.
+   Copyright (C) 2005-2007, 2009-2023 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Simon Josefsson. */
+
+#include <config.h>
+
+#include <stdio.h>
+
+ssize_t
+getline (char **lineptr, size_t *n, FILE *stream)
+{
+  return getdelim (lineptr, n, '\n', stream);
+}
diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in
index 3ef36bf8af6..3b33f39f73b 100644
--- a/lib/gnulib.mk.in
+++ b/lib/gnulib.mk.in
@@ -41,6 +41,10 @@
 #  --avoid=dup \
 #  --avoid=fchdir \
 #  --avoid=fstat \
+#  --avoid=iswblank \
+#  --avoid=iswctype \
+#  --avoid=iswdigit \
+#  --avoid=iswxdigit \
 #  --avoid=langinfo \
 #  --avoid=lock \
 #  --avoid=mbrtowc \
@@ -67,10 +71,12 @@
 #  --avoid=utime-h \
 #  --avoid=wchar \
 #  --avoid=wcrtomb \
+#  --avoid=wctype \
 #  --avoid=wctype-h \
 #  alignasof \
 #  alloca-opt \
 #  binary-io \
+#  boot-time \
 #  byteswap \
 #  c-ctype \
 #  c-strcase \
@@ -110,6 +116,7 @@
 #  fsusage \
 #  fsync \
 #  futimens \
+#  getline \
 #  getloadavg \
 #  getopt-gnu \
 #  getrandom \
@@ -174,11 +181,28 @@
 
 MOSTLYCLEANFILES += core *.stackdump
 # Start of GNU Make output.
+AAPT = @AAPT@
 ALLOCA = @ALLOCA@
 ALLOCA_H = @ALLOCA_H@
 ALSA_CFLAGS = @ALSA_CFLAGS@
 ALSA_LIBS = @ALSA_LIBS@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+ANDROID = @ANDROID@
+ANDROID_ABI = @ANDROID_ABI@
+ANDROID_BUILD_CFLAGS = @ANDROID_BUILD_CFLAGS@
+ANDROID_CC = @ANDROID_CC@
+ANDROID_CFLAGS = @ANDROID_CFLAGS@
+ANDROID_DEBUGGABLE = @ANDROID_DEBUGGABLE@
+ANDROID_JAR = @ANDROID_JAR@
+ANDROID_LDFLAGS = @ANDROID_LDFLAGS@
+ANDROID_LIBS = @ANDROID_LIBS@
+ANDROID_MIN_SDK = @ANDROID_MIN_SDK@
+ANDROID_OBJ = @ANDROID_OBJ@
+ANDROID_SDK_18_OR_EARLIER = @ANDROID_SDK_18_OR_EARLIER@
+ANDROID_SDK_8_OR_EARLIER = @ANDROID_SDK_8_OR_EARLIER@
+ANDROID_SHARED_USER_ID = @ANDROID_SHARED_USER_ID@
+ANDROID_SHARED_USER_NAME = @ANDROID_SHARED_USER_NAME@
+APKSIGNER = @APKSIGNER@
 APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@
 AR = @AR@
 ARFLAGS = @ARFLAGS@
@@ -218,6 +242,7 @@ CYGWIN_OBJ = @CYGWIN_OBJ@
 C_SWITCH_MACHINE = @C_SWITCH_MACHINE@
 C_SWITCH_SYSTEM = @C_SWITCH_SYSTEM@
 C_SWITCH_X_SITE = @C_SWITCH_X_SITE@
+D8 = @D8@
 DBUS_CFLAGS = @DBUS_CFLAGS@
 DBUS_LIBS = @DBUS_LIBS@
 DBUS_OBJ = @DBUS_OBJ@
@@ -262,6 +287,7 @@ GETOPT_H = @GETOPT_H@
 GETRANDOM_LIB = @GETRANDOM_LIB@
 GFILENOTIFY_CFLAGS = @GFILENOTIFY_CFLAGS@
 GFILENOTIFY_LIBS = @GFILENOTIFY_LIBS@
+GIF_CFLAGS = @GIF_CFLAGS@
 GL_CFLAG_ALLOW_WARNINGS = @GL_CFLAG_ALLOW_WARNINGS@
 GL_CFLAG_GNULIB_WARNINGS = @GL_CFLAG_GNULIB_WARNINGS@
 GL_COND_LIBTOOL_CONDITION = @GL_COND_LIBTOOL_CONDITION@
@@ -281,8 +307,10 @@ GL_COND_OBJ_FSTATAT_CONDITION = 
@GL_COND_OBJ_FSTATAT_CONDITION@
 GL_COND_OBJ_FSUSAGE_CONDITION = @GL_COND_OBJ_FSUSAGE_CONDITION@
 GL_COND_OBJ_FSYNC_CONDITION = @GL_COND_OBJ_FSYNC_CONDITION@
 GL_COND_OBJ_FUTIMENS_CONDITION = @GL_COND_OBJ_FUTIMENS_CONDITION@
+GL_COND_OBJ_GETDELIM_CONDITION = @GL_COND_OBJ_GETDELIM_CONDITION@
 GL_COND_OBJ_GETDTABLESIZE_CONDITION = @GL_COND_OBJ_GETDTABLESIZE_CONDITION@
 GL_COND_OBJ_GETGROUPS_CONDITION = @GL_COND_OBJ_GETGROUPS_CONDITION@
+GL_COND_OBJ_GETLINE_CONDITION = @GL_COND_OBJ_GETLINE_CONDITION@
 GL_COND_OBJ_GETLOADAVG_CONDITION = @GL_COND_OBJ_GETLOADAVG_CONDITION@
 GL_COND_OBJ_GETOPT_CONDITION = @GL_COND_OBJ_GETOPT_CONDITION@
 GL_COND_OBJ_GETRANDOM_CONDITION = @GL_COND_OBJ_GETRANDOM_CONDITION@
@@ -634,7 +662,6 @@ GL_GNULIB__EXIT = @GL_GNULIB__EXIT@
 GMALLOC_OBJ = @GMALLOC_OBJ@
 GMP_H = @GMP_H@
 GNULIBHEADERS_OVERRIDE_WINT_T = @GNULIBHEADERS_OVERRIDE_WINT_T@
-GNULIB_GETTIMEOFDAY = @GNULIB_GETTIMEOFDAY@
 GNULIB_WARN_CFLAGS = @GNULIB_WARN_CFLAGS@
 GNUSTEP_CFLAGS = @GNUSTEP_CFLAGS@
 GNU_OBJC_CFLAGS = @GNU_OBJC_CFLAGS@
@@ -892,6 +919,9 @@ INSTALL_PROGRAM = @INSTALL_PROGRAM@
 INSTALL_SCRIPT = @INSTALL_SCRIPT@
 INT32_MAX_LT_INTMAX_MAX = @INT32_MAX_LT_INTMAX_MAX@
 INT64_MAX_EQ_LONG_MAX = @INT64_MAX_EQ_LONG_MAX@
+JARSIGNER = @JARSIGNER@
+JAVAC = @JAVAC@
+JPEG_CFLAGS = @JPEG_CFLAGS@
 JSON_CFLAGS = @JSON_CFLAGS@
 JSON_LIBS = @JSON_LIBS@
 JSON_OBJ = @JSON_OBJ@
@@ -910,6 +940,7 @@ LIBGCCJIT_CFLAGS = @LIBGCCJIT_CFLAGS@
 LIBGCCJIT_LIBS = @LIBGCCJIT_LIBS@
 LIBGIF = @LIBGIF@
 LIBGMP = @LIBGMP@
+LIBGMP_CFLAGS = @LIBGMP_CFLAGS@
 LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@
 LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@
 LIBGNU_LIBDEPS = @LIBGNU_LIBDEPS@
@@ -927,6 +958,7 @@ LIBRESOLV = @LIBRESOLV@
 LIBS = @LIBS@
 LIBSECCOMP_CFLAGS = @LIBSECCOMP_CFLAGS@
 LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@
+LIBSELINUX_CFLAGS = @LIBSELINUX_CFLAGS@
 LIBSELINUX_LIBS = @LIBSELINUX_LIBS@
 LIBSOUND = @LIBSOUND@
 LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
@@ -974,6 +1006,18 @@ MODULES_SECONDARY_SUFFIX = @MODULES_SECONDARY_SUFFIX@
 MODULES_SUFFIX = @MODULES_SUFFIX@
 NANOSLEEP_LIB = @NANOSLEEP_LIB@
 NATIVE_COMPILATION_AOT = @NATIVE_COMPILATION_AOT@
+NDK_BUILD_ABI = @NDK_BUILD_ABI@
+NDK_BUILD_ANDROID_MK = @NDK_BUILD_ANDROID_MK@
+NDK_BUILD_ANY_CXX_MODULE = @NDK_BUILD_ANY_CXX_MODULE@
+NDK_BUILD_AR = @NDK_BUILD_AR@
+NDK_BUILD_ARCH = @NDK_BUILD_ARCH@
+NDK_BUILD_CC = @NDK_BUILD_CC@
+NDK_BUILD_CFLAGS = @NDK_BUILD_CFLAGS@
+NDK_BUILD_CXX = @NDK_BUILD_CXX@
+NDK_BUILD_CXX_SHARED = @NDK_BUILD_CXX_SHARED@
+NDK_BUILD_MODULES = @NDK_BUILD_MODULES@
+NDK_BUILD_NASM = @NDK_BUILD_NASM@
+NDK_BUILD_SDK = @NDK_BUILD_SDK@
 NEXT_ASSERT_H = @NEXT_ASSERT_H@
 NEXT_AS_FIRST_DIRECTIVE_ASSERT_H = @NEXT_AS_FIRST_DIRECTIVE_ASSERT_H@
 NEXT_AS_FIRST_DIRECTIVE_DIRENT_H = @NEXT_AS_FIRST_DIRECTIVE_DIRENT_H@
@@ -1242,6 +1286,7 @@ REPLACE_WRITE = @REPLACE_WRITE@
 REPLACE__EXIT = @REPLACE__EXIT@
 RSVG_CFLAGS = @RSVG_CFLAGS@
 RSVG_LIBS = @RSVG_LIBS@
+SDK_BUILD_TOOLS = @SDK_BUILD_TOOLS@
 SEPCHAR = @SEPCHAR@
 SETFATTR = @SETFATTR@
 SETTINGS_CFLAGS = @SETTINGS_CFLAGS@
@@ -1251,6 +1296,7 @@ SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@
 SIZEOF_LONG = @SIZEOF_LONG@
 SIZE_T_SUFFIX = @SIZE_T_SUFFIX@
 SMALL_JA_DIC = @SMALL_JA_DIC@
+SQLITE3_CFLAGS = @SQLITE3_CFLAGS@
 SQLITE3_LIBS = @SQLITE3_LIBS@
 STDCKDINT_H = @STDCKDINT_H@
 STDDEF_H = @STDDEF_H@
@@ -1259,6 +1305,7 @@ SUBDIR_MAKEFILES_IN = @SUBDIR_MAKEFILES_IN@
 SYSTEM_TYPE = @SYSTEM_TYPE@
 SYS_TIME_H_DEFINES_STRUCT_TIMESPEC = @SYS_TIME_H_DEFINES_STRUCT_TIMESPEC@
 TERMCAP_OBJ = @TERMCAP_OBJ@
+TIFF_CFLAGS = @TIFF_CFLAGS@
 TIMER_TIME_LIB = @TIMER_TIME_LIB@
 TIME_H_DEFINES_STRUCT_TIMESPEC = @TIME_H_DEFINES_STRUCT_TIMESPEC@
 TIME_H_DEFINES_TIME_UTC = @TIME_H_DEFINES_TIME_UTC@
@@ -1280,6 +1327,7 @@ W32_LIBS = @W32_LIBS@
 W32_OBJ = @W32_OBJ@
 W32_RES_LINK = @W32_RES_LINK@
 WARN_CFLAGS = @WARN_CFLAGS@
+WARN_JAVAFLAGS = @WARN_JAVAFLAGS@
 WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@
 WEBKIT_CFLAGS = @WEBKIT_CFLAGS@
 WEBKIT_LIBS = @WEBKIT_LIBS@
@@ -1298,6 +1346,7 @@ XARGS_LIMIT = @XARGS_LIMIT@
 XCB_LIBS = @XCB_LIBS@
 XCOMPOSITE_CFLAGS = @XCOMPOSITE_CFLAGS@
 XCOMPOSITE_LIBS = @XCOMPOSITE_LIBS@
+XCONFIGURE = @XCONFIGURE@
 XCRUN = @XCRUN@
 XDBE_CFLAGS = @XDBE_CFLAGS@
 XDBE_LIBS = @XDBE_LIBS@
@@ -1322,6 +1371,7 @@ XSYNC_CFLAGS = @XSYNC_CFLAGS@
 XSYNC_LIBS = @XSYNC_LIBS@
 XWIDGETS_OBJ = @XWIDGETS_OBJ@
 X_TOOLKIT_TYPE = @X_TOOLKIT_TYPE@
+ZIPALIGN = @ZIPALIGN@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_OBJC = @ac_ct_OBJC@
@@ -1344,6 +1394,7 @@ datarootdir = @datarootdir@
 docdir = @docdir@
 dvidir = @dvidir@
 emacs_major_version = @emacs_major_version@
+emacs_use_mailutils = @emacs_use_mailutils@
 etcdir = @etcdir@
 etcdocdir = @etcdocdir@
 exec_prefix = @exec_prefix@
@@ -1367,6 +1418,7 @@ 
gl_GNULIB_ENABLED_e80bf6f757095d2e5fc94dafb8f8fc8b_CONDITION = @gl_GNULIB_ENABLE
 gl_GNULIB_ENABLED_ef455225c00f5049c808c2eda3e76866_CONDITION = 
@gl_GNULIB_ENABLED_ef455225c00f5049c808c2eda3e76866_CONDITION@
 gl_GNULIB_ENABLED_euidaccess_CONDITION = 
@gl_GNULIB_ENABLED_euidaccess_CONDITION@
 gl_GNULIB_ENABLED_fd38c7e463b54744b77b98aeafb4fa7c_CONDITION = 
@gl_GNULIB_ENABLED_fd38c7e463b54744b77b98aeafb4fa7c_CONDITION@
+gl_GNULIB_ENABLED_getdelim_CONDITION = @gl_GNULIB_ENABLED_getdelim_CONDITION@
 gl_GNULIB_ENABLED_getdtablesize_CONDITION = 
@gl_GNULIB_ENABLED_getdtablesize_CONDITION@
 gl_GNULIB_ENABLED_getgroups_CONDITION = @gl_GNULIB_ENABLED_getgroups_CONDITION@
 gl_GNULIB_ENABLED_lchmod_CONDITION = @gl_GNULIB_ENABLED_lchmod_CONDITION@
@@ -1550,6 +1602,16 @@ libgnu_a_SOURCES += binary-io.h binary-io.c
 endif
 ## end   gnulib module binary-io
 
+## begin gnulib module boot-time
+ifeq (,$(OMIT_GNULIB_MODULE_boot-time))
+
+libgnu_a_SOURCES += boot-time.c
+
+EXTRA_DIST += boot-time-aux.h boot-time.h readutmp.h
+
+endif
+## end   gnulib module boot-time
+
 ## begin gnulib module byteswap
 ifeq (,$(OMIT_GNULIB_MODULE_byteswap))
 
@@ -2130,6 +2192,18 @@ gl_V_at = $(AM_V_GEN)
 endif
 ## end   gnulib module gen-header
 
+## begin gnulib module getdelim
+ifeq (,$(OMIT_GNULIB_MODULE_getdelim))
+
+ifneq (,$(gl_GNULIB_ENABLED_getdelim_CONDITION))
+ifneq (,$(GL_COND_OBJ_GETDELIM_CONDITION))
+libgnu_a_SOURCES += getdelim.c
+endif
+
+endif
+endif
+## end   gnulib module getdelim
+
 ## begin gnulib module getdtablesize
 ifeq (,$(OMIT_GNULIB_MODULE_getdtablesize))
 
@@ -2154,6 +2228,16 @@ endif
 endif
 ## end   gnulib module getgroups
 
+## begin gnulib module getline
+ifeq (,$(OMIT_GNULIB_MODULE_getline))
+
+ifneq (,$(GL_COND_OBJ_GETLINE_CONDITION))
+libgnu_a_SOURCES += getline.c
+endif
+
+endif
+## end   gnulib module getline
+
 ## begin gnulib module getloadavg
 ifeq (,$(OMIT_GNULIB_MODULE_getloadavg))
 
@@ -3124,8 +3208,9 @@ stdio.h: stdio.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H)
              -e 's/@''GNULIB_MDA_GETW''@/$(GL_GNULIB_MDA_GETW)/g' \
              -e 's/@''GNULIB_MDA_PUTW''@/$(GL_GNULIB_MDA_PUTW)/g' \
              -e 's/@''GNULIB_MDA_TEMPNAM''@/$(GL_GNULIB_MDA_TEMPNAM)/g' \
-             < $(srcdir)/stdio.in.h | \
-         sed -e 's|@''HAVE_DECL_FCLOSEALL''@|$(HAVE_DECL_FCLOSEALL)|g' \
+             < $(srcdir)/stdio.in.h > $@-t1
+       $(AM_V_at)sed \
+             -e 's|@''HAVE_DECL_FCLOSEALL''@|$(HAVE_DECL_FCLOSEALL)|g' \
              -e 's|@''HAVE_DECL_FPURGE''@|$(HAVE_DECL_FPURGE)|g' \
              -e 's|@''HAVE_DECL_FSEEKO''@|$(HAVE_DECL_FSEEKO)|g' \
              -e 's|@''HAVE_DECL_FTELLO''@|$(HAVE_DECL_FTELLO)|g' \
@@ -3144,6 +3229,8 @@ stdio.h: stdio.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H)
              -e 's|@''HAVE_RENAMEAT''@|$(HAVE_RENAMEAT)|g' \
              -e 's|@''HAVE_VASPRINTF''@|$(HAVE_VASPRINTF)|g' \
              -e 's|@''HAVE_VDPRINTF''@|$(HAVE_VDPRINTF)|g' \
+             < $@-t1 > $@-t2
+       $(AM_V_at)sed \
              -e 's|@''REPLACE_DPRINTF''@|$(REPLACE_DPRINTF)|g' \
              -e 's|@''REPLACE_FCLOSE''@|$(REPLACE_FCLOSE)|g' \
              -e 's|@''REPLACE_FDOPEN''@|$(REPLACE_FDOPEN)|g' \
@@ -3181,9 +3268,10 @@ stdio.h: stdio.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H)
              -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
              -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
              -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \
-             > $@-t
-       $(AM_V_at)mv $@-t $@
-MOSTLYCLEANFILES += stdio.h stdio.h-t
+             < $@-t2 > $@-t3
+       $(AM_V_at)rm -f $@-t1 $@-t2
+       $(AM_V_at)mv $@-t3 $@
+MOSTLYCLEANFILES += stdio.h stdio.h-t1 stdio.h-t2 stdio.h-t3
 
 ifneq (,$(GL_COND_OBJ_STDIO_READ_CONDITION))
 libgnu_a_SOURCES += stdio-read.c
@@ -3262,8 +3350,9 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) \
              -e 's/@''GNULIB_MDA_GCVT''@/$(GL_GNULIB_MDA_GCVT)/g' \
              -e 's/@''GNULIB_MDA_MKTEMP''@/$(GL_GNULIB_MDA_MKTEMP)/g' \
              -e 's/@''GNULIB_MDA_PUTENV''@/$(GL_GNULIB_MDA_PUTENV)/g' \
-             < $(srcdir)/stdlib.in.h | \
-         sed -e 's|@''HAVE__EXIT''@|$(HAVE__EXIT)|g' \
+             < $(srcdir)/stdlib.in.h > $@-t1
+       $(AM_V_at)sed \
+             -e 's|@''HAVE__EXIT''@|$(HAVE__EXIT)|g' \
              -e 's|@''HAVE_ALIGNED_ALLOC''@|$(HAVE_ALIGNED_ALLOC)|g' \
              -e 's|@''HAVE_ATOLL''@|$(HAVE_ATOLL)|g' \
              -e 
's|@''HAVE_CANONICALIZE_FILE_NAME''@|$(HAVE_CANONICALIZE_FILE_NAME)|g' \
@@ -3308,6 +3397,8 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) \
              -e 's|@''HAVE_SYS_LOADAVG_H''@|$(HAVE_SYS_LOADAVG_H)|g' \
              -e 's|@''HAVE_UNLOCKPT''@|$(HAVE_UNLOCKPT)|g' \
              -e 's|@''HAVE_DECL_UNSETENV''@|$(HAVE_DECL_UNSETENV)|g' \
+             < $@-t1 > $@-t2
+       $(AM_V_at)sed \
              -e 's|@''REPLACE__EXIT''@|$(REPLACE__EXIT)|g' \
              -e 's|@''REPLACE_ALIGNED_ALLOC''@|$(REPLACE_ALIGNED_ALLOC)|g' \
              -e 
's|@''REPLACE_CALLOC_FOR_CALLOC_GNU''@|$(REPLACE_CALLOC_FOR_CALLOC_GNU)|g' \
@@ -3352,9 +3443,10 @@ stdlib.h: stdlib.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) \
              -e '/definition of _Noreturn/r $(_NORETURN_H)' \
              -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
              -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \
-             > $@-t
-       $(AM_V_at)mv $@-t $@
-MOSTLYCLEANFILES += stdlib.h stdlib.h-t
+             < $@-t2 > $@-t3
+       $(AM_V_at)rm -f $@-t1 $@-t2
+       $(AM_V_at)mv $@-t3 $@
+MOSTLYCLEANFILES += stdlib.h stdlib.h-t1 stdlib.h-t2 stdlib.h-t3
 
 EXTRA_DIST += stdlib.in.h
 
@@ -3430,8 +3522,9 @@ string.h: string.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
              -e 's/@''GNULIB_MDA_MEMCCPY''@/$(GL_GNULIB_MDA_MEMCCPY)/g' \
              -e 's/@''GNULIB_MDA_STRDUP''@/$(GL_GNULIB_MDA_STRDUP)/g' \
              -e 's/@''GNULIB_FREE_POSIX''@/$(GL_GNULIB_FREE_POSIX)/g' \
-             < $(srcdir)/string.in.h | \
-         sed -e 's|@''HAVE_EXPLICIT_BZERO''@|$(HAVE_EXPLICIT_BZERO)|g' \
+             < $(srcdir)/string.in.h > $@-t1
+       $(AM_V_at)sed \
+             -e 's|@''HAVE_EXPLICIT_BZERO''@|$(HAVE_EXPLICIT_BZERO)|g' \
              -e 's|@''HAVE_FFSL''@|$(HAVE_FFSL)|g' \
              -e 's|@''HAVE_FFSLL''@|$(HAVE_FFSLL)|g' \
              -e 's|@''HAVE_MBSLEN''@|$(HAVE_MBSLEN)|g' \
@@ -3479,9 +3572,10 @@ string.h: string.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
              -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
              -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
              -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \
-             > $@-t
-       $(AM_V_at)mv $@-t $@
-MOSTLYCLEANFILES += string.h string.h-t
+             < $@-t1 > $@-t2
+       $(AM_V_at)rm -f $@-t1
+       $(AM_V_at)mv $@-t2 $@
+MOSTLYCLEANFILES += string.h string.h-t1 string.h-t2
 
 EXTRA_DIST += string.in.h
 
@@ -3918,6 +4012,8 @@ unistd.h: unistd.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
              -e 's/@''GNULIB_FDATASYNC''@/$(GL_GNULIB_FDATASYNC)/g' \
              -e 's/@''GNULIB_FSYNC''@/$(GL_GNULIB_FSYNC)/g' \
              -e 's/@''GNULIB_FTRUNCATE''@/$(GL_GNULIB_FTRUNCATE)/g' \
+             < $(srcdir)/unistd.in.h > $@-t1
+       $(AM_V_at)sed \
              -e 's/@''GNULIB_GETCWD''@/$(GL_GNULIB_GETCWD)/g' \
              -e 's/@''GNULIB_GETDOMAINNAME''@/$(GL_GNULIB_GETDOMAINNAME)/g' \
              -e 's/@''GNULIB_GETDTABLESIZE''@/$(GL_GNULIB_GETDTABLESIZE)/g' \
@@ -3979,8 +4075,9 @@ unistd.h: unistd.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
              -e 's/@''GNULIB_MDA_SWAB''@/$(GL_GNULIB_MDA_SWAB)/g' \
              -e 's/@''GNULIB_MDA_UNLINK''@/$(GL_GNULIB_MDA_UNLINK)/g' \
              -e 's/@''GNULIB_MDA_WRITE''@/$(GL_GNULIB_MDA_WRITE)/g' \
-             < $(srcdir)/unistd.in.h | \
-         sed -e 's|@''HAVE_CHOWN''@|$(HAVE_CHOWN)|g' \
+             < $@-t1 > $@-t2
+       $(AM_V_at)sed \
+             -e 's|@''HAVE_CHOWN''@|$(HAVE_CHOWN)|g' \
              -e 's|@''HAVE_COPY_FILE_RANGE''@|$(HAVE_COPY_FILE_RANGE)|g' \
              -e 's|@''HAVE_DUP3''@|$(HAVE_DUP3)|g' \
              -e 's|@''HAVE_EUIDACCESS''@|$(HAVE_EUIDACCESS)|g' \
@@ -4027,8 +4124,9 @@ unistd.h: unistd.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
              -e 's|@''HAVE_DECL_TTYNAME_R''@|$(HAVE_DECL_TTYNAME_R)|g' \
              -e 's|@''HAVE_OS_H''@|$(HAVE_OS_H)|g' \
              -e 's|@''HAVE_SYS_PARAM_H''@|$(HAVE_SYS_PARAM_H)|g' \
-         | \
-         sed -e 's|@''REPLACE_ACCESS''@|$(REPLACE_ACCESS)|g' \
+             < $@-t2 > $@-t3
+       $(AM_V_at)sed \
+             -e 's|@''REPLACE_ACCESS''@|$(REPLACE_ACCESS)|g' \
              -e 's|@''REPLACE_CHOWN''@|$(REPLACE_CHOWN)|g' \
              -e 's|@''REPLACE_CLOSE''@|$(REPLACE_CLOSE)|g' \
              -e 's|@''REPLACE_COPY_FILE_RANGE''@|$(REPLACE_COPY_FILE_RANGE)|g' 
\
@@ -4083,9 +4181,10 @@ unistd.h: unistd.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
              -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
              -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
              -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \
-             > $@-t
-       $(AM_V_at)mv $@-t $@
-MOSTLYCLEANFILES += unistd.h unistd.h-t
+             < $@-t3 > $@-t4
+       $(AM_V_at)rm -f $@-t1 $@-t2 $@-t3
+       $(AM_V_at)mv $@-t4 $@
+MOSTLYCLEANFILES += unistd.h unistd.h-t1 unistd.h-t2 unistd.h-t3 unistd.h-t4
 
 EXTRA_DIST += unistd.in.h
 
diff --git a/lib/mini-gmp.c b/lib/mini-gmp.c
index ea037b801dc..69a72bfd460 100644
--- a/lib/mini-gmp.c
+++ b/lib/mini-gmp.c
@@ -172,12 +172,19 @@ see https://www.gnu.org/licenses/.  */
     }                                                                  \
   } while (0)
 
+/* If mp_limb_t is of size smaller than int, plain u*v implies
+   automatic promotion to *signed* int, and then multiply may overflow
+   and cause undefined behavior. Explicitly cast to unsigned int for
+   that case. */
+#define gmp_umullo_limb(u, v) \
+  ((sizeof(mp_limb_t) >= sizeof(int)) ? (u)*(v) : (unsigned int)(u) * (v))
+
 #define gmp_udiv_qrnnd_preinv(q, r, nh, nl, d, di)                     \
   do {                                                                 \
     mp_limb_t _qh, _ql, _r, _mask;                                     \
     gmp_umul_ppmm (_qh, _ql, (nh), (di));                              \
     gmp_add_ssaaaa (_qh, _ql, _qh, _ql, (nh) + 1, (nl));               \
-    _r = (nl) - _qh * (d);                                             \
+    _r = (nl) - gmp_umullo_limb (_qh, (d));                            \
     _mask = -(mp_limb_t) (_r > _ql); /* both > and >= are OK */                
\
     _qh += _mask;                                                      \
     _r += _mask & (d);                                                 \
@@ -198,7 +205,7 @@ see https://www.gnu.org/licenses/.  */
     gmp_add_ssaaaa ((q), _q0, (q), _q0, (n2), (n1));                   \
                                                                        \
     /* Compute the two most significant limbs of n - q'd */            \
-    (r1) = (n1) - (d1) * (q);                                          \
+    (r1) = (n1) - gmp_umullo_limb ((d1), (q));                         \
     gmp_sub_ddmmss ((r1), (r0), (r1), (n0), (d1), (d0));               \
     gmp_umul_ppmm (_t1, _t0, (d0), (q));                               \
     gmp_sub_ddmmss ((r1), (r0), (r1), (r0), _t1, _t0);                 \
diff --git a/lib/nproc.c b/lib/nproc.c
index 2740c458c11..e3de1873a96 100644
--- a/lib/nproc.c
+++ b/lib/nproc.c
@@ -46,7 +46,7 @@
 # include <sys/param.h>
 #endif
 
-#if HAVE_SYS_SYSCTL_H && ! defined __GLIBC__
+#if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__)
 # include <sys/sysctl.h>
 #endif
 
@@ -306,7 +306,7 @@ num_processors_ignoring_omp (enum nproc_query query)
   /* Finally, as fallback, use the APIs that don't distinguish between
      NPROC_CURRENT and NPROC_ALL.  */
 
-#if HAVE_SYSCTL && ! defined __GLIBC__ && defined HW_NCPU
+#if HAVE_SYSCTL && !(defined __GLIBC__ && defined __linux__) && defined HW_NCPU
   { /* This works on macOS, FreeBSD, NetBSD, OpenBSD.
        macOS 10.14 does not allow mib to be const.  */
     int nprocs;
diff --git a/lib/readutmp.h b/lib/readutmp.h
new file mode 100644
index 00000000000..fa30fa9a004
--- /dev/null
+++ b/lib/readutmp.h
@@ -0,0 +1,334 @@
+/* Declarations for GNU's read utmp module.
+
+   Copyright (C) 1992-2007, 2009-2023 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by jla; revised by djm */
+
+#ifndef __READUTMP_H__
+#define __READUTMP_H__
+
+/* This file uses _GL_ATTRIBUTE_MALLOC, _GL_ATTRIBUTE_RETURNS_NONNULL,
+   HAVE_UTMP_H, HAVE_UTMPX_H, HAVE_STRUCT_UTMP_*, HAVE_STRUCT_UTMPX_*,
+   HAVE_UTMPNAME, HAVE_UTMPXNAME.  */
+#if !_GL_CONFIG_H_INCLUDED
+# error "Please include config.h first."
+#endif
+
+#include "idx.h"
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+
+/* AIX 4.3.3 has both utmp.h and utmpx.h, but only struct utmp
+   has the ut_exit member.  */
+#if (HAVE_UTMPX_H && HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_EXIT \
+     && ! HAVE_STRUCT_UTMPX_UT_EXIT)
+# undef HAVE_UTMPX_H
+#endif
+
+/* HPUX 10.20 needs utmp.h, for the definition of e.g., UTMP_FILE.  */
+#if HAVE_UTMP_H
+# include <utmp.h>
+#endif
+
+/* Needed for BOOT_TIME and USER_PROCESS.  */
+#if HAVE_UTMPX_H
+# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
+    /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
+       defined, work around the duplicate struct utmp_data declaration.  */
+#  define utmp_data gl_aix_4_3_workaround_utmp_data
+# endif
+# include <utmpx.h>
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Type of entries returned by read_utmp on all platforms.  */
+struct gl_utmp
+{
+  /* All 'char *' here are of arbitrary length and point to storage
+     with lifetime equal to that of this struct.  */
+  char *ut_user;                /* User name */
+  char *ut_id;                  /* Session ID */
+  char *ut_line;                /* seat / device */
+  char *ut_host;                /* for remote sessions: user@host or host,
+                                   for local sessions: the X11 display :N */
+  struct timespec ut_ts;        /* time */
+  pid_t ut_pid;                 /* process ID of ? */
+  pid_t ut_session;             /* process ID of session leader */
+  short ut_type;                /* BOOT_TIME, USER_PROCESS, or other */
+  struct { int e_termination; int e_exit; } ut_exit;
+};
+
+/* The following types, macros, and constants describe the 'struct gl_utmp'.  
*/
+#define UT_USER(UT) ((UT)->ut_user)
+#define UT_TIME_MEMBER(UT) ((UT)->ut_ts.tv_sec)
+#define UT_PID(UT) ((UT)->ut_pid)
+#define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V))
+#define UT_TYPE_NOT_DEFINED 0
+#define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination)
+#define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit)
+
+/* Type of entry returned by read_utmp().  */
+typedef struct gl_utmp STRUCT_UTMP;
+
+/* Size of the UT_USER (ut) member, or -1 if unbounded.  */
+enum { UT_USER_SIZE = -1 };
+
+/* Size of the ut->ut_id member, or -1 if unbounded.  */
+enum { UT_ID_SIZE = -1 };
+
+/* Size of the ut->ut_line member, or -1 if unbounded.  */
+enum { UT_LINE_SIZE = -1 };
+
+/* Size of the ut->ut_host member, or -1 if unbounded.  */
+enum { UT_HOST_SIZE = -1 };
+
+
+/* When read_utmp accesses a file (as opposed to fetching the information
+   from systemd), it uses the following low-level types and macros.
+   Keep them here, rather than moving them into readutmp.c, for backward
+   compatibility.  */
+
+#if HAVE_UTMPX_H
+
+/* <utmpx.h> defines 'struct utmpx' with the following fields:
+
+     Field        Type                       Platforms
+     ----------   ------                     ---------
+   ⎡ ut_user      char[]                     glibc, musl, macOS, FreeBSD, AIX, 
HP-UX, IRIX, Solaris, Cygwin
+   ⎣ ut_name      char[]                     NetBSD, Minix
+     ut_id        char[]                     glibc, musl, macOS, FreeBSD, 
NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_line      char[]                     glibc, musl, macOS, FreeBSD, 
NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_pid       pid_t                      glibc, musl, macOS, FreeBSD, 
NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_type      short                      glibc, musl, macOS, FreeBSD, 
NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+   ⎡ ut_tv        struct                     glibc, musl, macOS, FreeBSD, 
NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+   ⎢              { tv_sec; tv_usec; }
+   ⎣ ut_time      time_t                     Cygwin
+     ut_host      char[]                     glibc, musl, macOS, FreeBSD, 
NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_exit      struct                     glibc, musl, NetBSD, Minix, 
HP-UX, IRIX, Solaris
+                  { e_termination; e_exit; }
+     ut_session   [long] int                 glibc, musl, NetBSD, Minix, IRIX, 
Solaris
+   ⎡ ut_addr      [long] int                 HP-UX, Cygwin
+   ⎢ ut_addr_v6   [u]int[4]                  glibc, musl
+   ⎣ ut_ss        struct sockaddr_storage    NetBSD, Minix
+ */
+
+# if __GLIBC__ && _TIME_BITS == 64
+/* This is a near-copy of glibc's struct utmpx, which stops working
+   after the year 2038.  Unlike the glibc version, struct utmpx32
+   describes the file format even if time_t is 64 bits.  */
+#define _GL_UT_USER_SIZE  sizeof (((struct utmpx *) 0)->ut_user)
+#define _GL_UT_ID_SIZE    sizeof (((struct utmpx *) 0)->ut_id)
+#define _GL_UT_LINE_SIZE  sizeof (((struct utmpx *) 0)->ut_line)
+#define _GL_UT_HOST_SIZE  sizeof (((struct utmpx *) 0)->ut_host)
+struct utmpx32
+{
+  short int ut_type;               /* Type of login.  */
+  pid_t ut_pid;                    /* Process ID of login process.  */
+  char ut_line[_GL_UT_LINE_SIZE];  /* Devicename.  */
+  char ut_id[_GL_UT_ID_SIZE];      /* Inittab ID.  */
+  char ut_user[_GL_UT_USER_SIZE];  /* Username.  */
+  char ut_host[_GL_UT_HOST_SIZE];  /* Hostname for remote login. */
+  struct __exit_status ut_exit;    /* Exit status of a process marked
+                                      as DEAD_PROCESS.  */
+  /* The fields ut_session and ut_tv must be the same size when compiled
+     32- and 64-bit.  This allows files and shared memory to be shared
+     between 32- and 64-bit applications.  */
+  int ut_session;                  /* Session ID, used for windowing.  */
+  struct
+  {
+    /* Seconds.  Unsigned not signed, as glibc did not exist before 1970,
+       and if the format is still in use after 2038 its timestamps
+       will surely have the sign bit on.  This hack stops working
+       at 2106-02-07 06:28:16 UTC.  */
+    unsigned int tv_sec;
+    int tv_usec;                   /* Microseconds.  */
+  } ut_tv;                         /* Time entry was made.  */
+  int ut_addr_v6[4];               /* Internet address of remote host.  */
+  char ut_reserved[20];            /* Reserved for future use.  */
+};
+#  define UTMP_STRUCT_NAME utmpx32
+# else
+#  define UTMP_STRUCT_NAME utmpx
+# endif
+# define SET_UTMP_ENT setutxent
+# define GET_UTMP_ENT getutxent
+# define END_UTMP_ENT endutxent
+# ifdef HAVE_UTMPXNAME /* glibc, musl, macOS, NetBSD, Minix, IRIX, Solaris, 
Cygwin */
+#  define UTMP_NAME_FUNCTION utmpxname
+# elif defined UTXDB_ACTIVE /* FreeBSD */
+#  define UTMP_NAME_FUNCTION(x) setutxdb (UTXDB_ACTIVE, x)
+# endif
+
+#elif HAVE_UTMP_H
+
+/* <utmp.h> defines 'struct utmp' with the following fields:
+
+     Field        Type                       Platforms
+     ----------   ------                     ---------
+   ⎡ ut_user      char[]                     glibc, musl, AIX, HP-UX, IRIX, 
Solaris, Cygwin, Android
+   ⎣ ut_name      char[]                     macOS, old FreeBSD, NetBSD, 
OpenBSD, Minix
+     ut_id        char[]                     glibc, musl, AIX, HP-UX, IRIX, 
Solaris, Cygwin, Android
+     ut_line      char[]                     glibc, musl, macOS, old FreeBSD, 
NetBSD, OpenBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin, Android
+     ut_pid       pid_t                      glibc, musl, AIX, HP-UX, IRIX, 
Solaris, Cygwin, Android
+     ut_type      short                      glibc, musl, AIX, HP-UX, IRIX, 
Solaris, Cygwin, Android
+   ⎡ ut_tv        struct                     glibc, musl, Android
+   ⎢              { tv_sec; tv_usec; }
+   ⎣ ut_time      time_t                     macOS, old FreeBSD, NetBSD, 
OpenBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_host      char[]                     glibc, musl, macOS, old FreeBSD, 
NetBSD, OpenBSD, Minix, AIX, HP-UX, Cygwin, Android
+     ut_exit      struct                     glibc, musl, AIX, HP-UX, IRIX, 
Solaris, Android
+                  { e_termination; e_exit; }
+     ut_session   [long] int                 glibc, musl, Android
+   ⎡ ut_addr      [long] int                 HP-UX, Cygwin
+   ⎣ ut_addr_v6   [u]int[4]                  glibc, musl, Android
+ */
+
+# define UTMP_STRUCT_NAME utmp
+# define SET_UTMP_ENT setutent
+# define GET_UTMP_ENT getutent
+# define END_UTMP_ENT endutent
+# ifdef HAVE_UTMPNAME /* glibc, musl, NetBSD, Minix, AIX, HP-UX, IRIX, 
Solaris, Cygwin, Android */
+#  define UTMP_NAME_FUNCTION utmpname
+# endif
+
+#endif
+
+/* Evaluates to 1 if gl_utmp's ut_id field may ever have a non-zero value.  */
+#define HAVE_STRUCT_XTMP_UT_ID \
+  (READUTMP_USE_SYSTEMD \
+   || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID))
+
+/* Evaluates to 1 if gl_utmp's ut_pid field may ever have a non-zero value.  */
+#define HAVE_STRUCT_XTMP_UT_PID \
+  (READUTMP_USE_SYSTEMD \
+   || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID))
+
+/* Evaluates to 1 if gl_utmp's ut_host field may ever be non-empty.  */
+#define HAVE_STRUCT_XTMP_UT_HOST \
+  (READUTMP_USE_SYSTEMD \
+   || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST))
+
+/* Definition of UTMP_FILE.
+   On glibc systems, UTMP_FILE is "/var/run/utmp".  */
+#if !defined UTMP_FILE && defined _PATH_UTMP
+# define UTMP_FILE _PATH_UTMP
+#endif
+#ifdef UTMPX_FILE /* Solaris, SysVr4 */
+# undef UTMP_FILE
+# define UTMP_FILE UTMPX_FILE
+#endif
+#ifndef UTMP_FILE
+# define UTMP_FILE "/etc/utmp"
+#endif
+
+/* Definition of WTMP_FILE.
+   On glibc systems, UTMP_FILE is "/var/log/wtmp".  */
+#if !defined WTMP_FILE && defined _PATH_WTMP
+# define WTMP_FILE _PATH_WTMP
+#endif
+#ifdef WTMPX_FILE /* Solaris, SysVr4 */
+# undef WTMP_FILE
+# define WTMP_FILE WTMPX_FILE
+#endif
+#ifndef WTMP_FILE
+# define WTMP_FILE "/etc/wtmp"
+#endif
+
+/* In early versions of Android, <utmp.h> did not define BOOT_TIME, only
+   USER_PROCESS.  We need to use the value that is defined in newer versions
+   of Android.  */
+#if defined __ANDROID__ && !defined BOOT_TIME
+# define BOOT_TIME 2
+#endif
+
+/* Some platforms, such as OpenBSD, don't have an ut_type field and don't have
+   the BOOT_TIME and USER_PROCESS macros.  But we want to support them in
+   'struct gl_utmp'.  */
+#if !(HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+# define BOOT_TIME 2
+# define USER_PROCESS 0
+#endif
+
+/* Macros that test (UT)->ut_type.  */
+#ifdef BOOT_TIME
+# define UT_TYPE_BOOT_TIME(UT) ((UT)->ut_type == BOOT_TIME)
+#else
+# define UT_TYPE_BOOT_TIME(UT) 0
+#endif
+#ifdef USER_PROCESS
+# define UT_TYPE_USER_PROCESS(UT) ((UT)->ut_type == USER_PROCESS)
+#else
+# define UT_TYPE_USER_PROCESS(UT) 0
+#endif
+
+/* Determines whether an entry *UT corresponds to a user process.  */
+#define IS_USER_PROCESS(UT)                                    \
+  ((UT)->ut_user[0] && UT_TYPE_USER_PROCESS (UT))
+
+/* Define if read_utmp is not just a dummy.  */
+#if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H || defined __CYGWIN__ 
|| defined _WIN32
+# define READ_UTMP_SUPPORTED 1
+#endif
+
+/* Options for read_utmp.  */
+enum
+  {
+    READ_UTMP_CHECK_PIDS   = 1,
+    READ_UTMP_USER_PROCESS = 2,
+    READ_UTMP_BOOT_TIME    = 4,
+    READ_UTMP_NO_BOOT_TIME = 8
+  };
+
+/* Return a copy of (UT)->ut_user, without trailing spaces,
+   as a freshly allocated string.  */
+char *extract_trimmed_name (const STRUCT_UTMP *ut)
+  _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE
+  _GL_ATTRIBUTE_RETURNS_NONNULL;
+
+/* Read the utmp entries corresponding to file FILE into freshly-
+   malloc'd storage, set *UTMP_BUF to that pointer, set *N_ENTRIES to
+   the number of entries, and return zero.  If there is any error,
+   return -1, setting errno, and don't modify the parameters.
+   A good candidate for FILE is UTMP_FILE.
+   If OPTIONS & READ_UTMP_CHECK_PIDS is nonzero, omit entries whose
+   process-IDs do not currently exist.
+   If OPTIONS & READ_UTMP_USER_PROCESS is nonzero, omit entries which
+   do not correspond to a user process.
+   If OPTIONS & READ_UTMP_BOOT_TIME is nonzero, omit all entries except
+   the one that contains the boot time.
+   If OPTIONS & READ_UTMP_NO_BOOT_TIME is nonzero, omit the boot time
+   entries.
+
+   This function is not multithread-safe, since on many platforms it
+   invokes the functions setutxent, getutxent, endutxent.  These
+   functions are needed because they may lock FILE (so that we don't
+   read garbage when a concurrent process writes to FILE), but their
+   drawback is that they have a common global state.  */
+int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
+               int options);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __READUTMP_H__ */
diff --git a/lib/stdalign.in.h b/lib/stdalign.in.h
new file mode 100644
index 00000000000..b616c100fdc
--- /dev/null
+++ b/lib/stdalign.in.h
@@ -0,0 +1,49 @@
+/* A substitute for ISO C11 <stdalign.h>.
+
+   Copyright 2011-2023 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert and Bruno Haible.  */
+
+/* Define two obsolescent C11 macros, assuming alignas and alignof are
+   either keywords or alignasof-defined macros.  */
+
+#ifndef _@GUARD_PREFIX@_STDALIGN_H
+
+#if __GNUC__ >= 3
+@PRAGMA_SYSTEM_HEADER@
+#endif
+@PRAGMA_COLUMNS@
+
+/* We need to include the system's <stdalign.h> when it exists, because it 
might
+   define 'alignof' as a macro when it's not a keyword or compiler built-in.  
*/
+#if @HAVE_STDALIGN_H@
+/* The include_next requires a split double-inclusion guard.  */
+# @INCLUDE_NEXT@ @NEXT_STDALIGN_H@
+#endif
+
+#ifndef _@GUARD_PREFIX@_STDALIGN_H
+#define _@GUARD_PREFIX@_STDALIGN_H
+
+#if (defined alignas \
+     || (defined __STDC_VERSION__ && 202311 <= __STDC_VERSION__) \
+     || (defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER)))
+# define __alignas_is_defined 1
+#endif
+
+#define __alignof_is_defined 1
+
+#endif /* _@GUARD_PREFIX@_STDALIGN_H */
+#endif /* _@GUARD_PREFIX@_STDALIGN_H */
diff --git a/lib/time.in.h b/lib/time.in.h
index 06428adb1d0..06824da9d3d 100644
--- a/lib/time.in.h
+++ b/lib/time.in.h
@@ -143,6 +143,12 @@ _GL_CXXALIAS_SYS (timespec_get, int, (struct timespec *ts, 
int base));
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (timespec_get);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef timespec_get
+#  if HAVE_RAW_DECL_TIMESPEC_GET
+_GL_WARN_ON_USE (timespec_get, "timespec_get is unportable - "
+                 "use gnulib module timespec_get for portability");
+#  endif
 # endif
 
 /* Set *TS to the current time resolution, and return BASE.
@@ -154,6 +160,12 @@ _GL_FUNCDECL_SYS (timespec_getres, int, (struct timespec 
*ts, int base)
 #  endif
 _GL_CXXALIAS_SYS (timespec_getres, int, (struct timespec *ts, int base));
 _GL_CXXALIASWARN (timespec_getres);
+# elif defined GNULIB_POSIXCHECK
+#  undef timespec_getres
+#  if HAVE_RAW_DECL_TIMESPEC_GETRES
+_GL_WARN_ON_USE (timespec_getres, "timespec_getres is unportable - "
+                 "use gnulib module timespec_getres for portability");
+#  endif
 # endif
 
 /* Return the number of seconds that have elapsed since the Epoch.  */
@@ -170,6 +182,12 @@ _GL_CXXALIAS_SYS (time, time_t, (time_t *__tp));
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (time);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef time
+#  if HAVE_RAW_DECL_TIME
+_GL_WARN_ON_USE (time, "time has consistency problems - "
+                 "use gnulib module time for portability");
+#  endif
 # endif
 
 /* Sleep for at least RQTP seconds unless interrupted,  If interrupted,
@@ -195,6 +213,12 @@ _GL_CXXALIAS_SYS (nanosleep, int,
                   (struct timespec const *__rqtp, struct timespec *__rmtp));
 #  endif
 _GL_CXXALIASWARN (nanosleep);
+# elif defined GNULIB_POSIXCHECK
+#  undef nanosleep
+#  if HAVE_RAW_DECL_NANOSLEEP
+_GL_WARN_ON_USE (nanosleep, "nanosleep is unportable - "
+                 "use gnulib module nanosleep for portability");
+#  endif
 # endif
 
 /* Initialize time conversion information.  */
@@ -230,6 +254,12 @@ _GL_CXXALIAS_MDA (tzset, void, (void));
 _GL_CXXALIAS_SYS (tzset, void, (void));
 #  endif
 _GL_CXXALIASWARN (tzset);
+# elif defined GNULIB_POSIXCHECK
+#  undef tzset
+#  if HAVE_RAW_DECL_TZSET
+_GL_WARN_ON_USE (tzset, "tzset has portability problems - "
+                 "use gnulib module tzset for portability");
+#  endif
 # endif
 
 /* Return the 'time_t' representation of TP and normalize TP.  */
@@ -246,6 +276,12 @@ _GL_CXXALIAS_SYS (mktime, time_t, (struct tm *__tp));
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (mktime);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef mktime
+#  if HAVE_RAW_DECL_MKTIME
+_GL_WARN_ON_USE (mktime, "mktime has portability problems - "
+                 "use gnulib module mktime for portability");
+#  endif
 # endif
 
 /* Convert TIMER to RESULT, assuming local time and UTC respectively.  See
@@ -296,6 +332,17 @@ _GL_CXXALIAS_SYS (gmtime_r, struct tm *, (time_t const 
*restrict __timer,
 #  if @HAVE_DECL_LOCALTIME_R@
 _GL_CXXALIASWARN (gmtime_r);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef localtime_r
+#  if HAVE_RAW_DECL_LOCALTIME_R
+_GL_WARN_ON_USE (localtime_r, "localtime_r is unportable - "
+                 "use gnulib module time_r for portability");
+#  endif
+#  undef gmtime_r
+#  if HAVE_RAW_DECL_GMTIME_R
+_GL_WARN_ON_USE (gmtime_r, "gmtime_r is unportable - "
+                 "use gnulib module time_r for portability");
+#  endif
 # endif
 
 /* Convert TIMER to RESULT, assuming local time and UTC respectively.  See
@@ -316,6 +363,12 @@ _GL_CXXALIAS_SYS (localtime, struct tm *, (time_t const 
*__timer));
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (localtime);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef localtime
+#  if HAVE_RAW_DECL_LOCALTIME
+_GL_WARN_ON_USE (localtime, "localtime has portability problems - "
+                 "use gnulib module localtime for portability");
+#  endif
 # endif
 
 # if 0 || @REPLACE_GMTIME@
@@ -347,6 +400,12 @@ _GL_CXXALIAS_SYS (strptime, char *, (char const *restrict 
__buf,
                                      char const *restrict __format,
                                      struct tm *restrict __tm));
 _GL_CXXALIASWARN (strptime);
+# elif defined GNULIB_POSIXCHECK
+#  undef strptime
+#  if HAVE_RAW_DECL_STRPTIME
+_GL_WARN_ON_USE (strptime, "strptime is unportable - "
+                 "use gnulib module strptime for portability");
+#  endif
 # endif
 
 /* Convert *TP to a date and time string.  See
@@ -368,6 +427,12 @@ _GL_CXXALIAS_SYS (ctime, char *, (time_t const *__tp));
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (ctime);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef ctime
+#  if HAVE_RAW_DECL_CTIME
+_GL_WARN_ON_USE (ctime, "ctime has portability problems - "
+                 "use gnulib module ctime for portability");
+#  endif
 # endif
 
 /* Convert *TP to a date and time string.  See
@@ -392,6 +457,12 @@ _GL_CXXALIAS_SYS (strftime, size_t,
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (strftime);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef strftime
+#  if HAVE_RAW_DECL_STRFTIME
+_GL_WARN_ON_USE (strftime, "strftime has portability problems - "
+                 "use gnulib module strftime-fixes for portability");
+#  endif
 # endif
 
 # if defined _GNU_SOURCE && @GNULIB_TIME_RZ@ && ! @HAVE_TIMEZONE_T@
@@ -469,6 +540,12 @@ _GL_CXXALIAS_SYS (timegm, time_t, (struct tm *__tm));
 #  if __GLIBC__ >= 2
 _GL_CXXALIASWARN (timegm);
 #  endif
+# elif defined GNULIB_POSIXCHECK
+#  undef timegm
+#  if HAVE_RAW_DECL_TIMEGM
+_GL_WARN_ON_USE (timegm, "timegm is unportable - "
+                 "use gnulib module timegm for portability");
+#  endif
 # endif
 
 /* Encourage applications to avoid unsafe functions that can overrun
@@ -476,8 +553,10 @@ _GL_CXXALIASWARN (timegm);
    applications should use strftime (or even sprintf) instead.  */
 # if defined GNULIB_POSIXCHECK
 #  undef asctime
+#  if HAVE_RAW_DECL_ASCTIME
 _GL_WARN_ON_USE (asctime, "asctime can overrun buffers in some cases - "
                  "better use strftime (or even sprintf) instead");
+#  endif
 # endif
 # if defined GNULIB_POSIXCHECK
 #  undef asctime_r
@@ -488,8 +567,10 @@ _GL_WARN_ON_USE (asctime_r, "asctime_r can overrun buffers 
in some cases - "
 # endif
 # if defined GNULIB_POSIXCHECK
 #  undef ctime
+#  if HAVE_RAW_DECL_CTIME
 _GL_WARN_ON_USE (ctime, "ctime can overrun buffers in some cases - "
                  "better use strftime (or even sprintf) instead");
+#  endif
 # endif
 # if defined GNULIB_POSIXCHECK
 #  undef ctime_r
diff --git a/lisp/ChangeLog.13 b/lisp/ChangeLog.13
index 820110e2fba..49ff87f464c 100644
--- a/lisp/ChangeLog.13
+++ b/lisp/ChangeLog.13
@@ -4251,7 +4251,7 @@
        * doc-view.el (doc-view-mode): Support tramp, compressed files and
        files inside archives uniformly.
 
-2008-01-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-01-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * textmodes/sgml-mode.el (sgml-tag-syntax-table): Initialize this
        constant with a computation on sgml-specials rather than a literal
@@ -4656,7 +4656,7 @@
        (vc-git-dir-state): Use it instead of processing the status
        results here.
 
-2008-01-02  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-01-02  Eric S. Raymond  <esr@thyrsus.com>
 
        * progmodes/grep.el (grep-find-ignored-directories):
        Initialize from the value of vc-directory-exclusion-list.
@@ -4718,7 +4718,7 @@
        MS-Windows and MS-DOS.
        (ispell-grep-options): Use "-Ei" on MS-Windows and MS-DOS.
 
-2008-01-02  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-01-02  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-svn.el (vc-svn-modify-change comment): New function.
 
@@ -4727,7 +4727,7 @@
        * vc-git.el (vc-git-dir-state): Set the vc-backend property.
        Do not disable undo, with-temp-buffer does it by default.
 
-2008-01-01  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-01-01  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-svn.el (vc-svn-parse-status): Set the `unregistered' property
        correctly.
@@ -4802,7 +4802,7 @@
 
        * vc-hg.el (vc-hg-dir-state): Set the vc-backend property.
 
-2007-12-29  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-12-29  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-svn.el (vc-svn-parse-status): Recognize 'unregistered,
        'added, 'removed.
@@ -4943,7 +4943,7 @@
 
        * menu-bar.el (menu-bar-describe-menu): Remove dots from menu text.
 
-2007-12-28  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-12-28  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-hooks.el, vc.el: Move vc-directory-exclusion-list from vc.el
        to vc-hooks.el so it will be available to other modes, such as
@@ -4997,7 +4997,7 @@
        * calc/calccomp.el (math-to-percentsigns): Change placeholder
        for percent signs.
 
-2007-12-27  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-12-27  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-dired-ignorable-p, vc-dired-hook): Speed optimization;
        use completion-ignored-extensions to detect files that should be
@@ -5015,7 +5015,7 @@
        (ps-print-preprint-region): Use `ps-mark-active-p' instead of
        `region-active-p' for error checking.
 
-2007-12-27  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-12-27  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el, vc-sccs.el, vc-rcs.el, vc-cvs.el, vc-mcvs.el:
        Put new machinery in place to support editing of change comments
@@ -5029,7 +5029,7 @@
        * international/mule-cmds.el (select-safe-coding-system):
        When a buffer is modified, cancel the writing.
 
-2007-12-26  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-12-26  Eric S. Raymond  <esr@thyrsus.com>
 
        * log-view.el: Add Subversion and Mercurial log format samples.
 
@@ -9147,7 +9147,7 @@
        don't try other `or' branches regardless of the value returned by
        fill-region; just return t.
 
-2007-10-20  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-20  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-do-command): Condition out a misleading message when
        running asynchronously.
@@ -9197,7 +9197,7 @@
 
        * vc-hg.el: Require log-view at compile time.
 
-2007-10-20  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-20  Eric S. Raymond  <esr@thyrsus.com>
 
        * log-view.el (log-view-diff): Adapt log-view-diff for new VC API.
 
@@ -9812,7 +9812,7 @@
        * bs.el (bs--mark-unmark): New function.
        (bs-mark-current, bs-unmark-current): Use it.
 
-2007-10-11  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-11  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-diff):
        (vc-diff-internal): Merge a patch by Juanma Barranquero.  Also,
@@ -9830,7 +9830,7 @@
        Use `follow-call-process-filter' rather than `process-filter'.
        Simplify.
 
-2007-10-11  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-11  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-hooks.el (vc-registered): Robustify this function a bit
        against filenames with no directory component.
@@ -9919,7 +9919,7 @@
        * help-fns.el (describe-variable): Add missing "  " for multiline
        obsolescence info and missing EOL after global value.
 
-2007-10-10  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-10  Eric S. Raymond  <esr@thyrsus.com>
 
        * add-log.el:
        * ediff-vers.el:
@@ -9950,7 +9950,7 @@
        mode" in docstrings and messages.
        (follow-menu-filter): Fix arg passed to `bound-and-true-p'.
 
-2007-10-10  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-10  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-next-action): Rewrite completely; this principal
        entry point now operates on a current fileset selected either
@@ -10329,7 +10329,7 @@
        (smerge-refine-subst): New function holding most of smerge-refine.
        (smerge-refine): Use it.
 
-2007-10-08  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-08  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-default-wash-log): Remove unused code, the
        log washers all live in the backends now.
@@ -10390,7 +10390,7 @@
        * net/tramp-fish.el (tramp-fish-handle-process-file):
        Rewrite temporary file handling.
 
-2007-10-06  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-10-06  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el: Workfile version -> focus version change.  Port various
        comments from new VC to reduce the noise in the diff.
@@ -12758,7 +12758,7 @@
 2007-08-20  Thien-Thi Nguyen  <ttn@gnuvola.org>
 
        * vc-rcs.el (vc-rcs-annotate-command):
-       Fix bug introduced 2007-07-18T16:32:40Z!esr@snark.thyrsus.com:
+       Fix bug introduced 2007-07-18T16:32:40Z!esr@thyrsus.com:
        Add back :vc-annotate-prefix propertization.
 
 2007-08-20  Andreas Schwab  <schwab@suse.de>
@@ -14226,7 +14226,7 @@
        * vc-hooks.el (vc-find-root): Walk up the tree to find an existing
        `file' from which to start the search.
 
-2007-07-19  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-07-19  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-cvs.el (vc-cvs-checkin, vc-cvs-diff): Finish transition from
        having a single file argument to having a list of files as the
@@ -14245,7 +14245,7 @@
 
        * uniquify.el: Docstring fixes.
 
-2007-07-18  Eric S. Raymond  <esr@snark.thyrsus.com>
+2007-07-18  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (revision-granularity, create-repo): Document new vc
        backend properties.
diff --git a/lisp/ChangeLog.14 b/lisp/ChangeLog.14
index 09bc4f6c4eb..129621791c0 100644
--- a/lisp/ChangeLog.14
+++ b/lisp/ChangeLog.14
@@ -12125,7 +12125,7 @@
        * vc-dispatcher.el (top-level): Revert previous change: require cl
        when compiling.
 
-2008-05-16  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-16  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-default-status-printer)
        (vc-default-prettify-state-info): Enhance the state prettyprinter
@@ -12143,7 +12143,7 @@
        * net/tramp.el (tramp-handle-write-region): Fix check for short track.
        Reported by Glenn Morris <rgm@gnu.org>.
 
-2008-05-16  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-16  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el: Remove my analysis of SCCS/RCS concurrency issues from
        the end of the file, it was good work at one time but has been
@@ -12179,7 +12179,7 @@
        (ses-mode): Set indent-tabs-mode to nil.
        (ses-center): Use string-width rather than length.
 
-2008-05-15  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-15  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-cvs.el, vc-git.el, vc-hg.el, vc-hooks.el, vc-mcvs.el,
        * vc-rcs.el, vc-sccs.el, vc-svn.el, vc.el:
@@ -12204,7 +12204,7 @@
        * progmodes/cc-mode.el (declare-function): Add compat definition.
        (awk-mode-syntax-table, c-awk-unstick-NL-prop): Declare for compiler.
 
-2008-05-14  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-14  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-dispatcher.el (vc-dispatcher-selection): Change the returned
        list to a cons so the caller can get back both expanded and
@@ -12251,7 +12251,7 @@
        (tramp-echo-mark-marker): New defconst.
        (tramp-check-for-regexp): Use it.
 
-2008-05-14  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-14  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-deduce-fileset): Do the right thing when visiting a
        buffer (say, a log buffer or diff buffer) with a vc-dir buffer
@@ -12343,7 +12343,7 @@
        (dired-toggle-marks, dired-change-marks, dired-unmark-all-files):
        buffer-read-only -> inhibit-read-only.
 
-2008-05-12  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-12  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-expand-dirs): Stop this function from tossing out
        explicitly specified files.
@@ -12395,7 +12395,7 @@
        * emulation/cua-base.el: Put isearch-scroll property
        on cua-scroll-up and cua-scroll-down.
 
-2008-05-11  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-11  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-hooks.el (vc-recompute-state): Remove (dead code).
 
@@ -12416,7 +12416,7 @@
 
        * smerge-mode.el (smerge-command-prefix): Fix custom type.
 
-2008-05-10  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-10  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-dispatcher.el (vc-dir-next-directory, vc-dir-prev-directory):
        New functions implementing motion to next and previous directory.
@@ -12449,7 +12449,7 @@
 
        * vc-hooks.el (vc-prefix-map): Remove duplicate binding.
 
-2008-05-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-dir):
        * vc-hooks.el: Tweak the VC directory bindings.  These are now
@@ -12466,7 +12466,7 @@
 
        * simple.el (start-file-process): Clarify docstring.
 
-2008-05-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-sccs.el, vc-svn.el, vc-git.el, vc-hg.el, vc-mtn.el:
        Remove stub implementations of, and references to, wash-log.
@@ -12515,7 +12515,7 @@
        * vc.el (vc-version-diff, vc-print-log, vc-revert, vc-rollback)
        (vc-update): Remove unused let bindings.
 
-2008-05-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-deduce-fileset, vc-next-action, vc-version-diff)
        (vc-diff, vc-revert, vc-rollback, vc-update):
@@ -12609,7 +12609,7 @@
        (robin-current-package-name): Doc fix.
        (robin-activate): Don't use `iff' in docstring.
 
-2008-05-07  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-07  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el, vc-dispatcher.el: VC-Dired support removed.
        The code uses a ewoc-based implementation now.
@@ -12639,7 +12639,7 @@
        * progmodes/fortran.el (fortran-mode): Fix font-lock-syntactic-keywords
        oddness.
 
-2008-05-06  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-06  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-hooks.el (vc-find-file-hook):
        * vc-dispatcher.el (vc-resynch-window): Decouple vc-dispatcher
@@ -12692,7 +12692,7 @@
 
        * dired.el (dired-read-dir-and-switches): Fix up last change.
 
-2008-05-05  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-05  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-deduce-fileset): Lift all the policy and UI stuff
        out of this function, move it to vc-dispatcher-selection-set.
@@ -12770,7 +12770,7 @@
        * ls-lisp.el (ls-lisp-insert-directory): Use `string-width'
        instead of `length' for comparing length of user and group names.
 
-2008-05-03  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-03  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-dispatcher.el: New file, separates out the UI and command
        execution machinery from VCS-specific logic left in vc.el.
@@ -12789,7 +12789,7 @@
        * progmodes/grep.el (grep-mode-map): Bind "g" to recompile (like
        in dired &c).
 
-2008-05-02  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-02  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-arch.el, vc-bzr.el, vc-cvs.el, vc-git.el, vc-hg.el,
        * vc-hooks.el, vc-mcvs.el, vc-mtn.el, vc-rcs.el, vc-sccs.el,
@@ -12846,7 +12846,7 @@
        function that is no longer there.
        (hilit-chg-set): Remove running of highlight-changes-enable-hook.
 
-2008-05-02  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-02  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-default-dired-state-info): Change name of primitive
        to prettify-state-info, in preparation for ripping out dired mode.
@@ -12879,7 +12879,7 @@
        * progmodes/compile.el (compilation-auto-jump):
        Set window point to `pos' explicitly.
 
-2008-05-01  Eric S. Raymond  <esr@snark.thyrsus.com>
+2008-05-01  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-bzr.el (vc-bzr-state): Allow this to return 'ignored
        when appropriate.
diff --git a/lisp/ChangeLog.15 b/lisp/ChangeLog.15
index af3203444ae..6bd61211d47 100644
--- a/lisp/ChangeLog.15
+++ b/lisp/ChangeLog.15
@@ -11888,7 +11888,7 @@
        (log-edit-mode, log-edit-extra-flags, log-edit-mode):
        New declarations.
 
-2010-04-09  Eric Raymond  <esr@snark.thyrsus.com>
+2010-04-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc-hooks.el, vc-git.el: Improve documentation comments.
 
diff --git a/lisp/ChangeLog.16 b/lisp/ChangeLog.16
index 6dda3703e6d..f7dcda87466 100644
--- a/lisp/ChangeLog.16
+++ b/lisp/ChangeLog.16
@@ -10665,8 +10665,7 @@
        * textmodes/rst.el: Add comments.
        (rst-transition, rst-adornment): New faces.
        (rst-adornment-faces-alist): Make default safe to reevaluate.
-       Fixes
-       
http://sourceforge.net/tracker/?func=detail&atid=422030&aid=3479603&group_id=38414.
+       Fixes https://sourceforge.net/p/docutils/bugs/180/.
        Improve customization tags.
        (rst-define-level-faces): Clarify meaning.
 
diff --git a/lisp/ChangeLog.17 b/lisp/ChangeLog.17
index 5036eabf0fd..3377da7c54d 100644
--- a/lisp/ChangeLog.17
+++ b/lisp/ChangeLog.17
@@ -4218,7 +4218,7 @@
        (package-activate-1): Reload files given by 
`package--list-loaded-files'.
        Fix bug#10125, bug#18443, and bug#18448.
 
-2014-12-13  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-13  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc-svn.el (vc-svn-diff): Fix bug #19312.
 
@@ -4253,7 +4253,7 @@
        * files.el (directory-files-recursively): Don't follow symlinks to
        other directories.
 
-2014-12-12  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-12  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc-dav.el, vc/vc-git.el, vc/vc-hg.el, vc/vc-src.el:
        * vc/vc.el: latest-on-branch-p is no longer a public method.
@@ -4276,7 +4276,7 @@
 
        * emacs-lisp/let-alist.el: Add new package and macro.
 
-2014-12-10  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-10  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc-dispatcher.el, vc/vc-hooks.el, vc/vc-rcs.el:
        * vc/vc-sccs.el, vc/vc.el: Righteous featurectomy of vc-keep-workfiles,
@@ -4308,7 +4308,7 @@
        (ruby-toggle-string-quotes): New command that allows you to quickly
        toggle between single-quoted and double-quoted string literals.
 
-2014-12-09  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-09  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc-src.el (vc-src-do-comand): Prepend -- to file argument
        list, avoids problems witt names containing hyphens.
@@ -4429,7 +4429,7 @@
        * vc/vc-hg.el (vc-hg-dir-status-files): Only include ignores files
        when FILES is non-nil (bug#19304).
 
-2014-12-08  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-08  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc-arch.el: Move to obsolete directory so a test framework
        won't trip over bit-rot in it.  There has been no Arch snapshot
@@ -4593,7 +4593,7 @@
        more extensions when generating includes (programs)
        (bug#19254).
 
-2014-12-03  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-03  Eric S. Raymond  <esr@thyrsus.com>
 
        * files.el (file-tree-walk): Fix docstring.
 
@@ -4636,7 +4636,7 @@
 
        * whitespace.el (whitespace-big-indent-regexp): Add :version.
 
-2014-12-02  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-02  Eric S. Raymond  <esr@thyrsus.com>
 
        * subr.el (filter): New macro.  Because it's just silly for a Lisp
        not to have this in 2014.  And VC needs it.
@@ -4659,7 +4659,7 @@
        is no longer a public method.  It is now local to the one place
        it's used, in the RCS steal-lock method.
 
-2014-12-01  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-01  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc.el: In all backends: API simplification; could-register
        is no longer a public method.  (vc-cvs.el still has a private
@@ -4772,7 +4772,7 @@
        * net/eww.el (eww): Leave point in a place that doesn't cause
        scrolling when displaying "Loading...".
 
-2014-12-01  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-01  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc.el, vc/vc-cvs.el, vc/vc-rcs.el, vc/vc-svn.el: The 'merge'
        backend method of RCS/CVS/SVN is now 'merge-file', to contrast with
@@ -4791,7 +4791,7 @@
 
        * emacs-lisp/inline.el: New file.
 
-2014-12-01  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-12-01  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc.el, vc-hooks.el: All backends: API simplification;
        vc-state-heuristic is no longer a public method, having been
@@ -5226,7 +5226,7 @@
 
        * vc/vc.el: Fix a typo in the commentary.
 
-2014-11-20  Eric S. Raymond  <esr@snark.thyrsus.com>
+2014-11-20  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc/vc-src.el, vc/vc.el: Added support for SRC.  Needs more
        testing and a real log-view mode.
diff --git a/lisp/ChangeLog.3 b/lisp/ChangeLog.3
index 5c4d1e7a82a..29585129a8a 100644
--- a/lisp/ChangeLog.3
+++ b/lisp/ChangeLog.3
@@ -990,7 +990,7 @@
        (compilation-minor-mode): New function to toggle
        compilation-minor-mode; if setting it, call compilation-setup.
 
-1993-04-28  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-28  Eric S. Raymond  (esr@thyrsus.com)
 
        * bibtex.el: Installed Aaron Larson's new bibtex.el.  See the
        header comment for details.
@@ -1004,7 +1004,7 @@
 
        * files.el (file-truename): Undo last change.
 
-1993-04-27  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * files.el (file-truename): Do the right thing when $HOME = "".
 
@@ -1058,7 +1058,7 @@
        (find-tag-noselect): If NEXT-P is '-, pop location off
        tags-location-stack.
 
-1993-04-26  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * cmacexp.el: Installed Francesco Potortì's enhanced and
        cleaned-up version, see its commentary for details.
@@ -1070,13 +1070,13 @@
        Defvars added for many variables.
        (te-stty-string): Specify the characters explicitly--not `stty dec'.
 
-1993-04-26  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * files.el (cd): Handle leading "~" like an absolute filename.
 
        * dired.el: Changed fsets to defaliases.
 
-1993-04-25  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-25  Eric S. Raymond  (esr@thyrsus.com)
 
        * comint.el (comint-mod): Nuked.  A call to ring-mod replaces it.
        (comint-mem): Nuked.  A call to member replaces it.
@@ -1099,7 +1099,7 @@
        (vc-finish-logentry, vc-next-comment, vc-previous-comment):
        Replace *VC-comment-buffer* with a ring vector.
 
-1993-04-25  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-25  Eric S. Raymond  (esr@thyrsus.com)
 
        * simple.el (down-arrow): New function.
        Uses next-line-add-newlines to suppress addition of new lines at end of
@@ -1138,11 +1138,11 @@
        * shell.el (shell-prompt-pattern): Add `;' as potential prompt
        delimiter (for `es' and `rc' shells most particularly).
 
-1993-04-23  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * isearch.el: Replaced all fsets with defaliases.
 
-1993-04-23  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * bytecomp.el (define-function): Change name back to defaliases
        to get things in a known-good state.  The unload patch had been
@@ -1190,7 +1190,7 @@
        * ange-ftp.el (ange-ftp-binary-file-name-regexp):
        Match .z and .z-part-?? files.
 
-1993-04-21  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-21  Eric S. Raymond  (esr@thyrsus.com)
 
        * makefile.el: Rewritten and simplified, commentary added.  It now
        will usually detect when the makefile target or macro lists need
@@ -1223,7 +1223,7 @@
        * sendmail.el (mail-do-fcc): Make a numeric time zone indicator
        with current-time-zone--don't run `date'.
 
-1993-04-16  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-16  Eric S. Raymond  (esr@thyrsus.com)
 
        * bytecomp.el (byte-compile, byte-compile-keep-pending)
        (byte-compile-file-form-defmumble): Generate define-function
@@ -1237,7 +1237,7 @@
        * diff.el (diff-parse-differences): Small robustification ---
        don't lose if we call this with compilation-parsing-end nil
 
-1993-04-16  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-16  Eric S. Raymond  (esr@thyrsus.com)
 
        * electric.el (shrink-window-if-larger-than-buffer):
        Move to window.el.
@@ -1286,14 +1286,14 @@
 
        * isearch.el: Doc fixes.
 
-1993-04-14  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-14  Eric S. Raymond  (esr@thyrsus.com)
 
        * gud.el (gud-mode): Created C-c synonym bindings in the GUD
        buffer's local map.
 
        (gud-key-prefix): Change to C-x C-a.
 
-1993-04-14  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-14  Eric S. Raymond  (esr@thyrsus.com)
 
        * help-macro.el: Name changed from help-screen.el to fit in a
        14-character limit.
@@ -1301,7 +1301,7 @@
        * sun-curs.el: Name changed from sun-cursors.el to protect the
        innocents.
 
-1993-04-14  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-14  Eric S. Raymond  (esr@thyrsus.com)
 
        * finder.el: Rewritten.  The Finder is now a major mode with the
        ability to browse package commentary sections and a completely
@@ -1352,7 +1352,7 @@
        * rot13.el (rot13-other-window): Add autoload.
        (rot13-display-table): Use `vector', not `make-rope'.
 
-1993-04-10  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-10  Eric S. Raymond  (esr@thyrsus.com)
 
        * gud.el (gdb, sdb, dbx): Improve prompting a la grep.
 
@@ -1369,7 +1369,7 @@
        empty string in response to the keyword prompt, restore the old
        window configuration properly.
 
-1993-04-09  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-04-09  Eric S. Raymond  (esr@thyrsus.com)
 
        * emerge.el (emerge-with-ancestor): Applied Donald Erway's fix
        patch, which included the following explanatory comment: "D.Erway
@@ -1394,7 +1394,7 @@
 
        * autoload.el (generate-file-autoloads): Doc fix.
 
-1993-04-08  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-04-08  Eric S. Raymond  (esr@thyrsus.com)
 
        * gud.el: Massive changes, amounting nearly to a rewrite.  The new
        features include auto-configuring support for SVr4, more commands,
@@ -1508,7 +1508,7 @@
        Change MIPS RISC CC regexp (last one) to
        be anchored at bol, and to never match multiple lines.
 
-1993-04-03  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-04-03  Eric S. Raymond  (esr@thyrsus.com)
 
        * man.el, assoc.el: Installed Barry Warsaw's new and much more
        featureful man page browser.
@@ -1520,7 +1520,7 @@
 
        * comint.el: New comint-read-noecho.
 
-1993-04-02  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-04-02  Eric S. Raymond  (esr@thyrsus.com)
 
        * chistory.el (repeat-history-command): Bug fix.  Someone forgot a car.
 
@@ -1595,7 +1595,7 @@
        editing ability and be able to abort when called from a process
        filter.  Re-arranged and updated docstring.
 
-1993-03-30  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-30  Eric S. Raymond  (esr@thyrsus.com)
 
        * ring.el: Changed summary line.
 
@@ -1611,7 +1611,7 @@
 
        * buff-menu.el: Put back removed years in copyright notice.
 
-1993-03-29  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-29  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.el (vc-next-action, vc-print-log, vc-diff, vc-revert-buffer):
        Improve logic for parent buffer finding.
@@ -1652,7 +1652,7 @@
        rlogin-password-paranoia is set.
        (rlogin-with-args, rlogin-password): New functions.
 
-1993-03-28  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-28  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.el (vc-comment-to-changelog): A useful vc-checkin hook, added.
        (vc-checkout): Now rejects attempts to check out files via FTP.
@@ -1674,7 +1674,7 @@
        * ebuff-menu.el (electric-buffer-menu-mode-map): fillarray isn't a
        valid operation on maps any more.
 
-1993-03-27  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * refer.el: Installed.
 
@@ -1682,7 +1682,7 @@
 
        * lucid.el (try-face-font, find-face, get-face): New aliases.
 
-1993-03-27  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * abbrevlist.el, old-inf-lisp.el, old-screen.el, old-shell.el,
        * oshell.el: Removed.
@@ -1702,13 +1702,13 @@
        (set-case-syntax-delims, set-case-syntax-pair, set-case-syntax):
        Rename arg STRING to TABLE.  Do not set the standard case table.
 
-1993-03-26  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * loaddefs.el: Commented out function-key-error definition and
        uses in the global keymaps.  RMS and jimb objected to the amount
        of space these took up in the keybinding listings.
 
-1993-03-27  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * lpr.el (printify-buffer): Add, debugged from Roland McGrath's
        printify-buffer code in LCD.
@@ -1739,7 +1739,7 @@
        (set-visited-file-name): Kill local-write-file-hooks as local var.
        (basic-save-buffer): Use local-write-file-hooks.
 
-1993-03-26  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * yow.el (psychoanalyze-pinhead): Needed a prefrontal lobotomy.
        I gave it one.
@@ -1752,7 +1752,7 @@
 
        * uncompress.el: Add provide call.
 
-1993-03-25  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-25  Eric S. Raymond  (esr@thyrsus.com)
 
        * lisp-mnt.el (lm-last-modified-date): Fix return bug.
 
@@ -1784,7 +1784,7 @@
        * term/x-win.el: Bind M-next to an alias scroll-other-window-1
        to get better doc string output.
 
-1993-03-23  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * compile.el: Fix library headers.
 
@@ -1793,7 +1793,7 @@
        * files.el (insert-directory): Do chase symlinks before passing
        the directory name to ls.
 
-1993-03-23  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * buff-menu.el: Incorporated changes from Bob Weiner's enhanced
        buff-menu from the LCD archive.
@@ -1802,7 +1802,7 @@
 
        * replace.el (query-replace-map): Define backspace like delete.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * cookie.el: Created.  This file contains what was formerly the
        guts of spook.el, lightly hacked to support more than one
@@ -1822,7 +1822,7 @@
        * term/x-win.el (x-win-suspend-error):
        suspend-hook renamed from suspend-hooks.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * help.el, register.el, replace.el, reposition.el, rfc822.el,
        * rlogin.el, rot13.el, scribe.el, scroll-bar.el, sendmail.el,
@@ -1835,14 +1835,14 @@
        * diary-insert.el: Change the name to diary-ins.el.
        * calendar.el: Change all autoload references to diary-ins.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * man.el, mlconvert.el, mlsupport.el, modula2.el, mouse.el,
        * mpuz.el, netunam.el, novice.el, nroff-mode.el, options.el,
        * outline.el, page.el, paragraphs.el, picture.el, prolog.el,
        * rect.el: Add or correct Commentary sections.
 
-1993-03-22  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * abbrev.el, ada.el, add-log.el, array.el, autoinsert.el,
        * autoload.el, awk-mode.el, bib-mode.el, bibtex.el, buff-menu.el,
@@ -1889,19 +1889,19 @@
        * lucid.el: Alias lower-screen and raise-screen to lower-frame and
        raise-frame, the new names for those functions.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * bush.el: Deleted.
 
        * finder.el: Make sure that when new keywords are compiled, we see them
        immediately.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * vt100-led.el, bg-mouse.el, sup-mouse.el, sun-mouse.el:
        Moved to term directory.
 
-1993-03-18  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-18  Eric S. Raymond  (esr@thyrsus.com)
 
        * Makefile: Created.  This exists mainly so developers elsewhere
        can unlock the lisp files to accept an update tar, then relock
@@ -1916,7 +1916,7 @@
 
        * solar.el (solar-time-string): Round the time properly.
 
-1993-03-18  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-18  Eric S. Raymond  (esr@thyrsus.com)
 
        * abbrev.el, abbrevlist.el, add-log.el, apropos.el, array.el,
        * autoload.el, awk-mode.el, cal-french.el, cal-mayan.el,
@@ -1938,7 +1938,7 @@
        * tex-mode.el (tex-send-command): Fix the command sent so that no
        blank is inserted when replacing the asterisk with the file name.
 
-1993-03-18  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-18  Eric S. Raymond  (esr@thyrsus.com)
 
        * term/wyse50.el (function-key-map): Nuke code no longer bound to keys.
 
@@ -1949,7 +1949,7 @@
        Fix things so that bindings are added to the keymap already created by
        terminal initialization.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * help-screen.el: Installed, following release.  Now package
        writers can easily implement help screens resembling Emacs's
@@ -1961,7 +1961,7 @@
 
        * finder-inf.el: Deleted the RCS file.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * isearch.el, lselect.el, select.el, scroll-bar.el, texinfo.el,
        * pending-del.el, profile.el, texinfmt.el, ls-lisp.el, meese.el,
@@ -1974,14 +1974,14 @@
        * case-table.el, byte-run.el, ange-ftp.el, backquote.el:
        Add or correct library header comments.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * finder.el (finder-compile-keywords): Treat nil in a path
        argument as $PWD.
 
        (finder-by-keyword): Handle LFD as input gracefully.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc-hooks.el: Increment version number to match vc.el's.
 
@@ -2017,7 +2017,7 @@
        * loaddefs.el (minor-mode-alist): Make the mode line element for
        overwrite-mode be the symbol `overwrite-mode'.
 
-1993-03-16  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-16  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.el, vc-hooks.el: The macro vc-error-occurred has to move from
        vc.el to vc-hooks.el for C-x C-f of a nonexistent file to work.
@@ -2074,11 +2074,11 @@
        (rmail-summary-rmail-update): Do nothing if rmail buffer not visible.
        (rmail-summary-mode-map): Don't bind C-n, C-p.  Use ordinary move cmds.
 
-1993-03-12  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-12  Eric S. Raymond  (esr@thyrsus.com)
 
        * term/x-win.el: Added library headers.
 
-1993-03-12  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-12  Eric S. Raymond  (esr@thyrsus.com)
 
        * loaddefs.el (global-map): Fix a typo in the binding of
        [kp-backtab].
@@ -2092,7 +2092,7 @@
        * term/x-win.el: Cancel previous change, since it discarded
        earlier necessary changes.
 
-1993-03-11  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-11  Eric S. Raymond  (esr@thyrsus.com)
 
        * term/vt100.el:
        Added headers, commented out code the duplicates startup effects.
@@ -2115,7 +2115,7 @@
        suspend-hooks, not suspend-hook.  The latter is an obsolete name.
        Use add-hook instead of setting suspend-hooks directly.
 
-1993-03-11  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-11  Eric S. Raymond  (esr@thyrsus.com)
 
        A boatload of changes to terminal support and terminal capability
        initialization that make it a lot smarter, with a more uniform
@@ -2197,7 +2197,7 @@
        Likewise their Meta versions.
        Also add `ascii-character' properties.
 
-1993-03-09  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-09  Eric S. Raymond  (esr@thyrsus.com)
 
        * term/at386.el: Removed.  The new terminal initialization stuff
        makes it superfluous.  I wrote it, so I should know. :-)
@@ -3300,7 +3300,7 @@
        (ange-ftp-read-passwd, ange-ftp-process-filter): Uncomment out the
        calls to ange-ftp-repaint-buffer.
 
-1992-11-11  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-11-11  Eric S. Raymond  (esr@thyrsus.com)
 
        * c-mode.el (c-style-alist): Add quotes around C++ style name.
 
@@ -4680,7 +4680,7 @@
        * compile.el (compilation-parse-errors): Write progress messages
        on all regexp matches, not just errors.
 
-1992-08-04  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-08-04  Eric S. Raymond  (esr@thyrsus.com)
 
        * view.el (view-mode): Teach this how to use help-char.
 
@@ -4727,7 +4727,7 @@
 
        * etags.el (complete-tag): Error if no tags table loaded.
 
-1992-08-03  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-08-03  Eric S. Raymond  (esr@thyrsus.com)
 
        * ebuff-menu.el, echistory.el, help.el, hexl.el: Teach these packages
        to use help-char, and add the appropriate magic to doc strings.
@@ -5085,7 +5085,7 @@
 
        * diff.el (diff-rcs, diff-sccs): Delete.
 
-1992-07-27  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * tar-mode.el (tar-subfile-save-buffer): Whoever changed
        current-time forgot to check for breakage.  Added code to print
@@ -5201,7 +5201,7 @@
        * c++-mode.el (indent-c++-exp): Fix typo "innerloop-done".
        Make last-depth local.
 
-1992-07-23  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * flow-ctrl.el: Fixed set-input-mode call broken by new third
        arg for meta control.
@@ -5266,11 +5266,11 @@
        * byte-opt.el (disassemble-offset, byte-decompile-bytecode-1):
        Support for relative jumps removed.
 
-1992-07-22  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-22  Eric S. Raymond  (esr@thyrsus.com)
 
        * Removed all Last-Modified headers.
 
-1992-07-21  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-21  Eric S. Raymond  (esr@thyrsus.com)
 
        * files.el (trim-versions-without-asking): Non-nil, non-t value
        suppresses all trimming of excess backups.  This is so we can make
@@ -5335,7 +5335,7 @@
        Modified the remaining version by adding new argument GLOBAL
        and setting the parameters locally if GLOBAL is nil.
 
-1992-07-21  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-21  Eric S. Raymond  (esr@thyrsus.com)
 
        * Turfed r2bibtex.el.  Refbib.el turns out to be a newer version
        of the same package.
@@ -5484,7 +5484,7 @@
        (find-file-noselect): Don't remove the automount prefix here; let
        abbreviate-file-name take care of it.
 
-1992-07-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * Keywords added for [n-z]*.el.  Finder now under construction.
 
@@ -5495,7 +5495,7 @@
        ending with the latter may be deleted accidentally when space is
        low.
 
-1992-07-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * Keywords added for [a-m]*.el.  The keyword categories will
        probably need some tuning, but at least this will suffice
@@ -5508,7 +5508,7 @@
 
        * Changed all copying notices to GPL version 2.
 
-1992-07-16  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-16  Eric S. Raymond  (esr@thyrsus.com)
 
        * Finished decorating the library files with new standard headers.
 
@@ -5537,14 +5537,14 @@
        * byte-run.el (defsubst): Remove extra closing paren at the end
        of this function.
 
-1992-07-16  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-16  Eric S. Raymond  (esr@thyrsus.com)
 
        * At RMS's request, all occurrences of `elisp' changed to `Emacs Lisp'.
 
        * New library headers for [fghijklmn]*.el.  First steps towards
        keyword-based code finder via Keywords header.
 
-1992-07-15  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-15  Eric S. Raymond  (esr@thyrsus.com)
 
        * New library headers for [opqrst]*.el.  Ghod, this is boring.
 
@@ -5594,7 +5594,7 @@
        (set-frame-height, set-frame-width): Functions deleted; they are
        defined in frame.c.
 
-1992-07-14  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-14  Eric S. Raymond  (esr@thyrsus.com)
 
        * [uvwxy]*.el: Added headers for new Emacs Lisp documentation
        conventions.
@@ -5604,7 +5604,7 @@
        * calendar.el (calendar-mode): Change key bindings for all
        functions to make them consistent with Version 19 requirements.
 
-1992-07-13  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-13  Eric S. Raymond  (esr@thyrsus.com)
 
        * comint.el: Minor changes to comments to reflect the fact that
        comint has won its war and replaced shell mode.
@@ -5750,7 +5750,7 @@
        * etags.el (visit-tags-table): Remove automounter prefixes before
        setting tags-file-name.
 
-1992-07-06  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-07-06  Eric S. Raymond  (esr@thyrsus.com)
 
        * Moved gdb.el to =gdb.el.  The autoload generation for
        loaddefs.el was getting screwed up by the conflicting autoloads
@@ -6157,7 +6157,7 @@
 
        * info.el (Info-enable-edit): Now a user option.
 
-1992-06-03  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-06-03  Eric S. Raymond  (esr@thyrsus.com)
 
        * sendmail.el (mail-signature): Suppress move to end of buffer if
        we gave a prefix argument (requested by Bob Chassell).
@@ -6250,7 +6250,7 @@
 
        * flow-ctrl.el (evade-flow-control-memstr=): Rename from memstr=.
 
-1992-05-31  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-05-31  Eric S. Raymond  (esr@thyrsus.com)
 
        * bibtex.el: Merged in alarson's changes.
 
@@ -6261,7 +6261,7 @@
 
        * subr.el (lambda): Add docstring.
 
-1992-05-31  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-05-31  Eric S. Raymond  (esr@thyrsus.com)
 
        * gdb.el: Nuked --- subsumed by gdb entry point of gud.el.
 
@@ -6275,7 +6275,7 @@
        old version still exists in the ~n~ files if this loses, but
        the code looks good.
 
-1992-05-30  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-05-30  Eric S. Raymond  (esr@thyrsus.com)
 
        * profile.el: Installed.
 
@@ -6715,7 +6715,7 @@
 
        * term/new-at386.el: Rewritten to use function-key-map.
 
-1992-01-10  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1992-01-10  Eric S. Raymond  (esr@thyrsus.com)
 
        * flow-ctrl.el: Installed.
 
@@ -6751,14 +6751,14 @@
        (sendmail-send-it): Delete code for mail-aliases.
        (build-mail-aliases, expand-mail-aliases): Autoloads deleted.
 
-1991-12-14  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1991-12-14  Eric S. Raymond  (esr@thyrsus.com)
 
        * etags.el (find-tag-noselect): Fix subtle bug due to
        save-excursion.
 
        (tags-tag-match): New function, made smarter about exact matches.
 
-1991-12-13  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1991-12-13  Eric S. Raymond  (esr@thyrsus.com)
 
        * perl-mode.el: Installed.
 
@@ -6767,7 +6767,7 @@
        * sendmail.el (mail-default-headers): New user variable.
        (mail-setup): Insert value of that variable.
 
-1991-12-11  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1991-12-11  Eric S. Raymond  (esr@thyrsus.com)
 
        * c-mode.el: Added C++ style to c-style-alist.
 
@@ -6777,7 +6777,7 @@
 
        * man.el (nuke-nroff-bs): Simplify o^H+.  Delete "reformatting" msg.
 
-1991-12-08  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1991-12-08  Eric S. Raymond  (esr@thyrsus.com)
 
        * blackbox.el: Applied doc patch.  No functions affected.
 
@@ -12032,7 +12032,7 @@
 
 1988-12-22  Richard Stallman  (rms@mole.ai.mit.edu)
 
-       * term/at386.el: Eric Raymond's changes to work with keypad.el.
+       * term/at386.el: Eric S. Raymond's changes to work with keypad.el.
 
        * loaddefs.el (completion-ignored-extensions): Add .a and .ln.
 
diff --git a/lisp/ChangeLog.4 b/lisp/ChangeLog.4
index ecadb4a30d5..f0dd1295dd7 100644
--- a/lisp/ChangeLog.4
+++ b/lisp/ChangeLog.4
@@ -3384,7 +3384,7 @@
 
        * font-lock.el (shell-font-lock-keywords): Doc fix.
 
-1994-02-02  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1994-02-02  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc-hooks.el (vc-mode-line): Use force-mode-line-update instead
        of the Emacs 18 kluge.
@@ -3402,7 +3402,7 @@
        * files.el (file-relative-name): Allow for ancestors as well
        as descendants.
 
-1994-02-02  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1994-02-02  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.el (vc-parse-buffer): Arrange for old properties to get
        cleared when their match string is not found in the master file.
@@ -4811,7 +4811,7 @@
        (add-new-page): Insert new page in specified location.
        (original-page-delimiter): Set default value to "^^L".
 
-1993-11-15  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-11-15  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.el: vc-static-header-alist shouldn't have been declared const.
 
@@ -5535,7 +5535,7 @@
        * rmail.el (rmail-convert-to-babyl-format): Protect against
        nonsensical content-length values.
 
-1993-10-04  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-10-04  Eric S. Raymond  (esr@thyrsus.com)
 
        * vc.el (vc-next-action): Fix (throw ... ) invocation to work with 19;
        allows vc-next-action on all marked files in a dired buffer to work.
diff --git a/lisp/ChangeLog.5 b/lisp/ChangeLog.5
index 63cf5db94c4..96bda7650e2 100644
--- a/lisp/ChangeLog.5
+++ b/lisp/ChangeLog.5
@@ -4253,7 +4253,7 @@
        the autogenerated label in the minibuffer caused the killed text
        to appear in front of the bibtex entry.
 
-1995-01-05  Eric S. Raymond  <esr@locke.ccil.org>
+1995-01-05  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-do-command): Change RCS handling so rcsdiff won't strip
        away relative-pathname information.  This function no longer sets the
diff --git a/lisp/ChangeLog.6 b/lisp/ChangeLog.6
index 4e7366c80a8..0fc8ffcf591 100644
--- a/lisp/ChangeLog.6
+++ b/lisp/ChangeLog.6
@@ -7910,7 +7910,7 @@
 
        * paths.el (remote-shell-program): Fix typo checking /usr/bin/remsh.
 
-1995-06-26  Eric S. Raymond  <esr@snark.thyrsus.com>
+1995-06-26  Eric S. Raymond  <esr@thyrsus.com>
 
        * vc.el (vc-start-entry): Prevent lossage when doing a mass checkin
        from a VC-dired buffer.
diff --git a/lisp/ChangeLog.7 b/lisp/ChangeLog.7
index 667cd5e850a..17464042b76 100644
--- a/lisp/ChangeLog.7
+++ b/lisp/ChangeLog.7
@@ -3825,7 +3825,7 @@
 
        * progmodes/perl-mode.el (perl-mode): Add autoload cookie.
 
-1998-04-29  Eric S. Raymond  <esr@snark.thyrsus.com>
+1998-04-29  Eric S. Raymond  <esr@thyrsus.com>
 
        Many small changes that mostly eliminate the explicit mail separator
        variable and use the new rfc822-goto-eoh primitive instead:
@@ -13054,7 +13054,7 @@
        * mail/sendmail.el (mail-mode): Make adaptive-fill-regexp
        match more values.  Bind adaptive-fill-first-line-regexp too.
 
-1997-07-26  Eric S. Raymond  <esr@snark.thyrsus.com>
+1997-07-26  Eric S. Raymond  <esr@thyrsus.com>
 
        * telnet.el (telnet): Handle multiple telnet programs better.
        (telnet-host-properties): New variable.
diff --git a/lisp/Makefile.in b/lisp/Makefile.in
index 011383ed358..12aa18f3561 100644
--- a/lisp/Makefile.in
+++ b/lisp/Makefile.in
@@ -351,11 +351,7 @@ compile-first: $(COMPILE_FIRST)
 # TARGETS is set dynamically in the recursive call from 'compile-main'.
 # Do not build comp.el unless necessary not to exceed max-lisp-eval-depth
 # in normal builds.
-ifneq ($(HAVE_NATIVE_COMP),yes)
-compile-targets: $(filter-out ./emacs-lisp/comp-cstr.elc,$(filter-out 
./emacs-lisp/comp.elc,$(TARGETS)))
-else
 compile-targets: $(TARGETS)
-endif
 
 # Compile all the Elisp files that need it.  Beware: it approximates
 # 'no-byte-compile', so watch out for false-positives!
diff --git a/lisp/align.el b/lisp/align.el
index 79a75dcec79..13e31e2ad60 100644
--- a/lisp/align.el
+++ b/lisp/align.el
@@ -164,12 +164,14 @@ values may cause unexpected behavior at times."
   :group 'align)
 
 (defcustom align-highlight-change-face 'highlight
-  "The face to highlight with if changes are necessary."
+  "The face to highlight with if changes are necessary.
+Used by the `align-highlight-rule' command."
   :type 'face
   :group 'align)
 
 (defcustom align-highlight-nochange-face 'secondary-selection
-  "The face to highlight with if no changes are necessary."
+  "The face to highlight with if no changes are necessary.
+Used by the `align-highlight-rule' command."
   :type 'face
   :group 'align)
 
@@ -209,20 +211,20 @@ If nil, then no messages will ever be printed to the 
minibuffer."
 
 (defcustom align-dq-string-modes
   (append align-lisp-modes align-c++-modes align-perl-modes
-          '(python-mode vhdl-mode))
+          '(python-base-mode vhdl-mode))
   "A list of modes where double quoted strings should be excluded."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-sq-string-modes
-  (append align-perl-modes '(python-mode))
+  (append align-perl-modes '(python-base-mode))
   "A list of modes where single quoted strings should be excluded."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-open-comment-modes
   (append align-lisp-modes align-c++-modes align-perl-modes
-          '(python-mode makefile-mode vhdl-mode))
+          '(python-base-mode makefile-mode vhdl-mode))
   "A list of modes with a single-line comment syntax.
 These are comments as in Lisp, which have a beginning, but end with
 the line (i.e., `comment-end' is an empty string)."
@@ -448,7 +450,7 @@ The possible settings for `align-region-separate' are:
      (regexp   . ,(concat "[^=!<> \t\n]\\(\\s-*\\)="
                          "\\(\\s-*\\)\\([^>= \t\n]\\|$\\)"))
      (group    . (1 2))
-     (modes    . '(python-mode))
+     (modes    . '(python-base-mode))
      (tab-stop . nil))
 
     (make-assignment
@@ -476,7 +478,7 @@ The possible settings for `align-region-separate' are:
     (basic-comma-delimiter
      (regexp   . ",\\(\\s-*\\)[^# \t\n]")
      (repeat   . t)
-     (modes    . (append align-perl-modes '(python-mode)))
+     (modes    . (append align-perl-modes '(python-base-mode)))
      (run-if   . ,(lambda () current-prefix-arg)))
 
     (c++-comment
@@ -506,7 +508,7 @@ The possible settings for `align-region-separate' are:
 
     (python-chain-logic
      (regexp   . "\\(\\s-*\\)\\(\\<and\\>\\|\\<or\\>\\)")
-     (modes    . '(python-mode))
+     (modes    . '(python-base-mode))
      (valid    . ,(lambda ()
                     (save-excursion
                       (goto-char (match-end 2))
@@ -523,7 +525,7 @@ The possible settings for `align-region-separate' are:
 
     (basic-line-continuation
      (regexp   . "\\(\\s-*\\)\\\\$")
-     (modes    . '(python-mode makefile-mode)))
+     (modes    . '(python-base-mode makefile-mode)))
 
     (tex-record-separator
      (regexp . ,(lambda (end reverse)
@@ -568,7 +570,14 @@ The possible settings for `align-region-separate' are:
     (css-declaration
      (regexp . "^\\s-*\\(?:\\w-?\\)+:\\(\\s-*\\).*;")
      (group . (1))
-     (modes . '(css-mode html-mode))))
+     (modes . '(css-base-mode html-mode)))
+
+    (toml-assignment
+     (regexp . ,(rx (group (zero-or-more (syntax whitespace)))
+                    "="
+                    (group (zero-or-more (syntax whitespace)))))
+     (group . (1 2))
+     (modes . '(conf-toml-mode toml-ts-mode))))
   "A list describing all of the available alignment rules.
 The format is:
 
@@ -1261,6 +1270,14 @@ Otherwise, create a new marker at position POS, with 
type TYPE."
        (move-marker ,marker-var ,pos)
      (setq ,marker-var (copy-marker ,pos ,type))))
 
+(defun align--rule-should-run (rule)
+  "Given an `align-rules-list' entry RULE, return t if it should run.
+This is decided by the `modes' and `run-if' keys in the alist
+RULE.  Their meaning is documented in `align-rules-list' (which see)."
+  (let-alist rule
+    (not (or (and .modes (not (apply #'derived-mode-p (eval .modes))))
+             (and .run-if (not (funcall .run-if)))))))
+
 (defun align-region (beg end separate rules exclude-rules
                         &optional func)
   "Align a region based on a given set of alignment rules.
@@ -1298,23 +1315,20 @@ This feature (of passing a FUNC) is used internally to 
locate the
 position of exclusion areas, but could also be used for any other
 purpose where you might want to know where the regions that the
 aligner would have dealt with are."
-  (let ((end-mark (and end (copy-marker end t)))
-       (real-beg beg)
-       (report (and (not func) align-large-region beg end
-                    (>= (- end beg) align-large-region)))
-       (rule-index 1)
-       (rule-count (length rules))
-       markers)
+  (let* ((end-mark (and end (copy-marker end t)))
+        (real-beg beg)
+        (report (and (not func) align-large-region beg end
+                     (>= (- end beg) align-large-region)))
+         (rules (seq-filter #'align--rule-should-run rules))
+        (rule-index 1)
+        (rule-count (length rules))
+        markers)
     (if (and align-indent-before-aligning real-beg end-mark)
        (indent-region real-beg end-mark nil))
     (while rules
-      (let* ((rule (car rules))
-            (run-if (assq 'run-if rule))
-            (modes (assq 'modes rule)))
-       ;; unless the `run-if' form tells us not to, look for the
-       ;; rule..
-       (unless (or (and modes (not (apply #'derived-mode-p (eval (cdr 
modes)))))
-                   (and run-if (not (funcall (cdr run-if)))))
+      (let ((rule (car rules)))
+        (progn
+          ;; Search for a match for the rule.
          (let* ((case-fold-search case-fold-search)
                 (case-fold (assq 'case-fold rule))
                 (regexp  (cdr (assq 'regexp rule)))
@@ -1449,8 +1463,7 @@ aligner would have dealt with are."
                     ;; that we need it
                     (unless nil ;; group-c
                       (setq groups (or (cdr (assq 'group rule)) 1))
-                      (unless (listp groups)
-                        (setq groups (list groups)))
+                      (setq groups (ensure-list groups))
                       (setq first (car groups)))
 
                     (unless spacing-c
diff --git a/lisp/arc-mode.el b/lisp/arc-mode.el
index 0a971799746..81d3dfc3432 100644
--- a/lisp/arc-mode.el
+++ b/lisp/arc-mode.el
@@ -2026,6 +2026,7 @@ This doesn't recover lost files, it just undoes changes 
in the buffer itself."
     (setq p (+ p (point-min)))
     (while (string= "PK\001\002" (buffer-substring p (+ p 4)))
       (let* ((creator (get-byte (+ p 5)))
+             (gpflags (archive-l-e (+ p 8) 2))
             ;; (method  (archive-l-e (+ p 10) 2))
              (modtime (archive-l-e (+ p 12) 2))
              (moddate (archive-l-e (+ p 14) 2))
@@ -2037,7 +2038,12 @@ This doesn't recover lost files, it just undoes changes 
in the buffer itself."
              (efnname (let ((str (buffer-substring (+ p 46) (+ p 46 fnlen))))
                        (decode-coding-string
                         str
-                         (or (if (and w32-fname-encoding
+                         ;; Bit 11 of general purpose bit flags (bytes
+                         ;; 8-9) of Central Directory: 1 means UTF-8
+                         ;; encoded file names.
+                         (or (if (/= 0 (logand gpflags #x0800))
+                                 'utf-8-unix)
+                             (if (and w32-fname-encoding
                                       (memq creator
                                             ;; This should be just 10 and
                                             ;; 14, but InfoZip uses 0 and
diff --git a/lisp/auth-source-pass.el b/lisp/auth-source-pass.el
index dbcc9d05753..0f51755a250 100644
--- a/lisp/auth-source-pass.el
+++ b/lisp/auth-source-pass.el
@@ -1,6 +1,6 @@
 ;;; auth-source-pass.el --- Integrate auth-source with password-store -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 2015, 2017-2023 Free Software Foundation, Inc.
+;; Copyright (C) 2015-2023 Free Software Foundation, Inc.
 
 ;; Author: Damien Cassou <damien@cassou.me>,
 ;;         Nicolas Petton <nicolas@petton.fr>
@@ -122,9 +122,9 @@ HOSTS can be a string or a list of strings."
 
 (defun auth-source-pass--build-result-many (hosts ports users require max)
   "Return multiple `auth-source-pass--build-result' values."
-  (unless (listp hosts) (setq hosts (list hosts)))
-  (unless (listp users) (setq users (list users)))
-  (unless (listp ports) (setq ports (list ports)))
+  (setq hosts (ensure-list hosts))
+  (setq users (ensure-list users))
+  (setq ports (ensure-list ports))
   (let* ((auth-source-pass--match-regexp (auth-source-pass--match-regexp
                                           auth-source-pass-port-separator))
          (rv (auth-source-pass--find-match-many hosts users ports
diff --git a/lisp/auth-source.el b/lisp/auth-source.el
index e51fc02724a..365f6697ec8 100644
--- a/lisp/auth-source.el
+++ b/lisp/auth-source.el
@@ -899,8 +899,7 @@ Remove trailing \": \"."
 (defun auth-source-ensure-strings (values)
   (if (eq values t)
       values
-    (unless (listp values)
-      (setq values (list values)))
+    (setq values (ensure-list values))
     (mapcar (lambda (value)
              (if (numberp value)
                  (format "%s" value)
@@ -1958,20 +1957,23 @@ entries for git.gnus.org:
          (hosts (if (and hosts (listp hosts)) hosts `(,hosts)))
          (ports (plist-get spec :port))
          (ports (if (and ports (listp ports)) ports `(,ports)))
+         (users (plist-get spec :user))
+         (users (if (and users (listp users)) users `(,users)))
          ;; Loop through all combinations of host/port and pass each of these 
to
          ;; auth-source-macos-keychain-search-items
          (items (catch 'match
                   (dolist (host hosts)
                     (dolist (port ports)
-                      (let* ((port (if port (format "%S" port)))
-                             (items (apply 
#'auth-source-macos-keychain-search-items
-                                           coll
-                                           type
-                                           max
-                                           host port
-                                           search-spec)))
-                        (when items
-                          (throw 'match items)))))))
+                      (dolist (user users)
+                        (let ((items (apply
+                                      #'auth-source-macos-keychain-search-items
+                                      coll
+                                      type
+                                      max
+                                      host port user
+                                      search-spec)))
+                          (when items
+                            (throw 'match items))))))))
 
          ;; ensure each item has each key in `returned-keys'
          (items (mapcar (lambda (plist)
@@ -2003,8 +2005,9 @@ entries for git.gnus.org:
                      collect var))
      'utf-8)))
 
-(cl-defun auth-source-macos-keychain-search-items (coll _type _max host port
-                                                   &key label type user
+(cl-defun auth-source-macos-keychain-search-items (coll _type _max
+                                                        host port user
+                                                   &key label type
                                                    &allow-other-keys)
   (let* ((keychain-generic (eq type 'macos-keychain-generic))
          (args `(,(if keychain-generic
@@ -2022,47 +2025,49 @@ entries for git.gnus.org:
     (when port
       (if keychain-generic
           (setq args (append args (list "-s" port)))
-        (setq args (append args (list
-                                 (if (string-match "[0-9]+" port) "-P" "-r")
-                                 port)))))
+        (setq args (append args (if (string-match "[0-9]+" port)
+                                    (list "-P" port)
+                                  (list "-r" (substring
+                                              (format "%-4s" port)
+                                              0 4)))))))
 
-      (unless (equal coll "default")
-        (setq args (append args (list coll))))
+    (unless (equal coll "default")
+      (setq args (append args (list coll))))
 
-      (with-temp-buffer
-        (apply #'call-process "/usr/bin/security" nil t nil args)
-        (goto-char (point-min))
-        (while (not (eobp))
-          (cond
-           ((looking-at "^password: \\(?:0x[0-9A-F]+\\)? *\"\\(.+\\)\"")
-            (setq ret (auth-source-macos-keychain-result-append
-                       ret
-                       keychain-generic
-                       "secret"
-                       (let ((v (auth-source--decode-octal-string
-                                 (match-string 1))))
-                         (lambda () v)))))
-           ;; TODO: check if this is really the label
-           ;; match 0x00000007 <blob>="AppleID"
-           ((looking-at
-             "^[ ]+0x00000007 <blob>=\\(?:0x[0-9A-F]+\\)? *\"\\(.+\\)\"")
-            (setq ret (auth-source-macos-keychain-result-append
-                       ret
-                       keychain-generic
-                       "label"
-                       (auth-source--decode-octal-string (match-string 1)))))
-           ;; match "crtr"<uint32>="aapl"
-           ;; match "svce"<blob>="AppleID"
-           ((looking-at
-             "^[ ]+\"\\([a-z]+\\)\"[^=]+=\\(?:0x[0-9A-F]+\\)? *\"\\(.+\\)\"")
-            (setq ret (auth-source-macos-keychain-result-append
-                       ret
-                       keychain-generic
-                       (auth-source--decode-octal-string (match-string 1))
-                       (auth-source--decode-octal-string (match-string 2))))))
-          (forward-line)))
-      ;; return `ret' iff it has the :secret key
-      (and (plist-get ret :secret) (list ret))))
+    (with-temp-buffer
+      (apply #'call-process "/usr/bin/security" nil t nil args)
+      (goto-char (point-min))
+      (while (not (eobp))
+        (cond
+         ((looking-at "^password: \\(?:0x[0-9A-F]+\\)? *\"\\(.+\\)\"")
+          (setq ret (auth-source-macos-keychain-result-append
+                     ret
+                     keychain-generic
+                     "secret"
+                     (let ((v (auth-source--decode-octal-string
+                               (match-string 1))))
+                       (lambda () v)))))
+         ;; TODO: check if this is really the label
+         ;; match 0x00000007 <blob>="AppleID"
+         ((looking-at
+           "^[ ]+0x00000007 <blob>=\\(?:0x[0-9A-F]+\\)? *\"\\(.+\\)\"")
+          (setq ret (auth-source-macos-keychain-result-append
+                     ret
+                     keychain-generic
+                     "label"
+                     (auth-source--decode-octal-string (match-string 1)))))
+         ;; match "crtr"<uint32>="aapl"
+         ;; match "svce"<blob>="AppleID"
+         ((looking-at
+           "^[ ]+\"\\([a-z]+\\)\"[^=]+=\\(?:0x[0-9A-F]+\\)? *\"\\(.+\\)\"")
+          (setq ret (auth-source-macos-keychain-result-append
+                     ret
+                     keychain-generic
+                     (auth-source--decode-octal-string (match-string 1))
+                     (auth-source--decode-octal-string (match-string 2))))))
+        (forward-line)))
+    ;; return `ret' iff it has the :secret key
+    (and (plist-get ret :secret) (list ret))))
 
 (defun auth-source-macos-keychain-result-append (result generic k v)
   (push v result)
diff --git a/lisp/battery.el b/lisp/battery.el
index 4306d5b2058..c55fcbbee8c 100644
--- a/lisp/battery.el
+++ b/lisp/battery.el
@@ -29,9 +29,11 @@
 ;; - The `/sys/class/power_supply/' files of Linux >= 2.6.39.
 ;; - The `/proc/acpi/' directory structure of Linux 2.4.20 and 2.6.
 ;; - The `/proc/apm' file format of Linux version 1.3.58 or newer.
+;; - The Haiku ACPI battery driver.
 ;; - BSD by using the `apm' program.
 ;; - Darwin (macOS) by using the `pmset' program.
 ;; - Windows via the GetSystemPowerStatus API call.
+;; - Android 5 or later via the BatteryManager APIs.
 
 ;;; Code:
 
@@ -106,6 +108,12 @@ Value does not include \".\" or \"..\"."
               (file-readable-p "/proc/")
               (file-readable-p "/proc/apm"))
          #'battery-linux-proc-apm)
+        ;; Now try the Android battery status function.
+        ;; Note that even though the Linux kernel APIs are sometimes
+        ;; available on Android, they are badly implemented by Android
+        ;; kernels, so avoid using those.
+        ((eq system-type 'android)
+         #'battery-android)
        ((and (eq system-type 'berkeley-unix)
              (file-executable-p "/usr/sbin/apm"))
         #'battery-bsd-apm)
@@ -1072,6 +1080,78 @@ The following %-sequences are provided:
          (cons ?t (or remaining-time "N/A")))))
 
 
+;;; `BatteryManager' interface for Android.
+
+(declare-function android-query-battery "androidfns.c")
+
+(defun battery-android ()
+  "Get battery status information using Android.
+
+The following %-sequences are provided:
+%c Current capacity (mAh)
+%r Current rate of charge or discharge (mA)
+%L AC line status (verbose).
+%B Battery status (verbose)
+%b Battery status, empty means high, `-' means low,
+  `+' means charging and `?' means unknown.
+%d Temperature (in degrees Celsius)
+%p Battery load percentage.
+%m Remaining time (to charge) in minutes.
+%h Remaining time (to charge) in hours.
+%t Remaining time (to charge) in the form `h:min'."
+  (when-let* ((status (android-query-battery)))
+    (let* ((percentage nil)
+           (capacity nil)
+           (sym-status nil)
+           (symbol nil)
+           (rate nil)
+           (remaining nil)
+           (hours nil)
+           (minutes nil))
+      ;; Figure out the percentage.
+      (setq percentage (number-to-string (car status)))
+      ;; Figure out the capacity
+      (setq capacity (number-to-string (/ (cadr status) 1000)))
+      ;; Figure out the battery status.
+      (let ((percentage (car status)))
+        (cl-ecase (nth 4 status)
+          (2 (setq sym-status "charging" symbol "+"))
+          (3 (setq sym-status "discharging"
+                   symbol (if (< percentage 15) "-" " ")))
+          (5 (setq sym-status "full" symbol " "))
+          (4 (setq sym-status "not charging"
+                   symbol (if (< percentage 15) "-" " ")))
+          (1 (setq sym-status "unknown" symbol "?"))))
+      ;; Figure out the rate of charge.
+      (setq rate (/ (nth 3 status) 1000))
+      ;; Figure out the remaining time.
+      (let* ((time (nth 5 status))
+             (mins (/ time (* 1000 60)))
+             (hours-left (/ mins 60))
+             (mins (mod mins 60)))
+        (unless (eq time -1)
+          (setq remaining (format "%d:%d" hours-left mins)
+                hours (number-to-string hours-left)
+                minutes (number-to-string mins))))
+      ;; Return results.
+      (list (cons ?c capacity)
+            (cons ?p percentage)
+            (cons ?r rate)
+            (cons ?B sym-status)
+            (cons ?b symbol)
+            (cons ?m (or minutes "N/A"))
+            (cons ?h (or hours "N/A"))
+            (cons ?t (or remaining "N/A"))
+            (cons ?L (cl-case (nth 6 status)
+                       (0 "off-line")
+                       (1 "on-line")
+                       (2 "on-line (dock)")
+                       (3 "on-line (USB)")
+                       (4 "on-line (wireless)")
+                       (t "unknown")))
+            (cons ?t (/ (or (nth 7 status) 0) 10.0))))))
+
+
 ;;; Private functions.
 
 (defun battery-format (format alist)
diff --git a/lisp/bindings.el b/lisp/bindings.el
index f1a75b080be..68aa0a78099 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -226,9 +226,9 @@ mnemonics of the following coding systems:
 (put 'mode-line-mule-info 'risky-local-variable t)
 
 (defvar mode-line-client
-  `(""
-    (:propertize ("" (:eval (if (frame-parameter nil 'client) "@" "")))
-                help-echo ,(purecopy "emacsclient frame")))
+  `(:eval
+    (if (frame-parameter nil 'client)
+       ,(propertize "@" 'help-echo (purecopy "emacsclient frame"))))
   "Mode line construct for identifying emacsclient frames.")
 ;; Autoload if this file no longer dumped.
 ;;;###autoload
@@ -338,7 +338,10 @@ the symbol `mode-line-format-right-align' is processed by
   (let* ((rest (cdr (memq 'mode-line-format-right-align
                          mode-line-format)))
         (rest-str (format-mode-line `("" ,@rest)))
-        (rest-width (string-pixel-width rest-str)))
+        (rest-width (progn
+                       (add-face-text-property
+                        0 (length rest-str) 'mode-line t rest-str)
+                       (string-pixel-width rest-str))))
     (propertize " " 'display
                ;; The `right' spec doesn't work on TTY frames
                ;; when windows are split horizontally (bug#59620)
@@ -446,7 +449,7 @@ Keymap to display on minor modes.")
 
 (defvar mode-line-modes
   (let ((recursive-edit-help-echo
-         "Recursive edit, type M-C-c to get out"))
+         "Recursive edit, type C-M-c to get out"))
     (list (propertize "%[" 'help-echo recursive-edit-help-echo)
          "("
          `(:propertize ("" mode-name)
@@ -1266,6 +1269,10 @@ if `inhibit-field-text-motion' is non-nil."
 (define-key global-map [insertchar]    'overwrite-mode)
 (define-key global-map [C-insertchar]  'kill-ring-save)
 (define-key global-map [S-insertchar]  'yank)
+;; The next three keys are used on MS Windows and Android.
+(define-key global-map [copy]          'kill-ring-save)
+(define-key global-map [paste]         'yank)
+(define-key global-map [cut]           'kill-region)
 (define-key global-map [undo]          'undo)
 (define-key global-map [redo]          'repeat-complex-command)
 (define-key global-map [again]         'repeat-complex-command) ; Sun keyboard
@@ -1585,6 +1592,9 @@ if `inhibit-field-text-motion' is non-nil."
 (define-key special-event-map [sigusr1] 'ignore)
 (define-key special-event-map [sigusr2] 'ignore)
 
+;; Text conversion
+(define-key global-map [text-conversion] 'analyze-text-conversion)
+
 ;; Don't look for autoload cookies in this file.
 ;; Local Variables:
 ;; no-update-autoloads: t
diff --git a/lisp/bookmark.el b/lisp/bookmark.el
index 026257ff758..10ff2f5ebbf 100644
--- a/lisp/bookmark.el
+++ b/lisp/bookmark.el
@@ -89,13 +89,15 @@ To specify the file in which to save them, modify the 
variable
   :type 'file)
 
 (defcustom bookmark-watch-bookmark-file t
-  "If non-nil watch the default bookmark file.
+  "If non-nil reload the default bookmark file if it was changed.
 If this file has changed on disk since it was last loaded, query the user
 whether to load it again.  If the value is `silent' reload without querying.
 This file defaults to `bookmark-default-file'.  But during an Emacs session,
 `bookmark-load' and `bookmark-save' can redefine the current default file."
   :version "27.1"
-  :type 'boolean
+  :type '(choice (const :tag "Suggest to reload bookmark file if changed" t)
+                 (const :tag "Silently reload bookmark file if changed" silent)
+                 (const :tag "Ignore changes of bookmark file" nil))
   :group 'bookmark)
 
 (defcustom bookmark-version-control 'nospecial
diff --git a/lisp/button.el b/lisp/button.el
index 002064fbea0..b01595943fc 100644
--- a/lisp/button.el
+++ b/lisp/button.el
@@ -72,7 +72,12 @@ Mode-specific keymaps may want to use this as their parent 
keymap."
   ;; mode-line or header-line, the `mode-line' or `header-line' prefix
   ;; shouldn't be necessary!
   "<mode-line> <mouse-2>" #'push-button
-  "<header-line> <mouse-2>" #'push-button)
+  "<header-line> <mouse-2>" #'push-button
+  ;; `push-button' will automatically dispatch to
+  ;; `touch-screen-track-tap'.
+  "<mode-line> <touchscreen-down>" #'push-button
+  "<header-line> <touchscreen-down>" #'push-button
+  "<touchscreen-down>" #'push-button)
 
 (define-minor-mode button-mode
   "A minor mode for navigating to buttons with the TAB key."
@@ -454,18 +459,22 @@ instead of starting at the next button."
 
 (defun push-button (&optional pos use-mouse-action)
   "Perform the action specified by a button at location POS.
-POS may be either a buffer position or a mouse-event.  If
-USE-MOUSE-ACTION is non-nil, invoke the button's `mouse-action'
-property instead of its `action' property; if the button has no
-`mouse-action', the value of `action' is used instead.
+POS may be either a buffer position, a mouse-event, or a
+`touchscreen-down' event.  If USE-MOUSE-ACTION is non-nil, invoke
+the button's `mouse-action' property instead of its `action'
+property; if the button has no `mouse-action', the value of
+`action' is used instead.
+
+If POS is a `touchscreen-down' event, wait for the corresponding
+`touchscreen-up' event before calling `push-button'.
 
 The action in both cases may be either a function to call or a
 marker to display and is invoked using `button-activate' (which
 see).
 
 POS defaults to point, except when `push-button' is invoked
-interactively as the result of a mouse-event, in which case, the
-mouse event is used.
+interactively as the result of a mouse-event or touchscreen
+event, in which case, the position in the event event is used.
 
 If there's no button at POS, do nothing and return nil, otherwise
 return t.
@@ -483,7 +492,12 @@ pushing a button, use the `button-describe' command."
            (if str-button
                ;; mode-line, header-line, or display string event.
                (button-activate str t)
-             (push-button (posn-point posn) t)))))
+              (if (eq (car pos) 'touchscreen-down)
+                  ;; If touch-screen-track tap returns nil, then the
+                  ;; tap was cancelled.
+                  (when (touch-screen-track-tap pos)
+                    (push-button (posn-point posn) t))
+                (push-button (posn-point posn) t))))))
     ;; POS is just normal position
     (let ((button (button-at (or pos (point)))))
       (when button
diff --git a/lisp/calc/calc.el b/lisp/calc/calc.el
index a1545edba19..652cb8c1a88 100644
--- a/lisp/calc/calc.el
+++ b/lisp/calc/calc.el
@@ -1357,7 +1357,10 @@ Notations:  3.14e6     3.14 * 10^6
   (calc-set-mode-line)
   (calc-check-defines)
   (if calc-buffer-list (setq calc-stack (copy-sequence calc-stack)))
-  (add-to-list 'calc-buffer-list (current-buffer) t))
+  (add-to-list 'calc-buffer-list (current-buffer) t)
+  ;; While Calc buffers are read only, the on screen keyboard should
+  ;; be displayed in order to accept user input.
+  (setq-local touch-screen-display-keyboard t))
 
 (defvar calc-check-defines 'calc-check-defines)  ; Suitable for run-hooks.
 (defun calc-check-defines ()
@@ -1451,49 +1454,54 @@ See `window-dedicated-p' for what that means."
            (calc-grab-region (region-beginning) (region-end) nil)
          (when (= (prefix-numeric-value arg) -2)
            (calc-keypad))))
-    (when (get-buffer-window "*Calc Keypad*")
-      (calc-keypad)
-      (set-buffer (window-buffer)))
-    (if (derived-mode-p 'calc-mode)
-       (calc-quit)
-      (calc-create-buffer)
-      (setq calc-was-keypad-mode nil)
-      (if (or (eq full-display t)
-              (and (null full-display) calc-full-mode))
-          (switch-to-buffer (current-buffer) t)
-        (if (get-buffer-window (current-buffer))
-            (select-window (get-buffer-window (current-buffer)))
-          (if calc-window-hook
-              (run-hooks 'calc-window-hook)
-            (let ((w (get-largest-window)))
-              (if (and pop-up-windows
-                       (> (window-height w)
-                          (+ window-min-height calc-window-height 2)))
-                  (progn
-                    (setq w (split-window w
-                                          (- (window-height w)
-                                             calc-window-height 2)
-                                          nil))
-                    (set-window-buffer w (current-buffer))
-                    (select-window w))
-                (pop-to-buffer (current-buffer)))))))
-      (with-current-buffer (calc-trail-buffer)
-        (and calc-display-trail
-             (calc-trail-display 1 t)))
-      (message (substitute-command-keys
-                (concat "Welcome to the GNU Emacs Calculator!  
\\<calc-mode-map>"
-                        "Press \\[calc-help] or \\[calc-help-prefix] for help, 
\\[calc-quit] to quit")))
-      (run-hooks 'calc-start-hook)
-      (and (windowp full-display)
-           (window-point full-display)
-           (select-window full-display))
-      (and calc-make-windows-dedicated
-           (set-window-dedicated-p nil t))
-      (calc-check-defines)
-      (when (and calc-said-hello interactive)
-        (sit-for 2)
-        (message ""))
-      (setq calc-said-hello t))))
+    ;; If the selected window changes here, Emacs may think that the
+    ;; selected window is read only, and no on screen keyboard should
+    ;; be displayed.  Make sure that any active on screen keyboard is
+    ;; not hidden by accident.
+    (let ((touch-screen-display-keyboard t))
+      (when (get-buffer-window "*Calc Keypad*")
+        (calc-keypad)
+        (set-buffer (window-buffer)))
+      (if (derived-mode-p 'calc-mode)
+         (calc-quit)
+        (calc-create-buffer)
+        (setq calc-was-keypad-mode nil)
+        (if (or (eq full-display t)
+                (and (null full-display) calc-full-mode))
+            (switch-to-buffer (current-buffer) t)
+          (if (get-buffer-window (current-buffer))
+              (select-window (get-buffer-window (current-buffer)))
+            (if calc-window-hook
+                (run-hooks 'calc-window-hook)
+              (let ((w (get-largest-window)))
+                (if (and pop-up-windows
+                         (> (window-height w)
+                            (+ window-min-height calc-window-height 2)))
+                    (progn
+                      (setq w (split-window w
+                                            (- (window-height w)
+                                               calc-window-height 2)
+                                            nil))
+                      (set-window-buffer w (current-buffer))
+                      (select-window w))
+                  (pop-to-buffer (current-buffer)))))))
+        (with-current-buffer (calc-trail-buffer)
+          (and calc-display-trail
+               (calc-trail-display 1 t)))
+        (message (substitute-command-keys
+                  (concat "Welcome to the GNU Emacs Calculator!  
\\<calc-mode-map>"
+                          "Press \\[calc-help] or \\[calc-help-prefix] for 
help, \\[calc-quit] to quit")))
+        (run-hooks 'calc-start-hook)
+        (and (windowp full-display)
+             (window-point full-display)
+             (select-window full-display))
+        (and calc-make-windows-dedicated
+             (set-window-dedicated-p nil t))
+        (calc-check-defines)
+        (when (and calc-said-hello interactive)
+          (sit-for 2)
+          (message ""))
+        (setq calc-said-hello t)))))
 
 ;;;###autoload
 (defun full-calc (&optional interactive)
diff --git a/lisp/calendar/appt.el b/lisp/calendar/appt.el
index 11beee94e64..469b3f4023c 100644
--- a/lisp/calendar/appt.el
+++ b/lisp/calendar/appt.el
@@ -453,8 +453,7 @@ separate appointment."
     ;; It repeatedly reminds you of the date?
     ;; It would make more sense if it was eg the time of the appointment.
     ;; Let's allow it to be a list or not independent of the other elements.
-    (or (listp new-time)
-        (setq new-time (list new-time)))
+    (setq new-time (ensure-list new-time))
     ;; FIXME Link to diary entry?
     (calendar-set-mode-line
      (format " %s. %s" (appt-mode-line min-to-app)
diff --git a/lisp/calendar/cal-move.el b/lisp/calendar/cal-move.el
index 07a813bb705..b494659a8ee 100644
--- a/lisp/calendar/cal-move.el
+++ b/lisp/calendar/cal-move.el
@@ -157,9 +157,9 @@ EVENT is an event like `last-nonmenu-event'."
   (interactive (list (prefix-numeric-value current-prefix-arg)
                      last-nonmenu-event))
   (unless arg (setq arg 1))
-  (save-selected-window
-    ;; Nil if called from menu-bar.
-    (if (setq event (event-start event)) (select-window (posn-window event)))
+  (save-current-buffer
+    (when (event-start event)
+      (set-buffer (calendar-event-buffer event)))
     (calendar-cursor-to-nearest-date)
     (unless (zerop arg)
       (let ((old-date (calendar-cursor-to-date))
diff --git a/lisp/calendar/calendar.el b/lisp/calendar/calendar.el
index faa88e7f52f..02167d84b3e 100644
--- a/lisp/calendar/calendar.el
+++ b/lisp/calendar/calendar.el
@@ -1554,6 +1554,15 @@ first INDENT characters on the line."
       (when (window-live-p (get-buffer-window))
         (set-window-point (get-buffer-window) (point))))))
 
+(defun calendar-event-buffer (event)
+  "Return the Calendar buffer where EVENT happened.
+If EVENT's start falls within a window, use that window's buffer.
+Otherwise, use the selected window of EVENT's frame."
+  (let ((window-or-frame (posn-window (event-start event))))
+    (if (windowp window-or-frame)
+        (window-buffer window-or-frame)
+      (window-buffer (frame-selected-window window-or-frame)))))
+
 (defvar calendar-mode-map
   (let ((map (make-keymap)))
     (suppress-keymap map)
@@ -1916,10 +1925,13 @@ parameter ERROR is non-nil, otherwise just returns nil.
 If EVENT is non-nil, it's an event indicating the buffer position to
 use instead of point."
   (with-current-buffer
-      (if event (window-buffer (posn-window (event-start event)))
+      (if event (calendar-event-buffer event)
         (current-buffer))
     (save-excursion
       (and event (setq event (event-start event))
+           ;; (posn-point event) can be `menu-bar' if this command is
+           ;; invoked from the menu bar.
+           (integerp (posn-point event))
            (goto-char (posn-point event)))
       (let* ((segment (calendar-column-to-segment))
              (month (% (+ displayed-month (1- segment)) 12)))
@@ -2002,10 +2014,8 @@ handle dates in years BC."
 EVENT is an event like `last-nonmenu-event'."
   (interactive (let ((event (list last-nonmenu-event)))
                  (append (calendar-read-date 'noday) event)))
-  (save-selected-window
-    (and event
-         (setq event (event-start event))
-         (select-window (posn-window event)))
+  (with-current-buffer (or (and (not event) (current-buffer))
+                           (calendar-event-buffer event))
     (unless (and (= month displayed-month)
                  (= year displayed-year))
       (let ((old-date (calendar-cursor-to-date))
diff --git a/lisp/calendar/holidays.el b/lisp/calendar/holidays.el
index f30c5653c11..a65beca0e5b 100644
--- a/lisp/calendar/holidays.el
+++ b/lisp/calendar/holidays.el
@@ -359,7 +359,7 @@ use instead of point."
   (interactive (list last-nonmenu-event))
   ;; If called from a menu, with the calendar window not selected.
   (with-current-buffer
-      (if event (window-buffer (posn-window (event-start event)))
+      (if event (calendar-event-buffer event)
         (current-buffer))
     (message "Looking up holidays...")
     (let ((holiday-list (calendar-holiday-list))
@@ -590,7 +590,7 @@ use instead of point."
   (interactive (list last-nonmenu-event))
   ;; If called from a menu, with the calendar window not selected.
   (with-current-buffer
-      (if event (window-buffer (posn-window (event-start event)))
+      (if event (calendar-event-buffer event)
         (current-buffer))
     (setq calendar-mark-holidays-flag t)
     (message "Marking holidays...")
diff --git a/lisp/calendar/lunar.el b/lisp/calendar/lunar.el
index 5b22043102d..a9c3597a3f9 100644
--- a/lisp/calendar/lunar.el
+++ b/lisp/calendar/lunar.el
@@ -226,7 +226,7 @@ use instead of point."
   (interactive (list last-nonmenu-event))
   ;; If called from a menu, with the calendar window not selected.
   (with-current-buffer
-      (if event (window-buffer (posn-window (event-start event)))
+      (if event (calendar-event-buffer event)
         (current-buffer))
     (message "Computing phases of the moon...")
     (let ((m1 displayed-month)
diff --git a/lisp/cedet/mode-local.el b/lisp/cedet/mode-local.el
index e1746e1a541..c1a48bc50c8 100644
--- a/lisp/cedet/mode-local.el
+++ b/lisp/cedet/mode-local.el
@@ -89,7 +89,7 @@ Return nil if MODE has no parent."
   "Run FUNCTION on every file buffer with major mode in MODES.
 MODES can be a symbol or a list of symbols.
 FUNCTION does not have arguments."
-  (or (listp modes) (setq modes (list modes)))
+  (setq modes (ensure-list modes))
   (mode-local-map-file-buffers
    function (lambda ()
               (let ((mm (mode-local-equivalent-mode-p major-mode))
diff --git a/lisp/cedet/semantic/db-ebrowse.el 
b/lisp/cedet/semantic/db-ebrowse.el
index f8ea73cbdde..3e54b9e76cf 100644
--- a/lisp/cedet/semantic/db-ebrowse.el
+++ b/lisp/cedet/semantic/db-ebrowse.el
@@ -158,7 +158,8 @@ is specified by `semanticdb-default-save-directory'."
       ;; Call the EBROWSE command.
       (message "Creating ebrowse file: %s ..." savein)
       (call-process-region (point-min) (point-max)
-                          "ebrowse" nil "*EBROWSE OUTPUT*" nil
+                          ebrowse-program-name
+                           nil "*EBROWSE OUTPUT*" nil
                           (concat "--output-file=" savein)
                           "--very-verbose")
       )
diff --git a/lisp/cedet/semantic/decorate/include.el 
b/lisp/cedet/semantic/decorate/include.el
index c83de66ef0c..96bf8cec3b2 100644
--- a/lisp/cedet/semantic/decorate/include.el
+++ b/lisp/cedet/semantic/decorate/include.el
@@ -48,7 +48,7 @@
 ;;; Includes that are in a happy state!
 ;;
 (defface semantic-decoration-on-includes
-  nil
+  '((t (:inherit default)))
   "Overlay Face used on includes that are not in some other state.
 Used by the decoration style: `semantic-decoration-on-includes'."
   :group 'semantic-faces)
diff --git a/lisp/cedet/semantic/fw.el b/lisp/cedet/semantic/fw.el
index 30704760c1d..184a74c8c2e 100644
--- a/lisp/cedet/semantic/fw.el
+++ b/lisp/cedet/semantic/fw.el
@@ -434,9 +434,8 @@ FILE, NOWARN, RAWFILE, and WILDCARDS are passed into 
`find-file-noselect'."
 ;;       ))
 ;;   "Highlighted Semantic keywords.")
 
-;; (when (fboundp 'font-lock-add-keywords)
-;;   (font-lock-add-keywords 'emacs-lisp-mode
-;;                           semantic-fw-font-lock-keywords))
+;; (font-lock-add-keywords 'emacs-lisp-mode
+;;                         semantic-fw-font-lock-keywords)
 
 
 (provide 'semantic/fw)
diff --git a/lisp/cedet/semantic/grammar.el b/lisp/cedet/semantic/grammar.el
index 2dccd87a710..60c57210b8f 100644
--- a/lisp/cedet/semantic/grammar.el
+++ b/lisp/cedet/semantic/grammar.el
@@ -1156,18 +1156,13 @@ END is the limit of the search."
     ("^\\(\\(\\sw\\|\\s_\\)+\\)[ \n\r\t]*:"
      1 font-lock-function-name-face)
     (semantic--grammar-macros-matcher
-     1 ,(if (boundp 'font-lock-builtin-face)
-            'font-lock-builtin-face
-          'font-lock-preprocessor-face))
+     1 font-lock-builtin-face)
     ("\\$\\(\\sw\\|\\s_\\)*"
      0 font-lock-variable-name-face)
     ("<\\(\\(\\sw\\|\\s_\\)+\\)>"
      1 font-lock-type-face)
     (,semantic-grammar-lex-c-char-re
-     0 ,(if (boundp 'font-lock-constant-face)
-            'font-lock-constant-face
-          'font-lock-string-face)
-     t)
+     0 font-lock-constant-face t)
     ;; Must highlight :keyword here, because ':' is a punctuation in
     ;; grammar mode!
     ("[\r\n\t ]+:\\sw+\\>"
diff --git a/lisp/comint.el b/lisp/comint.el
index 718a972a582..777795bcb46 100644
--- a/lisp/comint.el
+++ b/lisp/comint.el
@@ -723,6 +723,9 @@ Entry to this mode runs the hooks on `comint-mode-hook'."
   (setq-local comint-last-input-start (point-min-marker))
   (setq-local comint-last-input-end (point-min-marker))
   (setq-local comint-last-output-start (make-marker))
+  ;; It is ok to let the input method edit prompt text, but RET must
+  ;; be processed by Emacs.
+  (setq text-conversion-style 'action)
   (make-local-variable 'comint-last-prompt)
   (make-local-variable 'comint-prompt-regexp)        ; Don't set; default
   (make-local-variable 'comint-input-ring-size)      ; ...to global val.
@@ -1437,7 +1440,7 @@ actual side-effect."
                (if dry-run (throw dry-run 'message))
               (goto-char (match-end 0))
               (message "Absolute reference cannot be expanded"))
-             ((looking-at "!-\\([0-9]+\\)\\(:?[0-9^$*-]+\\)?")
+             ((looking-at "!-\\([0-9]+\\):?\\([0-9^$*-]+\\)?")
               ;; Just a number of args from `number' lines backward.
                (if dry-run (throw dry-run 'history))
               (let ((number (1- (string-to-number
@@ -1461,7 +1464,7 @@ actual side-effect."
                              t t)
               (message "History item: previous"))
              ((looking-at
-               "!\\??\\({\\(.+\\)}\\|\\(\\sw+\\)\\)\\(:?[0-9^$*-]+\\)?")
+               "!\\??\\({\\(.+\\)}\\|\\(\\sw+\\)\\):?\\([0-9^$*-]+\\)?")
               ;; Most recent input starting with or containing (possibly
               ;; protected) string, maybe just a number of args.  Phew.
                (if dry-run (throw dry-run 'expand))
diff --git a/lisp/completion.el b/lisp/completion.el
index f119fc6028b..eed6e77da4c 100644
--- a/lisp/completion.el
+++ b/lisp/completion.el
@@ -322,9 +322,11 @@ This can be time consuming."
   :type 'boolean)
 
 (defcustom completion-search-distance 15000
-  "How far to search in the buffer when looking for completions.
-In number of characters.  If nil, search the whole buffer."
-  :type 'integer)
+  "How far in the buffer to search when looking for completions.
+Limit is measured in characters.  If nil, search the whole buffer."
+  :type '(choice
+          (const :tag "No limit" nil)
+          (integer :tag "Limit in characters")))
 
 (defcustom completions-merging-modes '(lisp c)
   "List of modes {`c' or `lisp'} for automatic completions merging.
diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el
index dbef5f47cd6..1021707907d 100644
--- a/lisp/cus-edit.el
+++ b/lisp/cus-edit.el
@@ -973,8 +973,7 @@ it as the third element in the list."
          (let ((prop (get var 'variable-interactive))
                (type (get var 'custom-type))
                (prompt (format prompt-val var)))
-           (unless (listp type)
-             (setq type (list type)))
+            (setq type (ensure-list type))
            (cond (prop
                   ;; Use VAR's `variable-interactive' property
                   ;; as an interactive spec for prompting.
@@ -2209,7 +2208,7 @@ and `face'."
 ;;; The `custom' Widget.
 
 (defface custom-button
-  '((((type x w32 ns haiku pgtk) (class color))        ; Like default mode line
+  '((((type x w32 ns haiku pgtk android) (class color))        ; Like default 
mode line
      :box (:line-width 2 :style released-button)
      :background "lightgrey" :foreground "black"))
   "Face for custom buffer buttons if `custom-raised-buttons' is non-nil."
@@ -2217,7 +2216,7 @@ and `face'."
   :group 'custom-faces)
 
 (defface custom-button-mouse
-  '((((type x w32 ns haiku pgtk) (class color))
+  '((((type x w32 ns haiku pgtk android) (class color))
      :box (:line-width 2 :style released-button)
      :background "grey90" :foreground "black")
     (t
@@ -2242,7 +2241,7 @@ and `face'."
       (if custom-raised-buttons 'custom-button-mouse 'highlight))
 
 (defface custom-button-pressed
-  '((((type x w32 ns haiku pgtk) (class color))
+  '((((type x w32 ns haiku pgtk android) (class color))
      :box (:line-width 2 :style pressed-button)
      :background "lightgrey" :foreground "black")
     (t :inverse-video t))
@@ -2330,6 +2329,7 @@ and `face'."
        (from (marker-position (widget-get widget :from)))
        (to (marker-position (widget-get widget :to))))
     (save-excursion
+      (custom-comment-preserve widget)
       (widget-value-set widget (widget-value widget))
       (custom-redraw-magic widget))
     (when (and (>= pos from) (<= pos to))
@@ -2509,7 +2509,9 @@ If INITIAL-STRING is non-nil, use that rather than 
\"Parent groups:\"."
   (let* ((null-comment (equal "" (widget-value widget))))
     (if (or (widget-get (widget-get widget :parent) :comment-shown)
            (not null-comment))
-       (widget-default-create widget)
+        (progn
+          (widget-default-create widget)
+          (widget-put (widget-get widget :parent) :comment-shown t))
       ;; `widget-default-delete' expects markers in these slots --
       ;; maybe it shouldn't.
       (widget-put widget :from (point-marker))
@@ -2542,6 +2544,14 @@ If INITIAL-STRING is non-nil, use that rather than 
\"Parent groups:\"."
     (and (equal "" val)
         (not (widget-get widget :comment-shown)))))
 
+;; This is useful when we want to redraw a widget, but we want to preserve
+;; edits made by the user in the comment widget.  (See Bug#64649)
+(defun custom-comment-preserve (widget)
+  "Preserve the comment that belongs to WIDGET."
+  (when (widget-get widget :comment-shown)
+    (let ((comment-widget (widget-get widget :comment-widget)))
+      (widget-put comment-widget :value (widget-value comment-widget)))))
+
 ;;; The `custom-variable' Widget.
 
 (defface custom-variable-obsolete
@@ -2821,12 +2831,16 @@ try matching its doc string against 
`custom-guess-doc-alist'."
 
       ;; The comment field
       (unless (eq state 'hidden)
-       (let* ((comment (get symbol 'variable-comment))
-              (comment-widget
-               (widget-create-child-and-convert
-                widget 'custom-comment
-                :parent widget
-                :value (or comment ""))))
+        (let ((comment-widget
+               (widget-create-child-and-convert
+                widget 'custom-comment
+                :parent widget
+                :value (or
+                        (and
+                         (widget-get widget :comment-shown)
+                         (widget-value (widget-get widget :comment-widget)))
+                        (get symbol 'variable-comment)
+                        ""))))
          (widget-put widget :comment-widget comment-widget)
          ;; Don't push it !!! Custom assumes that the first child is the
          ;; value one.
@@ -3533,7 +3547,15 @@ GNUstep or Macintosh OS Cocoa interface.")
                                    (const :format "PGTK "
                                           :sibling-args (:help-echo "\
 Pure-GTK interface.")
-                                          ns)
+                                          pgtk)
+                                    (const :format "Haiku "
+                                          :sibling-args (:help-echo "\
+Haiku interface.")
+                                          haiku)
+                                    (const :format "Android "
+                                          :sibling-args (:help-echo "\
+Android interface.")
+                                          android)
                                    (const :format "DOS "
                                           :sibling-args (:help-echo "\
 Plain MS-DOS.")
@@ -3717,7 +3739,8 @@ WIDGET should be a `custom-face' widget."
         `((t ,(widget-value child)))
        (widget-value child)))))
 
-(defun custom-face-get-current-spec (face)
+(defun custom-face-get-current-spec-unfiltered (face)
+  "Return the current spec for face FACE, without filtering it."
   (let ((spec (or (get face 'customized-face)
                  (get face 'saved-face)
                  (get face 'face-defface-spec)
@@ -3727,8 +3750,12 @@ WIDGET should be a `custom-face' widget."
     ;; If the user has changed this face in some other way,
     ;; edit it as the user has specified it.
     (if (not (face-spec-match-p face spec (selected-frame)))
-       (setq spec `((t ,(face-attr-construct face (selected-frame))))))
-    (custom-pre-filter-face-spec spec)))
+        (setq spec `((t ,(face-attr-construct face)))))
+    spec))
+
+(defun custom-face-get-current-spec (face)
+  "Return the current spec for face FACE, filtering it."
+  (custom-pre-filter-face-spec (custom-face-get-current-spec-unfiltered face)))
 
 (defun custom-toggle-hide-face (visibility-widget &rest _ignore)
   "Toggle the visibility of a `custom-face' parent widget.
@@ -3831,12 +3858,16 @@ the present value is saved to its :shown-value property 
instead."
         widget :visibility-widget 'custom-visibility)
        ;; The comment field
        (unless hiddenp
-         (let* ((comment (get symbol 'face-comment))
-                (comment-widget
-                 (widget-create-child-and-convert
-                  widget 'custom-comment
-                  :parent widget
-                  :value (or comment ""))))
+         (let ((comment-widget
+                 (widget-create-child-and-convert
+                  widget 'custom-comment
+                  :parent widget
+                  :value (or
+                          (and
+                           (widget-get widget :comment-shown)
+                           (widget-value (widget-get widget :comment-widget)))
+                          (get symbol 'face-comment)
+                          ""))))
            (widget-put widget :comment-widget comment-widget)
            (push comment-widget children))))
 
@@ -3848,8 +3879,8 @@ the present value is saved to its :shown-value property 
instead."
        (unless (widget-get widget :custom-form)
          (widget-put widget :custom-form custom-face-default-form))
 
-       (let* ((spec (or (widget-get widget :shown-value)
-                        (custom-face-get-current-spec symbol)))
+       (let* ((shown-value (widget-get widget :shown-value))
+               (spec (or shown-value (custom-face-get-current-spec symbol)))
               (form (widget-get widget :custom-form))
               (indent (widget-get widget :indent))
               face-alist face-entry spec-default spec-match editor)
@@ -3890,7 +3921,7 @@ the present value is saved to its :shown-value property 
instead."
                   widget 'sexp :value spec))))
           (push editor children)
           (widget-put widget :children children)
-         (custom-face-state-set widget))))))
+         (custom-face-state-set widget (not shown-value)))))))
 
 (defun cus--face-link (widget _format)
   (widget-create-child-and-convert
@@ -4010,13 +4041,18 @@ This is one of `set', `saved', `changed', `themed', or 
`rogue'."
        'changed
       state)))
 
-(defun custom-face-state-set (widget)
+(defun custom-face-state-set (widget &optional no-filter)
   "Set the state of WIDGET, a custom-face widget.
 If the user edited the widget, set the state to modified.  If not, the new
-state is one of the return values of `custom-face-state'."
+state is one of the return values of `custom-face-state'.
+Optional argument NO-FILTER means to check against an unfiltered spec."
   (let ((face (widget-value widget)))
     (widget-put widget :custom-state
-                (if (face-spec-match-p face (custom-face-widget-to-spec 
widget))
+                (if (face-spec-match-p
+                     face
+                     (if no-filter
+                         (custom-face-get-current-spec-unfiltered face)
+                       (custom-face-widget-to-spec widget)))
                     (custom-face-state face)
                   'modified))))
 
@@ -5108,8 +5144,7 @@ This function does not save the buffer."
 (defun custom-variable-menu-create (_widget symbol)
   "Ignoring WIDGET, create a menu entry for customization variable SYMBOL."
   (let ((type (get symbol 'custom-type)))
-    (unless (listp type)
-      (setq type (list type)))
+    (setq type (ensure-list type))
     (if (and type (widget-get type :custom-menu))
        (widget-apply type :custom-menu symbol)
       (vector (custom-unlispify-menu-entry symbol)
diff --git a/lisp/cus-face.el b/lisp/cus-face.el
index ec89b4f7ff6..a3a27263a7c 100644
--- a/lisp/cus-face.el
+++ b/lisp/cus-face.el
@@ -158,7 +158,7 @@
                    (or (and (consp real-value) (plist-get real-value :style))
                        'line))
                    (position (and (consp real-value)
-                                  (plist-get real-value :style))))
+                                  (plist-get real-value :position))))
               (list :color color :style style :position position))))
      ;; filter to make customized-value suitable for storing
      ,(lambda (cus-value)
diff --git a/lisp/cus-start.el b/lisp/cus-start.el
index 6ca7d7fcafd..6d83aaf4d14 100644
--- a/lisp/cus-start.el
+++ b/lisp/cus-start.el
@@ -231,6 +231,7 @@ Leaving \"Default\" unchecked is equivalent with specifying 
a default of
             (inverse-video display boolean)
             (visible-bell display boolean)
             (no-redraw-on-reenter display boolean)
+            (mouse-prefer-closest-glyph display boolean)
 
              ;; doc.c
              (text-quoting-style display
diff --git a/lisp/cus-theme.el b/lisp/cus-theme.el
index 5d3f2585976..ccd8ba51c9d 100644
--- a/lisp/cus-theme.el
+++ b/lisp/cus-theme.el
@@ -490,6 +490,29 @@ It includes all faces in list FACES."
     (with-current-buffer standard-output
       (describe-theme-1 theme))))
 
+(defun describe-theme-from-file (theme &optional file short)
+  "Describe THEME from its FILE without loading it.
+
+If FILE is nil try to look in `custom-theme-load-path' for the
+theme's file using the theme's name.
+If SHORT is non-nil, show only the first line of thene's documentation."
+    (let ((file (or file
+                  (locate-file (concat (symbol-name theme) "-theme.el")
+                              (custom-theme--load-path)
+                              '("" "c")))))
+      (with-temp-buffer
+        (insert-file-contents file)
+        (catch 'found
+          (let (sexp)
+            (while (setq sexp (let ((read-circle nil))
+                               (condition-case nil
+                                   (read (current-buffer))
+                                 (end-of-file nil))))
+              (when (eq (car-safe sexp) 'deftheme)
+               (throw 'found (if short
+                                  (car (split-string (nth 2 sexp) "\n"))
+                                (nth 2 sexp))))))))))
+
 (defun describe-theme-1 (theme)
   (prin1 theme)
   (princ " is a custom theme")
@@ -510,16 +533,9 @@ It includes all faces in list FACES."
            (princ "It is loaded but disabled."))
          (setq doc (get theme 'theme-documentation)))
       (princ "It is not loaded.")
-      ;; Attempt to grab the theme documentation
+      ;; Attempt to grab the theme documentation from file.
       (when fn
-       (with-temp-buffer
-         (insert-file-contents fn)
-         (let ((sexp (let ((read-circle nil))
-                       (condition-case nil
-                           (read (current-buffer))
-                         (end-of-file nil)))))
-            (and (eq (car-safe sexp) 'deftheme)
-                (setq doc (nth 2 sexp)))))))
+       (setq doc (describe-theme-from-file theme fn))))
     (princ "\n\nDocumentation:\n")
     (princ (if (stringp doc)
               (substitute-command-keys doc)
diff --git a/lisp/custom.el b/lisp/custom.el
index 083349e3591..5c134ff7f13 100644
--- a/lisp/custom.el
+++ b/lisp/custom.el
@@ -1208,7 +1208,7 @@ The command `customize-create-theme' writes theme files 
into this
 directory.  By default, Emacs searches for custom themes in this
 directory first---see `custom-theme-load-path'."
   :initialize #'custom-initialize-delay
-  :type 'string
+  :type 'directory
   :group 'customize
   :version "22.1")
 
diff --git a/lisp/desktop.el b/lisp/desktop.el
index 59298699914..f096f13ab80 100644
--- a/lisp/desktop.el
+++ b/lisp/desktop.el
@@ -455,7 +455,7 @@ If nil, deletes existing frames.
 If `keep', keeps existing frames and does not reuse them."
   :type '(choice (const :tag "Reuse existing frames" t)
                 (const :tag "Delete existing frames" nil)
-                (const :tag "Keep existing frames" :keep))
+                (const :tag "Keep existing frames" keep))
   :group 'desktop
   :version "24.4")
 
diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el
index a07406e4c0d..28513a2c61a 100644
--- a/lisp/dired-aux.el
+++ b/lisp/dired-aux.el
@@ -480,7 +480,8 @@ List has a form of (file-name full-file-name 
(attribute-list))."
     (if failures
        (dired-log-summary
         (format "%s: error" operation)
-        nil))))
+        nil)))
+  (dired-post-do-command))
 
 ;;;###autoload
 (defun dired-do-chmod (&optional arg)
@@ -531,7 +532,8 @@ has no effect on MS-Windows."
        (if num-modes num-modes
         (file-modes-symbolic-to-number modes (file-modes file 'nofollow)))
        'nofollow))
-    (dired-do-redisplay arg)))
+    (dired-do-redisplay arg))
+  (dired-post-do-command))
 
 ;;;###autoload
 (defun dired-do-chgrp (&optional arg)
@@ -634,7 +636,8 @@ Uses the shell command coming from variables `lpr-command' 
and
                                      lpr-switches))
                              " ")
                   'print arg file-list)))
-    (dired-run-shell-command (dired-shell-stuff-it command file-list nil))))
+    (dired-run-shell-command (dired-shell-stuff-it command file-list nil)))
+  (dired-post-do-command))
 
 (defun dired-mark-read-string (prompt initial op-symbol arg files
                                      &optional default-value collection)
@@ -918,7 +921,8 @@ Also see the `dired-confirm-shell-command' variable."
                                  nil file-list)
             ;; execute the shell command
             (dired-run-shell-command
-              (dired-shell-stuff-it command file-list nil arg)))))))
+              (dired-shell-stuff-it command file-list nil arg))))))
+  (dired-post-do-command))
 
 ;; Might use {,} for bash or csh:
 (defvar dired-mark-prefix ""
@@ -1547,7 +1551,8 @@ and `dired-compress-files-alist'."
                                "Compressed %d files to %s"
                                (length in-files))
                       (length in-files)
-                      (file-name-nondirectory out-file)))))))
+                      (file-name-nondirectory out-file))))))
+  (dired-post-do-command))
 
 ;;;###autoload
 (defun dired-compress-file (file)
@@ -2475,86 +2480,97 @@ Optional arg HOW-TO determines how to treat the target.
 
    For any other return value, TARGET is treated as a directory."
   (or op1 (setq op1 operation))
-  (let* ((fn-list (dired-get-marked-files nil arg nil nil t))
-        (rfn-list (mapcar #'dired-make-relative fn-list))
-        (dired-one-file        ; fluid variable inside dired-create-files
-         (and (consp fn-list) (null (cdr fn-list)) (car fn-list)))
-        (target-dir (dired-dwim-target-directory))
-        (default (and dired-one-file
-                      (not dired-dwim-target) ; Bug#25609
-                      (expand-file-name (file-name-nondirectory (car fn-list))
-                                        target-dir)))
-        (defaults (dired-dwim-target-defaults fn-list target-dir))
-        (target (expand-file-name ; fluid variable inside dired-create-files
-                 (minibuffer-with-setup-hook
-                     (lambda ()
-                        (setq-local minibuffer-default-add-function nil)
-                       (setq minibuffer-default defaults))
-                   (dired-mark-read-file-name
-                     (format "%s %%s %s: "
-                             (if dired-one-file op1 operation)
-                             (if (memq op-symbol '(symlink hardlink))
-                                 ;; Linking operations create links
-                                 ;; from the prompted file name; the
-                                 ;; other operations copy (etc) to the
-                                 ;; prompted file name.
-                                 "from" "to"))
-                    target-dir op-symbol arg rfn-list default))))
-        (into-dir
-          (progn
-            (when
-                (or
-                 (not dired-one-file)
-                 (and dired-create-destination-dirs-on-trailing-dirsep
-                      (directory-name-p target)))
-              (dired-maybe-create-dirs target))
-            (cond ((null how-to)
-                  ;; Allow users to change the letter case of
-                  ;; a directory on a case-insensitive
-                  ;; filesystem.  If we don't test these
-                  ;; conditions up front, file-directory-p
-                  ;; below will return t on a case-insensitive
-                  ;; filesystem, and Emacs will try to move
-                  ;; foo -> foo/foo, which fails.
-                  (if (and (file-name-case-insensitive-p (car fn-list))
-                           (eq op-symbol 'move)
-                           dired-one-file
-                           (string= (downcase
-                                     (expand-file-name (car fn-list)))
-                                    (downcase
-                                     (expand-file-name target)))
-                           (not (string=
-                                 (file-name-nondirectory (car fn-list))
-                                 (file-name-nondirectory target))))
-                      nil
-                    (file-directory-p target)))
-                 ((eq how-to t) nil)
-                 (t (funcall how-to target))))))
-    (if (and (consp into-dir) (functionp (car into-dir)))
-       (apply (car into-dir) operation rfn-list fn-list target (cdr into-dir))
-      (if (not (or dired-one-file into-dir))
-         (error "Marked %s: target must be a directory: %s" operation target))
-      (if (and (not (file-directory-p (car fn-list)))
-               (not (file-directory-p target))
-               (directory-name-p target))
-          (error "%s: Target directory does not exist: %s" operation target))
-      ;; rename-file bombs when moving directories unless we do this:
-      (or into-dir (setq target (directory-file-name target)))
-      (prog1
-          (dired-create-files
-           file-creator operation fn-list
-           (if into-dir                        ; target is a directory
-              ;; This function uses fluid variable target when called
-              ;; inside dired-create-files:
-              (lambda (from)
-                (expand-file-name (file-name-nondirectory from) target))
-            (lambda (_from) target))
-           marker-char)
-        (when (or (eq dired-do-revert-buffer t)
-                  (and (functionp dired-do-revert-buffer)
-                       (funcall dired-do-revert-buffer target)))
-          (dired-fun-in-all-buffers (file-name-directory target) nil
-                                    #'revert-buffer))))))
+  (let ((ret nil))
+    (let* ((fn-list (dired-get-marked-files nil arg nil nil t))
+          (rfn-list (mapcar #'dired-make-relative fn-list))
+          (dired-one-file      ; fluid variable inside dired-create-files
+           (and (consp fn-list) (null (cdr fn-list)) (car fn-list)))
+          (target-dir (dired-dwim-target-directory))
+          (default (and dired-one-file
+                        (not dired-dwim-target) ; Bug#25609
+                        (expand-file-name (file-name-nondirectory
+                                            (car fn-list))
+                                          target-dir)))
+          (defaults (dired-dwim-target-defaults fn-list target-dir))
+          (target (expand-file-name ; fluid variable inside dired-create-files
+                   (minibuffer-with-setup-hook
+                       (lambda ()
+                          (setq-local minibuffer-default-add-function nil)
+                         (setq minibuffer-default defaults))
+                     (dired-mark-read-file-name
+                       (format "%s %%s %s: "
+                               (if dired-one-file op1 operation)
+                               (if (memq op-symbol '(symlink hardlink))
+                                   ;; Linking operations create links
+                                   ;; from the prompted file name; the
+                                   ;; other operations copy (etc) to the
+                                   ;; prompted file name.
+                                   "from" "to"))
+                      target-dir op-symbol arg rfn-list default))))
+          (into-dir
+            (progn
+              (when
+                  (or
+                   (not dired-one-file)
+                   (and dired-create-destination-dirs-on-trailing-dirsep
+                        (directory-name-p target)))
+                (dired-maybe-create-dirs target))
+              (cond ((null how-to)
+                    ;; Allow users to change the letter case of
+                    ;; a directory on a case-insensitive
+                    ;; filesystem.  If we don't test these
+                    ;; conditions up front, file-directory-p
+                    ;; below will return t on a case-insensitive
+                    ;; filesystem, and Emacs will try to move
+                    ;; foo -> foo/foo, which fails.
+                    (if (and (file-name-case-insensitive-p (car fn-list))
+                             (eq op-symbol 'move)
+                             dired-one-file
+                             (string= (downcase
+                                       (expand-file-name (car fn-list)))
+                                      (downcase
+                                       (expand-file-name target)))
+                             (not (string=
+                                   (file-name-nondirectory (car fn-list))
+                                   (file-name-nondirectory target))))
+                        nil
+                      (file-directory-p target)))
+                   ((eq how-to t) nil)
+                   (t (funcall how-to target))))))
+      (setq ret
+            (if (and (consp into-dir) (functionp (car into-dir)))
+               (apply (car into-dir) operation rfn-list fn-list target
+                       (cdr into-dir))
+              (if (not (or dired-one-file into-dir))
+                 (error "Marked %s: target must be a directory: %s"
+                         operation target))
+              (if (and (not (file-directory-p (car fn-list)))
+                       (not (file-directory-p target))
+                       (directory-name-p target))
+                  (error "%s: Target directory does not exist: %s"
+                         operation target))
+              ;; rename-file bombs when moving directories unless we do this:
+              (or into-dir (setq target (directory-file-name target)))
+              (prog1
+                  (dired-create-files
+                   file-creator operation fn-list
+                   (if into-dir                        ; target is a directory
+                      ;; This function uses fluid variable target when called
+                      ;; inside dired-create-files:
+                      (lambda (from)
+                        (expand-file-name (file-name-nondirectory from)
+                                           target))
+                    (lambda (_from) target))
+                   marker-char)
+                (when (or (eq dired-do-revert-buffer t)
+                          (and (functionp dired-do-revert-buffer)
+                               (funcall dired-do-revert-buffer target)))
+                  (dired-fun-in-all-buffers (file-name-directory target) nil
+                                            #'revert-buffer))))))
+    (dired-post-do-command)
+    ;; The return value isn't very well defined but is used by
+    ;; `dired-test-bug30624'.
+    ret))
 
 ;; Read arguments for a marked-files command that wants a file name,
 ;; perhaps popping up the list of marked files.
@@ -2887,7 +2903,8 @@ Also see `dired-do-revert-buffer'."
                   (dired-get-marked-files nil arg))
     (user-error "Can't rename \".\" or \"..\" files"))
   (dired-do-create-files 'move #'dired-rename-file
-                        "Move" arg dired-keep-marker-rename "Rename"))
+                        "Move" arg dired-keep-marker-rename "Rename")
+  (dired-post-do-command))
 
 
 ;;; Operate on files matched by regexp
@@ -3579,14 +3596,18 @@ It's intended to override the default search function."
   "Search for a string through all marked files using Isearch."
   (interactive)
   (multi-isearch-files
-   (dired-get-marked-files nil nil #'dired-nondirectory-p nil t)))
+   (prog1 (dired-get-marked-files nil nil
+                                  #'dired-nondirectory-p nil t)
+     (dired-post-do-command))))
 
 ;;;###autoload
 (defun dired-do-isearch-regexp ()
   "Search for a regexp through all marked files using Isearch."
   (interactive)
-  (multi-isearch-files-regexp
-   (dired-get-marked-files nil nil 'dired-nondirectory-p nil t)))
+  (prog1 (multi-isearch-files-regexp
+          (dired-get-marked-files nil nil
+                                  'dired-nondirectory-p nil t))
+    (dired-post-do-command)))
 
 (declare-function fileloop-continue "fileloop" ())
 
@@ -3603,6 +3624,7 @@ To continue searching for next match, use command 
\\[fileloop-continue]."
    regexp
    (dired-get-marked-files nil nil #'dired-nondirectory-p)
    'default)
+  (dired-post-do-command)
   (fileloop-continue))
 
 ;;;###autoload
@@ -3626,6 +3648,7 @@ resume the query replace with the command 
\\[fileloop-continue]."
       (if (and buffer (with-current-buffer buffer
                        buffer-read-only))
          (error "File `%s' is visited read-only" file))))
+  (dired-post-do-command)
   (fileloop-initialize-replace
    from to (dired-get-marked-files nil nil #'dired-nondirectory-p)
    (if (equal from (downcase from)) nil 'default)
@@ -3675,6 +3698,7 @@ REGEXP should use constructs supported by your local 
`grep' command."
                 (user-error "No matches for: %s" regexp))
               (message "Searching...done")
               xrefs))))
+    (dired-post-do-command)
     (xref-show-xrefs fetcher nil)))
 
 ;;;###autoload
@@ -3767,6 +3791,7 @@ case, the VERBOSE argument is ignored."
                           (file-name-as-directory file)
                         file))
                     marked-files))))
+    (dired-post-do-command)
     (if mark-files
         (let ((transient-hook (make-symbol "vc-dir-mark-files")))
           (fset transient-hook
diff --git a/lisp/dired-x.el b/lisp/dired-x.el
index 5780f1353ad..398f55f2a24 100644
--- a/lisp/dired-x.el
+++ b/lisp/dired-x.el
@@ -300,8 +300,7 @@ Interactively, ask for EXTENSION.
 Prefixed with one \\[universal-argument], unmark files instead.
 Prefixed with two \\[universal-argument]'s, prompt for MARKER-CHAR and mark 
files with it."
   (interactive (dired--mark-suffix-interactive-spec))
-  (unless (listp extension)
-    (setq extension (list extension)))
+  (setq extension (ensure-list extension))
   (dired-mark-files-regexp
    (concat ".";; don't match names with nothing but an extension
            "\\("
@@ -325,8 +324,7 @@ Interactively, ask for SUFFIX.
 Prefixed with one \\[universal-argument], unmark files instead.
 Prefixed with two \\[universal-argument]'s, prompt for MARKER-CHAR and mark 
files with it."
   (interactive (dired--mark-suffix-interactive-spec))
-  (unless (listp suffix)
-    (setq suffix (list suffix)))
+  (setq suffix (ensure-list suffix))
   (dired-mark-files-regexp
    (concat ".";; don't match names with nothing but an extension
            "\\("
diff --git a/lisp/dired.el b/lisp/dired.el
index 90342069154..e96b85a173a 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -218,14 +218,19 @@ If t, they are marked if and as the files linked to were 
marked.
 If a character, new links are unconditionally marked with that character.")
 
 (defcustom dired-free-space 'first
-  "Whether and how to display the amount of free disk space in Dired buffers.
+  "Whether and how to display the disk space usage info in Dired buffers.
 If nil, don't display.
-If `separate', display on a separate line (along with used count).
-If `first', display only the free disk space on the first line,
-following the directory name."
-  :type '(choice (const :tag "On a separate line" separate)
-                 (const :tag "On the first line, after directory name" first)
-                 (const :tag "Don't display" nil))
+If `separate', display on a separate line, and include both the used
+and the free disk space.
+If `first', the default, display only the free disk space on the first
+line, following the directory name."
+  :type '(choice (const
+                  :tag
+                  "On separate line, display both used and free space" 
separate)
+                 (const
+                  :tag
+                  "On first line, after directory name, display only free 
space" first)
+                 (const :tag "Don't display disk space usage" nil))
   :version "29.1"
   :group 'dired)
 
@@ -289,7 +294,7 @@ then this will always be equivalent to `move'."
                (revert-buffer nil t)))))
   :type '(choice (const :tag "Don't allow dragging" nil)
                  (const :tag "Copy file to new location" t)
-                 (const :tag "Move file to new location" t)
+                 (const :tag "Move file to new location" move)
                  (const :tag "Create symbolic link to file" link))
   :group 'dired
   :version "29.1")
@@ -346,7 +351,7 @@ with the buffer narrowed to the listing."
   :type 'boolean)
 
 (defcustom dired-initial-position-hook nil
-  "This hook is used to position the point.
+  "Hook used to position point in a new Dired listing display.
 It is run by the function `dired-initial-position'."
   :group 'dired
   :type 'hook
@@ -577,9 +582,6 @@ element, for the listed directory.")
   "Keeps track of which switches to use for inserted subdirectories.
 This is an alist of the form (SUBDIR . SWITCHES).")
 
-(defvaralias 'dired-move-to-filename-regexp
-  'directory-listing-before-filename-regexp)
-
 (defvar dired-subdir-regexp "^. \\(.+\\)\\(:\\)\n"
   "Regexp matching a maybe hidden subdirectory line in `ls -lR' output.
 Subexpression 1 is the subdirectory proper, no trailing colon.
@@ -1773,7 +1775,10 @@ see `dired-use-ls-dired' for more details.")
          ((eq dired-free-space 'separate)
          (end-of-line)
          (insert " available " available)
-          (forward-line 1)
+          ;; The separate free-space line is considered part of the
+          ;; directory content, for the purposes of
+          ;; 'dired-hide-details-mode'.
+          (beginning-of-line)
           (point))
          ((eq dired-free-space 'first)
           (goto-char beg)
@@ -1872,6 +1877,9 @@ other marked file as well.  Otherwise, unmark all files."
                                      keymap)
   "Keymap applied to file names when `dired-mouse-drag-files' is enabled.")
 
+(defvar dired-click-to-select-mode)
+(defvar dired-click-to-select-map)
+
 (defun dired-insert-set-properties (beg end)
   "Add various text properties to the lines in the region, from BEG to END."
   (save-excursion
@@ -1893,30 +1901,30 @@ other marked file as well.  Otherwise, unmark all 
files."
                 (when (member (cl-incf i) dired-hide-details-preserved-columns)
                   (put-text-property opoint (point) 'invisible nil))
                 (setq opoint (point)))))
-          (when (and dired-mouse-drag-files (fboundp 'x-begin-drag))
-            (put-text-property (point)
-                              (save-excursion
-                                (dired-move-to-end-of-filename)
-                                 (backward-char)
-                                (point))
-                               'keymap
-                               dired-mouse-drag-files-map))
-         (add-text-properties
-          (point)
-          (progn
-            (dired-move-to-end-of-filename)
-            (point))
-          `(mouse-face
-            highlight
-            dired-filename t
-            help-echo ,(if (and dired-mouse-drag-files
-                                 (fboundp 'x-begin-drag))
-                            "down-mouse-1: drag this file to another program
+          (let ((beg (point)) (end (save-excursion
+                                    (dired-move-to-end-of-filename)
+                                    (1- (point)))))
+            (if dired-click-to-select-mode
+                (put-text-property beg end 'keymap
+                                   dired-click-to-select-map)
+              (when (and dired-mouse-drag-files (fboundp 'x-begin-drag))
+                (put-text-property beg end 'keymap
+                                   dired-mouse-drag-files-map)))
+           (add-text-properties
+            beg (1+ end)
+            `(mouse-face
+              highlight
+              dired-filename t
+              help-echo ,(if dired-click-to-select-mode
+                              "mouse-2: mark or unmark this file"
+                            (if (and dired-mouse-drag-files
+                                     (fboundp 'x-begin-drag))
+                                "down-mouse-1: drag this file to another 
program
 mouse-2: visit this file in other window"
-                          "mouse-2: visit this file in other window")))
-         (when (< (+ (point) 4) (line-end-position))
-           (put-text-property (+ (point) 4) (line-end-position)
-                              'invisible 'dired-hide-details-link))))
+                              "mouse-2: visit this file in other window"))))
+           (when (< (+ end 5) (line-end-position))
+             (put-text-property (+ end 5) (line-end-position)
+                                'invisible 'dired-hide-details-link)))))
       (forward-line 1))))
 
 (defun dired--make-directory-clickable ()
@@ -2287,7 +2295,9 @@ Do so according to the former subdir alist 
OLD-SUBDIR-ALIST."
   ": d"     #'epa-dired-do-decrypt
   ": v"     #'epa-dired-do-verify
   ": s"     #'epa-dired-do-sign
-  ": e"     #'epa-dired-do-encrypt)
+  ": e"     #'epa-dired-do-encrypt
+  ;; Click-to-select.
+  "<touchscreen-hold>" #'dired-enable-click-to-select-mode)
 
 (put 'dired-find-file :advertised-binding (kbd "RET"))
 
@@ -3552,9 +3562,9 @@ as returned by `dired-get-filename'.  LIMIT is the search 
limit."
 
 ;; FIXME document whatever dired-x is doing.
 (defun dired-initial-position (dirname)
-  "Where point should go in a new listing of DIRNAME.
-Point is assumed to be at the beginning of new subdir line.
-It runs the hook `dired-initial-position-hook'."
+  "Return position of point in a new listing of DIRNAME.
+Point is assumed to be at the beginning of a new subdir line.
+Runs the hook `dired-initial-position-hook'."
   (end-of-line)
   (and (featurep 'dired-x) dired-find-subdir
        (dired-goto-subdir dirname))
@@ -3700,6 +3710,11 @@ non-empty directories is allowed."
       (or nomessage
          (message "(No deletions requested)")))))
 
+(defun dired-post-do-command ()
+  "Disable `dired-click-to-select-mode' after an operation."
+  (when dired-click-to-select-mode
+    (dired-click-to-select-mode -1)))
+
 (defun dired-do-delete (&optional arg)
   "Delete all marked (or next ARG) files.
 `dired-recursive-deletes' controls whether deletion of
@@ -3717,7 +3732,8 @@ non-empty directories is allowed."
                                     m))
                             arg))
      arg t)
-    (dolist (m markers) (set-marker m nil))))
+    (dolist (m markers) (set-marker m nil)))
+  (dired-post-do-command))
 
 (defvar dired-deletion-confirmer 'yes-or-no-p) ; or y-or-n-p?
 
@@ -4938,6 +4954,100 @@ Interactively with prefix argument, read FILE-NAME."
   (interactive nil dired-mode)
   (eww-open-file (dired-get-file-for-visit)))
 
+
+;;; Click-To-Select mode
+
+(defvar dired-click-to-select-map (make-sparse-keymap)
+  "Keymap placed on files under `dired-click-to-select' mode.")
+
+(define-key dired-click-to-select-map [mouse-2]
+            #'dired-mark-for-click)
+
+(defun dired-mark-for-click (event)
+  "Mark or unmark the file underneath the mouse click at EVENT.
+See `dired-click-to-select-mode' for more details."
+  (interactive "e")
+  (let ((posn (event-start event))
+        (inhibit-read-only t))
+    (with-selected-window (posn-window posn)
+      (goto-char (posn-point posn))
+      (save-excursion
+        (dired-repeat-over-lines
+         1 (lambda ()
+             (let ((char (char-after)))
+               (when (or (not (looking-at-p dired-re-dot))
+                         (not (equal dired-marker-char dired-del-marker)))
+                 (delete-char 1)
+                 (insert (if (eq char dired-marker-char)
+                             ;; Insert a space to unmark the file if
+                             ;; it's already marked.
+                             ?\s
+                           ;; Otherwise mark the file.
+                           dired-marker-char))))))))))
+
+(defun dired-enable-click-to-select-mode (event)
+  "Enable `dired-click-to-select-mode' and mark the file under EVENT.
+If there is no file under EVENT, call `touch-screen-hold' with
+EVENT instead."
+  (interactive "e")
+  (let* ((posn (event-start event))
+         (window (posn-window posn))
+         (point (posn-point posn)))
+    (if (and window point
+             (get-text-property point 'dired-filename
+                                (window-buffer window)))
+        (progn (beep)
+               (touch-screen-inhibit-drag)
+               (with-selected-window window
+                 (goto-char point)
+                 (save-excursion (dired-mark 1))
+                 (dired-click-to-select-mode 1)))
+      (touch-screen-hold event))))
+
+(define-minor-mode dired-click-to-select-mode
+  "Toggle click-to-select inside this Dired buffer.
+When this minor mode is enabled, using `mouse-2' on a file name
+within a Dired buffer will toggle its mark instead of going to it
+within another window.
+
+Disabling this minor mode will unmark all files within the Dired
+buffer.
+
+`dired-click-to-select-mode' is automatically disabled after any
+Dired operation (command whose name starts with `dired-do')
+completes."
+  :group 'dired
+  :lighter " Click-To-Select"
+  (unless (derived-mode-p 'dired-mode 'wdired-mode)
+    (error "Not a Dired buffer"))
+  (if dired-click-to-select-mode
+      (setq-local tool-bar-map
+                  `(keymap (exit-click-to-select menu-item
+                            "Exit Click To Select Mode"
+                            dired-click-to-select-mode
+                            :help "Exit `dired-click-to-select-mode'."
+                            :image ,(tool-bar--image-expression "close")
+                            :enable t)))
+    ;; Reset the default tool bar.
+    (kill-local-variable 'tool-bar-map)
+    (dired-unmark-all-marks))
+  ;; Repropertize this Dired buffer.
+  (let ((inhibit-read-only t))
+    (remove-text-properties (point-min) (point-max)
+                            '(invisible nil
+                              keymap nil
+                              dired-filename nil
+                              help-echo nil
+                              mouse-face nil))
+    (when dired-make-directory-clickable
+      (dired--make-directory-clickable))
+    (dired-insert-set-properties (point-min) (point-max)))
+  ;; Redisplay the tool bar.
+  (force-mode-line-update))
+
+(define-obsolete-variable-alias 'dired-move-to-filename-regexp
+  'directory-listing-before-filename-regexp "30.1")
+
 (provide 'dired)
 
 (run-hooks 'dired-load-hook)           ; for your customizations
diff --git a/lisp/doc-view.el b/lisp/doc-view.el
index b14655fb274..e25e63a97ee 100644
--- a/lisp/doc-view.el
+++ b/lisp/doc-view.el
@@ -147,6 +147,8 @@
 (require 'filenotify)
 (eval-when-compile (require 'subr-x))
 
+(autoload 'imenu-unavailable-error "imenu")
+
 ;;;; Customization Options
 
 (defgroup doc-view nil
@@ -174,7 +176,7 @@ are available (see Info node `(emacs)Document View')."
      ;; non-MikTeX apps.  Was available under:
      ;; 
http://blog.miktex.org/post/2005/04/07/Starting-mgsexe-at-the-DOS-Prompt.aspx
      ((and (executable-find "mgs")
-           (= 0 (shell-command "mgs -q -dNODISPLAY -c quit")))
+           (eql 0 (shell-command "mgs -q -dNODISPLAY -c quit")))
       "mgs")))
    (t "gs"))
   "Program to convert PS and PDF files to PNG."
@@ -575,8 +577,8 @@ Typically \"page-%s.png\".")
         ;; file.  (TODO: We'd like to have something like that also
         ;; for other types, at least PS, but I don't know a good way
         ;; to test if a PS file is complete.)
-        (if (= 0 (call-process "pdfinfo" nil nil nil
-                               doc-view--buffer-file-name))
+        (if (eql 0 (call-process "pdfinfo" nil nil nil
+                                 doc-view--buffer-file-name))
             (revert)
           (when (called-interactively-p 'interactive)
             (message "Can't revert right now because the file is corrupted.")))
@@ -643,7 +645,7 @@ Typically \"page-%s.png\".")
       :help                     "Reset the current slice"
       :enabled                  (image-mode-window-get 'slice)])
     "---"
-    ["New Search"               (doc-view-search t)
+    ["New Search"               doc-view-new-search
      :help                      "Initiate a new search"]
     ["Search Forward"           doc-view-search
      :help                      "Jump to the next match or initiate a new 
search"]
@@ -666,6 +668,45 @@ Typically \"page-%s.png\".")
       :style radio :selected    (eq major-mode 'doc-view-mode)])
     ["Exit DocView Mode" doc-view-minor-mode]))
 
+(defvar doc-view-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    ;; Most of these items are the same as in the default tool bar
+    ;; map, but with extraneous items removed, and with extra search
+    ;; and navigation items.
+    (tool-bar-local-item-from-menu 'find-file "new" map
+                                   nil :label "New File"
+                                  :vert-only t)
+    (tool-bar-local-item-from-menu 'menu-find-file-existing "open" map
+                                   nil :label "Open" :vert-only t)
+    (tool-bar-local-item-from-menu 'dired "diropen" map nil :vert-only t)
+    (tool-bar-local-item-from-menu 'kill-this-buffer "close" map nil
+                                   :vert-only t)
+    (define-key-after map [separator-1] menu-bar-separator)
+    (tool-bar-local-item-from-menu 'doc-view-new-search "search"
+                                  map doc-view-mode-map :vert-only t
+                                   :help "Start a new search query.")
+    (tool-bar-local-item-from-menu 'doc-view-search-backward "left-arrow"
+                                  map doc-view-mode-map
+                                   :vert-only t
+                                   :enable 'doc-view--current-search-matches
+                                   :help "Move to the last search result.")
+    (tool-bar-local-item-from-menu 'doc-view-search "right-arrow"
+                                  map doc-view-mode-map :vert-only t
+                                   :enable 'doc-view--current-search-matches
+                                   :help "Move to the next search result.")
+    (define-key-after map [separator-2] menu-bar-separator)
+    (tool-bar-local-item-from-menu 'doc-view-previous-page "last-page"
+                                   map doc-view-mode-map :vert-only t
+                                   :enable '(> (doc-view-current-page) 1)
+                                   :help "Move to the next page.")
+    (tool-bar-local-item-from-menu 'doc-view-next-page "next-page"
+                                   map doc-view-mode-map :vert-only t
+                                   :enable '(< (doc-view-current-page)
+                                               (doc-view-last-page-number))
+                                   :help "Move to the last page.")
+    map)
+  "Like the default `tool-bar-map', but with additions for DocView.")
+
 ;;;; Navigation Commands
 
 (defun doc-view-last-page-number ()
@@ -1863,7 +1904,16 @@ If BACKWARD is non-nil, jump to the previous match."
        ;; We must convert to TXT first!
        (if doc-view--current-converter-processes
            (message "DocView: please wait till conversion finished.")
-         (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
+         (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))
+    ;; Update the tool bar items.
+    (force-mode-line-update)))
+
+(defun doc-view-new-search ()
+  "Initiate a new search query.
+Prompt for a string, then search for its appearances within
+the document text."
+  (interactive)
+  (doc-view-search t nil))
 
 (defun doc-view-search-next-match (arg)
   "Go to the ARGth next matching page."
@@ -1910,9 +1960,10 @@ structure is extracted by `doc-view--imenu-subtree'."
   (let ((fn (or file-name (buffer-file-name))))
     (when fn
       (let ((outline nil)
-            (fn (shell-quote-argument (expand-file-name fn))))
+            (fn (expand-file-name fn)))
         (with-temp-buffer
-          (insert (shell-command-to-string (format "mutool show %s outline" 
fn)))
+          (unless (eql 0 (call-process "mutool" nil (current-buffer) nil 
"show" fn "outline"))
+            (imenu-unavailable-error "Unable to create imenu index using 
`mutool'"))
           (goto-char (point-min))
           (while (re-search-forward doc-view--outline-rx nil t)
             (push `((level . ,(length (match-string 1)))
@@ -1961,7 +2012,7 @@ GOTO-PAGE-FN other than `doc-view-goto-page'."
 
 (defun doc-view-imenu-setup ()
   "Set up local state in the current buffer for imenu, if needed."
-  (when (and doc-view-imenu-enabled (executable-find "mutool"))
+  (when doc-view-imenu-enabled
     (setq-local imenu-create-index-function #'doc-view-imenu-index
                 imenu-submenus-on-top nil
                 imenu-sort-function nil
@@ -2236,8 +2287,13 @@ toggle between displaying the document or editing it as 
text.
     (setq mode-name "DocView"
          buffer-read-only t
          major-mode 'doc-view-mode)
-    (doc-view-imenu-setup)
+    (condition-case imenu-error
+        (doc-view-imenu-setup)
+      (imenu-unavailable (message "imenu support unavailable: %s"
+                                  (cadr imenu-error))))
     (doc-view-initiate-display)
+    ;; Replace the tool bar map with `doc-view-tool-bar-map'.
+    (setq-local tool-bar-map doc-view-tool-bar-map)
     ;; Switch off view-mode explicitly, because doc-view-mode is the
     ;; canonical view mode for PDF/PS/DVI files.  This could be
     ;; switched on automatically depending on the value of
diff --git a/lisp/elec-pair.el b/lisp/elec-pair.el
index 416c95e7a34..e101cbc9e6f 100644
--- a/lisp/elec-pair.el
+++ b/lisp/elec-pair.el
@@ -162,6 +162,20 @@ Before attempting a skip, if 
`electric-pair-skip-whitespace' is
 non-nil, this function is called.  It move point to a new buffer
 position, presumably skipping only whitespace in between.")
 
+(defun electric-pair-analyze-conversion (string)
+  "Notice that STRING has been deleted by an input method.
+If the last character of STRING is an electric pair character,
+and the character after point is too, then delete that other
+character."
+  (let* ((prev (aref string (1- (length string))))
+         (next (char-after))
+         (syntax-info (electric-pair-syntax-info prev))
+         (syntax (car syntax-info))
+         (pair (cadr syntax-info)))
+    (when (and next pair (memq syntax '(?\( ?\" ?\$))
+               (eq pair next))
+      (delete-char 1))))
+
 (defun electric-pair--skip-whitespace ()
   "Skip whitespace forward, not crossing comment or string boundaries."
   (let ((saved (point))
diff --git a/lisp/emacs-lisp/advice.el b/lisp/emacs-lisp/advice.el
index 56f0ae2212c..3265809f592 100644
--- a/lisp/emacs-lisp/advice.el
+++ b/lisp/emacs-lisp/advice.el
@@ -3131,6 +3131,7 @@ usage: (defadvice FUNCTION (CLASS NAME [POSITION] 
[ARGLIST] FLAG...)
           [DOCSTRING] [INTERACTIVE-FORM]
           BODY...)"
   (declare (doc-string 3) (indent 2)
+           (obsolete "use advice-add or define-advice" "30.1")
            (debug (&define name  ;; thing being advised.
                            (name ;; class is [&or "before" "around" "after"
                                  ;;               "activation" "deactivation"]
diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el
index 74e4a83c4a5..3bd77da9015 100644
--- a/lisp/emacs-lisp/byte-opt.el
+++ b/lisp/emacs-lisp/byte-opt.el
@@ -794,6 +794,17 @@ for speeding up processing.")
                            make-marker copy-marker point-marker mark-marker
                            set-marker
                            kbd key-description
+                           skip-chars-forward skip-chars-backward
+                           skip-syntax-forward skip-syntax-backward
+                           current-column current-indentation
+                           char-syntax syntax-class-to-char
+                           parse-partial-sexp goto-char forward-line
+                           next-window previous-window minibuffer-window
+                           selected-frame selected-window
+                           standard-case-table standard-syntax-table
+                           syntax-table
+                           frame-first-window frame-root-window
+                           frame-selected-window
                            always))
                   t)
                  ((eq head 'if)
@@ -981,7 +992,7 @@ for speeding up processing.")
   (let ((nargs (length (cdr form))))
     (cond
      ((= nargs 1)
-      `(progn (cadr form) t))
+      `(progn ,(cadr form) t))
      ((>= nargs 3)
       ;; At least 3 arguments: transform to N-1 binary comparisons,
       ;; since those have their own byte-ops which are particularly
@@ -1049,23 +1060,26 @@ See Info node `(elisp) Integer Basics'."
   (and (integerp o) (<= -536870912 o 536870911)))
 
 (defun byte-optimize-equal (form)
-  ;; Replace `equal' or `eql' with `eq' if at least one arg is a
-  ;; symbol or fixnum.
-  (byte-optimize-binary-predicate
-   (if (= (length (cdr form)) 2)
-       (if (or (byte-optimize--constant-symbol-p (nth 1 form))
-               (byte-optimize--constant-symbol-p (nth 2 form))
-               (byte-optimize--fixnump (nth 1 form))
-               (byte-optimize--fixnump (nth 2 form)))
-           (cons 'eq (cdr form))
-         form)
-     ;; Arity errors reported elsewhere.
-     form)))
+  (cond ((/= (length (cdr form)) 2) form)  ; Arity errors reported elsewhere.
+        ;; Anything is identical to itself.
+        ((and (eq (nth 1 form) (nth 2 form)) (symbolp (nth 1 form))) t)
+        ;; Replace `equal' or `eql' with `eq' if at least one arg is a
+        ;; symbol or fixnum.
+        ((or (byte-optimize--constant-symbol-p (nth 1 form))
+             (byte-optimize--constant-symbol-p (nth 2 form))
+             (byte-optimize--fixnump (nth 1 form))
+             (byte-optimize--fixnump (nth 2 form)))
+         (byte-optimize-binary-predicate (cons 'eq (cdr form))))
+        (t (byte-optimize-binary-predicate form))))
 
 (defun byte-optimize-eq (form)
-  (pcase (cdr form)
-    ((or `(,x nil) `(nil ,x)) `(not ,x))
-    (_ (byte-optimize-binary-predicate form))))
+  (cond ((/= (length (cdr form)) 2) form)  ; arity error
+        ;; Anything is identical to itself.
+        ((and (eq (nth 1 form) (nth 2 form)) (symbolp (nth 1 form))) t)
+        ;; Strength-reduce comparison with `nil'.
+        ((null (nth 1 form)) `(not ,(nth 2 form)))
+        ((null (nth 2 form)) `(not ,(nth 1 form)))
+        (t (byte-optimize-binary-predicate form))))
 
 (defun byte-optimize-member (form)
   (cond
@@ -1738,6 +1752,7 @@ See Info node `(elisp) Integer Basics'."
          base64-decode-string base64-encode-string base64url-encode-string
          buffer-hash buffer-line-statistics
          compare-strings concat copy-alist copy-hash-table copy-sequence elt
+         equal equal-including-properties
          featurep get
          gethash hash-table-count hash-table-rehash-size
          hash-table-rehash-threshold hash-table-size hash-table-test
@@ -1871,7 +1886,7 @@ See Info node `(elisp) Integer Basics'."
          ;; fileio.c
          default-file-modes
          ;; fns.c
-         eql equal equal-including-properties
+         eql
          hash-table-p identity proper-list-p safe-length
          secure-hash-algorithms
          ;; frame.c
@@ -1932,7 +1947,7 @@ See Info node `(elisp) Integer Basics'."
 (let ((pure-fns
        '(
          ;; character.c
-         characterp
+         characterp max-char
          ;; data.c
          % * + - / /= 1+ 1- < <= = > >= aref arrayp ash atom bare-symbol
          bool-vector-count-consecutive bool-vector-count-population
@@ -2138,7 +2153,7 @@ See Info node `(elisp) Integer Basics'."
    '(byte-constant byte-dup byte-stack-ref byte-stack-set byte-discard
      byte-discardN byte-discardN-preserve-tos
      byte-symbolp byte-consp byte-stringp byte-listp byte-numberp byte-integerp
-     byte-eq byte-not
+     byte-not
      byte-cons byte-list1 byte-list2 byte-list3 byte-list4 byte-listN
      byte-interactive-p)
    ;; How about other side-effect-free-ops?  Is it safe to move an
@@ -2146,11 +2161,16 @@ See Info node `(elisp) Integer Basics'."
    ;; No, it is not, because the unwind-protect forms can alter
    ;; the inside of the object to which nth would apply.
    ;; For the same reason, byte-equal was deleted from this list.
+   ;;
+   ;; In particular, `byte-eq' isn't here despite `eq' being nominally
+   ;; pure because it is currently affected by `symbols-with-pos-enabled'
+   ;; and so cannot be sunk past an unwind op that might end a binding of
+   ;; that variable.  Yes, this is unsatisfactory.
    "Byte-codes that can be moved past an unbind.")
 
 (defconst byte-compile-side-effect-and-error-free-ops
   '(byte-constant byte-dup byte-symbolp byte-consp byte-stringp byte-listp
-    byte-integerp byte-numberp byte-eq byte-equal byte-not byte-car-safe
+    byte-integerp byte-numberp byte-eq byte-not byte-car-safe
     byte-cdr-safe byte-cons byte-list1 byte-list2 byte-list3 byte-list4
     byte-listN byte-point byte-point-max
     byte-point-min byte-following-char byte-preceding-char
@@ -2161,10 +2181,11 @@ See Info node `(elisp) Integer Basics'."
   (append
    '(byte-varref byte-nth byte-memq byte-car byte-cdr byte-length byte-aref
      byte-symbol-value byte-get byte-concat2 byte-concat3 byte-sub1 byte-add1
-     byte-eqlsign byte-gtr byte-lss byte-leq byte-geq byte-diff byte-negate
-     byte-plus byte-max byte-min byte-mult byte-char-after byte-char-syntax
-     byte-buffer-substring byte-string= byte-string< byte-nthcdr byte-elt
-     byte-member byte-assq byte-quo byte-rem byte-substring)
+     byte-eqlsign byte-equal byte-gtr byte-lss byte-leq byte-geq byte-diff
+     byte-negate byte-plus byte-max byte-min byte-mult byte-char-after
+     byte-char-syntax byte-buffer-substring byte-string= byte-string<
+     byte-nthcdr byte-elt byte-member byte-assq byte-quo byte-rem
+     byte-substring)
    byte-compile-side-effect-and-error-free-ops))
 
 ;; This crock is because of the way DEFVAR_BOOL variables work.
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index e3210438a1b..30c2480018a 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -489,8 +489,7 @@ Return the compile-time value of FORM."
   ;; 3.2.3.1, "Processing of Top Level Forms".  The semantics are very
   ;; subtle: see test/lisp/emacs-lisp/bytecomp-tests.el for interesting
   ;; cases.
-  (let ((print-symbols-bare t))         ; Possibly redundant binding.
-    (setf form (macroexp-macroexpand form byte-compile-macro-environment)))
+  (setf form (macroexp-macroexpand form byte-compile-macro-environment))
   (if (eq (car-safe form) 'progn)
       (cons (car form)
             (mapcar (lambda (subform)
@@ -568,11 +567,10 @@ Only conses are traversed and duplicated, not arrays or 
any other structure."
                               ;; Don't compile here, since we don't know
                               ;; whether to compile as byte-compile-form
                               ;; or byte-compile-file-form.
-                              (let* ((print-symbols-bare t) ; Possibly 
redundant binding.
-                                     (expanded
-                                      (macroexpand--all-toplevel
-                                       form
-                                       macroexpand-all-environment)))
+                              (let ((expanded
+                                     (macroexpand--all-toplevel
+                                      form
+                                      macroexpand-all-environment)))
                                 (eval (byte-run-strip-symbol-positions
                                        (bytecomp--copy-tree expanded))
                                       lexical-binding)
@@ -1883,8 +1881,6 @@ It is too wide if it has any lines longer than the 
largest of
          (byte-compile-dynamic byte-compile-dynamic)
          (byte-compile-dynamic-docstrings
           byte-compile-dynamic-docstrings)
-         ;;            (byte-compile-generate-emacs19-bytecodes
-         ;;             byte-compile-generate-emacs19-bytecodes)
          (byte-compile-warnings byte-compile-warnings)
          ;; Indicate that we're not currently loading some file.
          ;; This is used in `macroexp-file-name' to make sure that
@@ -1977,6 +1973,10 @@ also be compiled."
       (emacs-lisp-compilation-mode))
     (let ((directories (list default-directory))
          (default-directory default-directory)
+          (ignore-files-regexp
+           (if byte-compile-ignore-files
+               (mapconcat #'identity byte-compile-ignore-files "\\|")
+             regexp-unmatchable))
          (skip-count 0)
          (fail-count 0)
          (file-count 0)
@@ -1997,9 +1997,7 @@ also be compiled."
                      (or (null arg) (eq 0 arg)
                          (y-or-n-p (concat "Check " source "? ")))
                       ;; Directory is requested to be ignored
-                      (not (string-match-p
-                            (regexp-opt byte-compile-ignore-files)
-                            source))
+                      (not (string-match-p ignore-files-regexp source))
                       (setq directories (nconc directories (list source))))
                ;; It is an ordinary file.  Decide whether to compile it.
                (if (and (string-match emacs-lisp-file-regexp source)
@@ -2009,9 +2007,7 @@ also be compiled."
                         (not (auto-save-file-name-p source))
                         (not (member source (dir-locals--all-files directory)))
                         ;; File is requested to be ignored
-                        (not (string-match-p
-                              (regexp-opt byte-compile-ignore-files)
-                              source)))
+                        (not (string-match-p ignore-files-regexp source)))
                    (progn (cl-incf
                            (pcase (byte-recompile-file source force arg)
                              ('no-byte-compile skip-count)
@@ -2495,8 +2491,7 @@ Call from the source buffer."
     ;; Spill output for the native compiler here
     (push (make-byte-to-native-top-level :form form :lexical lexical-binding)
           byte-to-native-top-level-forms))
-  (let ((print-symbols-bare t)          ; Possibly redundant binding.
-        (print-escape-newlines t)
+  (let ((print-escape-newlines t)
         (print-length nil)
         (print-level nil)
         (print-quoted t)
@@ -2530,8 +2525,7 @@ list that represents a doc string reference.
   ;; in the input buffer (now current), not in the output buffer.
   (let ((dynamic-docstrings byte-compile-dynamic-docstrings))
     (with-current-buffer byte-compile--outbuffer
-      (let (position
-            (print-symbols-bare t))     ; Possibly redundant binding.
+      (let (position)
         ;; Insert the doc string, and make it a comment with #@LENGTH.
         (when (and (>= (nth 1 info) 0) dynamic-docstrings)
           (setq position (byte-compile-output-as-comment
@@ -2627,8 +2621,7 @@ list that represents a doc string reference.
               byte-compile-jump-tables nil))))
 
 (defun byte-compile-preprocess (form &optional _for-effect)
-  (let ((print-symbols-bare t))         ; Possibly redundant binding.
-    (setq form (macroexpand-all form byte-compile-macro-environment)))
+  (setq form (macroexpand-all form byte-compile-macro-environment))
   ;; FIXME: We should run byte-optimize-form here, but it currently does not
   ;; recurse through all the code, so we'd have to fix this first.
   ;; Maybe a good fix would be to merge byte-optimize-form into
@@ -4369,9 +4362,6 @@ This function is never called when `lexical-binding' is 
nil."
 
 ;; more complicated compiler macros
 
-(byte-defop-compiler char-before)
-(byte-defop-compiler backward-char)
-(byte-defop-compiler backward-word)
 (byte-defop-compiler list)
 (byte-defop-compiler concat)
 (byte-defop-compiler (indent-to-column byte-indent-to) byte-compile-indent-to)
@@ -4382,40 +4372,6 @@ This function is never called when `lexical-binding' is 
nil."
 (byte-defop-compiler (/ byte-quo) byte-compile-quo)
 (byte-defop-compiler nconc)
 
-;; Is this worth it?  Both -before and -after are written in C.
-(defun byte-compile-char-before (form)
-  (cond ((or (= 1 (length form))
-            (and (= 2 (length form)) (not (nth 1 form))))
-        (byte-compile-form '(char-after (1- (point)))))
-       ((= 2 (length form))
-        (byte-compile-form (list 'char-after (if (numberp (nth 1 form))
-                                                 (1- (nth 1 form))
-                                               `(1- (or ,(nth 1 form)
-                                                        (point)))))))
-       (t (byte-compile-subr-wrong-args form "0-1"))))
-
-;; backward-... ==> forward-... with negated argument.
-;; Is this worth it?  Both -backward and -forward are written in C.
-(defun byte-compile-backward-char (form)
-  (cond ((or (= 1 (length form))
-            (and (= 2 (length form)) (not (nth 1 form))))
-        (byte-compile-form '(forward-char -1)))
-       ((= 2 (length form))
-        (byte-compile-form (list 'forward-char (if (numberp (nth 1 form))
-                                                   (- (nth 1 form))
-                                                 `(- (or ,(nth 1 form) 1))))))
-       (t (byte-compile-subr-wrong-args form "0-1"))))
-
-(defun byte-compile-backward-word (form)
-  (cond ((or (= 1 (length form))
-            (and (= 2 (length form)) (not (nth 1 form))))
-        (byte-compile-form '(forward-word -1)))
-       ((= 2 (length form))
-        (byte-compile-form (list 'forward-word (if (numberp (nth 1 form))
-                                                   (- (nth 1 form))
-                                                 `(- (or ,(nth 1 form) 1))))))
-       (t (byte-compile-subr-wrong-args form "0-1"))))
-
 (defun byte-compile-list (form)
   (let ((count (length (cdr form))))
     (cond ((= count 0)
@@ -5866,6 +5822,96 @@ and corresponding effects."
 (put 'remq  'compiler-macro #'bytecomp--check-memq-args)
 (put 'delq  'compiler-macro #'bytecomp--check-memq-args)
 
+;; Implement `char-before', `backward-char' and `backward-word' in
+;; terms of `char-after', `forward-char' and `forward-word' which have
+;; their own byte-ops.
+
+(put 'char-before 'compiler-macro #'bytecomp--char-before)
+(defun bytecomp--char-before (form &optional arg &rest junk-args)
+  (if junk-args
+      form    ; arity error
+    `(char-after (1- (or ,arg (point))))))
+
+(put 'backward-char 'compiler-macro #'bytecomp--backward-char)
+(defun bytecomp--backward-char (form &optional arg &rest junk-args)
+  (if junk-args
+      form    ; arity error
+    `(forward-char (- (or ,arg 1)))))
+
+(put 'backward-word 'compiler-macro #'bytecomp--backward-word)
+(defun bytecomp--backward-word (form &optional arg &rest junk-args)
+  (if junk-args
+      form    ; arity error
+    `(forward-word (- (or ,arg 1)))))
+
+(defun bytecomp--check-keyword-args (form arglist allowed-keys required-keys)
+  (let ((fun (car form)))
+    (cl-flet ((missing (form keyword)
+               (byte-compile-warn-x
+                form
+                "`%S´ called without required keyword argument %S"
+                fun keyword))
+             (unrecognized (form keyword)
+               (byte-compile-warn-x
+                form
+                "`%S´ called with unknown keyword argument %S"
+                fun keyword))
+             (duplicate (form keyword)
+               (byte-compile-warn-x
+                form
+                "`%S´ called with repeated keyword argument %S"
+                fun keyword))
+              (missing-val (form keyword)
+               (byte-compile-warn-x
+                form
+                "missing value for keyword argument %S"
+                keyword)))
+      (let* ((seen '())
+            (l arglist))
+       (while (consp l)
+         (let ((key (car l)))
+           (cond ((and (keywordp key) (memq key allowed-keys))
+                  (cond ((memq key seen)
+                         (duplicate l key))
+                        (t
+                         (push key seen))))
+                 (t (unrecognized l key)))
+            (when (null (cdr l))
+              (missing-val l key)))
+         (setq l (cddr l)))
+        (dolist (key required-keys)
+         (unless (memq key seen)
+           (missing form key))))))
+  form)
+
+(put 'make-process 'compiler-macro
+     #'(lambda (form &rest args)
+         (bytecomp--check-keyword-args
+          form args
+          '(:name
+            :buffer :command :coding :noquery :stop :connection-type
+            :filter :sentinel :stderr :file-handler)
+          '(:name :command))))
+
+(put 'make-pipe-process 'compiler-macro
+     #'(lambda (form &rest args)
+         (bytecomp--check-keyword-args
+          form args
+          '(:name :buffer :coding :noquery :stop :filter :sentinel)
+          '(:name))))
+
+(put 'make-network-process 'compiler-macro
+     #'(lambda (form &rest args)
+         (bytecomp--check-keyword-args
+          form args
+          '(:name
+            :buffer :host :service :type :family :local :remote :coding
+            :nowait :noquery :stop :filter :filter-multibyte :sentinel
+            :log :plist :tls-parameters :server :broadcast :dontroute
+            :keepalive :linger :oobinline :priority :reuseaddr :bindtodevice
+            :use-external-socket)
+          '(:name :service))))
+
 (provide 'byte-compile)
 (provide 'bytecomp)
 
diff --git a/lisp/emacs-lisp/checkdoc.el b/lisp/emacs-lisp/checkdoc.el
index c5e69d5ef56..3c4b6baca53 100644
--- a/lisp/emacs-lisp/checkdoc.el
+++ b/lisp/emacs-lisp/checkdoc.el
@@ -2042,8 +2042,7 @@ from the comment."
                           (condition-case nil
                               (setq lst (read (current-buffer)))
                             (error (setq lst nil))) ; error in text
-                           (if (not (listp lst)) ; not a list of args
-                               (setq lst (list lst)))
+                           (setq lst (ensure-list lst))
                           (if (and lst (not (symbolp (car lst)))) ;weird arg
                               (setq lst nil))
                           (while lst
@@ -2382,7 +2381,7 @@ Code:, and others referenced in the style guide."
        err
        (or
        ;; * Commentary Section
-        (if (and (not (lm-commentary-mark))
+        (if (and (not (lm-commentary-start))
                  ;; No need for a commentary section in test files.
                  (not (string-match
                        (rx (or (seq (or "-test.el" "-tests.el") string-end)
@@ -2419,10 +2418,10 @@ Code:, and others referenced in the style guide."
        (if (or (not checkdoc-force-history-flag)
                (file-exists-p "ChangeLog")
                (file-exists-p "../ChangeLog")
-                (lm-history-mark))
+                (lm-history-start))
            nil
          (progn
-           (goto-char (or (lm-commentary-mark) (point-min)))
+            (goto-char (or (lm-commentary-start) (point-min)))
            (cond
             ((re-search-forward
               "write\\s-+to\\s-+the\\s-+Free Software Foundation, Inc."
@@ -2443,7 +2442,7 @@ Code:, and others referenced in the style guide."
        err
        (or
        ;; * Code section
-       (if (not (lm-code-mark))
+        (if (not (lm-code-start))
            (let ((cont t)
                  pos)
              (goto-char (point-min))
@@ -2494,7 +2493,7 @@ Code:, and others referenced in the style guide."
       ;; Let's spellcheck the commentary section.  This is the only
       ;; section that is easy to pick out, and it is also the most
       ;; visible section (with the finder).
-      (let ((cm (lm-commentary-mark)))
+      (let ((cm (lm-commentary-start)))
         (when cm
           (save-excursion
             (goto-char cm)
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
index 1297ce6d2e5..d205ce78b4b 100644
--- a/lisp/emacs-lisp/cl-generic.el
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -1108,10 +1108,10 @@ MET-NAME is as returned by 
`cl--generic-load-hist-format'."
          (qualifiers   (cl--generic-method-qualifiers method))
          (call-con     (cl--generic-method-call-con method))
          (function     (cl--generic-method-function method))
-         (args (help-function-arglist (if (not (eq call-con 'curried))
-                                          function
-                                        (funcall function #'ignore))
-                                      'names))
+         (function (if (not (eq call-con 'curried))
+                              function
+                            (funcall function #'ignore)))
+         (args (help-function-arglist function 'names))
          (docstring (documentation function))
          (qual-string
           (if (null qualifiers) ""
diff --git a/lisp/emacs-lisp/cl-indent.el b/lisp/emacs-lisp/cl-indent.el
index 8920579755e..ee50f572157 100644
--- a/lisp/emacs-lisp/cl-indent.el
+++ b/lisp/emacs-lisp/cl-indent.el
@@ -192,7 +192,7 @@ the standard Lisp indent package."
     (list
      (cond ((not (lisp-extended-loop-p (elt state 1)))
            (+ loop-indentation lisp-simple-loop-indentation))
-          ((looking-at "^\\s-*\\(:?\\sw+\\|;\\)")
+          ((looking-at "^\\s-*\\(?::?\\sw+\\|;\\)")
            (+ loop-indentation lisp-loop-keyword-indentation))
           (t
            (+ loop-indentation lisp-loop-forms-indentation)))
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index 23ca692a131..7473de3493f 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -389,7 +389,7 @@ more details.
 \(fn NAME ARGLIST [DOCSTRING] BODY...)"
   (declare (debug
             ;; Same as defun but use cl-lambda-list.
-            (&define [&name sexp]   ;Allow (setf ...) additionally to symbols.
+            (&define [&name symbolp]
                      cl-lambda-list
                      cl-declarations-or-string
                      [&optional ("interactive" interactive)]
@@ -2037,7 +2037,16 @@ a `let' form, except that the list of symbols can be 
computed at run-time."
    ;; *after* handling `function', but we want to stop macroexpansion from
    ;; being applied infinitely, so we use a cache to return the exact `form'
    ;; being expanded even though we don't receive it.
-   ((eq f (car cl--labels-convert-cache)) (cdr cl--labels-convert-cache))
+   ;; In Common Lisp, we'd use the `&whole' arg instead (see
+   ;; "Macro Lambda Lists" in the CLHS).
+   ((let ((symbols-with-pos-enabled nil)) ;Don't rewrite #'<X@5> => #'<X@3>
+      (eq f (car cl--labels-convert-cache)))
+    ;; This value should be `eq' to the `&whole' form.
+    ;; If this is not the case, we have a bug.
+    (prog1 (cdr cl--labels-convert-cache)
+      ;; Drop it, so it can't accidentally interfere with some
+      ;; unrelated subsequent use of `function' with the same symbol.
+      (setq cl--labels-convert-cache nil)))
    (t
     (let* ((found (assq f macroexpand-all-environment))
            (replacement (and found
@@ -2045,6 +2054,8 @@ a `let' form, except that the list of symbols can be 
computed at run-time."
                                (funcall (cdr found) cl--labels-magic)))))
       (if (and replacement (eq cl--labels-magic (car replacement)))
           (nth 1 replacement)
+        ;; FIXME: Here, we'd like to return the `&whole' form, but since ELisp
+        ;; doesn't have that, we approximate it via `cl--labels-convert-cache'.
         (let ((res `(function ,f)))
           (setq cl--labels-convert-cache (cons f res))
           res))))))
@@ -2064,13 +2075,15 @@ info node `(cl) Function Bindings' for details.
 
 \(fn ((FUNC ARGLIST BODY...) ...) FORM...)"
   (declare (indent 1)
-           (debug ((&rest [&or (symbolp form)
-                               (&define [&name symbolp "@cl-flet@"]
+           (debug ((&rest [&or (&define [&name symbolp "@cl-flet@"]
                                         [&name [] gensym] ;Make it unique!
                                         cl-lambda-list
                                         cl-declarations-or-string
                                         [&optional ("interactive" interactive)]
-                                        def-body)])
+                                        def-body)
+                               (&define [&name symbolp "@cl-flet@"]
+                                        [&name [] gensym] ;Make it unique!
+                                        def-form)])
                    cl-declarations body)))
   (let ((binds ()) (newenv macroexpand-all-environment))
     (dolist (binding bindings)
diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el
index 375f003592c..b8b086b6c16 100644
--- a/lisp/emacs-lisp/comp.el
+++ b/lisp/emacs-lisp/comp.el
@@ -39,6 +39,23 @@
 (require 'warnings)
 (require 'comp-cstr)
 
+;; These variables and functions are defined in comp.c
+(defvar native-comp-enable-subr-trampolines)
+(defvar comp-installed-trampolines-h)
+(defvar comp-subr-arities-h)
+(defvar native-comp-eln-load-path)
+(defvar comp-native-version-dir)
+(defvar comp-deferred-pending-h)
+(defvar comp--no-native-compile)
+
+(declare-function comp-el-to-eln-rel-filename "comp.c")
+(declare-function native-elisp-load "comp.c")
+(declare-function comp--release-ctxt "comp.c")
+(declare-function comp--init-ctxt "comp.c")
+(declare-function comp--compile-ctxt-to-file "comp.c")
+(declare-function comp-el-to-eln-filename "comp.c")
+(declare-function comp--install-trampoline "comp.c")
+
 (defgroup comp nil
   "Emacs Lisp native compiler."
   :group 'lisp)
@@ -1304,34 +1321,47 @@ clashes."
           (make-temp-file (comp-c-func-name function-name "freefn-")
                           nil ".eln")))
   (let* ((f (symbol-function function-name))
+         (byte-code (byte-compile function-name))
          (c-name (comp-c-func-name function-name "F"))
-         (func (make-comp-func-l :name function-name
-                                 :c-name c-name
-                                 :doc (documentation f t)
-                                 :int-spec (interactive-form f)
-                                 :command-modes (command-modes f)
-                                 :speed (comp-spill-speed function-name)
-                                 :pure (comp-spill-decl-spec function-name
-                                                             'pure)
-                                 :defining-symbol function-name)))
+         (func
+          (if (comp-lex-byte-func-p byte-code)
+              (make-comp-func-l :name function-name
+                                :c-name c-name
+                                :doc (documentation f t)
+                                :int-spec (interactive-form f)
+                                :command-modes (command-modes f)
+                                :speed (comp-spill-speed function-name)
+                                :pure (comp-spill-decl-spec function-name
+                                                            'pure)
+                                :defining-symbol function-name)
+            (make-comp-func-d :name function-name
+                              :c-name c-name
+                              :doc (documentation f t)
+                              :int-spec (interactive-form f)
+                              :command-modes (command-modes f)
+                              :speed (comp-spill-speed function-name)
+                              :pure (comp-spill-decl-spec function-name
+                                                          'pure)
+                              :defining-symbol function-name))))
       (when (byte-code-function-p f)
         (signal 'native-compiler-error
                 '("can't native compile an already byte-compiled function")))
-      (setf (comp-func-byte-func func)
-            (byte-compile (comp-func-name func)))
+      (setf (comp-func-byte-func func) byte-code)
       (let ((lap (byte-to-native-lambda-lap
                   (gethash (aref (comp-func-byte-func func) 1)
                            byte-to-native-lambdas-h))))
         (cl-assert lap)
         (comp-log lap 2 t)
-        (let ((arg-list (aref (comp-func-byte-func func) 0)))
-          (setf (comp-func-l-args func)
-                (comp-decrypt-arg-list arg-list function-name)
-                (comp-func-lap func)
-                lap
-                (comp-func-frame-size func)
-                (comp-byte-frame-size (comp-func-byte-func func))))
-        (setf (comp-ctxt-top-level-forms comp-ctxt)
+        (if (comp-func-l-p func)
+            (let ((arg-list (aref (comp-func-byte-func func) 0)))
+              (setf (comp-func-l-args func)
+                    (comp-decrypt-arg-list arg-list function-name)))
+          (setf (comp-func-d-lambda-list func) (cadr f)))
+        (setf (comp-func-lap func)
+              lap
+              (comp-func-frame-size func)
+              (comp-byte-frame-size (comp-func-byte-func func))
+              (comp-ctxt-top-level-forms comp-ctxt)
               (list (make-byte-to-native-func-def :name function-name
                                                   :c-name c-name)))
         (comp-add-func-to-ctxt func))))
@@ -1427,11 +1457,8 @@ clashes."
   (unless byte-to-native-top-level-forms
     (signal 'native-compiler-error-empty-byte (list filename)))
   (unless (comp-ctxt-output comp-ctxt)
-    (setf (comp-ctxt-output comp-ctxt) (comp-el-to-eln-filename
-                                        filename
-                                        (or native-compile-target-directory
-                                            (when byte+native-compile
-                                              (car (last 
native-comp-eln-load-path)))))))
+    (setf (comp-ctxt-output comp-ctxt)
+          (comp-el-to-eln-filename filename native-compile-target-directory)))
   (setf (comp-ctxt-speed comp-ctxt) (alist-get 'native-comp-speed
                                                byte-native-qualities)
         (comp-ctxt-debug comp-ctxt) (alist-get 'native-comp-debug
@@ -4377,8 +4404,9 @@ last directory in `native-comp-eln-load-path')."
   (comp-ensure-native-compiler)
   (let ((comp-running-batch-compilation t)
         (native-compile-target-directory
-            (if for-tarball
-                (car (last native-comp-eln-load-path)))))
+         (if for-tarball
+             (car (last native-comp-eln-load-path))
+           native-compile-target-directory)))
     (cl-loop for file in command-line-args-left
              if (or (null byte+native-compile)
                     (cl-notany (lambda (re) (string-match re file))
@@ -4420,6 +4448,8 @@ variable \"NATIVE_DISABLED\" is set, only byte compile."
       (batch-byte-compile)
     (cl-assert (length= command-line-args-left 1))
     (let* ((byte+native-compile t)
+           (native-compile-target-directory
+            (car (last native-comp-eln-load-path)))
            (byte-to-native-output-buffer-file nil)
            (eln-file (car (batch-native-compile))))
       (comp-write-bytecode-file eln-file)
diff --git a/lisp/emacs-lisp/easy-mmode.el b/lisp/emacs-lisp/easy-mmode.el
index 20681374ee3..529f6e90e88 100644
--- a/lisp/emacs-lisp/easy-mmode.el
+++ b/lisp/emacs-lisp/easy-mmode.el
@@ -142,8 +142,6 @@ it is disabled.")
           (insert (format "\\{%s}" keymap-sym)))
         (buffer-string)))))
 
-;;;###autoload
-(defalias 'easy-mmode-define-minor-mode #'define-minor-mode)
 ;;;###autoload
 (defmacro define-minor-mode (mode doc &rest body)
   "Define a new minor mode MODE.
@@ -442,8 +440,6 @@ No problems result if this variable is not bound.
 ;;; make global minor mode
 ;;;
 
-;;;###autoload
-(defalias 'easy-mmode-define-global-mode #'define-globalized-minor-mode)
 ;;;###autoload
 (defalias 'define-global-minor-mode #'define-globalized-minor-mode)
 ;;;###autoload
@@ -841,6 +837,12 @@ Interactively, COUNT is the prefix numeric argument, and 
defaults to 1."
            ,@body))
        (put ',prev-sym 'definition-name ',base))))
 
+;; When deleting these two, also delete them from loaddefs-gen.el.
+;;;###autoload
+(define-obsolete-function-alias 'easy-mmode-define-minor-mode 
#'define-minor-mode "30.1")
+;;;###autoload
+(define-obsolete-function-alias 'easy-mmode-define-global-mode 
#'define-globalized-minor-mode "30.1")
+
 (provide 'easy-mmode)
 
 ;;; easy-mmode.el ends here
diff --git a/lisp/emacs-lisp/eieio.el b/lisp/emacs-lisp/eieio.el
index 9a1f5b9db0f..ccdb52d6a1f 100644
--- a/lisp/emacs-lisp/eieio.el
+++ b/lisp/emacs-lisp/eieio.el
@@ -652,8 +652,7 @@ If SLOT is unbound, bind it to the list containing ITEM."
        (setq ov (list item))
       (setq ov (eieio-oref object slot))
       ;; turn it into a list.
-      (unless (listp ov)
-       (setq ov (list ov)))
+      (setq ov (ensure-list ov))
       ;; Do the combination
       (if (not (member item ov))
          (setq ov
diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el
index 18d3eb37af3..3a786bb321b 100644
--- a/lisp/emacs-lisp/eldoc.el
+++ b/lisp/emacs-lisp/eldoc.el
@@ -991,7 +991,8 @@ the docstrings eventually produced, using
  "mark-paragraph" "mouse-set-point" "move-" "move-beginning-of-"
  "move-end-of-" "newline" "next-" "other-window" "pop-global-mark"
  "previous-" "recenter" "right-" "scroll-" "self-insert-command"
- "split-window-" "up-list")
+ "split-window-" "up-list" "touch-screen-handle-touch"
+ "analyze-text-conversion")
 
 (provide 'eldoc)
 
diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el
index 96b7fe3b400..0c17a603e8b 100644
--- a/lisp/emacs-lisp/ert.el
+++ b/lisp/emacs-lisp/ert.el
@@ -237,7 +237,9 @@ in batch mode, an error is signaled.
                             `(:expected-result-type ,expected-result))
                         ,@(when tags-supplied-p
                             `(:tags ,tags))
-                        :body (lambda () ,@body)
+                        ;; Add `nil' after the body to enable compiler warnings
+                        ;; about unused computations at the end.
+                        :body (lambda () ,@body nil)
                         :file-name ,(or (macroexp-file-name) 
buffer-file-name)))
          ',name))))
 
diff --git a/lisp/emacs-lisp/find-func.el b/lisp/emacs-lisp/find-func.el
index bf890fc35a9..d393ccc759a 100644
--- a/lisp/emacs-lisp/find-func.el
+++ b/lisp/emacs-lisp/find-func.el
@@ -591,7 +591,7 @@ otherwise uses `variable-at-point'."
     (list (intern (completing-read
                    (format-prompt "Find %s" symb prompt-type)
                    obarray predicate
-                   t nil nil (and symb (symbol-name symb)))))))
+                   'lambda nil nil (and symb (symbol-name symb)))))))
 
 (defun find-function-do-it (symbol type switch-fn)
   "Find Emacs Lisp SYMBOL in a buffer and display it.
diff --git a/lisp/emacs-lisp/lisp-mnt.el b/lisp/emacs-lisp/lisp-mnt.el
index 1fa1297e787..cb7cff43555 100644
--- a/lisp/emacs-lisp/lisp-mnt.el
+++ b/lisp/emacs-lisp/lisp-mnt.el
@@ -1,9 +1,8 @@
 ;;; lisp-mnt.el --- utility functions for Emacs Lisp maintainers  -*- 
lexical-binding:t -*-
 
-;; Copyright (C) 1992, 1994, 1997, 2000-2023 Free Software Foundation,
-;; Inc.
+;; Copyright (C) 1992-2023 Free Software Foundation, Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Maintainer: emacs-devel@gnu.org
 ;; Created: 14 Jul 1992
 ;; Keywords: docs
@@ -52,7 +51,7 @@
 ;;
 ;;    * Copyright line, which looks more or less like this:
 ;;
-;;       ;; Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
+;;       ;; Copyright (C) 1999-2001 Free Software Foundation, Inc.
 ;;
 ;;    * A blank line
 ;;
@@ -68,7 +67,7 @@
 ;; ;;  Noah Friedman <friedman@ai.mit.edu>
 ;; ;;  Joe Wells <jbw@maverick.uswest.com>
 ;; ;;  Dave Brennan <brennan@hal.com>
-;; ;;  Eric Raymond <esr@snark.thyrsus.com>
+;; ;;  Eric S. Raymond <esr@thyrsus.com>
 ;;
 ;;    * Maintainer line --- should be a single name/address as in the Author
 ;; line, or an address only.  If there is no maintainer
@@ -187,7 +186,6 @@ If the given section does not exist, return nil."
       (goto-char (point-min))
       (if (re-search-forward (lm-get-header-re header 'section) nil t)
           (line-beginning-position (if after 2))))))
-(defalias 'lm-section-mark 'lm-section-start)
 
 (defun lm-section-end (header)
   "Return the buffer location of the end of a given section.
@@ -230,12 +228,10 @@ a section."
 (defun lm-code-start ()
   "Return the buffer location of the `Code' start marker."
   (lm-section-start "Code"))
-(defalias 'lm-code-mark 'lm-code-start)
 
 (defun lm-commentary-start ()
   "Return the buffer location of the `Commentary' start marker."
   (lm-section-start lm-commentary-header))
-(defalias 'lm-commentary-mark 'lm-commentary-start)
 
 (defun lm-commentary-end ()
   "Return the buffer location of the `Commentary' section end."
@@ -244,7 +240,6 @@ a section."
 (defun lm-history-start ()
   "Return the buffer location of the `History' start marker."
   (lm-section-start lm-history-header))
-(defalias 'lm-history-mark 'lm-history-start)
 
 (defun lm-copyright-mark ()
   "Return the buffer location of the `Copyright' line."
@@ -258,7 +253,7 @@ a section."
   "Return the contents of the header named HEADER."
   (goto-char (point-min))
   (let ((case-fold-search t))
-    (when (and (re-search-forward (lm-get-header-re header) (lm-code-mark) t)
+    (when (and (re-search-forward (lm-get-header-re header) (lm-code-start) t)
               ;;   RCS ident likes format "$identifier: data$"
               (looking-at
                (if (save-excursion
@@ -402,7 +397,7 @@ ISO-DATE non-nil means return the date in ISO 8601 format."
     (when (progn (goto-char (point-min))
                 (re-search-forward
                  "\\$[I]d: [^ ]+ [^ ]+ \\([^/]+\\)/\\([^/]+\\)/\\([^ ]+\\) "
-                 (lm-code-mark) t))
+                  (lm-code-start) t))
       (let ((dd (match-string 3))
            (mm (match-string 2))
            (yyyy (match-string 1)))
@@ -420,7 +415,7 @@ ISO-DATE non-nil means return the date in ISO 8601 format."
 This can be found in an RCS or SCCS header."
   (lm-with-file file
     (or (lm-header "version")
-       (let ((header-max (lm-code-mark)))
+        (let ((header-max (lm-code-start)))
          (goto-char (point-min))
          (cond
           ;; Look for an RCS header
@@ -524,6 +519,7 @@ says display \"OK\" in temp buffer for files that have no 
problems.
 Optional argument VERBOSE specifies verbosity level.
 Optional argument NON-FSF-OK if non-nil means a non-FSF
 copyright notice is allowed."
+  ;; FIXME: Make obsolete in favor of checkdoc?
   (interactive (list nil nil t))
   (let* ((ret (and verbose "Ok"))
         name)
@@ -557,19 +553,18 @@ copyright notice is allowed."
                "`Keywords:' tag missing")
               ((not (lm-keywords-finder-p))
                "`Keywords:' has no valid finder keywords (see 
`finder-known-keywords')")
-              ((not (lm-commentary-mark))
+               ((not (lm-commentary-start))
                "Can't find a `Commentary' section marker")
-              ((not (lm-history-mark))
+               ((not (lm-history-start))
                "Can't find a `History' section marker")
-              ((not (lm-code-mark))
+               ((not (lm-code-start))
                "Can't find a `Code' section marker")
               ((progn
                  (goto-char (point-max))
                  (not
                   (re-search-backward
-                   (concat "^;;;[ \t]+" name "[ \t]+ends here[ \t]*$"
-                           "\\|^;;;[ \t]+ End of file[ \t]+" name)
-                   nil t)))
+                    (rx bol ";;; " (regexp name) " ends here")
+                    nil t)))
                "Can't find the footer line")
               ((not (and (lm-copyright-mark) (lm-crack-copyright)))
                "Can't find a valid copyright notice")
@@ -631,6 +626,11 @@ Prompts for bug subject TOPIC.  Leaves you in a mail 
buffer."
     (message "%s"
      (substitute-command-keys "Type \\[mail-send] to send bug report."))))
 
+(define-obsolete-function-alias 'lm-section-mark #'lm-section-start "30.1")
+(define-obsolete-function-alias 'lm-code-mark #'lm-code-start "30.1")
+(define-obsolete-function-alias 'lm-commentary-mark #'lm-commentary-start 
"30.1")
+(define-obsolete-function-alias 'lm-history-mark #'lm-history-start "30.1")
+
 (provide 'lisp-mnt)
 
 ;;; lisp-mnt.el ends here
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 1990630608d..b1fc65b09ac 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -31,11 +31,6 @@
 (eval-when-compile (require 'cl-lib))
 (eval-when-compile (require 'subr-x))
 
-(defvar font-lock-comment-face)
-(defvar font-lock-doc-face)
-(defvar font-lock-keywords-case-fold-search)
-(defvar font-lock-string-face)
-
 (define-abbrev-table 'lisp-mode-abbrev-table ()
   "Abbrev table for Lisp mode.")
 
@@ -134,7 +129,7 @@
         (purecopy (concat "^\\s-*("
                           (regexp-opt
                            '(;; Elisp
-                              "defconst" "defcustom"
+                              "defconst" "defcustom" "defvar-keymap"
                               ;; CL
                               "defconstant"
                              "defparameter" "define-symbol-macro")
@@ -361,7 +356,7 @@ This will generate compile-time constants from BINDINGS."
                  "define-globalized-minor-mode" "define-skeleton"
                  "define-widget" "ert-deftest"))
      (el-vdefs '("defconst" "defcustom" "defvaralias" "defvar-local"
-                 "defface"))
+                 "defface" "define-error"))
      (el-tdefs '("defgroup" "deftheme"))
      (el-errs '("user-error"))
      ;; Common-Lisp constructs supported by EIEIO.  FIXME: namespace.
diff --git a/lisp/emacs-lisp/loaddefs-gen.el b/lisp/emacs-lisp/loaddefs-gen.el
index fa3436bfd1e..967e75feaca 100644
--- a/lisp/emacs-lisp/loaddefs-gen.el
+++ b/lisp/emacs-lisp/loaddefs-gen.el
@@ -433,7 +433,8 @@ don't include."
                     ;; have an autoload cookie on the first column of a
                     ;; doc string or the like.  (The Emacs tree
                     ;; shouldn't contain any such instances.)
-                    (not (ppss-string-terminator (syntax-ppss))))
+                    (not (ppss-string-terminator
+                          (save-match-data (syntax-ppss)))))
             ;; ... and if we have one of these names, then alter outfile.
             (let* ((aname (match-string 2))
                    (to-file (if aname
diff --git a/lisp/emacs-lisp/macroexp.el b/lisp/emacs-lisp/macroexp.el
index 4f23fd379e5..8e35d1c205e 100644
--- a/lisp/emacs-lisp/macroexp.el
+++ b/lisp/emacs-lisp/macroexp.el
@@ -107,8 +107,7 @@ each clause."
 
 (defun macroexp--compiler-macro (handler form)
   (condition-case-unless-debug err
-      (let ((symbols-with-pos-enabled t))
-        (apply handler form (cdr form)))
+      (apply handler form (cdr form))
     (error
      (message "Warning: Optimization failure for %S: Handler: %S\n%S"
               (car form) handler err)
@@ -227,21 +226,19 @@ It should normally be a symbol with position and it 
defaults to FORM."
 (defun macroexp-macroexpand (form env)
   "Like `macroexpand' but checking obsolescence."
   (let* ((macroexpand-all-environment env)
-         (new-form
-          (macroexpand form env)))
-    (if (and (not (eq form new-form))   ;It was a macro call.
-             (car-safe form)
-             (symbolp (car form))
-             (get (car form) 'byte-obsolete-info))
-        (let* ((fun (car form))
-               (obsolete (get fun 'byte-obsolete-info)))
-          (macroexp-warn-and-return
-           (macroexp--obsolete-warning
-            fun obsolete
-            (if (symbolp (symbol-function fun))
-                "alias" "macro"))
-           new-form (list 'obsolete fun) nil fun))
-      new-form)))
+         new-form)
+    (while (not (eq form (setq new-form (macroexpand-1 form env))))
+      (let ((fun (car-safe form)))
+        (setq form
+              (if (and fun (symbolp fun)
+                       (get fun 'byte-obsolete-info))
+                  (macroexp-warn-and-return
+                   (macroexp--obsolete-warning
+                    fun (get fun 'byte-obsolete-info)
+                    (if (symbolp (symbol-function fun)) "alias" "macro"))
+                   new-form (list 'obsolete fun) nil fun)
+                new-form))))
+    form))
 
 (defun macroexp--unfold-lambda (form &optional name)
   (or name (setq name "anonymous lambda"))
@@ -781,40 +778,38 @@ test of free variables in the following ways:
 
 (defun internal-macroexpand-for-load (form full-p)
   ;; Called from the eager-macroexpansion in readevalloop.
-  (let ((symbols-with-pos-enabled t)
-        (print-symbols-bare t))
-    (cond
-     ;; Don't repeat the same warning for every top-level element.
-     ((eq 'skip (car macroexp--pending-eager-loads)) form)
-     ;; If we detect a cycle, skip macro-expansion for now, and output a 
warning
-     ;; with a trimmed backtrace.
-     ((and load-file-name (member load-file-name 
macroexp--pending-eager-loads))
-      (let* ((bt (delq nil
-                       (mapcar #'macroexp--trim-backtrace-frame
-                               (macroexp--backtrace))))
-             (elem `(load ,(file-name-nondirectory load-file-name)))
-             (tail (member elem (cdr (member elem bt)))))
-        (if tail (setcdr tail (list '…)))
-        (if (eq (car-safe (car bt)) 'macroexpand-all) (setq bt (cdr bt)))
-        (if macroexp--debug-eager
-            (debug 'eager-macroexp-cycle)
-          (error "Eager macro-expansion skipped due to cycle:\n  %s"
-                 (mapconcat #'prin1-to-string (nreverse bt) " => ")))
-        (push 'skip macroexp--pending-eager-loads)
-        form))
-     (t
-      (condition-case err
-          (let ((macroexp--pending-eager-loads
-                 (cons load-file-name macroexp--pending-eager-loads)))
-            (if full-p
-                (macroexpand--all-toplevel form)
-              (macroexpand form)))
-        (error
-         ;; Hopefully this shouldn't happen thanks to the cycle detection,
-         ;; but in case it does happen, let's catch the error and give the
-         ;; code a chance to macro-expand later.
-         (error "Eager macro-expansion failure: %S" err)
-         form))))))
+  (cond
+   ;; Don't repeat the same warning for every top-level element.
+   ((eq 'skip (car macroexp--pending-eager-loads)) form)
+   ;; If we detect a cycle, skip macro-expansion for now, and output a warning
+   ;; with a trimmed backtrace.
+   ((and load-file-name (member load-file-name macroexp--pending-eager-loads))
+    (let* ((bt (delq nil
+                     (mapcar #'macroexp--trim-backtrace-frame
+                             (macroexp--backtrace))))
+           (elem `(load ,(file-name-nondirectory load-file-name)))
+           (tail (member elem (cdr (member elem bt)))))
+      (if tail (setcdr tail (list '…)))
+      (if (eq (car-safe (car bt)) 'macroexpand-all) (setq bt (cdr bt)))
+      (if macroexp--debug-eager
+          (debug 'eager-macroexp-cycle)
+        (error "Eager macro-expansion skipped due to cycle:\n  %s"
+               (mapconcat #'prin1-to-string (nreverse bt) " => ")))
+      (push 'skip macroexp--pending-eager-loads)
+      form))
+   (t
+    (condition-case err
+        (let ((macroexp--pending-eager-loads
+               (cons load-file-name macroexp--pending-eager-loads)))
+          (if full-p
+              (macroexpand--all-toplevel form)
+            (macroexpand form)))
+      (error
+       ;; Hopefully this shouldn't happen thanks to the cycle detection,
+       ;; but in case it does happen, let's catch the error and give the
+       ;; code a chance to macro-expand later.
+       (error "Eager macro-expansion failure: %S" err)
+       form)))))
 
 ;; ¡¡¡ Big Ugly Hack !!!
 ;; src/bootstrap-emacs is mostly used to compile .el files, so it needs
diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el
index 7a48ba47434..b55eb431668 100644
--- a/lisp/emacs-lisp/map.el
+++ b/lisp/emacs-lisp/map.el
@@ -50,18 +50,20 @@
 
 ARGS is a list of elements to be matched in the map.
 
-Each element of ARGS can be of the form (KEY PAT), in which case KEY is
-evaluated and searched for in the map.  The match fails if for any KEY
-found in the map, the corresponding PAT doesn't match the value
-associated with the KEY.
+Each element of ARGS can be of the form (KEY PAT [DEFAULT]),
+which looks up KEY in the map and matches the associated value
+against `pcase' pattern PAT.  DEFAULT specifies the fallback
+value to use when KEY is not present in the map.  If omitted, it
+defaults to nil.  Both KEY and DEFAULT are evaluated.
 
 Each element can also be a SYMBOL, which is an abbreviation of
 a (KEY PAT) tuple of the form (\\='SYMBOL SYMBOL).  When SYMBOL
 is a keyword, it is an abbreviation of the form (:SYMBOL SYMBOL),
 useful for binding plist values.
 
-Keys in ARGS not found in the map are ignored, and the match doesn't
-fail."
+An element of ARGS fails to match if PAT does not match the
+associated value or the default value.  The overall pattern fails
+to match if any element of ARGS fails to match."
   `(and (pred mapp)
         ,@(map--make-pcase-bindings args)))
 
@@ -71,12 +73,13 @@ fail."
 KEYS can be a list of symbols, in which case each element will be
 bound to the looked up value in MAP.
 
-KEYS can also be a list of (KEY VARNAME) pairs, in which case
-KEY is an unquoted form.
+KEYS can also be a list of (KEY VARNAME [DEFAULT]) sublists, in
+which case KEY and DEFAULT are unquoted forms.
 
 MAP can be an alist, plist, hash-table, or array."
   (declare (indent 2)
-           (debug ((&rest &or symbolp ([form symbolp])) form body)))
+           (debug ((&rest &or symbolp ([form symbolp &optional form]))
+                   form body)))
   `(pcase-let ((,(map--make-pcase-patterns keys) ,map))
      ,@body))
 
@@ -595,11 +598,21 @@ Example:
     (map-into \\='((1 . 3)) \\='(hash-table :test eql))"
   (map--into-hash map (cdr type)))
 
+(defmacro map--pcase-map-elt (key default map)
+  "A macro to make MAP the last argument to `map-elt'.
+
+This allows using default values for `map-elt', which can't be
+done using `pcase--flip'.
+
+KEY is the key sought in the map.  DEFAULT is the default value."
+  `(map-elt ,map ,key ,default))
+
 (defun map--make-pcase-bindings (args)
   "Return a list of pcase bindings from ARGS to the elements of a map."
   (mapcar (lambda (elt)
             (cond ((consp elt)
-                   `(app (pcase--flip map-elt ,(car elt)) ,(cadr elt)))
+                   `(app (map--pcase-map-elt ,(car elt) ,(caddr elt))
+                         ,(cadr elt)))
                   ((keywordp elt)
                    (let ((var (intern (substring (symbol-name elt) 1))))
                      `(app (pcase--flip map-elt ,elt) ,var)))
diff --git a/lisp/emacs-lisp/oclosure.el b/lisp/emacs-lisp/oclosure.el
index 29b69b0cd8c..8ed923a1c16 100644
--- a/lisp/emacs-lisp/oclosure.el
+++ b/lisp/emacs-lisp/oclosure.el
@@ -50,7 +50,7 @@
 ;; - coercion wrappers, as in "Threesomes, with and without blame"
 ;;   https://dl.acm.org/doi/10.1145/1706299.1706342, or
 ;;   "On the Runtime Complexity of Type-Directed Unboxing"
-;;   http://sv.c.titech.ac.jp/minamide/papers.html
+;;   https://sv.c.titech.ac.jp/minamide/papers.html
 ;; - An efficient `negate' operation such that
 ;;   (negate (negate f)) returns just `f' and (negate #'<) returns #'>=.
 ;; - Autoloads (tho currently our bytecode functions (and hence OClosures)
diff --git a/lisp/emacs-lisp/package-vc.el b/lisp/emacs-lisp/package-vc.el
index db8b41aee6a..747fe696204 100644
--- a/lisp/emacs-lisp/package-vc.el
+++ b/lisp/emacs-lisp/package-vc.el
@@ -62,6 +62,18 @@
 (defconst package-vc--elpa-packages-version 1
   "Version number of the package specification format understood by 
package-vc.")
 
+(defconst package-vc--backend-type
+  `(choice :convert-widget
+           ,(lambda (widget)
+              (let (opts)
+                (dolist (be vc-handled-backends)
+                  (when (or (vc-find-backend-function be 'clone)
+                            (alist-get 'clone (get be 'vc-functions)))
+                    (push (widget-convert (list 'const be)) opts)))
+                (widget-put widget :args opts))
+              widget))
+  "The type of VC backends that support cloning package VCS repositories.")
+
 (defcustom package-vc-heuristic-alist
   `((,(rx bos "http" (? "s") "://"
           (or (: (? "www.") "github.com"
@@ -94,22 +106,27 @@
                  (+ (or alnum "-" "." "_")) (? "/")))
           eos)
      . Bzr))
-  "Heuristic mapping URL regular expressions to VC backends."
+  "Alist mapping repository URLs to VC backends.
+`package-vc-install' consults this alist to determine the VC
+backend from the repository URL when you call it without
+specifying a backend.  Each element of the alist has the form
+\(URL-REGEXP . BACKEND).  `package-vc-install' will use BACKEND of
+the first association for which the URL of the repository matches
+the URL-REGEXP of the association.  If no match is found,
+`package-vc-install' uses `package-vc-default-backend' instead."
   :type `(alist :key-type (regexp :tag "Regular expression matching URLs")
-                :value-type (choice :tag "VC Backend"
-                                    ,@(mapcar (lambda (b) `(const ,b))
-                                              vc-handled-backends)))
+                :value-type ,package-vc--backend-type)
   :version "29.1")
 
 (defcustom package-vc-default-backend 'Git
-  "Default VC backend used when cloning a package repository.
-If no repository type was specified or could be guessed by
-`package-vc-heuristic-alist', this is the default VC backend
-used as fallback.  The value must be a member of
-`vc-handled-backends' and the named backend must implement
-the `clone' function."
-  :type `(choice ,@(mapcar (lambda (b) (list 'const b))
-                           vc-handled-backends))
+  "Default VC backend to use for cloning package repositories.
+`package-vc-install' uses this backend when you specify neither
+the backend nor a repository URL that's recognized via
+`package-vc-heuristic-alist'.
+
+The value must be a member of `vc-handled-backends' that supports
+the `clone' VC function."
+  :type package-vc--backend-type
   :version "29.1")
 
 (defcustom package-vc-register-as-project t
@@ -140,20 +157,21 @@ the `clone' function."
                (package-desc-create :name name :kind 'vc))
            spec)))))))
 
-(defcustom package-vc-selected-packages '()
-  "List of packages that must be installed.
-Each member of the list is of the form (NAME . SPEC), where NAME
-is a symbol designating the package and SPEC is one of:
+
+(defcustom package-vc-selected-packages nil
+  "List of packages to install from their VCS repositories.
+Each element is of the form (NAME . SPEC), where NAME is a symbol
+designating the package and SPEC is one of:
 
 - nil, if any package version can be installed;
 - a version string, if that specific revision is to be installed;
-- a property list, describing a package specification.  For more
-  details, please consult the subsection \"Specifying Package
-  Sources\" in the Info node `(emacs)Fetching Package Sources'.
+- a property list, describing a package specification.  For possible
+  values, see the subsection \"Specifying Package Sources\" in the
+  Info node `(emacs)Fetching Package Sources'.
 
-This user option will be automatically updated to store package
-specifications for packages that are not specified in any
-archive."
+The command `package-vc-install' updates the value of this user
+option to store package specifications for packages that are not
+specified in any archive."
   :type '(alist :tag "List of packages you want to be installed"
                 :key-type (symbol :tag "Package")
                 :value-type
@@ -344,20 +362,27 @@ asynchronously."
         "\n")
        nil pkg-file nil 'silent))))
 
-(defcustom package-vc-allow-side-effects nil
-  "Whether to process :make and :shell-command spec arguments.
+(defcustom package-vc-allow-build-commands nil
+  "Whether to run extra build commands when installing VC packages.
+
+Some packages specify \"make\" targets or other shell commands
+that should run prior to building the package, by including the
+:make or :shell-command keywords in their specification.  By
+default, Emacs ignores these keywords when installing and
+upgrading VC packages, but if the value is a list of package
+names (symbols), the build commands will be run for those
+packages.  If the value is t, always respect :make and
+:shell-command keywords.
 
 It may be necessary to run :make and :shell-command arguments in
 order to initialize a package or build its documentation, but
 please be careful when changing this option, as installing and
 updating a package can run potentially harmful code.
 
-When set to a list of symbols (packages), run commands for only
-packages in the list.  When nil, never run commands.  Otherwise
-when non-nil, run commands for any package with :make or
-:shell-command specified.
-
-Package specs are loaded from trusted package archives."
+This applies to package specifications that come from your
+configured package archives, as well as from entries in
+`package-vc-selected-packages' and specifications that you give
+to `package-vc-install' directly."
   :type '(choice (const :tag "Run for all packages" t)
                  (repeat :tag "Run only for selected packages" (symbol :tag 
"Package name"))
                  (const :tag "Never run" nil))
@@ -395,7 +420,8 @@ otherwise it's assumed to be an Info file."
       (with-temp-buffer
         (insert-file-contents file)
         (setq file (make-temp-file "ox-texinfo-"))
-        (org-export-to-file 'texinfo file)
+        (let ((default-directory docs-directory))
+          (org-export-to-file 'texinfo file))
         (setq clean-up t)))
     (with-current-buffer (get-buffer-create " *package-vc doc*")
       (erase-buffer)
@@ -414,15 +440,15 @@ otherwise it's assumed to be an Info file."
     (when clean-up
       (delete-file file))))
 
-(defun package-vc-install-dependencies (requirements)
-  "Install missing dependencies, and return missing ones.
-The return value will be nil if everything was found, or a list
-of (NAME VERSION) pairs of all packages that couldn't be found.
+(defun package-vc-install-dependencies (deps)
+  "Install missing dependencies according to DEPS.
+
+DEPS is a list of elements (PACKAGE VERSION-LIST), where
+PACKAGE is a package name and VERSION-LIST is the required
+version of that package.
 
-REQUIREMENTS should be a list of additional requirements; each
-element in this list should have the form (PACKAGE VERSION-LIST),
-where PACKAGE is a package name and VERSION-LIST is the required
-version of that package."
+Return a list of dependencies that couldn't be met (or nil, when
+this function successfully installs all given dependencies)."
   (let ((to-install '()) (missing '()))
     (cl-labels ((search (pkg)
                   "Attempt to find all dependencies for PKG."
@@ -451,14 +477,12 @@ version of that package."
                              (desc (cadr (assoc package pac))))
                         (and desc (seq-some
                                    (apply-partially #'depends-on-p target)
-                                   (package-desc-reqs desc))))))
+                                   (mapcar #'car (package-desc-reqs desc)))))))
                 (dependent-order (a b)
                   (let ((desc-a (package-desc-name a))
                         (desc-b (package-desc-name b)))
-                    (or (not desc-a) (not desc-b)
-                        (not (depends-on-p desc-b desc-a))
-                        (depends-on-p desc-a desc-b)))))
-      (mapc #'search requirements)
+                    (depends-on-p desc-a desc-b))))
+      (mapc #'search deps)
       (cl-callf sort to-install #'version-order)
       (cl-callf seq-uniq to-install #'duplicate-p)
       (cl-callf sort to-install #'dependent-order))
@@ -524,9 +548,9 @@ documentation and marking the package as installed."
       (package-vc--generate-description-file pkg-desc pkg-file)
 
       ;; Process :make and :shell-command arguments before building 
documentation
-      (when (or (eq package-vc-allow-side-effects t)
+      (when (or (eq package-vc-allow-build-commands t)
                 (memq (package-desc-name pkg-desc)
-                      package-vc-allow-side-effects))
+                      package-vc-allow-build-commands))
         (package-vc--make pkg-spec pkg-desc))
 
       ;; Detect a manual
@@ -719,7 +743,10 @@ installed package."
 
 ;;;###autoload
 (defun package-vc-upgrade-all ()
-  "Attempt to upgrade all installed VC packages."
+  "Upgrade all installed VC packages.
+
+This may fail if the local VCS state of one of the packages
+conflicts with its remote repository state."
   (interactive)
   (dolist (package package-alist)
     (dolist (pkg-desc (cdr package))
@@ -729,7 +756,10 @@ installed package."
 
 ;;;###autoload
 (defun package-vc-upgrade (pkg-desc)
-  "Attempt to upgrade the package PKG-DESC."
+  "Upgrade the package described by PKG-DESC from package's VC repository.
+
+This may fail if the local VCS state of the package conflicts
+with the remote repository state."
   (interactive (list (package-vc--read-package-desc "Upgrade VC package: " t)))
   ;; HACK: To run `package-vc--unpack-1' after checking out the new
   ;; revision, we insert a hook into `vc-post-command-functions', and
@@ -792,34 +822,45 @@ If no such revision can be found, return nil."
 
 ;;;###autoload
 (defun package-vc-install (package &optional rev backend name)
-  "Fetch a PACKAGE and set it up for using with Emacs.
-
-If PACKAGE is a string containing an URL, download the package
-from the repository at that URL; the function will try to guess
-the name of the package from the URL.  This can be overridden by
-passing the optional argument NAME.  If PACKAGE is a cons-cell,
-it should have the form (NAME . SPEC), where NAME is a symbol
-indicating the package name and SPEC is a plist as described in
-`package-vc-selected-packages'.  Otherwise PACKAGE should be a
-symbol whose name is the package name, and the URL for the
-package will be taken from the package's metadata.
+  "Fetch a package described by PACKAGE and set it up for use with Emacs.
+
+PACKAGE specifies which package to install, where to find its
+source repository and how to build it.
+
+If PACKAGE is a symbol, install the package with that name
+according to metadata that package archives provide for it.  This
+is the simplest way to call this function, but it only works if
+the package you want to install is listed in a package archive
+you have configured.
+
+If PACKAGE is a string, it specifies the URL of the package
+repository.  In this case, optional argument BACKEND specifies
+the VC backend to use for cloning the repository; if it's nil,
+this function tries to infer which backend to use according to
+the value of `package-vc-heuristic-alist' and if that fails it
+uses `package-vc-default-backend'.  Optional argument NAME
+specifies the package name in this case; if it's nil, this
+package uses `file-name-base' on the URL to obtain the package
+name, otherwise NAME is the package name as a symbol.
+
+PACKAGE can also be a cons cell (PNAME . SPEC) where PNAME is the
+package name as a symbol, and SPEC is a plist that specifes how
+to fetch and build the package.  For possible values, see the
+subsection \"Specifying Package Sources\" in the Info
+node `(emacs)Fetching Package Sources'.
 
 By default, this function installs the last revision of the
 package available from its repository.  If REV is a string, it
-describes the revision to install, as interpreted by the VC
-backend.  The special value `:last-release' (interactively, the
-prefix argument), will use the commit of the latest release, if
-it exists.  The last release is the latest revision which changed
-the \"Version:\" header of the package's main Lisp file.
-
-Optional argument BACKEND specifies the VC backend to use for cloning
-the package's repository; this is only possible if NAME-OR-URL is a URL,
-a string.  If BACKEND is omitted or nil, the function
-uses `package-vc-heuristic-alist' to guess the backend.
-Note that by default, a VC package will be prioritized over a
-regular package, but it will not remove a VC package.
-
-\(fn PACKAGE &optional REV BACKEND)"
+describes the revision to install, as interpreted by the relevant
+VC backend.  The special value `:last-release' (interactively,
+the prefix argument), says to use the commit of the latest
+release, if it exists.  The last release is the latest revision
+which changed the \"Version:\" header of the package's main Lisp
+file.
+
+If you use this function to install a package that you also have
+installed from a package archive, the version this function
+installs takes precedence."
   (interactive
    (progn
      ;; Initialize the package system to get the list of package
@@ -895,7 +936,7 @@ for the last released version of the package."
 
 ;;;###autoload
 (defun package-vc-install-from-checkout (dir name)
-  "Set up the package NAME in DIR by linking it into the ELPA directory.
+  "Install the package NAME from its source directory DIR.
 Interactively, prompt the user for DIR, which should be a directory
 under version control, typically one created by `package-vc-checkout'.
 If invoked interactively with a prefix argument, prompt the user
@@ -937,13 +978,17 @@ prompt for the name of the package to rebuild."
 
 ;;;###autoload
 (defun package-vc-prepare-patch (pkg-desc subject revisions)
-  "Send patch for REVISIONS to maintainer of the package PKG using SUBJECT.
-The function uses `vc-prepare-patch', passing SUBJECT and
-REVISIONS directly.  PKG-DESC must be a package description.
+  "Email patches for REVISIONS to maintainer of package PKG-DESC using SUBJECT.
+
+PKG-DESC is a package descriptor and SUBJECT is the subject of
+the message.
+
 Interactively, prompt for PKG-DESC, SUBJECT, and REVISIONS.  When
 invoked with a numerical prefix argument, use the last N
 revisions.  When invoked interactively in a Log View buffer with
-marked revisions, use those."
+marked revisions, use those.
+
+See also `vc-prepare-patch'."
   (interactive
    (list (package-vc--read-package-desc "Package to prepare a patch for: " t)
          (and (not vc-prepare-patches-separately)
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index bbe5f00fde1..e1172d69bf0 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -609,7 +609,7 @@ package."
   (package-archive-priority (package-desc-archive pkg-desc)))
 
 (defun package--parse-elpaignore (pkg-desc)
-  "Return the of regular expression to match files ignored by PKG-DESC."
+  "Return a list of regular expressions to match files ignored by PKG-DESC."
   (let* ((pkg-dir (file-name-as-directory (package-desc-dir pkg-desc)))
          (ignore (expand-file-name ".elpaignore" pkg-dir))
          files)
@@ -2330,12 +2330,25 @@ from ELPA by either using `\\[package-upgrade]' or
       (mapc #'package-upgrade upgradeable))))
 
 (defun package--dependencies (pkg)
-  "Return a list of all dependencies PKG has.
-This is done recursively."
-  ;; Can we have circular dependencies?  Assume "nope".
-  (when-let* ((desc (cadr (assq pkg package-archive-contents)))
-              (deps (mapcar #'car (package-desc-reqs desc))))
-    (delete-dups (apply #'nconc deps (mapcar #'package--dependencies deps)))))
+  "Return a list of all transitive dependencies of PKG.
+If PKG is a package descriptor, the return value is a list of
+package descriptors.  If PKG is a symbol designating a package,
+the return value is a list of symbols designating packages."
+  (when-let* ((desc (if (package-desc-p pkg) pkg
+                      (cadr (assq pkg package-archive-contents)))))
+    ;; Can we have circular dependencies?  Assume "nope".
+    (let ((all (named-let more ((pkg-desc desc))
+                 (let (deps)
+                   (dolist (req (package-desc-reqs pkg-desc))
+                     (setq deps (nconc
+                                 (catch 'found
+                                   (dolist (p (apply #'append (mapcar #'cdr 
(package--alist))))
+                                     (when (and (string= (car req) 
(package-desc-name p))
+                                                (version-list-<= (cadr req) 
(package-desc-version p)))
+                                       (throw 'found (more p)))))
+                                 deps)))
+                   (delete-dups (cons pkg-desc deps))))))
+      (remq pkg (mapcar (if (package-desc-p pkg) #'identity 
#'package-desc-name) all)))))
 
 (defun package-strip-rcs-id (str)
   "Strip RCS version ID from the version string STR.
@@ -2625,6 +2638,57 @@ will be deleted."
                   removable))
         (message "Nothing to autoremove")))))
 
+(defun package-isolate (packages &optional temp-init)
+  "Start an uncustomised Emacs and only load a set of PACKAGES.
+If TEMP-INIT is non-nil, or when invoked with a prefix argument,
+the Emacs user directory is set to a temporary directory."
+  (interactive
+   (cl-loop for p in (cl-loop for p in (package--alist) append (cdr p))
+           unless (package-built-in-p p)
+           collect (cons (package-desc-full-name p) p) into table
+           finally return
+           (list (cl-loop for c in (completing-read-multiple
+                                     "Isolate packages: " table
+                                     nil t)
+                          collect (alist-get c table nil nil #'string=))
+                  current-prefix-arg)))
+  (let* ((name (concat "package-isolate-"
+                       (mapconcat #'package-desc-full-name packages ",")))
+         (all-packages (delete-consecutive-dups
+                        (sort (append packages (mapcan #'package--dependencies 
packages))
+                              (lambda (p0 p1)
+                                (string< (package-desc-name p0) 
(package-desc-name p1))))))
+         initial-scratch-message package-load-list)
+    (with-temp-buffer
+      (insert ";; This is an isolated testing environment, with these packages 
enabled:\n\n")
+      (dolist (package all-packages)
+        (push (list (package-desc-name package)
+                    (package-version-join (package-desc-version package)))
+              package-load-list)
+        (insert ";; - " (package-desc-full-name package))
+        (unless (memq package packages)
+          (insert " (dependency)"))
+        (insert "\n"))
+      (insert "\n")
+      (setq initial-scratch-message (buffer-string)))
+    (apply #'start-process (concat "*" name "*") nil
+           (list (expand-file-name invocation-name invocation-directory)
+                 "--quick" "--debug-init"
+                 "--init-directory" (if temp-init
+                                        (make-temp-file name t)
+                                      user-emacs-directory)
+                 (format "--eval=%S"
+                         `(progn
+                            (setq initial-scratch-message 
,initial-scratch-message)
+
+                            (require 'package)
+                            ,@(mapcar
+                               (lambda (dir)
+                                 `(add-to-list 'package-directory-list ,dir))
+                               (cons package-user-dir package-directory-list))
+                            (setq package-load-list ',package-load-list)
+                            (package-initialize)))))))
+
 
 ;;;; Package description buffer.
 
@@ -3088,18 +3152,36 @@ either a full name or nil, and EMAIL is a valid email 
address."
 
     "--"
     ("Filter Packages"
-     ["Filter by Archive" package-menu-filter-by-archive :help "Filter 
packages by archive"]
-     ["Filter by Description" package-menu-filter-by-description :help "Filter 
packages by description"]
-     ["Filter by Keyword" package-menu-filter-by-keyword :help "Filter 
packages by keyword"]
-     ["Filter by Name" package-menu-filter-by-name :help "Filter packages by 
name"]
+     ["Filter by Archive" package-menu-filter-by-archive
+      :help
+      "Prompt for archive(s), display only packages from those archives"]
+     ["Filter by Description" package-menu-filter-by-description
+      :help
+      "Prompt for regexp, display only packages with matching description"]
+     ["Filter by Keyword" package-menu-filter-by-keyword
+      :help
+      "Prompt for keyword(s), display only packages with matching keywords"]
+     ["Filter by Name" package-menu-filter-by-name
+      :help
+      "Prompt for regexp, display only packages whose names match the regexp"]
      ["Filter by Name or Description" 
package-menu-filter-by-name-or-description
-      :help "Filter packages by name or description"]
-     ["Filter by Status" package-menu-filter-by-status :help "Filter packages 
by status"]
-     ["Filter by Version" package-menu-filter-by-version :help "Filter 
packages by version"]
-     ["Filter Marked" package-menu-filter-marked :help "Filter packages marked 
for upgrade"]
-     ["Clear Filter" package-menu-clear-filter :help "Clear package list 
filter"])
-
-    ["Hide by Regexp" package-menu-hide-package :help "Hide all packages 
matching a regexp"]
+      :help
+      "Prompt for regexp, display only packages whose name or description 
matches"]
+     ["Filter by Status" package-menu-filter-by-status
+      :help
+      "Prompt for status(es), display only packages with those statuses"]
+     ["Filter by Upgrades available" package-menu-filter-upgradable
+      :help "Display only installed packages for which upgrades are available"]
+     ["Filter by Version" package-menu-filter-by-version
+      :help
+      "Prompt for version and comparison operator, display only packages of 
matching versions"]
+     ["Filter Marked" package-menu-filter-marked
+      :help "Display only packages marked for installation or deletion"]
+     ["Clear Filter" package-menu-clear-filter
+      :help "Clear package list filtering, display the entire list again"])
+
+    ["Hide by Regexp" package-menu-hide-package
+     :help "Toggle visibility of obsolete and unwanted packages"]
     ["Display Older Versions" package-menu-toggle-hiding
      :style toggle :selected (not package-menu--hide-packages)
      :help "Display package even if a newer version is already installed"]
@@ -4295,7 +4377,7 @@ STATUS can be a single status, a string, or a list of 
strings.
 If STATUS is nil or the empty string, show all packages.
 
 When called interactively, prompt for STATUS.  To specify
-several possible status values, type them seperated by commas."
+several possible status values, type them separated by commas."
   (interactive (list (completing-read "Filter by status: "
                                       '("avail-obso"
                                         "available"
@@ -4641,13 +4723,14 @@ DESC must be a `package-desc' object."
         vars)
     (dolist-with-progress-reporter (group custom-current-group-alist)
         "Scanning for modified user options..."
-      (dolist (ent (get (cdr group) 'custom-group))
-        (when (and (custom-variable-p (car ent))
-                   (boundp (car ent))
-                   (not (eq (custom--standard-value (car ent))
-                            (default-toplevel-value (car ent))))
-                   (file-in-directory-p (car group) (package-desc-dir desc)))
-          (push (car ent) vars))))
+      (when (and (car group)
+                 (file-in-directory-p (car group) (package-desc-dir desc)))
+        (dolist (ent (get (cdr group) 'custom-group))
+          (when (and (custom-variable-p (car ent))
+                     (boundp (car ent))
+                     (not (eq (custom--standard-value (car ent))
+                              (default-toplevel-value (car ent)))))
+            (push (car ent) vars)))))
     (dlet ((reporter-prompt-for-summary-p t))
       (reporter-submit-bug-report maint name vars))))
 
diff --git a/lisp/emacs-lisp/pp.el b/lisp/emacs-lisp/pp.el
index 550fab2f4b3..50e0e7d1da4 100644
--- a/lisp/emacs-lisp/pp.el
+++ b/lisp/emacs-lisp/pp.el
@@ -25,7 +25,6 @@
 ;;; Code:
 
 (require 'cl-lib)
-(defvar font-lock-verbose)
 
 (defgroup pp nil
   "Pretty printer for Emacs Lisp."
@@ -226,8 +225,10 @@ it inserts and pretty-prints that arg at point."
                     (if (eq lif 'defun) (setq lif 2))
                     (when (natnump lif)
                       (goto-char (match-end 0))
-                      (forward-sexp lif)
-                      (funcall newline)))))
+                      ;; Do nothing if there aren't enough args.
+                      (ignore-error scan-error
+                        (forward-sexp lif)
+                        (funcall newline))))))
               (save-excursion
                 (pp-fill (1+ paired) (1- (point)))))
             ;; Now the sexp either ends beyond `fill-column' or is
diff --git a/lisp/emacs-lisp/rx.el b/lisp/emacs-lisp/rx.el
index e82490ffee5..afc9826eefa 100644
--- a/lisp/emacs-lisp/rx.el
+++ b/lisp/emacs-lisp/rx.el
@@ -26,7 +26,7 @@
 ;; The translation to string regexp is done by a macro and does not
 ;; incur any extra processing during run time.  Example:
 ;;
-;;  (rx bos (or (not (any "^"))
+;;  (rx bos (or (not "^")
 ;;              (seq "^" (or " *" "["))))
 ;;
 ;; => "\\`\\(?:[^^]\\|\\^\\(?: \\*\\|\\[\\)\\)"
@@ -35,8 +35,43 @@
 ;; Olin Shivers's SRE, with concessions to Emacs regexp peculiarities,
 ;; and the older Emacs package Sregex.
 
+;;; Legacy syntax still accepted by rx:
+;;
+;; These are constructs from earlier rx and sregex implementations
+;; that were mistakes, accidents or just not very good ideas in hindsight.
+
+;; Obsolete: accepted but not documented
+;;
+;; Obsolete                     Preferred
+;; --------------------------------------------------------
+;; (not word-boundary)          not-word-boundary
+;; (not-syntax X)               (not (syntax X))
+;; not-wordchar                 (not wordchar)
+;; (not-char ...)               (not (any ...))
+;; any                          nonl, not-newline
+;; (repeat N FORM)              (= N FORM)
+;; (syntax CHARACTER)           (syntax NAME)
+;; (syntax CHAR-SYM)      [1]   (syntax NAME)
+;; (category chinse-two-byte)   (category chinese-two-byte)
+;; unibyte                      ascii
+;; multibyte                    nonascii
+;; --------------------------------------------------------
+;; [1]  where CHAR-SYM is a symbol with single-character name
+
+;; Obsolescent: accepted and documented but discouraged
+;;
+;; Obsolescent                    Preferred
+;; --------------------------------------------------------
+;; (and ...)                      (seq ...), (: ...), (sequence ...)
+;; anything                       anychar
+;; minimal-match, maximal-match   lazy ops: ??, *?, +?
+
+;; FIXME: Prepare a phase-out by emitting compile-time warnings about
+;; at least some of the legacy constructs above.
+
 ;;; Code:
 
+
 ;; The `rx--translate...' functions below return (REGEXP . PRECEDENCE),
 ;; where REGEXP is a list of string expressions that will be
 ;; concatenated into a regexp, and PRECEDENCE is one of
@@ -126,27 +161,23 @@ Each entry is:
   (or (cdr (assq name rx--local-definitions))
       (get name 'rx-definition)))
 
-(defun rx--expand-def (form)
-  "FORM expanded (once) if a user-defined construct; otherwise nil."
-  (cond ((symbolp form)
-         (let ((def (rx--lookup-def form)))
-           (and def
-                (if (cdr def)
-                    (error "Not an `rx' symbol definition: %s" form)
-                  (car def)))))
-        ((and (consp form) (symbolp (car form)))
-         (let* ((op (car form))
-                (def (rx--lookup-def op)))
+(defun rx--expand-def-form (form)
+  "List FORM expanded (once) if a user-defined construct; otherwise nil."
+  (let ((op (car form)))
+    (and (symbolp op)
+         (let ((def (rx--lookup-def op)))
            (and def
                 (if (cdr def)
-                    (rx--expand-template
-                     op (cdr form) (nth 0 def) (nth 1 def))
+                    (rx--expand-template op (cdr form) (nth 0 def) (nth 1 def))
                   (error "Not an `rx' form definition: %s" op)))))))
 
-;; TODO: Additions to consider:
-;; - A construct like `or' but without the match order guarantee,
-;;   maybe `unordered-or'.  Useful for composition or generation of
-;;   alternatives; permits more effective use of regexp-opt.
+(defun rx--expand-def-symbol (symbol)
+  "SYM expanded (once) if a user-defined name; otherwise nil."
+  (let ((def (rx--lookup-def symbol)))
+    (and def
+         (if (cdr def)
+             (error "Not an `rx' symbol definition: %s" symbol)
+           (car def)))))
 
 (defun rx--translate-symbol (sym)
   "Translate an rx symbol.  Return (REGEXP . PRECEDENCE)."
@@ -167,28 +198,19 @@ Each entry is:
     ('not-word-boundary           (cons (list "\\B") t))
     ('symbol-start                (cons (list "\\_<") t))
     ('symbol-end                  (cons (list "\\_>") t))
-    ('not-wordchar                (cons (list "\\W") t))
+    ('not-wordchar                (rx--translate '(not wordchar)))
     (_
      (cond
       ((let ((class (cdr (assq sym rx--char-classes))))
          (and class (cons (list (concat "[[:" (symbol-name class) ":]]")) t))))
 
-      ((let ((expanded (rx--expand-def sym)))
+      ((let ((expanded (rx--expand-def-symbol sym)))
          (and expanded (rx--translate expanded))))
 
       ;; For compatibility with old rx.
       ((let ((entry (assq sym rx-constituents)))
-         (and (progn
-                (while (and entry (not (stringp (cdr entry))))
-                  (setq entry
-                        (if (symbolp (cdr entry))
-                            ;; Alias for another entry.
-                            (assq (cdr entry) rx-constituents)
-                          ;; Wrong type, try further down the list.
-                          (assq (car entry)
-                                (cdr (memq entry rx-constituents))))))
-                entry)
-              (cons (list (cdr entry)) nil))))
+         (and entry (rx--translate-compat-symbol-entry entry))))
+
       (t (error "Unknown rx symbol `%s'" sym))))))
 
 (defun rx--enclose (left-str rexp right-str)
@@ -254,83 +276,225 @@ Left-fold the list L, starting with X, by the binary 
function F."
     (setq l (cdr l)))
   x)
 
-(defun rx--normalise-or-arg (form)
-  "Normalize the `or' argument FORM.
-Characters become strings, user-definitions and `eval' forms are expanded,
-and `or' forms are normalized recursively."
-  (cond ((characterp form)
+;; FIXME: flatten nested `or' patterns when performing char-pattern combining.
+;; The only reason for not flattening is to ensure regexp-opt processing
+;; (which we do for entire `or' patterns, not subsequences), but we
+;; obviously want to translate
+;;   (or "a" space (or "b" (+ nonl) word) "c")
+;;   -> (or (in "ab" space) (+ nonl) (in "c" word))
+
+;; FIXME: normalise `seq', both the construct and implicit sequences,
+;; so that they are flattened, adjacent strings concatenated, and
+;; empty strings removed. That would give more opportunities for regexp-opt:
+;;  (or "a" (seq "ab" (seq "c" "d") "")) -> (or "a" "abcd")
+
+;; FIXME: Since `rx--normalise-char-pattern' recurses through `or', `not' and
+;; `intersection', we may end up normalising subtrees multiple times
+;; which wastes time (but should be idempotent).
+;; One way to avoid this is to aggressively normalise the entire tree
+;; before translating anything at all, but we must then recurse through
+;; all constructs and probably copy them.
+;; Such normalisation could normalise synonyms, eliminate `minimal-match'
+;; and `maximal-match' and convert affected `1+' to either `+' or `+?' etc.
+;; We would also consolidate the user-def lookup, both modern and legacy,
+;; in one place.
+
+(defun rx--normalise-char-pattern (form)
+  "Normalize FORM as a pattern matching a single-character.
+Characters become strings, `any' forms and character classes become
+`rx--char-alt' forms, user-definitions and `eval' forms are expanded,
+and `or', `not' and `intersection' forms are normalized recursively.
+
+A `rx--char-alt' form is shaped (rx--char-alt INTERVALS . CLASSES)
+where INTERVALS is a sorted list of disjoint nonadjacent intervals,
+each a cons of characters, and CLASSES an unordered list of unique
+name-normalised character classes."
+  (defvar rx--builtin-forms)
+  (defvar rx--builtin-symbols)
+  (cond ((consp form)
+         (let ((op (car form))
+               (body (cdr form)))
+           (cond ((memq op '(or |))
+                  ;; Normalise the constructor to `or' and the args 
recursively.
+                  (cons 'or (mapcar #'rx--normalise-char-pattern body)))
+                 ;; Convert `any' forms and char classes now so that we
+                 ;; don't need to do it later on.
+                 ((memq op '(any in char))
+                  (cons 'rx--char-alt (rx--parse-any body)))
+                 ((memq op '(not intersection))
+                  (cons op (mapcar #'rx--normalise-char-pattern body)))
+                 ((eq op 'eval)
+                  (rx--normalise-char-pattern (rx--expand-eval body)))
+                 ((memq op rx--builtin-forms) form)
+                 ((let ((expanded (rx--expand-def-form form)))
+                    (and expanded
+                         (rx--normalise-char-pattern expanded))))
+                 (t form))))
+        ;; FIXME: Should we expand legacy definitions from
+        ;; `rx-constituents' here as well?
+        ((symbolp form)
+         (cond ((let ((class (assq form rx--char-classes)))
+                  (and class
+                       `(rx--char-alt nil . (,(cdr class))))))
+               ((memq form rx--builtin-symbols) form)
+               ((let ((expanded (rx--expand-def-symbol form)))
+                  (and expanded
+                       (rx--normalise-char-pattern expanded))))
+               (t form)))
+        ((characterp form)
          (char-to-string form))
-        ((and (consp form) (memq (car form) '(or |)))
-         (cons (car form) (mapcar #'rx--normalise-or-arg (cdr form))))
-        ((and (consp form) (eq (car form) 'eval))
-         (rx--normalise-or-arg (rx--expand-eval (cdr form))))
-        (t
-         (let ((expanded (rx--expand-def form)))
-           (if expanded
-               (rx--normalise-or-arg expanded)
-             form)))))
-
-(defun rx--all-string-or-args (body)
-  "If BODY only consists of strings or such `or' forms, return all the strings.
-Otherwise throw `rx--nonstring'."
+        (t form)))
+
+(defun rx--char-alt-union (a b)
+  "Union of the (INTERVALS . CLASSES) pairs A and B."
+  (let* ((a-cl (cdr a))
+         (b-cl (cdr b))
+         (classes (if (and a-cl b-cl)
+                      (let ((acc a-cl))
+                        (dolist (c b-cl)
+                          (unless (memq c a-cl)
+                            (push c acc)))
+                        acc)
+                    (or a-cl b-cl))))
+    (cons (rx--interval-set-union (car a) (car b)) classes)))
+
+(defun rx--intersection-intervals (forms)
+  "Intersection of the normalised FORMS, as an interval set."
+  (rx--foldl #'rx--interval-set-intersection '((0 . #x3fffff))
+             (mapcar (lambda (x)
+                       (let ((char (rx--reduce-to-char-alt x)))
+                         (if (and char (null (cdr char)))
+                             (car char)
+                           (error "Cannot be used in rx intersection: %S"
+                                  (rx--human-readable x)))))
+                     forms)))
+
+(defun rx--reduce-to-char-alt (form)
+  "Transform FORM into (INTERVALS . CLASSES) or nil if not possible.
+Process `or', `intersection' and `not'.
+FORM must be normalised (from `rx--normalise-char-pattern')."
+  (cond
+   ((stringp form)
+    (and (= (length form) 1)
+         (let ((c (aref form 0)))
+           (list (list (cons c c))))))
+   ((consp form)
+    (let ((head (car form)))
+      (cond
+       ;; FIXME: Transform `digit', `xdigit', `cntrl', `ascii', `nonascii'
+       ;; to ranges? That would allow them to be negated and intersected.
+       ((eq head 'rx--char-alt) (cdr form))
+       ((eq head 'not)
+        (unless (= (length form) 2)
+          (error "rx `not' form takes exactly one argument"))
+        (let ((arg (rx--reduce-to-char-alt (cadr form))))
+          ;; Only interval sets without classes are closed under complement.
+          (and arg (null (cdr arg))
+               (list (rx--interval-set-complement (car arg))))))
+       ((eq head 'or)
+        (let ((args (cdr form)))
+          (let ((acc '(nil)))  ; union identity
+            (while (and args
+                        (let ((char (rx--reduce-to-char-alt (car args))))
+                          (setq acc (and char (rx--char-alt-union acc char)))))
+              (setq args (cdr args)))
+            acc)))
+       ((eq head 'intersection)
+        (list (rx--intersection-intervals (cdr form))))
+       )))
+   ((memq form '(nonl not-newline any))
+    '(((0 . 9) (11 . #x3fffff))))
+   ((memq form '(anychar anything))
+    '(((0 . #x3fffff))))
+   ;; FIXME: A better handling of `unmatchable' would be:
+   ;;   * (seq ... unmatchable ...) -> unmatchable
+   ;;   * any or-pattern branch that is `unmatchable' is deleted
+   ;;   * (REPEAT unmatchable) -> "", if REPEAT accepts 0 repetitions
+   ;;   * (REPEAT unmatchable) -> unmatchable, otherwise
+   ;; if it's worth the trouble (probably not).
+   ((eq form 'unmatchable)
+    '(nil))
+   ))
+
+(defun rx--optimise-or-args (args)
+  "Optimise `or' arguments.  Return a new rx form.
+Each element of ARGS should have been normalised using
+`rx--normalise-char-pattern'."
+  (if (null args)
+      ;; No arguments.
+      '(rx--char-alt nil . nil)         ; FIXME: not `unmatchable'?
+    ;; Join consecutive single-char branches into a char alt where possible.
+    ;; Ideally we should collect all single-char branches but that might
+    ;; alter matching order in some cases.
+    (let ((branches nil)
+          (prev-char nil))
+      (while args
+        (let* ((item (car args))
+               (item-char (rx--reduce-to-char-alt item)))
+          (if item-char
+              (setq prev-char (if prev-char
+                                  (rx--char-alt-union prev-char item-char)
+                                item-char))
+            (when prev-char
+              (push (cons 'rx--char-alt prev-char) branches)
+              (setq prev-char nil))
+            (push item branches)))
+        (setq args (cdr args)))
+      (when prev-char
+        (push (cons 'rx--char-alt prev-char) branches))
+      (if (cdr branches)
+          (cons 'or (nreverse branches))
+        (car branches)))))
+
+(defun rx--all-string-branches-p (forms)
+  "Whether FORMS are all strings or `or' forms with the same property."
+  (rx--every (lambda (x) (or (stringp x)
+                             (and (eq (car-safe x) 'or)
+                                  (rx--all-string-branches-p (cdr x)))))
+             forms))
+
+(defun rx--collect-or-strings (forms)
+  "All strings from FORMS, which are strings or `or' forms."
   (mapcan (lambda (form)
-            (cond ((stringp form) (list form))
-                  ((and (consp form) (memq (car form) '(or |)))
-                   (rx--all-string-or-args (cdr form)))
-                  (t (throw 'rx--nonstring nil))))
-          body))
+            (if (stringp form)
+                (list form)
+              ;; must be an `or' form
+              (rx--collect-or-strings (cdr form))))
+          forms))
+
+;; TODO: Write a more general rx-level factoriser to replace
+;; `regexp-opt' for our purposes.  It would handle non-literals:
+;;
+;;    (or "ab" (: "a" space) "bc" (: "b" (+ digit)))
+;; -> (or (: "a" (in "b" space)) (: "b" (or "c" (+ digit))))
+;;
+;; As a minor side benefit we would get less useless bracketing.
+;; The main problem is how to deal with matching order, which `regexp-opt'
+;; alters in its own way.
 
 (defun rx--translate-or (body)
   "Translate an or-pattern of zero or more rx items.
 Return (REGEXP . PRECEDENCE)."
-  ;; FIXME: Possible improvements:
-  ;;
-  ;; - Flatten sub-patterns first: (or (or A B) (or C D)) -> (or A B C D)
-  ;;   Then call regexp-opt on runs of string arguments. Example:
-  ;;   (or (+ digit) "CHARLIE" "CHAN" (+ blank))
-  ;;   -> (or (+ digit) (or "CHARLIE" "CHAN") (+ blank))
-  ;;
-  ;; - Optimize single-character alternatives better:
-  ;;     * classes: space, alpha, ...
-  ;;     * (syntax S), for some S (whitespace, word)
-  ;;   so that (or "@" "%" digit (any "A-Z" space) (syntax word))
-  ;;        -> (any "@" "%" digit "A-Z" space word)
-  ;;        -> "[A-Z@%[:digit:][:space:][:word:]]"
   (cond
    ((null body)                    ; No items: a never-matching regexp.
     (rx--empty))
    ((null (cdr body))              ; Single item.
     (rx--translate (car body)))
    (t
-    (let* ((args (mapcar #'rx--normalise-or-arg body))
-           (all-strings (catch 'rx--nonstring (rx--all-string-or-args args))))
-      (cond
-       (all-strings                       ; Only strings.
-        (cons (list (regexp-opt all-strings nil))
-              t))
-       ((rx--every #'rx--charset-p args)  ; All charsets.
-        (rx--translate-union nil args))
-       (t
-        (cons (append (car (rx--translate (car args)))
-                      (mapcan (lambda (item)
-                                (cons "\\|" (car (rx--translate item))))
-                              (cdr args)))
-              nil)))))))
-
-(defun rx--charset-p (form)
-  "Whether FORM looks like a charset, only consisting of character intervals
-and set operations."
-  (or (and (consp form)
-           (or (and (memq (car form) '(any in char))
-                    (rx--every (lambda (x) (not (symbolp x))) (cdr form)))
-               (and (memq (car form) '(not or | intersection))
-                    (rx--every #'rx--charset-p (cdr form)))))
-      (characterp form)
-      (and (stringp form) (= (length form) 1))
-      (and (or (symbolp form) (consp form))
-           (let ((expanded (rx--expand-def form)))
-             (and expanded
-                  (rx--charset-p expanded))))))
+    (let ((args (mapcar #'rx--normalise-char-pattern body)))
+      (if (rx--all-string-branches-p args)
+          ;; All branches are strings: use `regexp-opt'.
+          (cons (list (regexp-opt (rx--collect-or-strings args) nil))
+                t)
+        (let ((form (rx--optimise-or-args args)))
+          (if (eq (car-safe form) 'or)
+              (let ((branches (cdr form)))
+                (cons (append (car (rx--translate (car branches)))
+                              (mapcan (lambda (item)
+                                        (cons "\\|" (car (rx--translate 
item))))
+                                      (cdr branches)))
+                      nil))
+            (rx--translate form))))))))
 
 (defun rx--string-to-intervals (str)
   "Decode STR as intervals: A-Z becomes (?A . ?Z), and the single
@@ -385,7 +549,7 @@ INTERVALS is a list of (START . END) with START ≤ END, 
sorted by START."
 (defun rx--parse-any (body)
   "Parse arguments of an (any ...) construct.
 Return (INTERVALS . CLASSES), where INTERVALS is a sorted list of
-disjoint intervals (each a cons of chars), and CLASSES
+disjoint nonadjacent intervals (each a cons of chars), and CLASSES
 a list of named character classes in the order they occur in BODY."
   (let ((classes nil)
         (strings nil)
@@ -412,106 +576,131 @@ a list of named character classes in the order they 
occur in BODY."
            (sort (append conses
                          (mapcan #'rx--string-to-intervals strings))
                  #'car-less-than-car))
-          (reverse classes))))
+          (nreverse classes))))
 
 (defun rx--generate-alt (negated intervals classes)
   "Generate a character alternative.  Return (REGEXP . PRECEDENCE).
 If NEGATED is non-nil, negate the result; INTERVALS is a sorted
 list of disjoint intervals and CLASSES a list of named character
 classes."
-  (let ((items (append intervals classes)))
-    ;; Move lone ] and range ]-x to the start.
-    (let ((rbrac-l (assq ?\] items)))
-      (when rbrac-l
-        (setq items (cons rbrac-l (delq rbrac-l items)))))
-
-    ;; Split x-] and move the lone ] to the start.
-    (let ((rbrac-r (rassq ?\] items)))
-      (when (and rbrac-r (not (eq (car rbrac-r) ?\])))
-        (setcdr rbrac-r ?\\)
-        (setq items (cons '(?\] . ?\]) items))))
-
-    ;; Split ,-- (which would end up as ,- otherwise).
-    (let ((dash-r (rassq ?- items)))
-      (when (eq (car dash-r) ?,)
-        (setcdr dash-r ?,)
-        (setq items (nconc items '((?- . ?-))))))
-
-    ;; Remove - (lone or at start of interval)
-    (let ((dash-l (assq ?- items)))
-      (when dash-l
-        (if (eq (cdr dash-l) ?-)
-            (setq items (delq dash-l items))   ; Remove lone -
-          (setcar dash-l ?.))                  ; Reduce --x to .-x
-        (setq items (nconc items '((?- . ?-))))))
-
-    ;; Deal with leading ^ and range ^-x.
-    (when (and (consp (car items))
-               (eq (caar items) ?^)
-               (cdr items))
-      ;; Move ^ and ^-x to second place.
-      (setq items (cons (cadr items)
-                        (cons (car items) (cddr items)))))
-
-    (cond
-     ;; Empty set: if negated, any char, otherwise match-nothing.
-     ((null items)
+  ;; No, this is not pretty code.  You try doing it in a way that is both
+  ;; elegant and efficient.  Or just one of the two.  I dare you.
+
+  ;; Detect whether the interval set is better described in
+  ;; complemented form.  This is not just a matter of aesthetics: any
+  ;; range that straddles the char-raw boundary will be mutilated by the
+  ;; regexp engine.  Ranges from ASCII to raw bytes will exclude the
+  ;; all non-ASCII non-raw bytes, and ranges from non-ASCII Unicode
+  ;; to raw bytes are ignored.
+  (unless (or classes
+              ;; Any interval set covering #x3fff7f should be negated.
+              (rx--every (lambda (iv) (not (<= (car iv) #x3fff7f (cdr iv))))
+                         intervals))
+    (setq negated (not negated))
+    (setq intervals (rx--interval-set-complement intervals)))
+  (cond
+   ;; Single character.
+   ((and intervals (eq (caar intervals) (cdar intervals))
+         (null (cdr intervals))
+         (null classes))
+    (let ((ch (caar intervals)))
       (if negated
-          (rx--translate-symbol 'anything)
-        (rx--empty)))
-     ;; Single non-negated character.
-     ((and (null (cdr items))
-           (consp (car items))
-           (eq (caar items) (cdar items))
-           (not negated))
-      (cons (list (regexp-quote (char-to-string (caar items))))
-            t))
-     ;; Negated newline.
-     ((and (equal items '((?\n . ?\n)))
-           negated)
-      (rx--translate-symbol 'nonl))
-     ;; At least one character or class, possibly negated.
-     (t
+          (if (eq ch ?\n)
+              ;; Single negated newline.
+              (rx--translate-symbol 'nonl)
+            ;; Single negated character (other than newline).
+            (cons (list (string ?\[ ?^ ch ?\])) t))
+        ;; Single literal character.
+        (cons (list (regexp-quote (char-to-string ch))) t))))
+
+   ;; Empty set (or any char).
+   ((and (null intervals) (null classes))
+    (if negated
+        (rx--translate-symbol 'anychar)
+      (rx--empty)))
+
+   ;; More than one character, or at least one class.
+   (t
+    (let ((dash nil) (caret nil))
+      ;; Move ] and range ]-x to the start.
+      (let ((rbrac-l (assq ?\] intervals)))
+        (when rbrac-l
+          (setq intervals (cons rbrac-l (remq rbrac-l intervals)))))
+
+      ;; Split x-] and move the lone ] to the start.
+      (let ((rbrac-r (rassq ?\] intervals)))
+        (when (and rbrac-r (not (eq (car rbrac-r) ?\])))
+          (setcdr rbrac-r ?\\)
+          (setq intervals (cons '(?\] . ?\]) intervals))))
+
+      ;; Split ,-- (which would end up as ,- otherwise).
+      (let ((dash-r (rassq ?- intervals)))
+        (when (eq (car dash-r) ?,)
+          (setcdr dash-r ?,)
+          (setq dash "-")))
+
+      ;; Remove - (lone or at start of interval)
+      (let ((dash-l (assq ?- intervals)))
+        (when dash-l
+          (if (eq (cdr dash-l) ?-)
+              (setq intervals (remq dash-l intervals))   ; Remove lone -
+            (setcar dash-l ?.))                          ; Reduce --x to .-x
+          (setq dash "-")))
+
+      ;; Deal with leading ^ and range ^-x in non-negated set.
+      (when (and (eq (caar intervals) ?^)
+                 (not negated))
+        (if (eq (cdar intervals) ?^)
+            ;; single leading ^
+            (if (or (cdr intervals) classes)
+                ;; something else to put before the ^
+                (progn
+                  (setq intervals (cdr intervals))   ; remove lone ^
+                  (setq caret "^"))                  ; put ^ (almost) last
+              ;; nothing else but a lone -
+              (setq intervals (cons '(?- . ?-) intervals))  ; move - first
+              (setq dash nil))
+          ;; split ^-x to _-x^
+          (setq intervals `((?_ . ,(cdar intervals)) (?^ . ?^)
+                            . ,(cdr intervals)))))
+
       (cons
        (list
         (concat
          "["
          (and negated "^")
-         (mapconcat (lambda (item)
-                      (cond ((symbolp item)
-                             (format "[:%s:]" item))
-                            ((eq (car item) (cdr item))
-                             (char-to-string (car item)))
-                            ((eq (1+ (car item)) (cdr item))
-                             (string (car item) (cdr item)))
+         (mapconcat (lambda (iv)
+                      (cond ((eq (car iv) (cdr iv))
+                             (char-to-string (car iv)))
+                            ((eq (1+ (car iv)) (cdr iv))
+                             (string (car iv) (cdr iv)))
+                            ;; Ranges that go between normal chars and raw 
bytes
+                            ;; must be split to avoid being mutilated
+                            ;; by Emacs's regexp parser.
+                            ((<= (car iv) #x3fff7f (cdr iv))
+                             (string (car iv) ?- #x3fff7f
+                                     #x3fff80 ?- (cdr iv)))
                             (t
-                             (string (car item) ?- (cdr item)))))
-                    items nil)
+                             (string (car iv) ?- (cdr iv)))))
+                    intervals)
+         (mapconcat (lambda (cls) (format "[:%s:]" cls)) classes)
+         caret                          ; ^ or nothing
+         dash                           ; - or nothing
          "]"))
        t)))))
 
+(defun rx--translate-char-alt (negated body)
+  "Translate a (rx--char-alt ...) construct.  Return (REGEXP . PRECEDENCE).
+If NEGATED, negate the sense."
+  (rx--generate-alt negated (car body) (cdr body)))
+
 (defun rx--translate-any (negated body)
   "Translate an (any ...) construct.  Return (REGEXP . PRECEDENCE).
 If NEGATED, negate the sense."
   (let ((parsed (rx--parse-any body)))
     (rx--generate-alt negated (car parsed) (cdr parsed))))
 
-(defun rx--intervals-to-alt (negated intervals)
-  "Generate a character alternative from an interval set.
-Return (REGEXP . PRECEDENCE).
-INTERVALS is a sorted list of disjoint intervals.
-If NEGATED, negate the sense."
-  ;; Detect whether the interval set is better described in
-  ;; complemented form.  This is not just a matter of aesthetics: any
-  ;; range from ASCII to raw bytes will automatically exclude the
-  ;; entire non-ASCII Unicode range by the regexp engine.
-  (if (rx--every (lambda (iv) (not (<= (car iv) #x3ffeff (cdr iv))))
-                 intervals)
-      (rx--generate-alt negated intervals nil)
-    (rx--generate-alt
-     (not negated) (rx--complement-intervals intervals) nil)))
-
-;; FIXME: Consider turning `not' into a variadic operator, following SRE:
+;; TODO: Consider turning `not' into a variadic operator, following SRE:
 ;; (not A B) = (not (or A B)) = (intersection (not A) (not B)), and
 ;; (not) = anychar.
 ;; Maybe allow singleton characters as arguments.
@@ -521,43 +710,27 @@ If NEGATED, negate the sense."
 If NEGATED, negate the sense (thus making it positive)."
   (unless (and body (null (cdr body)))
     (error "rx `not' form takes exactly one argument"))
-  (let ((arg (car body)))
-    (cond
-     ((and (consp arg)
-           (pcase (car arg)
-             ((or 'any 'in 'char)
-              (rx--translate-any      (not negated) (cdr arg)))
-             ('syntax
-              (rx--translate-syntax   (not negated) (cdr arg)))
-             ('category
-              (rx--translate-category (not negated) (cdr arg)))
-             ('not
-              (rx--translate-not      (not negated) (cdr arg)))
-             ((or 'or '|)
-              (rx--translate-union    (not negated) (cdr arg)))
-             ('intersection
-              (rx--translate-intersection (not negated) (cdr arg))))))
-     ((let ((class (cdr (assq arg rx--char-classes))))
-        (and class
-             (rx--generate-alt (not negated) nil (list class)))))
-     ((eq arg 'word-boundary)
-      (rx--translate-symbol
-       (if negated 'word-boundary 'not-word-boundary)))
-     ((characterp arg)
-      (rx--generate-alt (not negated) (list (cons arg arg)) nil))
-     ((and (stringp arg) (= (length arg) 1))
-      (let ((char (string-to-char arg)))
-        (rx--generate-alt (not negated) (list (cons char char)) nil)))
-     ((let ((expanded (rx--expand-def arg)))
-        (and expanded
-             (rx--translate-not negated (list expanded)))))
-     (t (error "Illegal argument to rx `not': %S" arg)))))
-
-(defun rx--complement-intervals (intervals)
-  "Complement of the interval list INTERVALS."
+  (let ((arg (rx--normalise-char-pattern (car body))))
+    (pcase arg
+      (`(not . ,args)
+       (rx--translate-not      (not negated) args))
+      (`(syntax . ,args)
+       (rx--translate-syntax   (not negated) args))
+      (`(category . ,args)
+       (rx--translate-category (not negated) args))
+      ('word-boundary                     ; legacy syntax
+       (rx--translate-symbol (if negated 'word-boundary 'not-word-boundary)))
+      (_ (let ((char (rx--reduce-to-char-alt arg)))
+           (if char
+               (rx--generate-alt (not negated) (car char) (cdr char))
+             (error "Illegal argument to rx `not': %S"
+                    (rx--human-readable arg))))))))
+
+(defun rx--interval-set-complement (ivs)
+  "Complement of the interval set IVS."
   (let ((compl nil)
         (c 0))
-    (dolist (iv intervals)
+    (dolist (iv ivs)
       (when (< c (car iv))
         (push (cons c (1- (car iv))) compl))
       (setq c (1+ (cdr iv))))
@@ -565,8 +738,8 @@ If NEGATED, negate the sense (thus making it positive)."
       (push (cons c (max-char)) compl))
     (nreverse compl)))
 
-(defun rx--intersect-intervals (ivs-a ivs-b)
-  "Intersection of the interval lists IVS-A and IVS-B."
+(defun rx--interval-set-intersection (ivs-a ivs-b)
+  "Intersection of the interval sets IVS-A and IVS-B."
   (let ((isect nil))
     (while (and ivs-a ivs-b)
       (let ((a (car ivs-a))
@@ -588,60 +761,91 @@ If NEGATED, negate the sense (thus making it positive)."
                        ivs-a)))))))
     (nreverse isect)))
 
-(defun rx--union-intervals (ivs-a ivs-b)
-  "Union of the interval lists IVS-A and IVS-B."
-  (rx--complement-intervals
-   (rx--intersect-intervals
-    (rx--complement-intervals ivs-a)
-    (rx--complement-intervals ivs-b))))
-
-(defun rx--charset-intervals (charset)
-  "Return a sorted list of non-adjacent disjoint intervals from CHARSET.
-CHARSET is any expression allowed in a character set expression:
-characters, single-char strings, `any' forms (no classes permitted),
-or `not', `or' or `intersection' forms whose arguments are charsets."
-  (pcase charset
-    (`(,(or 'any 'in 'char) . ,body)
-     (let ((parsed (rx--parse-any body)))
-       (when (cdr parsed)
-         (error
-          "Character class not permitted in set operations: %S"
-          (cadr parsed)))
-       (car parsed)))
-    (`(not ,x) (rx--complement-intervals (rx--charset-intervals x)))
-    (`(,(or 'or '|) . ,body) (rx--charset-union body))
-    (`(intersection . ,body) (rx--charset-intersection body))
-    ((pred characterp)
-     (list (cons charset charset)))
-    ((guard (and (stringp charset) (= (length charset) 1)))
-     (let ((char (string-to-char charset)))
-       (list (cons char char))))
-    (_ (let ((expanded (rx--expand-def charset)))
-         (if expanded
-             (rx--charset-intervals expanded)
-           (error "Bad character set: %S" charset))))))
-
-(defun rx--charset-union (charsets)
-  "Union of CHARSETS, as a set of intervals."
-  (rx--foldl #'rx--union-intervals nil
-             (mapcar #'rx--charset-intervals charsets)))
-
-(defconst rx--charset-all (list (cons 0 (max-char))))
-
-(defun rx--charset-intersection (charsets)
-  "Intersection of CHARSETS, as a set of intervals."
-  (rx--foldl #'rx--intersect-intervals rx--charset-all
-             (mapcar #'rx--charset-intervals charsets)))
-
-(defun rx--translate-union (negated body)
-  "Translate an (or ...) construct of charsets.  Return (REGEXP . PRECEDENCE).
-If NEGATED, negate the sense."
-  (rx--intervals-to-alt negated (rx--charset-union body)))
+(defun rx--interval-set-union (ivs-a ivs-b)
+  "Union of the interval sets IVS-A and IVS-B."
+  (let ((union nil))
+    (while (and ivs-a ivs-b)
+      (let ((a (car ivs-a))
+            (b (car ivs-b)))
+        (cond
+         ((< (1+ (cdr a)) (car b))      ; a before b, not adacent
+          (push a union)
+          (setq ivs-a (cdr ivs-a)))
+         ((< (1+ (cdr b)) (car a))      ; b before a, not adacent
+          (push b union)
+          (setq ivs-b (cdr ivs-b)))
+         (t                             ; a and b adjacent or overlap
+          (setq ivs-a (cdr ivs-a))
+          (setq ivs-b (cdr ivs-b))
+          (if (< (cdr a) (cdr b))
+              (push (cons (min (car a) (car b))
+                          (cdr b))
+                    ivs-b)
+            (push (cons (min (car a) (car b))
+                        (cdr a))
+                  ivs-a))))))
+    (nconc (nreverse union) (or ivs-a ivs-b))))
+
+(defun rx--human-readable (form)
+  "Turn FORM into something that is more human-readable, for error messages."
+  ;; FIXME: Should we produce a string instead?
+  ;; That way we wouldn't have problems with ? and ??, and we could escape
+  ;; single chars.
+  ;; We could steal `xr--rx-to-string' and just file off the serials.
+  (let ((recurse (lambda (op skip)
+                   (cons op (append (take skip (cdr form))
+                                    (mapcar #'rx--human-readable
+                                            (nthcdr skip (cdr form))))))))
+  (pcase form
+    ;; strings are more readable than numbers for single chars
+    ((pred characterp) (char-to-string form))
+    ;; resugar `rx--char-alt'
+    (`(rx--char-alt ((,c . ,c)) . nil)
+     (char-to-string form))
+    (`(rx--char-alt nil . (,class))
+     class)
+    ;; TODO: render in complemented form if more readable that way?
+    (`(rx--char-alt ,ivs . ,classes)
+     (let ((strings (mapcan (lambda (iv)
+                              (let ((beg (car iv))
+                                    (end (cdr iv)))
+                                (cond
+                                 ;; single char
+                                 ((eq beg end)
+                                  (list (string beg)))
+                                 ;; two chars
+                                 ((eq end (1+ beg))
+                                  (list (string beg) (string end)))
+                                 ;; first char is hyphen
+                                 ((eq beg ?-)
+                                  (cons (string "-")
+                                        (if (eq end (+ ?- 2))
+                                            (list (string (1+ ?-) end))
+                                          (list (string (1+ ?-) ?- end)))))
+                                 ;; other range
+                                 (t (list (string beg ?- end))))))
+                            ivs)))
+       `(any ,@strings ,@classes)))
+    ;; avoid numbers as ops
+    (`(?  . ,_) (funcall recurse '\? 0))
+    (`(??  . ,_) (funcall recurse '\?? 0))
+    ;; recurse on arguments
+    (`(repeat ,_ ,_) (funcall recurse (car form) 1))
+    (`(,(or '** 'repeat) . ,_) (funcall recurse (car form) 2))
+    (`(,(or '= '>= 'group-n 'submatch-n) . ,_) (funcall recurse (car form) 1))
+    (`(,(or 'backref 'syntax 'not-syntax 'category
+            'eval 'regex 'regexp 'literal)
+       . ,_)
+     form)
+    (`(,_ . ,_) (funcall recurse (car form) 0))
+    (_ form))))
 
 (defun rx--translate-intersection (negated body)
   "Translate an (intersection ...) construct.  Return (REGEXP . PRECEDENCE).
 If NEGATED, negate the sense."
-  (rx--intervals-to-alt negated (rx--charset-intersection body)))
+  (rx--generate-alt negated (rx--intersection-intervals
+                             (mapcar #'rx--normalise-char-pattern body))
+                    nil))
 
 (defun rx--atomic-regexp (item)
   "ITEM is (REGEXP . PRECEDENCE); return a regexp of precedence t."
@@ -777,7 +981,10 @@ Return (REGEXP . PRECEDENCE)."
                 (setq syntax char)))))))
       (unless syntax
         (error "Unknown rx syntax name `%s'" sym)))
-    (cons (list (string ?\\ (if negated ?S ?s) syntax))
+    ;; Produce \w and \W instead of \sw and \Sw, for smaller size.
+    (cons (list (if (eq syntax ?w)
+                    (string ?\\ (if negated ?W ?w))
+                  (string ?\\ (if negated ?S ?s) syntax)))
           t)))
 
 (defconst rx--categories
@@ -888,15 +1095,15 @@ Return (REGEXP . PRECEDENCE)."
                                (opt "^")
                                (opt "]")
                                (* (or (seq "[:" (+ (any "a-z")) ":]")
-                                      (not (any "]"))))
+                                      (not "]")))
                                "]")
                           (not (any "*+?^$[\\"))
                           (seq "\\"
-                               (or anything
-                                   (seq (any "sScC_") anything)
+                               (or anychar
+                                   (seq (any "sScC_") anychar)
                                    (seq "("
-                                        (* (or (not (any "\\"))
-                                               (seq "\\" (not (any ")")))))
+                                        (* (or (not "\\")
+                                               (seq "\\" (not ")"))))
                                         "\\)"))))
                       eos)
                     t)))
@@ -928,6 +1135,36 @@ DEF is the definition tuple.  Return (REGEXP . 
PRECEDENCE)."
         (error "The `%s' form did not expand to a string" (car form)))
       (cons (list regexp) nil))))
 
+(defun rx--translate-compat-symbol-entry (entry)
+  "Translate a compatibility symbol definition for ENTRY.
+Return (REGEXP . PRECEDENCE) or nil if none."
+  (and (progn
+         (while (and entry (not (stringp (cdr entry))))
+           (setq entry
+                 (if (symbolp (cdr entry))
+                     ;; Alias for another entry.
+                     (assq (cdr entry) rx-constituents)
+                   ;; Wrong type, try further down the list.
+                   (assq (car entry)
+                         (cdr (memq entry rx-constituents))))))
+         entry)
+       (cons (list (cdr entry)) nil)))
+
+(defun rx--translate-compat-form-entry (orig-form entry)
+  "Translate a compatibility ORIG-FORM definition for ENTRY.
+Return (REGEXP . PRECEDENCE) or nil if none."
+  (and (progn
+         (while (and entry (not (consp (cdr entry))))
+           (setq entry
+                 (if (symbolp (cdr entry))
+                     ;; Alias for another entry.
+                     (assq (cdr entry) rx-constituents)
+                   ;; Wrong type, try further down the list.
+                   (assq (car entry)
+                         (cdr (memq entry rx-constituents))))))
+         entry)
+       (rx--translate-compat-form (cdr entry) orig-form)))
+
 (defun rx--substitute (bindings form)
   "Substitute BINDINGS in FORM.  BINDINGS is an alist of (NAME . VALUES)
 where VALUES is a list to splice into FORM wherever NAME occurs.
@@ -1023,6 +1260,7 @@ can expand to any number of values."
       ((or 'seq : 'and 'sequence) (rx--translate-seq body))
       ((or 'or '|)              (rx--translate-or body))
       ((or 'any 'in 'char)      (rx--translate-any nil body))
+      ('rx--char-alt            (rx--translate-char-alt nil body))
       ('not-char                (rx--translate-any t body))
       ('not                     (rx--translate-not nil body))
       ('intersection            (rx--translate-intersection nil body))
@@ -1063,23 +1301,13 @@ can expand to any number of values."
        (cond
         ((not (symbolp op)) (error "Bad rx operator `%S'" op))
 
-        ((let ((expanded (rx--expand-def form)))
+        ((let ((expanded (rx--expand-def-form form)))
            (and expanded
                 (rx--translate expanded))))
 
         ;; For compatibility with old rx.
         ((let ((entry (assq op rx-constituents)))
-           (and (progn
-                  (while (and entry (not (consp (cdr entry))))
-                    (setq entry
-                          (if (symbolp (cdr entry))
-                              ;; Alias for another entry.
-                              (assq (cdr entry) rx-constituents)
-                            ;; Wrong type, try further down the list.
-                            (assq (car entry)
-                                  (cdr (memq entry rx-constituents))))))
-                  entry)
-                (rx--translate-compat-form (cdr entry) form))))
+           (and entry (rx--translate-compat-form-entry form entry))))
 
         (t (error "Unknown rx form `%s'" op)))))))
 
diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el
index e23db8e999c..dbc061d8a70 100644
--- a/lisp/emacs-lisp/shortdoc.el
+++ b/lisp/emacs-lisp/shortdoc.el
@@ -153,14 +153,14 @@ A FUNC form can have any number of `:no-eval' (or 
`:no-value'),
    :eval (let* ((old '((foo . bar)))
                 (new (copy-alist old)))
            (eq old new)))
-  ;; FIXME: Outputs "\.rose" for the symbol `.rose'.
-  ;; (let-alist
-  ;;     :eval (let ((colors '((rose . red)
-  ;;                           (lily . white))))
-  ;;             (let-alist colors
-  ;;               (if (eq .rose 'red)
-  ;;                   .lily))))
-  )
+  ;; FIXME: Outputs "\.rose" for the symbol `.rose'.  It would be
+  ;; better if that could be cleaned up.
+  (let-alist
+      :eval (let ((colors '((rose . red)
+                            (lily . white))))
+              (let-alist colors
+                (if (eq .rose 'red)
+                    .lily)))))
 
 (define-short-documentation-group string
   "Making Strings"
@@ -642,6 +642,8 @@ A FUNC form can have any number of `:no-eval' (or 
`:no-value'),
   (delete
    :eval (delete 2 (list 1 2 3 4))
    :eval (delete "a" (list "a" "b" "c" "d")))
+  (remq
+   :eval (remq 'b '(a b c)))
   (remove
    :eval (remove 2 '(1 2 3 4))
    :eval (remove "a" '("a" "b" "c" "d")))
@@ -686,8 +688,6 @@ A FUNC form can have any number of `:no-eval' (or 
`:no-value'),
   (member
    :eval (member 2 '(1 2 3))
    :eval (member "b" '("a" "b" "c")))
-  (remq
-   :eval (remq 'b '(a b c)))
   (member-ignore-case
    :eval (member-ignore-case "foo" '("bar" "Foo" "zot")))
   "Association Lists"
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index 9e906930b92..572822351b1 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -312,9 +312,13 @@ it makes no sense to convert it to a string using
 Like `let', bind variables in BINDINGS and then evaluate BODY,
 but with the twist that BODY can evaluate itself recursively by
 calling NAME, where the arguments passed to NAME are used
-as the new values of the bound variables in the recursive invocation."
+as the new values of the bound variables in the recursive invocation.
+
+This construct can only be used with lexical binding."
   (declare (indent 2) (debug (symbolp (&rest (symbolp form)) body)))
   (require 'cl-lib)
+  (unless lexical-binding
+    (error "`named-let' requires lexical binding"))
   (let ((fargs (mapcar (lambda (b) (if (consp b) (car b) b)) bindings))
         (aargs (mapcar (lambda (b) (if (consp b) (cadr b))) bindings)))
     ;; According to the Scheme semantics of named let, `name' is not in scope
@@ -337,12 +341,15 @@ as the new values of the bound variables in the recursive 
invocation."
     ;; Keeping a work buffer around is more efficient than creating a
     ;; new temporary buffer.
     (with-current-buffer (get-buffer-create " *string-pixel-width*")
-      ;; `display-line-numbers-mode' is enabled in internal buffers
-      ;; that breaks width calculation, so need to disable (bug#59311)
+      ;; If `display-line-numbers-mode' is enabled in internal
+      ;; buffers, it breaks width calculation, so disable it (bug#59311)
       (when (bound-and-true-p display-line-numbers-mode)
         (display-line-numbers-mode -1))
       (delete-region (point-min) (point-max))
-      (insert string)
+      ;; Disable line-prefix and wrap-prefix, for the same reason.
+      (setq line-prefix nil
+           wrap-prefix nil)
+      (insert (propertize string 'line-prefix nil 'wrap-prefix nil))
       (car (buffer-text-pixel-size nil nil t)))))
 
 ;;;###autoload
diff --git a/lisp/emacs-lisp/timer.el b/lisp/emacs-lisp/timer.el
index 7544279d8aa..468c46519fd 100644
--- a/lisp/emacs-lisp/timer.el
+++ b/lisp/emacs-lisp/timer.el
@@ -1,6 +1,6 @@
 ;;; timer.el --- run a function with args at some time in future -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 1996, 2001-2023 Free Software Foundation, Inc.
+;; Copyright (C) 1996-2023 Free Software Foundation, Inc.
 
 ;; Maintainer: emacs-devel@gnu.org
 ;; Package: emacs
@@ -226,8 +226,6 @@ the time of the current timer.  That's because the activated
 timer will fire right away."
   (timer--activate timer (not dont-wait) reuse-cell 'idle))
 
-(defalias 'disable-timeout #'cancel-timer)
-
 (defun cancel-timer (timer)
   "Remove TIMER from the list of active timers."
   (timer--check timer)
@@ -348,7 +346,6 @@ This function is called, by name, directly by the C code."
                    (memq timer timer-list))
           (setf (timer--triggered timer) nil))))))
 
-;; This function is incompatible with the one in levents.el.
 (defun timeout-event-p (event)
   "Non-nil if EVENT is a timeout event."
   (and (listp event) (eq (car event) 'timer-event)))
@@ -448,6 +445,7 @@ If REPEAT is non-nil, repeat the timer every REPEAT seconds.
 
 This function returns a timer object which you can use in `cancel-timer'.
 This function is for compatibility; see also `run-with-timer'."
+  (declare (obsolete run-with-timer "30.1"))
   (run-with-timer secs repeat function object))
 
 (defun run-with-idle-timer (secs repeat function &rest args)
@@ -580,6 +578,9 @@ If the user does not answer after SECONDS seconds, return 
DEFAULT-VALUE."
   (dolist (timer timer-idle-list)
     (if (timerp timer) ;; FIXME: Why test?
         (setf (timer--triggered timer) nil))))
+
+(define-obsolete-function-alias 'disable-timeout #'cancel-timer "30.1")
+
 
 (provide 'timer)
 
diff --git a/lisp/emulation/cua-base.el b/lisp/emulation/cua-base.el
index a6d6f47ead5..6d21088cb29 100644
--- a/lisp/emulation/cua-base.el
+++ b/lisp/emulation/cua-base.el
@@ -1134,7 +1134,7 @@ If ARG is the atom `-', scroll upward by nearly full 
screen."
 
 (defun cua--M/H-key (map key fct)
   ;; bind H-KEY or M-KEY to FCT in MAP
-  (unless (listp key) (setq key (list key)))
+  (setq key (ensure-list key))
   (define-key map (vector (cons cua--rectangle-modifier-key key)) fct))
 
 (defun cua--self-insert-char-p (def)
diff --git a/lisp/emulation/viper-cmd.el b/lisp/emulation/viper-cmd.el
index c0aa9dd7b46..78114db0972 100644
--- a/lisp/emulation/viper-cmd.el
+++ b/lisp/emulation/viper-cmd.el
@@ -4724,15 +4724,15 @@ Please, specify your level now: "))
 (defun viper-submit-report ()
   "Submit bug report on Viper."
   (interactive)
-  (defvar x-display-color-p)
+  (defvar display-color-p)
   (defvar viper-frame-parameters)
   (defvar viper-minibuffer-emacs-face)
   (defvar viper-minibuffer-vi-face)
   (defvar viper-minibuffer-insert-face)
   (let ((reporter-prompt-for-summary-p t)
-        (x-display-color-p (if (viper-window-display-p)
-                              (x-display-color-p)
-                             'non-x))
+        (display-color-p (if (viper-window-display-p)
+                             (display-color-p)
+                           'non-x))
         (viper-frame-parameters (frame-parameters (selected-frame)))
        (viper-minibuffer-emacs-face (if (viper-has-face-support-p)
                                          (facep
@@ -4790,7 +4790,7 @@ Please, specify your level now: "))
                        'viper-expert-level
                        'major-mode
                        'window-system
-                        'x-display-color-p
+                        'display-color-p
                        'viper-frame-parameters
                        'viper-minibuffer-vi-face
                        'viper-minibuffer-insert-face
diff --git a/lisp/epa-file.el b/lisp/epa-file.el
index 4d8ca11e809..a27f241c0c3 100644
--- a/lisp/epa-file.el
+++ b/lisp/epa-file.el
@@ -123,9 +123,16 @@ encryption is used."
              (cons "Opening input file" (cdr error))))))
 
 (defun epa--wrong-password-p (context)
+  "Return whether a wrong password caused the error in CONTEXT."
   (let ((error-string (epg-context-error-output context)))
+    ;; Use a strict regexp here that really only matches "wrong
+    ;; passphrase" errors to avoid hiding diagnostic information
+    ;; (bug#65316).  Below regexp also can fail to match non-English
+    ;; messages, since at least the "decryption failed" part of it
+    ;; seems to be localized.  But since this means false negatives
+    ;; this is probably OK.
     (and (string-match
-          "decryption failed: \\(Bad session key\\|No secret key\\)"
+          "decryption failed: \\(Bad session key\\|Bad passphrase\\)"
           error-string)
          (match-string 1 error-string))))
 
diff --git a/lisp/epg.el b/lisp/epg.el
index 9da5a36ba3d..b8423d82a26 100644
--- a/lisp/epg.el
+++ b/lisp/epg.el
@@ -1264,8 +1264,7 @@ callback data (if any)."
        keys string field index)
     (if name
        (progn
-         (unless (listp name)
-           (setq name (list name)))
+          (setq name (ensure-list name))
          (while name
            (setq args (append args (list list-keys-option (car name)))
                  name (cdr name))))
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 363509d17fa..eb3ec39fedd 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -1045,13 +1045,25 @@ Conditionally try to reconnect and take appropriate 
action."
       ;; unexpected disconnect
       (erc-process-sentinel-2 event buffer))))
 
+(cl-defmethod erc--reveal-prompt ()
+  (remove-text-properties erc-insert-marker erc-input-marker
+                          '(display nil)))
+
+(cl-defmethod erc--conceal-prompt ()
+  (add-text-properties erc-insert-marker (1- erc-input-marker)
+                       `(display ,erc-prompt-hidden)))
+
+(defun erc--prompt-hidden-p ()
+  (and (marker-position erc-insert-marker)
+       (eq (get-text-property erc-insert-marker 'erc-prompt) 'hidden)))
+
 (defun erc--unhide-prompt ()
   (remove-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert t)
   (when (and (marker-position erc-insert-marker)
              (marker-position erc-input-marker))
     (with-silent-modifications
-      (remove-text-properties erc-insert-marker erc-input-marker
-                              '(display nil)))))
+      (put-text-property erc-insert-marker (1- erc-input-marker) 'erc-prompt t)
+      (erc--reveal-prompt))))
 
 (defun erc--unhide-prompt-on-self-insert ()
   (when (and (eq this-command #'self-insert-command)
@@ -1059,6 +1071,8 @@ Conditionally try to reconnect and take appropriate 
action."
     (erc--unhide-prompt)))
 
 (defun erc--hide-prompt (proc)
+  "Hide prompt in all buffers of server.
+Change value of property `erc-prompt' from t to `hidden'."
   (erc-with-all-buffers-of-server proc nil
     (when (and erc-hide-prompt
                (or (eq erc-hide-prompt t)
@@ -1072,8 +1086,9 @@ Conditionally try to reconnect and take appropriate 
action."
                (marker-position erc-input-marker)
                (get-text-property erc-insert-marker 'erc-prompt))
       (with-silent-modifications
-        (add-text-properties erc-insert-marker (1- erc-input-marker)
-                             `(display ,erc-prompt-hidden)))
+        (put-text-property erc-insert-marker (1- erc-input-marker)
+                           'erc-prompt 'hidden)
+        (erc--conceal-prompt))
       (add-hook 'pre-command-hook #'erc--unhide-prompt-on-self-insert 91 t))))
 
 (defun erc-process-sentinel (cproc event)
diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el
index 89a6cd131c0..8c1188e64a2 100644
--- a/lisp/erc/erc-button.el
+++ b/lisp/erc/erc-button.el
@@ -279,8 +279,13 @@ themselves."
          " entries are deprecated. Either use a variable or a function"
          " that conditionally calls `erc-button-add-button'.")))))
 
-(defvar erc-button-nickname-callback-function #'erc-nick-popup
-  "Escape hatch for those needing a different nickname callback.")
+(defvar erc-button-nickname-callback-function #'erc-button--perform-nick-popup
+  "Escape hatch for users needing a non-standard nick-button callback.
+Value should be a function accepting a NICK and any number of
+trailing arguments that are as yet unspecified.  Runs when
+clicking \\`<mouse-1>' or hitting \\`RET' atop a nickname button.")
+(make-obsolete-variable 'erc-button-nickname-callback-function
+                        "default provides essential functionality" "30.1")
 
 (defun erc-button-add-buttons ()
   "Find external references in the current buffer and make buttons of them.
@@ -745,6 +750,10 @@ In server buffers, also prompt for a channel."
           (funcall code nick)
         (eval code `((nick . ,nick)))))))
 
+(defun erc-button--perform-nick-popup (nick &rest _)
+  "Call `erc-nick-popup' with NICK."
+  (erc-nick-popup nick))
+
 ;;; Callback functions
 (defun erc-button-describe-symbol (symbol-name)
   "Describe SYMBOL-NAME.
@@ -778,33 +787,31 @@ and `apropos' for other symbols."
     (erc-button-add-button from (point) fun nick-p data regexp)))
 
 ;;;###autoload
-(defun erc-button--display-error-notice-with-keys (&optional parsed buffer
-                                                             &rest strings)
+(defun erc-button--display-error-notice-with-keys (maybe-buffer &rest strings)
   "Add help keys to STRINGS for configuration-related admonishments.
-Return inserted result.  Expect PARSED to be an `erc-response'
-object, a string, or nil.  Expect BUFFER to be a buffer, a string,
-or nil.  As a special case, allow PARSED to be a buffer as long
-as BUFFER is a string or nil.  If STRINGS contains any trailing
+Return inserted result.  Expect MAYBE-BUFFER to be an ERC buffer,
+a string, or nil.  When it's a buffer, specify the `buffer'
+argument when calling `erc-display-message'.  Otherwise, add it
+to STRINGS.  If STRINGS contains any trailing non-nil
 non-strings, concatenate leading string members before applying
 `format'.  Otherwise, just concatenate everything."
-  (when (stringp buffer)
-    (push buffer strings)
-    (setq buffer nil))
-  (when (stringp parsed)
-    (push parsed strings)
-    (setq parsed nil))
-  (when (bufferp parsed)
-    (cl-assert (null buffer))
-    (setq buffer parsed
-          parsed nil))
-  (let* ((op (if (seq-every-p #'stringp (cdr strings))
+  (let* ((buffer (if (bufferp maybe-buffer)
+                     maybe-buffer
+                   (when (stringp maybe-buffer)
+                     (push maybe-buffer strings))
+                   'active))
+         (op (if (seq-every-p (lambda (o) (or (not o) (stringp o)))
+                              (cdr strings))
                  #'concat
                (let ((head (pop strings)))
-                 (while (stringp (car strings))
+                 (while (or (stringp (car strings))
+                            (and strings (not (car strings))))
                    (setq head (concat head (pop strings))))
                  (push head strings))
                #'format))
          (string (apply op strings))
+         (erc-insert-modify-hook (remove 'erc-add-timestamp
+                                         erc-insert-modify-hook))
          (erc-insert-post-hook
           (cons (lambda ()
                   (setq string (buffer-substring (point-min)
@@ -815,7 +822,7 @@ non-strings, concatenate leading string members before 
applying
              erc-button--display-error-with-buttons
              erc-button-describe-symbol 1)
             ,@erc-button-alist)))
-    (erc-display-message parsed '(t notice error) (or buffer 'active) string)
+    (erc-display-message nil '(t notice error) buffer string)
     string))
 
 ;;;###autoload
diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el
index 08c11d518a8..85971797c2f 100644
--- a/lisp/erc/erc-common.el
+++ b/lisp/erc/erc-common.el
@@ -475,12 +475,11 @@ Use the CASEMAPPING ISUPPORT parameter to determine the 
style."
 
 (defmacro erc--with-dependent-type-match (type &rest features)
   "Massage Custom :type TYPE with :match function that pre-loads FEATURES."
-  `(backquote (,(car type)
-               :match
-               ,(list '\, `(lambda (w v)
+  `(backquote-list* ',(car type)
+                    :match (lambda (w v)
                              ,@(mapcar (lambda (ft) `(require ',ft)) features)
-                             (,(widget-get (widget-convert type) :match) w v)))
-               ,@(cdr type))))
+                             (,(widget-get (widget-convert type) :match) w v))
+                    ',(cdr type)))
 
 (provide 'erc-common)
 
diff --git a/lisp/erc/erc-compat.el b/lisp/erc/erc-compat.el
index f451aaee754..109b5d245ab 100644
--- a/lisp/erc/erc-compat.el
+++ b/lisp/erc/erc-compat.el
@@ -418,7 +418,7 @@ If START or END is negative, it counts from the end."
   (require 'url-irc)
   (let* ((url (url-generic-parse-url string))
          (url-irc-function
-          (if (function-equal url-irc-function 'url-irc-erc)
+          (if (eq url-irc-function 'url-irc-erc)
               (lambda (host port chan user pass)
                 (erc-handle-irc-url host port chan user pass (url-type url)))
             url-irc-function)))
diff --git a/lisp/erc/erc-fill.el b/lisp/erc/erc-fill.el
index a65c95f1d85..f4835f71278 100644
--- a/lisp/erc/erc-fill.el
+++ b/lisp/erc/erc-fill.el
@@ -116,11 +116,29 @@ Set to nil to disable."
   "The column at which a filled paragraph is broken."
   :type 'integer)
 
+(defcustom erc-fill-wrap-margin-width nil
+  "Starting width in columns of dedicated stamp margin.
+When nil, ERC normally pretends its value is one column greater
+than the `string-width' of the formatted `erc-timestamp-format'.
+However, when `erc-fill-wrap-margin-side' is `left' or
+\"resolves\" to `left', ERC uses the width of the prompt if it's
+wider on MOTD's end, which really only matters when `erc-prompt'
+is a function."
+  :package-version '(ERC . "5.6") ; FIXME sync on release
+  :type '(choice (const nil) integer))
+
+(defcustom erc-fill-wrap-margin-side nil
+  "Margin side to use with `erc-fill-wrap-mode'.
+A value of nil means ERC should decide based on the value of
+`erc-insert-timestamp-function', which does not work for
+user-defined functions."
+  :package-version '(ERC . "5.6") ; FIXME sync on release
+  :type '(choice (const nil) (const left) (const right)))
+
 (defcustom erc-fill-line-spacing nil
   "Extra space between messages on graphical displays.
-This may need adjusting depending on how your faces are
-configured.  Its value should be larger than that of the variable
-`line-spacing', if set.  If unsure, try 0.5."
+Its value should be larger than that of the variable
+`line-spacing', if set.  When unsure, start with 0.5."
   :package-version '(ERC . "5.6") ; FIXME sync on release
   :type '(choice (const nil) number))
 
@@ -243,6 +261,14 @@ messages less than a day apart."
   ;; `kill-line' anyway so that users can see the error.
   (erc-fill--wrap-move #'kill-line #'kill-visual-line arg))
 
+(defun erc-fill--wrap-escape-hidden-speaker ()
+  "Move to start of message text when left of speaker.
+Basically mimic what `move-beginning-of-line' does with invisible text."
+  (when-let ((erc-fill-wrap-merge)
+             (prop (get-text-property (point) 'display))
+             ((or (equal prop "") (eq 'margin (car-safe (car-safe prop))))))
+    (goto-char (text-property-not-all (point) (pos-eol) 'display prop))))
+
 (defun erc-fill--wrap-beginning-of-line (arg)
   "Defer to `move-beginning-of-line' or `beginning-of-visual-line'."
   (interactive "^p")
@@ -252,10 +278,22 @@ messages less than a day apart."
   (if (get-text-property (point) 'erc-prompt)
       (goto-char erc-input-marker)
     ;; Mimic what `move-beginning-of-line' does with invisible text.
-    (when-let ((erc-fill-wrap-merge)
-               (empty (get-text-property (point) 'display))
-               ((string-empty-p empty)))
-      (goto-char (text-property-not-all (point) (pos-eol) 'display empty)))))
+    (erc-fill--wrap-escape-hidden-speaker)))
+
+(defun erc-fill--wrap-previous-line (&optional arg try-vscroll)
+  "Move to ARGth previous logical or screen line."
+  (interactive "^p\np")
+  (if erc-fill--wrap-visual-keys
+      (with-no-warnings (previous-line arg try-vscroll))
+    (prog1 (previous-logical-line arg try-vscroll)
+      (erc-fill--wrap-escape-hidden-speaker))))
+
+(defun erc-fill--wrap-next-line (&optional arg try-vscroll)
+  "Move to ARGth next logical or screen line."
+  (interactive "^p\np")
+  (if erc-fill--wrap-visual-keys
+      (with-no-warnings (next-line arg try-vscroll))
+    (next-logical-line arg try-vscroll)))
 
 (defun erc-fill--wrap-end-of-line (arg)
   "Defer to `move-end-of-line' or `end-of-visual-line'."
@@ -278,21 +316,46 @@ is 0, reset to value of `erc-fill-wrap-visual-keys'."
                                        ('non-input nil))))
   (message "erc-fill-wrap movement: %S" erc-fill--wrap-visual-keys))
 
+(defun erc-fill-wrap-toggle-truncate-lines (arg)
+  "Toggle `truncate-lines' and maybe reinstate `visual-line-mode'."
+  (interactive "P")
+  (let ((wantp (if arg
+                   (natnump (prefix-numeric-value arg))
+                 (not truncate-lines)))
+        (buffer (current-buffer)))
+    (if wantp
+        (setq truncate-lines t)
+      (walk-windows (lambda (window)
+                      (when (eq buffer (window-buffer window))
+                        (set-window-hscroll window 0)))
+                    nil t)
+      (visual-line-mode +1)))
+  (force-mode-line-update))
+
 (defvar-keymap erc-fill-wrap-mode-map ; Compat 29
   :doc "Keymap for ERC's `fill-wrap' module."
   :parent visual-line-mode-map
   "<remap> <kill-line>" #'erc-fill--wrap-kill-line
   "<remap> <move-end-of-line>" #'erc-fill--wrap-end-of-line
   "<remap> <move-beginning-of-line>" #'erc-fill--wrap-beginning-of-line
+  "<remap> <toggle-truncate-lines>" #'erc-fill-wrap-toggle-truncate-lines
+  "<remap> <next-line>" #'erc-fill--wrap-next-line
+  "<remap> <previous-line>" #'erc-fill--wrap-previous-line
   "C-c a" #'erc-fill-wrap-cycle-visual-movement
   ;; Not sure if this is problematic because `erc-bol' takes no args.
   "<remap> <erc-bol>" #'erc-fill--wrap-beginning-of-line)
 
-(defvar erc-match-mode)
 (defvar erc-button-mode)
-(defvar erc-match--hide-fools-offset-bounds)
+(defvar erc-legacy-invisible-bounds-p)
 
 (defun erc-fill--wrap-ensure-dependencies ()
+  (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p))
+    (when erc-legacy-invisible-bounds-p
+      (erc--warn-once-before-connect  'erc-fill-wrap-mode
+        "Module `fill-wrap' is incompatible with the obsolete compatibility"
+        " flag `erc-legacy-invisible-bounds-p'.  Disabling locally in %s."
+        (current-buffer))
+      (setq-local erc-legacy-invisible-bounds-p nil)))
   (let (missing-deps)
     (unless erc-fill-mode
       (push 'fill missing-deps)
@@ -317,44 +380,66 @@ is 0, reset to value of `erc-fill-wrap-visual-keys'."
 ;;;###autoload(put 'fill-wrap 'erc--feature 'erc-fill)
 (define-erc-module fill-wrap nil
   "Fill style leveraging `visual-line-mode'.
-This local module displays nicks overhanging leftward to a common
-offset, as determined by the option `erc-fill-static-center'.  It
-depends on the `fill' and `button' modules and assumes the option
-`erc-insert-timestamp-function' is `erc-insert-timestamp-right'
-or the default `erc-insert-timestamp-left-and-right', so that it
-can display right-hand stamps in the right margin.  A value of
-`erc-insert-timestamp-left' is unsupported.  To use it, either
-include `fill-wrap' in `erc-modules' or set `erc-fill-function'
-to `erc-fill-wrap' (recommended).  You can also manually invoke
-one of the minor-mode toggles if really necessary."
+This module displays nicks overhanging leftward to a common
+offset, as determined by the option `erc-fill-static-center'.  To
+use it, either include `fill-wrap' in `erc-modules' or set
+`erc-fill-function' to `erc-fill-wrap'.  Most users will want to
+enable the `scrolltobottom' module as well.  Once active, use
+\\[erc-fill-wrap-nudge] to adjust the width of the indent and the
+stamp margin, and use \\[erc-fill-wrap-toggle-truncate-lines] for
+cycling between logical- and screen-line oriented command
+movement.  Also see related options `erc-fill-line-spacing' and
+`erc-fill-wrap-merge'.
+
+This module imposes various restrictions on the appearance of
+timestamps.  Most notably, it insists on displaying them in the
+margins.  Users preferring left-sided stamps may notice that ERC
+also displays the prompt in the left margin, possibly truncating
+or padding it to constrain it to the margin's width.
+Additionally, this module assumes that users providing their own
+`erc-insert-timestamp-function' have also customized the option
+`erc-fill-wrap-margin-side' to an explicit side.  When stamps
+appear in the right margin, which they do by default, users may
+find that ERC actually appends them to copy-as-killed messages
+without an intervening space.  This normally poses at most a
+minor inconvenience, however users of the `log' module may prefer
+a workaround provided by `erc-stamp-prefix-log-filter', which
+strips trailing stamps from logged messages and instead prepends
+them to every line.
+
+As a so-called \"local\" module, `fill-wrap' depends on the
+global modules `fill', `stamp', and `button'; it activates them
+as needed when initializing.  Please note that enabling and
+disabling this module by invoking one of its minor-mode toggles
+is not recommended."
   ((erc-fill--wrap-ensure-dependencies)
-   ;; Restore or initialize local state variables.
    (erc--restore-initialize-priors erc-fill-wrap-mode
      erc-fill--wrap-visual-keys erc-fill-wrap-visual-keys
-     erc-fill--wrap-value erc-fill-static-center)
+     erc-fill--wrap-value erc-fill-static-center
+     erc-stamp--margin-width erc-fill-wrap-margin-width
+     left-margin-width left-margin-width
+     right-margin-width right-margin-width)
+   (setq erc-stamp--margin-left-p
+         (or (eq erc-fill-wrap-margin-side 'left)
+             (eq (default-value 'erc-insert-timestamp-function)
+                 #'erc-insert-timestamp-left)))
    (setq erc-fill--function #'erc-fill-wrap)
-   ;; Internal integrations.
    (add-function :after (local 'erc-stamp--insert-date-function)
                  #'erc-fill--wrap-stamp-insert-prefixed-date)
-   (when (or erc-stamp-mode (memq 'stamp erc-modules))
-     (erc-stamp--display-margin-mode +1))
-   (when (or (bound-and-true-p erc-match-mode) (memq 'match erc-modules))
-     (require 'erc-match)
-     (setq erc-match--hide-fools-offset-bounds t))
    (when erc-fill-wrap-merge
      (add-hook 'erc-button--prev-next-predicate-functions
                #'erc-fill--wrap-merged-button-p nil t))
+   (erc-stamp--display-margin-mode +1)
    (visual-line-mode +1))
-  ((when erc-stamp--display-margin-mode
-     (erc-stamp--display-margin-mode -1))
+  ((visual-line-mode -1)
+   (erc-stamp--display-margin-mode -1)
    (kill-local-variable 'erc-fill--wrap-value)
    (kill-local-variable 'erc-fill--function)
    (kill-local-variable 'erc-fill--wrap-visual-keys)
    (remove-hook 'erc-button--prev-next-predicate-functions
                 #'erc-fill--wrap-merged-button-p t)
    (remove-function (local 'erc-stamp--insert-date-function)
-                    #'erc-fill--wrap-stamp-insert-prefixed-date)
-   (visual-line-mode -1))
+                    #'erc-fill--wrap-stamp-insert-prefixed-date))
   'local)
 
 (defvar-local erc-fill--wrap-length-function nil
@@ -381,18 +466,22 @@ parties.")
                        (widen)
                        (when (eq 'erc-timestamp (field-at-pos m))
                          (set-marker m (field-end m)))
-                       (and (eq 'PRIVMSG (get-text-property m 'erc-command))
-                            (not (eq (get-text-property m 'erc-ctcp) 'ACTION))
-                            (cons (get-text-property m 'erc-timestamp)
-                                  (get-text-property (1+ m) 'erc-data)))))
+                       (and-let*
+                           (((eq 'PRIVMSG (get-text-property m 'erc-command)))
+                            ((not (eq (get-text-property m 'erc-ctcp)
+                                      'ACTION)))
+                            (spr (next-single-property-change m 'erc-speaker)))
+                         (cons (get-text-property m 'erc-timestamp)
+                               (get-text-property spr 'erc-speaker)))))
               (ts (pop props))
+              (props)
               ((not (time-less-p (erc-stamp--current-time) ts)))
               ((time-less-p (time-subtract (erc-stamp--current-time) ts)
                             erc-fill--wrap-max-lull))
-              (nick  (buffer-substring-no-properties
-                      (1+ (point-min)) (- (point) 2)))
-              (props)
-              ((erc-nick-equal-p (car props) nick))))
+              (speaker (next-single-property-change (point-min) 'erc-speaker))
+              ((not (eq (get-text-property speaker 'erc-ctcp) 'ACTION)))
+              (nick (get-text-property speaker 'erc-speaker))
+              ((erc-nick-equal-p props nick))))
     (set-marker erc-fill--wrap-last-msg (point-min))))
 
 (defun erc-fill--wrap-stamp-insert-prefixed-date (&rest args)
@@ -476,8 +565,8 @@ Offer to repeat command in a manner similar to
    \\`=' Increase indentation by one column
    \\`-' Decrease indentation by one column
    \\`0' Reset indentation to the default
-   \\`+' Shift right margin rightward (shrink) by one column
-   \\`_' Shift right margin leftward (grow) by one column
+   \\`+' Shift margin boundary rightward by one column
+   \\`_' Shift margin boundary leftward by one column
    \\`)' Reset the right margin to the default
 
 Note that misalignment may occur when messages contain
@@ -489,8 +578,9 @@ decorations applied by third-party modules."
   (unless (get-buffer-window)
     (user-error "Command called in an undisplayed buffer"))
   (let* ((total (erc-fill--wrap-nudge arg))
-         (win-ratio (/ (float (- (window-point) (window-start)))
-                       (- (window-end nil t) (window-start)))))
+         (leftp erc-stamp--margin-left-p)
+         ;; Anchor current line vertically.
+         (line (count-screen-lines (window-start) (window-point))))
     (when (zerop arg)
       (setq arg 1))
     (erc-compat-call
@@ -505,25 +595,27 @@ decorations applied by third-party modules."
                        (lambda ()
                          (interactive)
                          (cl-incf total (erc-fill--wrap-nudge a))
-                         (recenter (round (* win-ratio (window-height))))))))
+                         (recenter line)))))
        (dolist (key '(?\) ?_ ?+))
          (let ((a (pcase key
                     (?\) 0)
-                    (?_ (- (abs arg)))
-                    (?+ (abs arg)))))
+                    (?_ (if leftp (abs arg) (- (abs arg))))
+                    (?+ (if leftp (- (abs arg)) (abs arg))))))
            (define-key map (vector (list key))
                        (lambda ()
                          (interactive)
-                         (erc-stamp--adjust-right-margin (- a))
-                         (recenter (round (* win-ratio (window-height))))))))
+                         (erc-stamp--adjust-margin (- a) (zerop a))
+                         (when leftp (erc-stamp--refresh-left-margin-prompt))
+                         (recenter line)))))
        map)
      t
      (lambda ()
-       (message "Fill prefix: %d (%+d col%s)"
-                erc-fill--wrap-value total (if (> (abs total) 1) "s" "")))
+       (message "Fill prefix: %d (%+d col%s); Margin: %d"
+                erc-fill--wrap-value total (if (> (abs total) 1) "s" "")
+                (if leftp left-margin-width right-margin-width)))
      "Use %k for further adjustment"
      1)
-    (recenter (round (* win-ratio (window-height))))))
+    (recenter line)))
 
 (defun erc-fill-regarding-timestamp ()
   "Fills a text such that messages start at column `erc-fill-static-center'."
@@ -536,6 +628,7 @@ decorations applied by third-party modules."
   "Get length of timestamp if inserted left."
   (if (and (boundp 'erc-timestamp-format)
            erc-timestamp-format
+           ;; FIXME use a more robust test than symbol equivalence.
            (eq erc-insert-timestamp-function 'erc-insert-timestamp-left)
            (not erc-hide-timestamps))
       (length (format-time-string erc-timestamp-format))
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el
index d9ededa8e68..b37855cbecc 100644
--- a/lisp/erc/erc-goodies.el
+++ b/lisp/erc/erc-goodies.el
@@ -800,9 +800,7 @@ servers.  If called from a program, PROC specifies the 
server process."
    (list (read-string "Search for: ")
          (if current-prefix-arg
              nil erc-server-process)))
-  (if (fboundp 'multi-occur)
-      (multi-occur (erc-buffer-list nil proc) string)
-    (error "`multi-occur' is not defined as a function")))
+  (multi-occur (erc-buffer-list nil proc) string))
 
 (provide 'erc-goodies)
 
diff --git a/lisp/erc/erc-log.el b/lisp/erc/erc-log.el
index d3106da4017..472cc1388a4 100644
--- a/lisp/erc/erc-log.el
+++ b/lisp/erc/erc-log.el
@@ -445,6 +445,15 @@ You can save every individual message by putting this 
function on
            (set-buffer-modified-p nil))))))
   t)
 
+;; This is a kludge to avoid littering erc-truncate.el with forward
+;; declarations needed only for a corner-case compatibility check.
+(defun erc-log--call-when-logging-enabled-sans-module (fn)
+  (when (and (erc-logging-enabled)
+             (not (or erc-log-mode (memq 'log erc-modules))))
+    (let ((dirfile (and (stringp erc-log-channels-directory)
+                        erc-log-channels-directory)))
+      (funcall fn dirfile))))
+
 (provide 'erc-log)
 
 ;;; erc-log.el ends here
diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el
index a5b0af41b2a..50db8a132ec 100644
--- a/lisp/erc/erc-match.el
+++ b/lisp/erc/erc-match.el
@@ -655,24 +655,10 @@ See `erc-log-match-format'."
                                        (get-buffer (car buffer-cons))))))
     (switch-to-buffer buffer-name)))
 
-(defvar-local erc-match--hide-fools-offset-bounds nil)
-
 (defun erc-hide-fools (match-type _nickuserhost _message)
   "Hide comments from designated fools."
   (when (eq match-type 'fool)
-    (erc-match--hide-message)))
-
-(defun erc-match--hide-message ()
-  (progn ; FIXME raise sexp
-    (if erc-match--hide-fools-offset-bounds
-        (let ((beg (point-min))
-              (end (point-max)))
-          (save-restriction
-            (widen)
-            (erc--merge-prop (1- beg) (1- end) 'invisible 'erc-match)))
-      ;; Before ERC 5.6, this also used to add an `intangible'
-      ;; property, but the docs say it's now obsolete.
-      (erc--merge-prop (point-min) (point-max) 'invisible 'erc-match))))
+    (erc--hide-message 'match-fools)))
 
 (defun erc-beep-on-match (match-type _nickuserhost _message)
   "Beep when text matches.
@@ -682,19 +668,31 @@ This function is meant to be called from 
`erc-text-matched-hook'."
 
 (defun erc-match--modify-invisibility-spec ()
   "Add an `erc-match' property to the local spec."
+  ;; Hopefully, this will be extended to do the same for other
+  ;; invisible properties managed by this module.
   (if erc-match-mode
-      (add-to-invisibility-spec 'erc-match)
+      (erc-match-toggle-hidden-fools +1)
     (erc-with-all-buffers-of-server nil nil
-      (remove-from-invisibility-spec 'erc-match))))
+      (erc-match-toggle-hidden-fools -1))))
 
-(defun erc-match-toggle-hidden-fools ()
+(defun erc-match-toggle-hidden-fools (arg)
   "Toggle fool visibility.
-Expect `erc-hide-fools' or a function that does something similar
-to be in `erc-text-matched-hook'."
-  (interactive)
-  (if (memq 'erc-match (ensure-list buffer-invisibility-spec))
-      (remove-from-invisibility-spec 'erc-match)
-    (add-to-invisibility-spec 'erc-match)))
+Expect the function `erc-hide-fools' or similar to be present in
+`erc-text-matched-hook'."
+  (interactive "P")
+  (erc-match--toggle-hidden 'match-fools arg))
+
+(defun erc-match--toggle-hidden (prop arg)
+  "Toggle invisibility for spec member PROP.
+Treat ARG in a manner similar to mode toggles defined by
+`define-minor-mode'."
+  (when arg
+    (setq arg (prefix-numeric-value arg)))
+  (if (memq prop (ensure-list buffer-invisibility-spec))
+      (unless (natnump arg)
+        (remove-from-invisibility-spec prop))
+    (when (or (not arg) (natnump arg))
+      (add-to-invisibility-spec prop))))
 
 (provide 'erc-match)
 
diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el
index bf4ef1d35a9..ba7990e87d6 100644
--- a/lisp/erc/erc-networks.el
+++ b/lisp/erc/erc-networks.el
@@ -66,7 +66,7 @@
 (declare-function erc-set-active-buffer "erc" (buffer))
 
 (declare-function erc-button--display-error-notice-with-keys
-                  (parsed &rest strings))
+                  (maybe-buffer &rest strings))
 
 ;; Variables
 
@@ -1295,17 +1295,16 @@ shutting down the connection."
                          erc-network)))
      (erc-display-message parsed 'notice nil m)
      nil)
-    ((and
-      (guard (eq erc-network erc-networks--name-missing-sentinel))
-      ;; This can happen theoretically, e.g., when adjusting settings
-      ;; on a proxy service that partially impersonates IRC but isn't
-      ;; currently conveying anything through to a real network.  The
-      ;; service may send a 422 but no NETWORK param (or *any* 005s).
-      (let m (concat "Failed to determine network.  Please set entry for \""
-                     erc-server-announced-name "\" in `erc-networks-alist'"
-                     " or consider calling `erc-tls' with the keyword `:id'."
-                     "  See Info:\"(erc) Network Identifier\" for more.")))
-     (erc-display-error-notice parsed m)
+    ((guard (eq erc-network erc-networks--name-missing-sentinel))
+     ;; This can happen theoretically, e.g., when adjusting settings
+     ;; on a proxy service that partially impersonates IRC but isn't
+     ;; currently conveying anything through to a real network.  The
+     ;; service may send a 422 but no NETWORK param (or *any* 005s).
+     (erc-button--display-error-notice-with-keys
+      "Failed to determine network. Please set entry for \""
+      erc-server-announced-name "\" in `erc-networks-alist' or consider"
+      " calling `erc-tls' with the keyword `:id'."
+      " See Info:\"(erc) Network Identifier\" for more.")
      (if erc-networks--allow-unknown-network
          (progn
            (erc-display-error-notice
@@ -1325,9 +1324,9 @@ Copy source (prefix) from MOTD-ish message as a last 
resort."
   (unless erc-server-announced-name
     (require 'erc-button)
     (erc-button--display-error-notice-with-keys
-     parsed "Failed to determine server name.  Using \""
+     "Failed to determine server name. Using \""
      (setq erc-server-announced-name (erc-response.sender parsed)) "\" instead"
-     ".  If this was unexpected, consider reporting it via \\[erc-bug]" "."))
+     ". If this was unexpected, consider reporting it via \\[erc-bug]."))
   nil)
 
 (defun erc-unset-network-name (_nick _ip _reason)
@@ -1508,7 +1507,7 @@ to be a false alarm.  If `erc-reuse-buffers' is nil, let
                                proc)
       (require 'erc-button)
       (erc-button--display-error-notice-with-keys
-       parsed "Unexpected state detected.  Please report via \\[erc-bug].")))
+       "Unexpected state detected. Please report via \\[erc-bug].")))
 
   ;; For now, retain compatibility with erc-server-NNN-functions.
   (or (erc-networks--ensure-announced proc parsed)
diff --git a/lisp/erc/erc-nicks.el b/lisp/erc/erc-nicks.el
index 3f753adc625..a7d0b0769f2 100644
--- a/lisp/erc/erc-nicks.el
+++ b/lisp/erc/erc-nicks.el
@@ -61,7 +61,7 @@
 ;; 2007/09 - erc-highlight-nicknames.el
 ;;           Initial release by by André Riemann
 
-;; [1] <http://www.github.com/leathekd/erc-hl-nicks>
+;; [1] <https://www.github.com/leathekd/erc-hl-nicks>
 ;; [2] <https://www.emacswiki.org/emacs/ErcHighlightNicknames>
 
 ;;; Code:
@@ -480,6 +480,12 @@ Abandon search after examining LIMIT faces."
   "Uniquely colorize nicknames in target buffers."
   ((if erc--target
        (progn
+         (erc-with-server-buffer
+           (unless erc-nicks-mode
+             (erc--warn-once-before-connect 'erc-nicks-mode
+               "Module `nicks' must be enabled or disabled session-wide."
+               " Toggling it in individual target buffers is unsupported.")
+             (erc-nicks-mode +1))) ; but do it anyway
          (setq erc-nicks--downcased-skip-nicks
                (mapcar #'erc-downcase erc-nicks-skip-nicks))
          (add-function :filter-return (local 'erc-button--modify-nick-function)
diff --git a/lisp/erc/erc-speedbar.el b/lisp/erc/erc-speedbar.el
index f5fbaac767d..625d59530b0 100644
--- a/lisp/erc/erc-speedbar.el
+++ b/lisp/erc/erc-speedbar.el
@@ -439,7 +439,6 @@ The INDENT level is ignored."
 (defvar erc-status-sidebar-buffer-name)
 (declare-function erc-status-sidebar-set-window-preserve-size
                   "erc-status-sidebar" nil)
-(declare-function erc-status-sidebar-mode--unhook "erc-status-sidebar" nil)
 
 (defvar erc-speedbar--buffer-options
   '((speedbar-update-flag . t)
@@ -490,36 +489,64 @@ The INDENT level is ignored."
           (cl-assert (buffer-live-p speedbar-buffer))
           (if (or (and force (< arg 0))
                   (and (not force) (get-buffer-window speedbar-buffer nil)))
-              (erc-speedbar-close-nicknames-window nil)
+              ;; Close associated windows and stop updating but leave timer.
+              (progn
+                (dolist (window (get-buffer-window-list speedbar-buffer nil t))
+                  (unless (frame-root-window-p window)
+                    (when erc-speedbar--hidden-speedbar-frame
+                      (cl-assert
+                       (not (eq (window-frame window)
+                                erc-speedbar--hidden-speedbar-frame))))
+                    (delete-window window)))
+                (with-current-buffer speedbar-buffer
+                  (setq speedbar-update-flag nil)
+                  (speedbar-set-mode-line-format)))
             (when (or (not force) (>= arg 0))
               (with-selected-frame speedbar-frame
                 (erc-speedbar--emulate-sidebar-set-window-preserve-size)))))
-      (when (or (not force) (>= arg 0))
-        (let ((speedbar-frame-parameters (backquote-list*
-                                          '(visibility . nil)
-                                          '(no-other-frame . t)
-                                          speedbar-frame-parameters))
-              (speedbar-after-create-hook #'erc-speedbar--emulate-sidebar))
-          (erc-speedbar-browser)
-          ;; If we put the remaining parts in the "create hook" along
-          ;; with everything else, the frame with `window-main-window'
-          ;; gets raised and steals focus if you've switched away from
-          ;; Emacs in the meantime.
-          (make-frame-invisible speedbar-frame)
-          (select-frame (setq speedbar-frame (previous-frame)))
-          (erc-speedbar--emulate-sidebar-set-window-preserve-size))))))
+      (when-let (((or (not force) (>= arg 0)))
+                 (speedbar-frame-parameters (backquote-list*
+                                             '(visibility . nil)
+                                             '(no-other-frame . t)
+                                             speedbar-frame-parameters))
+                 (speedbar-after-create-hook #'erc-speedbar--emulate-sidebar))
+        (erc-install-speedbar-variables)
+        ;; Run before toggling mode to prevent timer from being
+        ;; created twice.
+        (speedbar-change-initial-expansion-list "ERC")
+        (speedbar-frame-mode 1)
+        ;; If we put the remaining parts in the "create hook" along
+        ;; with everything else, the frame with `window-main-window'
+        ;; gets raised and steals focus if you've switched away from
+        ;; Emacs in the meantime.
+        (make-frame-invisible speedbar-frame)
+        (select-frame (setq speedbar-frame (previous-frame)))
+        (erc-speedbar--emulate-sidebar-set-window-preserve-size))))
+  (cl-assert (not (cdr (erc-speedbar--get-timers))) t))
 
 (defun erc-speedbar--ensure (&optional force)
   (when (or (erc-server-buffer) force)
     (when erc-track-mode
       (cl-pushnew '(derived-mode . speedbar-mode)
                   erc-track--switch-fallback-blockers :test #'equal))
+    (unless speedbar-update-flag
+      (erc-button--display-error-notice-with-keys
+       (erc-server-buffer)
+       "Module `nickbar' needs `speedbar-update-flag' to be non-nil"
+       (and (not (display-graphic-p)) " in text terminals")
+       ". Setting to t for the current Emacs session."
+       " Customize it permanently to avoid this message.")
+      (setq speedbar-update-flag t))
     (erc-speedbar--toggle-nicknames-sidebar +1)
-    (speedbar-enable-update)))
+    (with-current-buffer speedbar-buffer
+      (setq speedbar-update-flag t)
+      (speedbar-set-mode-line-format))))
+
+(defvar erc-speedbar--shutting-down-p nil)
 
 ;;;###autoload(autoload 'erc-nickbar-mode "erc-speedbar" nil t)
 (define-erc-module nickbar nil
-  "Show nicknames in a side window.
+  "Show nicknames for current target buffer in a side window.
 When enabling, create a speedbar session if one doesn't exist and
 show its buffer in an `erc-status-sidebar' window instead of a
 separate frame.  When disabling, close the window or, with a
@@ -527,8 +554,8 @@ negative prefix arg, destroy the session.
 
 WARNING: this module may produce unwanted side effects, like the
 raising of frames or the stealing of input focus.  If you witness
-such an occurrence, and can reproduce it, please file a bug
-report with \\[erc-bug]."
+such a thing and can reproduce it, please file a bug report with
+\\[erc-bug]."
   ((add-hook 'erc--setup-buffer-hook #'erc-speedbar--ensure)
    (erc-speedbar--ensure)
    (unless (or erc--updating-modules-p
@@ -542,31 +569,44 @@ report with \\[erc-bug]."
          (erc-error "Not initializing `erc-nickbar-mode' in %s"
                     (current-buffer))))))
   ((remove-hook 'erc--setup-buffer-hook #'erc-speedbar--ensure)
-   (speedbar-disable-update)
    (when erc-track-mode
      (setq erc-track--switch-fallback-blockers
            (remove '(derived-mode . speedbar-mode)
                    erc-track--switch-fallback-blockers)))
    (erc-speedbar--toggle-nicknames-sidebar -1)
-   (when-let ((arg erc--module-toggle-prefix-arg)
+   (when-let (((not erc-speedbar--shutting-down-p))
+              (arg erc--module-toggle-prefix-arg)
               ((numberp arg))
               ((< arg 0)))
-     (erc-speedbar-close-nicknames-window 'kill))))
+     (with-current-buffer speedbar-buffer
+       (dframe-close-frame)
+       (setq erc-speedbar--hidden-speedbar-frame nil)))))
+
+(defun erc-speedbar--get-timers ()
+  (cl-remove #'dframe-timer-fn timer-idle-list
+             :key #'timer--function
+             :test-not #'eq))
 
 (defun erc-speedbar--dframe-controlled (arg)
+  (when speedbar-buffer
+    (cl-assert (eq speedbar-buffer (current-buffer))))
   (when (and erc-speedbar--hidden-speedbar-frame (numberp arg) (< arg 0))
     (when erc-nickbar-mode
-      (erc-nickbar-mode -1))
+      (let ((erc-speedbar--shutting-down-p t))
+        (erc-nickbar-mode -1)))
     (setq speedbar-frame erc-speedbar--hidden-speedbar-frame
           erc-speedbar--hidden-speedbar-frame nil)
     ;; It's unknown whether leaving the frame invisible interferes
-    ;; with the upstream teardown procedure.
+    ;; with the upstream teardown sequence.
     (when (display-graphic-p)
       (make-frame-visible speedbar-frame))
-    (speedbar-frame-mode arg)
-    (when speedbar-buffer
-      (kill-buffer speedbar-buffer)
-      (setq speedbar-buffer nil))))
+    (speedbar-frame-mode arg) ; -1
+    ;; As of Emacs 29, `dframe-set-timer' can't remove `dframe-timer'.
+    (cl-assert (= 1 (length (erc-speedbar--get-timers))) t)
+    (cancel-function-timers #'dframe-timer-fn)
+    ;; `dframe-close-frame' kills the buffer but no function in
+    ;; erc-speedbar.el resets this to nil.
+    (setq speedbar-buffer nil)))
 
 (defun erc-speedbar-toggle-nicknames-window-lock ()
   "Toggle whether nicknames window is selectable with \\[other-window]."
@@ -578,20 +618,6 @@ report with \\[erc-bug]."
       (set-window-parameter window 'no-other-window (not val))
       (message "nick-window: %s" (if val "selectable" "protected")))))
 
-(defun erc-speedbar-close-nicknames-window (kill)
-  (interactive "P")
-  (if kill
-      (with-current-buffer speedbar-buffer
-        (dframe-close-frame)
-        (cl-assert (not erc-nickbar-mode))
-        (setq erc-speedbar--hidden-speedbar-frame nil))
-    (dolist (window (get-buffer-window-list speedbar-buffer nil t))
-      (unless (frame-root-window-p window)
-        (when erc-speedbar--hidden-speedbar-frame
-          (cl-assert (not (eq (window-frame window)
-                              erc-speedbar--hidden-speedbar-frame))))
-        (delete-window window)))))
-
 
 ;;;; Nicks integration
 
diff --git a/lisp/erc/erc-stamp.el b/lisp/erc/erc-stamp.el
index 83ee4a200ed..a021cd26607 100644
--- a/lisp/erc/erc-stamp.el
+++ b/lisp/erc/erc-stamp.el
@@ -281,49 +281,60 @@ This option only matters when 
`erc-insert-timestamp-function' is
 set to `erc-insert-timestamp-right' or that option's default,
 `erc-insert-timestamp-left-and-right'.  If the value is a
 positive integer, alignment occurs that many columns from the
-right edge.  If the value is `margin', the stamp appears in the
-right margin when visible.
+right edge.
 
 Enabling this option produces a side effect in that stamps aren't
 indented in saved logs.  When its value is an integer, this
 option adds a space after the end of a message if the stamp
 doesn't already start with one.  And when its value is t, it adds
-a single space, unconditionally.  And while this option never
-adds a space when its value is `margin', ERC does offer a
-workaround in `erc-stamp-prefix-log-filter', which strips
-trailing stamps from messages and puts them before every line."
-  :type '(choice boolean integer (const margin))
+a single space, unconditionally."
+  :type '(choice boolean integer)
   :package-version '(ERC . "5.6")) ; FIXME sync on release
 
-(defcustom erc-stamp-right-margin-width nil
-  "Width in columns of the right margin.
-When this option is nil, pretend its value is one column greater
-than the `string-width' of the formatted `erc-timestamp-format'.
-This option only matters when `erc-timestamp-use-align-to' is set
-to `margin'."
-  :package-version '(ERC . "5.6") ; FIXME sync on release
-  :type '(choice (const nil) integer))
-
-(defun erc-stamp--display-margin-force (orig &rest r)
-  (let ((erc-timestamp-use-align-to 'margin))
-    (apply orig r)))
-
-(defun erc-stamp--adjust-right-margin (cols)
-  "Adjust right margin by COLS.
-When COLS is zero, reset width to `erc-stamp-right-margin-width'
-or one col more than the `string-width' of
-`erc-timestamp-format'."
-  (let ((width
-         (if (zerop cols)
-             (or erc-stamp-right-margin-width
-                 (1+ (string-width (or erc-timestamp-last-inserted-right
-                                       (erc-format-timestamp
-                                        (current-time)
-                                        erc-timestamp-format)))))
-           (+ right-margin-width cols))))
-    (setq right-margin-width width)
+(defvar-local erc-stamp--margin-width nil
+  "Width in columns of margin for `erc-stamp--display-margin-mode'.
+Only consulted when resetting or initializing margin.")
+
+(defvar-local erc-stamp--margin-left-p nil
+  "Whether `erc-stamp--display-margin-mode' uses the left margin.
+During initialization, the mode respects this variable's existing
+value if it already has a local binding.  Otherwise, modules can
+bind this to any value while enabling the mode.  If it's nil, ERC
+will check to see if `erc-insert-timestamp-function' is
+`erc-insert-timestamp-left', interpreting the latter as a non-nil
+value.  It'll then coerce any non-nil value to t.")
+
+(defun erc-stamp--init-margins-on-connect (&rest _)
+  (let ((existing (if erc-stamp--margin-left-p
+                      left-margin-width
+                    right-margin-width)))
+    (erc-stamp--adjust-margin existing 'resetp)))
+
+(defun erc-stamp--adjust-margin (cols &optional resetp)
+  "Adjust managed margin by increment COLS.
+With RESETP, set margin's width to COLS.  However, if COLS is
+zero, set the width to a non-nil `erc-stamp--margin-width'.
+Otherwise, go with the `string-width' of `erc-timestamp-format'.
+However, when `erc-stamp--margin-left-p' is non-nil and the
+prompt is wider, use its width instead."
+  (let* ((leftp erc-stamp--margin-left-p)
+         (width
+          (if resetp
+              (or (and (not (zerop cols)) cols)
+                  erc-stamp--margin-width
+                  (max (if leftp (string-width (erc-prompt)) 0)
+                       (1+ (string-width
+                            (or (if leftp
+                                    erc-timestamp-last-inserted
+                                  erc-timestamp-last-inserted-right)
+                                (erc-format-timestamp
+                                 (current-time) erc-timestamp-format))))))
+            (+ (if leftp left-margin-width right-margin-width) cols))))
+    (set (if leftp 'left-margin-width 'right-margin-width) width)
     (when (eq (current-buffer) (window-buffer))
-      (set-window-margins nil left-margin-width width))))
+      (set-window-margins nil
+                          (if leftp width left-margin-width)
+                          (if leftp right-margin-width width)))))
 
 ;;;###autoload
 (defun erc-stamp-prefix-log-filter (text)
@@ -348,39 +359,100 @@ non-nil."
         (zerop (forward-line))))
   "")
 
+(defvar erc-stamp--inherited-props '(line-prefix wrap-prefix))
+
 (declare-function erc--remove-text-properties "erc" (string))
 
-;; If people want to use this directly, we can convert it into
-;; a local module.
+;; Currently, `erc-insert-timestamp-right' hard codes its display
+;; property to use `right-margin', and `erc-insert-timestamp-left'
+;; does the same for `left-margin'.  However, there's no reason a
+;; trailing stamp couldn't be displayed on the left and vice versa.
 (define-minor-mode erc-stamp--display-margin-mode
   "Internal minor mode for built-in modules integrating with `stamp'.
-It binds `erc-timestamp-use-align-to' to `margin' around calls to
-`erc-insert-timestamp-function' in the current buffer, and sets
-the right window margin to `erc-stamp-right-margin-width'.  It
-also arranges to remove most text properties when a user kills
-message text so that stamps will be visible when yanked."
+Arranges for displaying stamps in a single margin, with the
+variable `erc-stamp--margin-left-p' controlling which one.
+Provides `erc-stamp--margin-width' and `erc-stamp--adjust-margin'
+to help manage the chosen margin's width.  Also removes `display'
+properties in killed text to reveal stamps.  The invoking module
+should set controlling variables, like `erc-stamp--margin-width'
+and `erc-stamp--margin-left-p', before activating the mode."
   :interactive nil
   (if erc-stamp--display-margin-mode
       (progn
         (setq fringes-outside-margins t)
         (when (eq (current-buffer) (window-buffer))
           (set-window-buffer (selected-window) (current-buffer)))
-        (erc-stamp--adjust-right-margin 0)
+        (setq erc-stamp--margin-left-p (and erc-stamp--margin-left-p t))
+        (if (or erc-server-connected (not (functionp erc-prompt)))
+            (erc-stamp--init-margins-on-connect)
+          (add-hook 'erc-after-connect
+                    #'erc-stamp--init-margins-on-connect nil t))
         (add-function :filter-return (local 'filter-buffer-substring-function)
                       #'erc--remove-text-properties)
-        (add-function :around (local 'erc-insert-timestamp-function)
-                      #'erc-stamp--display-margin-force))
+        (add-hook 'erc--setup-buffer-hook
+                  #'erc-stamp--refresh-left-margin-prompt nil t)
+        (when erc-stamp--margin-left-p
+          (add-hook 'erc--refresh-prompt-hook
+                    #'erc-stamp--display-prompt-in-left-margin nil t)))
     (remove-function (local 'filter-buffer-substring-function)
                      #'erc--remove-text-properties)
-    (remove-function (local 'erc-insert-timestamp-function)
-                     #'erc-stamp--display-margin-force)
-    (kill-local-variable 'right-margin-width)
+    (remove-hook 'erc-after-connect
+                 #'erc-stamp--init-margins-on-connect t)
+    (remove-hook 'erc--refresh-prompt-hook
+                 #'erc-stamp--display-prompt-in-left-margin t)
+    (remove-hook 'erc--setup-buffer-hook
+                 #'erc-stamp--refresh-left-margin-prompt t)
+    (kill-local-variable (if erc-stamp--margin-left-p
+                             'left-margin-width
+                           'right-margin-width))
     (kill-local-variable 'fringes-outside-margins)
+    (kill-local-variable 'erc-stamp--margin-left-p)
+    (kill-local-variable 'erc-stamp--margin-width)
     (when (eq (current-buffer) (window-buffer))
       (set-window-margins nil left-margin-width nil)
       (set-window-buffer (selected-window) (current-buffer)))))
 
-(defun erc-insert-timestamp-left (string)
+(defvar-local erc-stamp--last-prompt nil)
+
+(defun erc-stamp--display-prompt-in-left-margin ()
+  "Show prompt in the left margin with padding."
+  (when (or (not erc-stamp--last-prompt) (functionp erc-prompt)
+            (> (string-width erc-stamp--last-prompt) left-margin-width))
+    (let ((s (buffer-substring erc-insert-marker (1- erc-input-marker))))
+      ;; Prevent #("abc" n m (display ((...) #("abc" p q (display...))))
+      (remove-text-properties 0 (length s) '(display nil) s)
+      (when (and erc-stamp--last-prompt
+                 (>= (string-width erc-stamp--last-prompt) left-margin-width))
+        (let ((sm (truncate-string-to-width s (1- left-margin-width) 0 nil t)))
+          ;; This papers over a subtle off-by-1 bug here.
+          (unless (equal sm s)
+            (setq s (concat sm (substring s -1))))))
+      (setq erc-stamp--last-prompt (string-pad s left-margin-width nil t))))
+  (put-text-property erc-insert-marker (1- erc-input-marker)
+                     'display `((margin left-margin) ,erc-stamp--last-prompt))
+  erc-stamp--last-prompt)
+
+(defun erc-stamp--refresh-left-margin-prompt ()
+  "Forcefully-recompute display property of prompt in left margin."
+  (with-silent-modifications
+    (unless (functionp erc-prompt)
+      (setq erc-stamp--last-prompt nil))
+    (erc--refresh-prompt)))
+
+(cl-defmethod erc--reveal-prompt
+  (&context (erc-stamp--display-margin-mode (eql t))
+            (erc-stamp--margin-left-p (eql t)))
+  (put-text-property erc-insert-marker (1- erc-input-marker)
+                     'display `((margin left-margin) ,erc-stamp--last-prompt)))
+
+(cl-defmethod erc--conceal-prompt
+  (&context (erc-stamp--display-margin-mode (eql t))
+            (erc-stamp--margin-left-p (eql t)))
+  (let ((prompt (string-pad erc-prompt-hidden left-margin-width nil 'start)))
+    (put-text-property erc-insert-marker (1- erc-input-marker)
+                       'display `((margin left-margin) ,prompt))))
+
+(cl-defmethod erc-insert-timestamp-left (string)
   "Insert timestamps at the beginning of the line."
   (goto-char (point-min))
   (let* ((ignore-p (and erc-timestamp-only-if-changed-flag
@@ -392,6 +464,22 @@ message text so that stamps will be visible when yanked."
     (erc-put-text-property 0 len 'invisible erc-stamp--invisible-property s)
     (insert s)))
 
+(cl-defmethod erc-insert-timestamp-left
+  (string &context (erc-stamp--display-margin-mode (eql t)))
+  (unless (and erc-timestamp-only-if-changed-flag
+               (string-equal string erc-timestamp-last-inserted))
+    (goto-char (point-min))
+    (insert-before-markers-and-inherit
+     (setq erc-timestamp-last-inserted string))
+    (dolist (p erc-stamp--inherited-props)
+      (when-let ((v (get-text-property (point) p)))
+        (put-text-property (point-min) (point) p v)))
+    (erc-put-text-property (point-min) (point) 'invisible
+                           erc-stamp--invisible-property)
+    (put-text-property (point-min) (point) 'field 'erc-timestamp)
+    (put-text-property (point-min) (point)
+                       'display `((margin left-margin) ,string))))
+
 (defun erc-insert-aligned (string pos)
   "Insert STRING at the POSth column.
 
@@ -408,7 +496,11 @@ property to get to the POSth column."
 ;; Silence byte-compiler
 (defvar erc-fill-column)
 
-(defvar erc-stamp--inherited-props '(line-prefix wrap-prefix))
+(defvar erc-stamp--omit-properties-on-folded-lines nil
+  "Skip properties before right stamps occupying their own line.
+This escape hatch restores pre-5.6 behavior that left leading
+white space alone (unpropertized) for right-sided stamps folded
+onto their own line.")
 
 (defun erc-insert-timestamp-right (string)
   "Insert timestamp on the right side of the screen.
@@ -465,6 +557,9 @@ printed just after each line's text (no alignment)."
       ;; For compatibility reasons, the `erc-timestamp' field includes
       ;; intervening white space unless a hard break is warranted.
       (pcase erc-timestamp-use-align-to
+        ((guard erc-stamp--display-margin-mode)
+         (put-text-property 0 (length string)
+                            'display `((margin right-margin) ,string) string))
         ((and 't (guard (< col pos)))
          (insert " ")
          (put-text-property from (point) 'display `(space :align-to ,pos)))
@@ -475,11 +570,8 @@ printed just after each line's text (no alignment)."
          (let ((s (+ erc-timestamp-use-align-to (string-width string))))
            (put-text-property from (point) 'display
                               `(space :align-to (- right ,s)))))
-        ('margin
-         (put-text-property 0 (length string)
-                            'display `((margin right-margin) ,string)
-                            string))
-        ((guard (>= col pos)) (newline) (indent-to pos) (setq from (point)))
+        ((guard (>= col pos)) (newline) (indent-to pos)
+         (when erc-stamp--omit-properties-on-folded-lines (setq from (point))))
         (_ (indent-to pos)))
       (insert string)
       (dolist (p erc-stamp--inherited-props)
diff --git a/lisp/erc/erc-status-sidebar.el b/lisp/erc/erc-status-sidebar.el
index b8bd7b0065e..cf3d20aeffa 100644
--- a/lisp/erc/erc-status-sidebar.el
+++ b/lisp/erc/erc-status-sidebar.el
@@ -45,8 +45,8 @@
 ;; Use M-x erc-status-sidebar-kill RET to kill the sidebar buffer and
 ;; close the sidebar on all frames.
 
-;; In addition to the commands above, you can also try the all-in-one,
-;; "DWIM" command, `erc-bufbar-mode'.  See its doc string for usage.
+;; In addition to the commands above, you can also try the all-in-one
+;; entry point `erc-bufbar-mode'.  See its doc string for usage.
 
 ;; If you want the status sidebar enabled whenever you use ERC, add
 ;; `bufbar' to `erc-modules'.  Note that this library also has a major
@@ -130,8 +130,11 @@ buffers, using the functions
   `erc-status-sidebar-pad-hierarchy'
 
 for the above-mentioned purposes.  ERC also accepts a list of
-functions to preform these roles a la carte.  See doc strings for
-a description of their expected arguments and return values."
+functions to preform these roles a la carte.  Since the members
+of the above sets aren't really interoperable, we don't offer
+them here as customization choices, but you can still specify
+them manually.  See doc strings for a description of their
+expected arguments and return values."
   :package-version '(ERC . "5.6") ; FIXME sync on release
   :type '(choice (const channels-only)
                  (const all-mixed)
@@ -158,10 +161,12 @@ ACTION parameter."
                               :key-type symbol
                               :value-type (sexp :tag "Value")))))
 
-(defcustom erc-status-sidebar-singular t
-  "Whether to show the sidebar on all frames or just one (default)."
-  :package-version '(ERC . "5.6") ; FIXME sync on release
-  :type 'boolean)
+(defvar erc-status-sidebar--singular-p t
+  "Whether to restrict the sidebar to a single frame.
+This variable only affects `erc-bufbar-mode'.  Disabling it does
+not arrange for automatically showing the sidebar in all frames.
+Rather, disabling it allows for displaying the sidebar in the
+selected frame even if it's already showing in some other frame.")
 
 (defvar hl-line-mode)
 (declare-function hl-line-highlight "hl-line" nil)
@@ -178,7 +183,7 @@ ACTION parameter."
 
 If NO-CREATION is non-nil, the window is not created."
   (let ((sidebar-window (get-buffer-window erc-status-sidebar-buffer-name
-                                           erc-status-sidebar-singular)))
+                                           erc-status-sidebar--singular-p)))
     (unless (or sidebar-window no-creation)
       (with-current-buffer (erc-status-sidebar-get-buffer)
         (setq-local vertical-scroll-bar nil))
@@ -214,7 +219,7 @@ The erc-status-sidebar buffer is left alone, but the window
 containing it on the current frame is closed.  See
 `erc-status-sidebar-kill'."
   (interactive "P")
-  (mapcar #'delete-window
+  (mapcar #'delete-window ; FIXME use `mapc'.
           (get-buffer-window-list (erc-status-sidebar-get-buffer)
                                   nil (if all-frames t))))
 
@@ -223,10 +228,8 @@ containing it on the current frame is closed.  See
   `(let ((buffer-read-only nil))
      ,@body))
 
-;;;###autoload
-(defun erc-status-sidebar-open ()
-  "Open or create a sidebar."
-  (interactive)
+(defun erc-status-sidebar--open ()
+  "Maybe open the sidebar, respecting `erc-status-sidebar--singular-p'."
   (save-excursion
     (if (erc-status-sidebar-buffer-exists-p)
         (erc-status-sidebar-get-window)
@@ -237,11 +240,15 @@ containing it on the current frame is closed.  See
 ;;;###autoload(autoload 'erc-bufbar-mode "erc-status-sidebar" nil t)
 (define-erc-module bufbar nil
   "Show `erc-track'-like activity in a side window.
-When enabling, show the sidebar immediately if called from a
-connected ERC buffer.  Otherwise, arrange for doing so on connect
-or whenever next displaying a new ERC buffer.  When disabling,
-hide the status window if it's showing.  With a negative prefix
-arg, also shutdown the session."
+When enabling, show the sidebar immediately in the current frame
+if called from a connected ERC buffer.  Otherwise, arrange for
+doing so on connect or whenever next displaying a new ERC buffer.
+When disabling, hide the status window in all frames.  With a
+negative prefix arg, also shutdown the session.  Normally, this
+module only allows one sidebar window in an Emacs session.  To
+override this, use `erc-status-sidebar-open' to force creation
+and `erc-status-sidebar-close' to hide a single instance on the
+current frame only."
   ((unless erc-track-mode
      (unless (memq 'track erc-modules)
        (erc--warn-once-before-connect 'erc-bufbar-mode
@@ -249,30 +256,38 @@ arg, also shutdown the session."
          " This will affect \C-]all\C-] ERC sessions."
          " Add `track' to `erc-modules' to silence this message."))
      (erc-track-mode +1))
-   (add-hook 'erc--setup-buffer-hook #'erc-status-sidebar-open)
+   (add-hook 'erc--setup-buffer-hook #'erc-status-sidebar--open)
    (unless erc--updating-modules-p
      (if (erc-with-server-buffer erc-server-connected)
-         (erc-status-sidebar-open)
-       (setq erc-bufbar-mode nil)
+         (erc-status-sidebar--open)
        (when (derived-mode-p 'erc-mode)
          (erc-error "Not initializing `erc-bufbar-mode' in %s"
                     (current-buffer))))))
-  ((remove-hook 'erc--setup-buffer-hook #'erc-status-sidebar-open)
-   (erc-status-sidebar-close erc-status-sidebar-singular)
+  ((remove-hook 'erc--setup-buffer-hook #'erc-status-sidebar--open)
+   (erc-status-sidebar-close 'all-frames)
    (when-let ((arg erc--module-toggle-prefix-arg)
               ((numberp arg))
               ((< arg 0)))
      (erc-status-sidebar-kill))))
 
+;;;###autoload
+(defun erc-status-sidebar-open ()
+  "Open or create a sidebar window in the current frame.
+When `erc-bufbar-mode' is active, do this even if one already
+exists in another frame."
+  (interactive)
+  (let ((erc-status-sidebar--singular-p (not erc-bufbar-mode)))
+    (erc-status-sidebar--open)))
+
 ;;;###autoload
 (defun erc-status-sidebar-toggle ()
   "Toggle the sidebar open/closed on the current frame.
-Do this regardless of `erc-status-sidebar-singular'."
+When opening, and `erc-bufbar-mode' is active, create a sidebar
+even if one already exists in another frame."
   (interactive)
   (if (get-buffer-window erc-status-sidebar-buffer-name nil)
       (erc-status-sidebar-close)
-    (let (erc-status-sidebar-singular)
-      (erc-status-sidebar-open))))
+    (erc-status-sidebar-open)))
 
 (defun erc-status-sidebar-get-channame (buffer)
   "Return name of BUFFER with all leading \"#\" characters removed."
@@ -413,11 +428,10 @@ name stand out."
                      erc-status-sidebar-pad-hierarchy))
                   (v v)))
                (chanlist (apply sort-fn (funcall list-fn nil) nil))
-               (window nil)
-               (winstart nil))
+               (windows nil))
     (with-current-buffer (erc-status-sidebar-get-buffer)
-      (setq window (get-buffer-window nil erc-status-sidebar-singular)
-            winstart (and window (window-start window)))
+      (dolist (window (get-buffer-window-list nil nil t))
+        (push (cons window (window-start window)) windows))
       (erc-status-sidebar-writable
        (delete-region (point-min) (point-max))
        (goto-char (point-min))
@@ -443,9 +457,8 @@ name stand out."
             0 cnlen 'help-echo
             "mouse-1: switch to buffer in other window" channame)
            (funcall insert-fn channame chanbuf chanlist)))
-       (when winstart
-         (set-window-point window winstart)
-         (with-selected-window window (recenter 0)))
+       (when windows
+         (map-apply #'set-window-start windows))
        (when (and erc-status-sidebar-highlight-active-buffer
                   (marker-buffer erc-status-sidebar--active-marker))
          (goto-char erc-status-sidebar--active-marker)
@@ -519,14 +532,28 @@ highlighted."
     erc-kill-server-hook
     erc-kick-hook
     erc-disconnected-hook
-    erc-quit-hook))
+    erc-quit-hook)
+  "Hooks to refresh the sidebar on.
+This may be set locally in the status-sidebar buffer under
+various conditions, like when the option
+`erc-status-sidebar-highlight-active-buffer' is non-nil.")
+
+(defvar erc-status-sidebar--highlight-refresh-triggers
+  '(window-selection-change-functions)
+  "Triggers enabled with `erc-status-sidebar-highlight-active-buffer'.")
+
+(defun erc-status-sidebar--refresh-unless-input ()
+  "Run `erc-status-sidebar-refresh' unless there are unread commands.
+Also abstain when the user is interacting with the minibuffer."
+  (unless (or (input-pending-p) (minibuffer-window-active-p (selected-window)))
+    (erc-status-sidebar-refresh)))
 
 (defun erc-status-sidebar--post-refresh (&rest _ignore)
   "Schedule sidebar refresh for execution after command stack is cleared.
 
 Ignore arguments in IGNORE, allowing this function to be added to
 hooks that invoke it with arguments."
-  (run-at-time 0 nil #'erc-status-sidebar-refresh))
+  (run-at-time 0 nil #'erc-status-sidebar--refresh-unless-input))
 
 (defun erc-status-sidebar-mode--unhook ()
   "Remove hooks installed by `erc-status-sidebar-mode'."
@@ -541,7 +568,7 @@ hooks that invoke it with arguments."
 Note that preserve status needs to be reset when the window is
 manually resized, so `erc-status-sidebar-mode' adds this function
 to the `window-configuration-change-hook'."
-  (when (and (eq (selected-window) (let (erc-status-sidebar-singular)
+  (when (and (eq (selected-window) (let (erc-status-sidebar--singular-p)
                                      (erc-status-sidebar-get-window)))
              (fboundp 'window-preserve-size))
     (unless (eq (window-total-width) (window-min-size nil t))
@@ -563,6 +590,10 @@ to the `window-configuration-change-hook'."
 
   (add-hook 'window-configuration-change-hook
             #'erc-status-sidebar-set-window-preserve-size nil t)
+  (when erc-status-sidebar-highlight-active-buffer
+    (setq-local erc-status-sidebar-refresh-triggers
+                `(,@erc-status-sidebar--highlight-refresh-triggers
+                  ,@erc-status-sidebar-refresh-triggers)))
   (dolist (hk erc-status-sidebar-refresh-triggers)
     (add-hook hk #'erc-status-sidebar--post-refresh))
 
diff --git a/lisp/erc/erc-truncate.el b/lisp/erc/erc-truncate.el
index 8430a68d92b..48d8408a85a 100644
--- a/lisp/erc/erc-truncate.el
+++ b/lisp/erc/erc-truncate.el
@@ -24,10 +24,8 @@
 
 ;;; Commentary:
 
-;; This implements buffer truncation (and optional log file writing
-;; support for the Emacs IRC client.  Use `erc-truncate-mode' to switch
-;; on.  Use `erc-enable-logging' to enable logging of the stuff which
-;; is getting truncated.
+;; This file implements buffer truncation through the `truncate'
+;; module, with optional `log' module integration.
 
 ;;; Code:
 
@@ -50,15 +48,41 @@ This prevents the query buffer from getting too large, 
which can
 bring any grown Emacs to its knees after a few days worth of
 tracking heavy-traffic channels."
   ;;enable
-  ((add-hook 'erc-insert-done-hook #'erc-truncate-buffer))
+  ((add-hook 'erc-insert-done-hook #'erc-truncate-buffer)
+   (add-hook 'erc-connect-pre-hook #'erc-truncate--warn-about-logging))
   ;; disable
-  ((remove-hook 'erc-insert-done-hook #'erc-truncate-buffer)))
+  ((remove-hook 'erc-insert-done-hook #'erc-truncate-buffer)
+   (remove-hook 'erc-connect-pre-hook #'erc-truncate--warn-about-logging)))
+
+(defun erc-truncate--warn-about-logging (&rest _)
+  (when (and (not erc--target)
+             (fboundp 'erc-log--call-when-logging-enabled-sans-module))
+    ;; We could also enable `erc-log-mode' here, but the risk of
+    ;; lasting damage is nonzero.
+    (erc-log--call-when-logging-enabled-sans-module
+     (lambda (dirfile)
+       ;; Emit a real Emacs warning because the message may be
+       ;; truncated away before it can be read if merely inserted.
+       (erc-button--display-error-notice-with-keys-and-warn
+        "The `truncate' module no longer enables logging implicitly."
+        " If you want ERC to write logs before truncating, add `log' to"
+        " `erc-modules' using something like \\[customize-option]."
+        " To silence this message, don't `require' `erc-log'."
+        (and dirfile " Alternatively, change the value of")
+        (and dirfile " `erc-log-channels-directory', or move ")
+        dirfile (and dirfile " elsewhere."))))))
 
 ;;;###autoload
 (defun erc-truncate-buffer-to-size (size &optional buffer)
-  "Truncates the buffer to the size SIZE.
-If BUFFER is not provided, the current buffer is assumed.  The deleted
-region is logged if `erc-logging-enabled' returns non-nil."
+  "Truncate BUFFER or the current buffer to SIZE.
+Log the deleted region when the `log' module is active and
+`erc-logging-enabled' returns non-nil.
+
+Note that prior to ERC 5.6, whenever erc-log.el happened to be
+loaded and the option `erc-enable-logging' was left at its
+default value, this function would cause logging to commence
+regardless of whether `erc-log-mode' was enabled or `log' was
+present in `erc-modules'."
   ;; If buffer is non-nil, but get-buffer does not return anything,
   ;; then this is a bug.  If buffer is a buffer name, get the buffer
   ;; object.  If buffer is nil, use the current buffer.
@@ -93,6 +117,9 @@ region is logged if `erc-logging-enabled' returns non-nil."
          ;; (not (memq 'erc-save-buffer-in-logs
          ;;             erc-insert-post-hook))
          ;; Comments?
+          ;; The comments above concern pre-5.6 behavior and reflect
+          ;; an obsolete understanding of how `erc-logging-enabled'
+          ;; behaves in practice.
           (run-hook-with-args 'erc--pre-clear-functions end)
          ;; disable undoing for the truncating
          (buffer-disable-undo)
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 03c21059a92..7375b5308ea 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -2879,19 +2879,23 @@ this option to nil."
           (cl-assert (< erc-insert-marker erc-input-marker))
           (cl-assert (= (field-end erc-insert-marker) erc-input-marker)))))
 
+(defvar erc--refresh-prompt-hook nil)
+
 (defun erc--refresh-prompt ()
   "Re-render ERC's prompt when the option `erc-prompt' is a function."
   (erc--assert-input-bounds)
-  (when (functionp erc-prompt)
-    (save-excursion
-      (goto-char erc-insert-marker)
-      (set-marker-insertion-type erc-insert-marker nil)
-      ;; Avoid `erc-prompt' (the named function), which appends a
-      ;; space, and `erc-display-prompt', which propertizes all but
-      ;; that space.
-      (insert-and-inherit (funcall erc-prompt))
-      (set-marker-insertion-type erc-insert-marker t)
-      (delete-region (point) (1- erc-input-marker)))))
+  (unless (erc--prompt-hidden-p)
+    (when (functionp erc-prompt)
+      (save-excursion
+        (goto-char erc-insert-marker)
+        (set-marker-insertion-type erc-insert-marker nil)
+        ;; Avoid `erc-prompt' (the named function), which appends a
+        ;; space, and `erc-display-prompt', which propertizes all but
+        ;; that space.
+        (insert-and-inherit (funcall erc-prompt))
+        (set-marker-insertion-type erc-insert-marker t)
+        (delete-region (point) (1- erc-input-marker))))
+    (run-hooks 'erc--refresh-prompt-hook)))
 
 (defun erc-display-line-1 (string buffer)
   "Display STRING in `erc-mode' BUFFER.
@@ -3007,22 +3011,51 @@ If STRING is nil, the function does nothing."
 (defvar erc--compose-text-properties nil
   "Non-nil when `erc-put-text-property' defers to `erc--merge-prop'.")
 
+;; To save space, we could maintain a map of all readable property
+;; values and optionally dispense archetypal constants in their place
+;; in order to ensure all occurrences of some list (a b) across all
+;; text-properties in all ERC buffers are actually the same object.
 (defun erc--merge-prop (from to prop val &optional object)
-  "Compose existing PROP values with VAL between FROM and TO in OBJECT.
+  "Combine existing PROP values with VAL between FROM and TO in OBJECT.
 For spans where PROP is non-nil, cons VAL onto the existing
 value, ensuring a proper list.  Otherwise, just set PROP to VAL.
-See also `erc-button-add-face'."
+When VAL is itself a list, prepend its members onto an existing
+value.  See also `erc-button-add-face'."
   (let ((old (get-text-property from prop object))
         (pos from)
         (end (next-single-property-change from prop object to))
         new)
     (while (< pos to)
-      (setq new (if old (cons val (ensure-list old)) val))
+      (setq new (if old
+                    (if (listp val)
+                        (append val (ensure-list old))
+                      (cons val (ensure-list old)))
+                  val))
       (put-text-property pos end prop new object)
       (setq pos end
             old (get-text-property pos prop object)
             end (next-single-property-change pos prop object to)))))
 
+(defvar erc-legacy-invisible-bounds-p nil
+  "Whether to hide trailing rather than preceding newlines.
+Beginning in ERC 5.6, invisibility extends from a message's
+preceding newline to its last non-newline character.")
+(make-obsolete-variable 'erc-legacy-invisible-bounds-p
+                        "decremented interval now permanent" "30.1")
+
+(defun erc--hide-message (value)
+  "Apply `invisible' text-property with VALUE to current message.
+Expect to run in a narrowed buffer during message insertion."
+  (if erc-legacy-invisible-bounds-p
+      ;; Before ERC 5.6, this also used to add an `intangible'
+      ;; property, but the docs say it's now obsolete.
+      (erc--merge-prop (point-min) (point-max) 'invisible value)
+    (let ((beg (point-min))
+          (end (point-max)))
+      (save-restriction
+        (widen)
+        (erc--merge-prop (1- beg) (1- end) 'invisible value)))))
+
 (defun erc-display-message-highlight (type string)
   "Highlight STRING according to TYPE, where erc-TYPE-face is an ERC face.
 
@@ -4804,7 +4837,7 @@ If FACE is non-nil, it will be used to propertize the 
prompt.  If it is nil,
         ;; shall remain part of the prompt.
         (setq prompt (propertize prompt
                                  'rear-nonsticky t
-                                 'erc-prompt t
+                                 'erc-prompt t ; t or `hidden'
                                  'field 'erc-prompt
                                  'front-sticky t
                                  'read-only t))
@@ -6350,7 +6383,9 @@ This option mainly prevents text accidentally entered 
into Emacs
 from being sent to the server.  Offending sources include
 terminal multiplexers, desktop-automation scripts, and anything
 capable of rapidly submitting successive lines of prompt input.
-For example, entering \"one\\ntwo\\nthree\\n\" will send \"one\"
+For example, if you could somehow manage to type \"one \\`RET'
+two \\`RET' three \\`RET'\" at the prompt in less than
+`erc-accidental-paste-threshold-seconds', ERC would send \"one\"
 to the server, leave \"two\" at the prompt, and insert \"three\"
 into an \"overflow\" buffer.  See `erc-inhibit-multiline-input'
 and `erc-warn-about-blank-lines' for suppression involving input
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index 732bbb3f1fa..25dccbd695c 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -148,6 +148,10 @@ to writing a completion function."
   (eshell-cmpl--custom-variable-docstring 'pcomplete-dir-ignore)
   :type (get 'pcomplete-dir-ignore 'custom-type))
 
+(defcustom eshell-cmpl-remote-file-ignore nil
+  (eshell-cmpl--custom-variable-docstring 'pcomplete-remote-file-ignore)
+  :type (get 'pcomplete-remote-file-ignore 'custom-type))
+
 (defcustom eshell-cmpl-ignore-case (eshell-under-windows-p)
   (eshell-cmpl--custom-variable-docstring 'completion-ignore-case)
   :type (get 'completion-ignore-case 'custom-type))
@@ -248,6 +252,8 @@ to writing a completion function."
               eshell-cmpl-file-ignore)
   (setq-local pcomplete-dir-ignore
               eshell-cmpl-dir-ignore)
+  (setq-local pcomplete-remote-file-ignore
+              eshell-cmpl-remote-file-ignore)
   (setq-local completion-ignore-case
               eshell-cmpl-ignore-case)
   (setq-local pcomplete-autolist
@@ -325,6 +331,15 @@ to writing a completion function."
             "Failed to evaluate argument form during completion: %S" arg)
      (propertize "\0" 'eshell-argument-stub 'error))))
 
+;; Code stolen from `eshell-plain-command'.
+(defun eshell-external-command-p (command)
+  "Whether an external command shall be called."
+  (let* ((esym (eshell-find-alias-function command))
+        (sym (or esym (intern-soft command))))
+    (not (and sym (fboundp sym)
+             (or esym eshell-prefer-lisp-functions
+                 (not (eshell-search-path command)))))))
+
 (defun eshell-complete-parse-arguments ()
   "Parse the command line arguments for `pcomplete-argument'."
   (when (and eshell-no-completion-during-jobs
@@ -406,6 +421,14 @@ to writing a completion function."
        args posns)
       (setq args (nreverse evaled-args)
             posns (nreverse evaled-posns)))
+    ;; Determine, whether remote file names shall be completed.  They
+    ;; shouldn't for external commands, or when in a pipe.  Respect
+    ;; also `eshell-cmpl-remote-file-ignore', which could be set by
+    ;; the user.
+    (setq-local pcomplete-remote-file-ignore
+                (or eshell-cmpl-remote-file-ignore
+                    eshell-in-pipeline-p ; does not work
+                    (eshell-external-command-p (car args))))
     ;; Convert arguments to forms that Pcomplete can understand.
     (cons (mapcar
            (lambda (arg)
diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el
index 5284df9ab59..640d3676750 100644
--- a/lisp/eshell/em-dirs.el
+++ b/lisp/eshell/em-dirs.el
@@ -429,9 +429,13 @@ in the minibuffer:
          (and eshell-cd-shows-directory
               (eshell-printn result)))
        (run-hooks 'eshell-directory-change-hook)
-       (if eshell-list-files-after-cd
-           ;; Let-bind eshell-last-command around this?
-           (eshell-plain-command "ls" (cdr args)))
+        (when eshell-list-files-after-cd
+          ;; Call "ls", but don't update the last-command information.
+          (let ((eshell-last-command-name)
+                (eshell-last-command-status)
+                (eshell-last-arguments))
+            (eshell-protect
+             (eshell-plain-command "ls" (cdr args)))))
        nil))))
 
 (put 'eshell/cd 'eshell-no-numeric-conversions t)
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index d00f8c93cd1..1141b673e97 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -69,6 +69,15 @@ by zsh for filename generation."
   :type 'hook
   :group 'eshell-glob)
 
+(defcustom eshell-glob-splice-results nil
+  "If non-nil, the results of glob patterns will be spliced in-place.
+When splicing, the resulting command is as though the user typed
+each result individually.  Otherwise, the glob results a single
+argument as a list."
+  :version "30.1"
+  :type 'boolean
+  :group 'eshell-glob)
+
 (defcustom eshell-glob-include-dot-files nil
   "If non-nil, glob patterns will match files beginning with a dot."
   :type 'boolean
@@ -139,12 +148,15 @@ This mimics the behavior of zsh if non-nil, but bash if 
nil."
 (defun eshell-no-command-globbing (terms)
   "Don't glob the command argument.  Reflect this by modifying TERMS."
   (ignore
-   (when (and (listp (car terms))
-             (eq (caar terms) 'eshell-extended-glob))
-     (setcar terms (cadr (car terms))))))
+   (pcase (car terms)
+     ((or `(eshell-extended-glob ,term)
+          `(eshell-splice-args (eshell-extended-glob ,term)))
+      (setcar terms term)))))
 
 (defun eshell-add-glob-modifier ()
   "Add `eshell-extended-glob' to the argument modifier list."
+  (when eshell-glob-splice-results
+    (add-to-list 'eshell-current-modifiers 'eshell-splice-args t))
   (add-to-list 'eshell-current-modifiers 'eshell-extended-glob))
 
 (defun eshell-parse-glob-chars ()
@@ -326,7 +338,9 @@ regular expressions, and these cannot support the above 
constructs."
     (or (and eshell-glob-matches (sort eshell-glob-matches #'string<))
        (if eshell-error-if-no-glob
            (error "No matches found: %s" glob)
-         glob))))
+          (if eshell-glob-splice-results
+              (list glob)
+            glob)))))
 
 ;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
 (defun eshell-glob-entries (path globs only-dirs)
diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el
index 2c199ec160f..9d4b72b01df 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -381,20 +381,19 @@ Input is entered into the input history ring, if the 
value of
 variable `eshell-input-filter' returns non-nil when called on the
 input."
   (when (and (funcall eshell-input-filter input)
-             (if (eq eshell-hist-ignoredups 'erase)
-                 ;; Remove any old occurrences of the input, and put
-                 ;; the new one at the end.
-                 (unless (ring-empty-p eshell-history-ring)
-                   (ring-remove eshell-history-ring
-                               (ring-member eshell-history-ring input))
-                   t)
-               ;; Always add...
-               (or (null eshell-hist-ignoredups)
-                   ;; ... or add if it's not already present at the
-                   ;; end.
-                  (not (ring-p eshell-history-ring))
-                  (ring-empty-p eshell-history-ring)
-                  (not (string-equal (eshell-get-history 0) input)))))
+             (pcase eshell-hist-ignoredups
+               ('nil t)                 ; Always add to history
+               ('erase                  ; Add, removing any old occurrences
+                (when-let ((old-index (ring-member eshell-history-ring input)))
+                  ;; Remove the old occurence of this input so we can
+                  ;; add it to the end.  FIXME: Should we try to
+                  ;; remove multiple old occurrences, e.g. if the user
+                  ;; recently changed to using `erase'?
+                  (ring-remove eshell-history-ring old-index))
+                t)
+               (_                       ; Add if not already the latest entry
+                (or (ring-empty-p eshell-history-ring)
+                    (not (string-equal (eshell-get-history 0) input))))))
     (eshell-put-history input))
   (setq eshell-save-history-index eshell-history-index)
   (setq eshell-history-index nil))
@@ -534,7 +533,7 @@ See also `eshell-read-history'."
          (forward-line 3)
          (while (search-backward "completion" nil 'move)
            (replace-match "history reference")))
-       (eshell-redisplay)
+        (redisplay)
        (message "Hit space to flush")
        (let ((ch (read-event)))
          (if (eq ch ?\ )
diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el
index 9b53bf29559..30f39d14b40 100644
--- a/lisp/eshell/em-ls.el
+++ b/lisp/eshell/em-ls.el
@@ -274,11 +274,7 @@ instead."
           ;; use the fancy highlighting in `eshell-ls' rather than font-lock
           (when eshell-ls-use-colors
             (font-lock-mode -1)
-            (setq font-lock-defaults nil)
-            (if (boundp 'font-lock-buffers)
-                (setq font-lock-buffers
-                      (delq (current-buffer)
-                            (symbol-value 'font-lock-buffers)))))
+            (setq font-lock-defaults nil))
           (require 'em-glob)
           (let* ((insert-func 'insert)
                  (error-func 'insert)
diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el
index bfb0dad60ef..1d67f1af990 100644
--- a/lisp/eshell/em-pred.el
+++ b/lisp/eshell/em-pred.el
@@ -301,16 +301,25 @@ This function is specially for adding onto 
`eshell-parse-argument-hook'."
                    (modifiers (eshell-parse-modifiers))
                   (preds (car modifiers))
                   (mods (cdr modifiers)))
-             (if (or preds mods)
-                 ;; has to go at the end, which is only natural since
-                 ;; syntactically it can only occur at the end
-                 (setq eshell-current-modifiers
-                       (append
-                        eshell-current-modifiers
-                        (list
-                         (lambda (lst)
-                           (eshell-apply-modifiers
-                            lst preds mods modifier-string))))))))
+              (when (or preds mods)
+                ;; Has to go at the end, which is only natural since
+                ;; syntactically it can only occur at the end.
+                (setq eshell-current-modifiers
+                      (append
+                       eshell-current-modifiers
+                       (list
+                        (lambda (lst)
+                          (eshell-apply-modifiers
+                           lst preds mods modifier-string)))))
+                (when (memq 'eshell-splice-args eshell-current-modifiers)
+                  ;; If splicing results, ensure that
+                  ;; `eshell-splice-args' is the last modifier.
+                  ;; Eshell's command parsing can't handle it anywhere
+                  ;; else.
+                  (setq eshell-current-modifiers
+                        (append (delq 'eshell-splice-args
+                                      eshell-current-modifiers)
+                                (list 'eshell-splice-args)))))))
          (goto-char (1+ end))
          (eshell-finish-arg))))))
 
diff --git a/lisp/eshell/em-smart.el b/lisp/eshell/em-smart.el
index d8b7fadc2c2..d5002a59d14 100644
--- a/lisp/eshell/em-smart.el
+++ b/lisp/eshell/em-smart.el
@@ -214,7 +214,7 @@ The options are `begin', `after' or `end'."
      0 frame)
     (if affected
        (let (window-scroll-functions) ;;FIXME: Why?
-         (eshell-redisplay)))))
+          (redisplay)))))
 
 (defun eshell-smart-display-setup ()
   "Set the point to somewhere in the beginning of the last command."
@@ -261,7 +261,7 @@ and the end of the buffer are still visible."
        (recenter -1)
        ;; trigger the redisplay now, so that we catch any attempted
        ;; point motion; this is to cover for a redisplay bug
-       (eshell-redisplay))
+        (redisplay))
     (let ((top-point (point)))
       (and (memq 'eshell-smart-display-move pre-command-hook)
           (>= (point) eshell-last-input-start)
diff --git a/lisp/eshell/em-term.el b/lisp/eshell/em-term.el
index ab26da857b7..3f76c349a7e 100644
--- a/lisp/eshell/em-term.el
+++ b/lisp/eshell/em-term.el
@@ -55,10 +55,11 @@ which commands are considered visual in nature."
   :type 'hook)
 
 (defcustom eshell-visual-commands
-  '("vi" "vim"                          ; what is going on??
+  '("vi" "vim" "nvim"                   ; what is going on??
     "screen" "tmux" "top" "htop"        ; ok, a valid program...
     "less" "more"                       ; M-x view-file
     "lynx" "links" "ncftp"              ; eww, ange-ftp
+    "ncmpcpp"                           ; M-x mpc
     "mutt" "pine" "tin" "trn" "elm")    ; GNUS!!
   "A list of commands that present their output in a visual fashion.
 
@@ -66,7 +67,7 @@ Commands listed here are run in a term buffer.
 
 See also `eshell-visual-subcommands' and `eshell-visual-options'."
   :type '(repeat string)
-  :version "29.1")
+  :version "30.1")
 
 (defcustom eshell-visual-subcommands
   nil
diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el
index b7ef0f0c40c..509b2d31819 100644
--- a/lisp/eshell/em-unix.el
+++ b/lisp/eshell/em-unix.el
@@ -692,19 +692,56 @@ Concatenate FILE(s), or standard input, to standard 
output.")
 
 ;; special front-end functions for compilation-mode buffers
 
+(defun eshell-compile (command args &optional method mode)
+  "Run an external COMMAND with ARGS using a compilation buffer when possible.
+COMMAND should be a list of command-line arguments.  By default,
+if the command is outputting to the screen and is not part of a
+pipeline or subcommand, open an compilation buffer to hold the
+results; otherwise, write the output on stdout.
+
+If METHOD is `interactive', always open a compilation buffer.  If
+METHOD is `plain', always write to stdout.
+
+MODE, if specified, is the major mode to set in the compilation
+buffer (see `compilation-start')."
+  (if (and (not (eq method 'interactive))
+           (or (eq method 'plain)
+               eshell-in-pipeline-p
+               eshell-in-subcommand-p
+               (not (eshell-interactive-output-p))))
+      (throw 'eshell-replace-command
+              (eshell-parse-command (concat "*" command) args))
+    (compile
+     (mapconcat #'shell-quote-argument
+                (eshell-stringify-list (flatten-tree (cons command args)))
+                " ")
+     mode)))
+
+(defun eshell/compile (&rest args)
+  "Run an external COMMAND using a compilation buffer when possible.
+See `eshell-compile'."
+  (eshell-eval-using-options
+   "compile" args
+   '((?m "mode" t mode "the mode to set in the compilation buffer")
+     (?i "interactive" 'interactive method "always open a compilation buffer")
+     (?p "plain" 'plain method "always write to stdout")
+     :usage "[-p | -i] [-m MODE] COMMAND...
+Run COMMAND in a compilation buffer when outputting to the screen and
+not part of a pipeline or subcommand."
+     :parse-leading-options-only)
+   (when (stringp mode)
+     (setq mode (intern mode)))
+   (eshell-compile (car args) (cdr args) method mode)))
+
+(put 'eshell/compile 'eshell-no-numeric-conversions t)
+
 (defun eshell/make (&rest args)
   "Use `compile' to do background makes.
 Fallback to standard make when called synchronously."
-  (if (and eshell-current-subjob-p
-          (eshell-interactive-output-p))
-      (let ((compilation-process-setup-function
-            (list 'lambda nil
-                  (list 'setq 'process-environment
-                        (list 'quote (eshell-copy-environment))))))
-       (compile (concat "make " (eshell-flatten-and-stringify args))))
-    (throw 'eshell-replace-command
-          (eshell-parse-command "*make" (eshell-stringify-list
-                                         (flatten-tree args))))))
+  (eshell-compile "make" args
+                  ;; Use plain output unless we're executing in the
+                  ;; background.
+                  (not eshell-current-subjob-p)))
 
 (put 'eshell/make 'eshell-no-numeric-conversions t)
 
@@ -777,22 +814,10 @@ and if it's not part of a command pipeline.  Otherwise, 
it calls the
 external command."
   (if (and maybe-use-occur eshell-no-grep-available)
       (eshell-poor-mans-grep args)
-    (if (or eshell-plain-grep-behavior
-           (not (and (eshell-interactive-output-p)
-                     (not eshell-in-pipeline-p)
-                     (not eshell-in-subcommand-p))))
-       (throw 'eshell-replace-command
-              (eshell-parse-command (concat "*" command)
-                                    (eshell-stringify-list
-                                     (flatten-tree args))))
-      (let* ((args (mapconcat 'identity
-                             (mapcar 'shell-quote-argument
-                                     (eshell-stringify-list
-                                      (flatten-tree args)))
-                             " "))
-            (cmd (format "%s -n %s" command args))
-            compilation-scroll-output)
-       (grep cmd)))))
+    (eshell-compile command (cons "-n" args)
+                    (and eshell-plain-grep-behavior
+                         'interactive)
+                     #'grep-mode)))
 
 (defun eshell/grep (&rest args)
   "Use Emacs grep facility instead of calling external grep."
@@ -816,8 +841,7 @@ external command."
 
 (defun eshell/glimpse (&rest args)
   "Use Emacs grep facility instead of calling external glimpse."
-  (let (null-device)
-    (eshell-grep "glimpse" (append '("-z" "-y") args))))
+  (eshell-grep "glimpse" (append '("-z" "-y") args)))
 
 ;; completions rules for some common UNIX commands
 
@@ -998,14 +1022,6 @@ Show wall-clock time elapsed during execution of 
COMMAND.")
   "Make \"whoami\" Tramp aware."
   (eshell-user-login-name))
 
-(defvar eshell-diff-window-config nil)
-
-(defun eshell-diff-quit ()
-  "Restore the window configuration previous to diff'ing."
-  (interactive)
-  (if eshell-diff-window-config
-      (set-window-configuration eshell-diff-window-config)))
-
 (defun eshell-nil-blank-string (string)
   "Return STRING, or nil if STRING contains only blank characters."
   (cond
@@ -1028,8 +1044,7 @@ Show wall-clock time elapsed during execution of 
COMMAND.")
          (throw 'eshell-replace-command
                 (eshell-parse-command "*diff" orig-args)))
       (let ((old (car (last args 2)))
-           (new (car (last args)))
-           (config (current-window-configuration)))
+            (new (car (last args))))
        (if (= (length args) 2)
            (setq args nil)
          (setcdr (last args 3) nil))
@@ -1041,18 +1056,6 @@ Show wall-clock time elapsed during execution of 
COMMAND.")
              (error
               (throw 'eshell-replace-command
                      (eshell-parse-command "*diff" orig-args))))
-         (when (fboundp 'diff-mode)
-           (add-hook
-            'compilation-finish-functions
-            (lambda (buff _msg)
-               (with-current-buffer buff
-                 (diff-mode)
-                  (setq-local eshell-diff-window-config config)
-                 (local-set-key [?q] #'eshell-diff-quit)
-                 (if (fboundp 'turn-on-font-lock-if-enabled)
-                     (turn-on-font-lock-if-enabled))
-                 (goto-char (point-min))))
-            nil t))
          (pop-to-buffer (current-buffer))))))
   nil)
 
@@ -1088,6 +1091,9 @@ Show wall-clock time elapsed during execution of 
COMMAND.")
 (put 'eshell/occur 'eshell-no-numeric-conversions t)
 
 (define-obsolete-function-alias 'nil-blank-string #'eshell-nil-blank-string 
"29.1")
+(defvar eshell-diff-window-config nil)
+(make-obsolete-variable 'eshell-diff-window-config "no longer used." "30.1")
+(define-obsolete-function-alias 'eshell-diff-quit #'ignore "30.1")
 
 (provide 'em-unix)
 
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index aa1e8f77ea5..26be1127880 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -541,7 +541,7 @@ If the form has no `type', the syntax is parsed as if 
`type' were
 (defun eshell-parse-delimiter ()
   "Parse an argument delimiter, which is essentially a command operator."
   ;; this `eshell-operator' keyword gets parsed out by
-  ;; `eshell-separate-commands'.  Right now the only possibility for
+  ;; `eshell-split-commands'.  Right now the only possibility for
   ;; error is an incorrect output redirection specifier.
   (when (looking-at "[&|;\n]\\s-*")
     (let ((end (match-end 0)))
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 94aa2ed8906..80066263396 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -364,8 +364,6 @@ This only returns external (non-Lisp) processes."
 
 ;; Command parsing
 
-(defvar eshell--sep-terms)
-
 (defmacro eshell-with-temp-command (region &rest body)
   "Narrow the buffer to REGION and execute the forms in BODY.
 
@@ -404,38 +402,33 @@ COMMAND can either be a string, or a cons cell 
demarcating a buffer
 region.  TOPLEVEL, if non-nil, means that the outermost command (the
 user's input command) is being parsed, and that pre and post command
 hooks should be run before and after the command."
-  (let* (eshell--sep-terms
-        (terms
-         (append
-          (if (consp command)
-              (eshell-parse-arguments (car command) (cdr command))
-             (eshell-with-temp-command command
-               (goto-char (point-max))
-               (eshell-parse-arguments (point-min) (point-max))))
-          args))
-        (commands
-         (mapcar
-           (lambda (cmd)
-             (setq cmd
-                   (if (or (not (car eshell--sep-terms))
-                           (string= (car eshell--sep-terms) ";"))
-                       (eshell-parse-pipeline cmd)
-                     `(eshell-do-subjob
-                       (cons :eshell-background
-                             ,(eshell-parse-pipeline cmd)))))
-             (setq eshell--sep-terms (cdr eshell--sep-terms))
-             (if eshell-in-pipeline-p
-                 cmd
-               `(eshell-trap-errors ,cmd)))
-          (eshell-separate-commands terms "[&;]" nil 'eshell--sep-terms))))
-    (let ((cmd commands))
-      (while cmd
-        ;; Copy I/O handles so each full statement can manipulate them
-        ;; if they like.  Steal the handles for the last command in
-        ;; the list; we won't use the originals again anyway.
-        (setcar cmd `(eshell-with-copied-handles
-                      ,(car cmd) ,(not (cdr cmd))))
-       (setq cmd (cdr cmd))))
+  (pcase-let*
+    ((terms
+      (append
+       (if (consp command)
+           (eshell-parse-arguments (car command) (cdr command))
+         (eshell-with-temp-command command
+           (goto-char (point-max))
+           (eshell-parse-arguments (point-min) (point-max))))
+       args))
+     (`(,sub-chains . ,sep-terms)
+      (eshell-split-commands terms "[&;]" nil t))
+     (commands
+      (mapcar
+       (lambda (cmd)
+         (let ((sep (pop sep-terms)))
+           (setq cmd (eshell-parse-pipeline cmd))
+           (when (equal sep "&")
+             (setq cmd `(eshell-do-subjob (cons :eshell-background ,cmd))))
+           (unless eshell-in-pipeline-p
+             (setq cmd `(eshell-trap-errors ,cmd)))
+           ;; Copy I/O handles so each full statement can manipulate
+           ;; them if they like.  Steal the handles for the last
+           ;; command in the list; we won't use the originals again
+           ;; anyway.
+           (setq cmd `(eshell-with-copied-handles ,cmd ,(not sep)))
+           cmd))
+       sub-chains)))
     (if toplevel
        `(eshell-commands (progn
                             (run-hooks 'eshell-pre-command-hook)
@@ -635,49 +628,40 @@ This means an exit code of 0."
 
 (defun eshell-parse-pipeline (terms)
   "Parse a pipeline from TERMS, return the appropriate Lisp forms."
-  (let* (eshell--sep-terms
-        (bigpieces (eshell-separate-commands terms "\\(&&\\|||\\)"
-                                             nil 'eshell--sep-terms))
-        (bp bigpieces)
-        (results (list t))
-        final)
-    (while bp
-      (let ((subterms (car bp)))
-       (let* ((pieces (eshell-separate-commands subterms "|"))
-              (p pieces))
-         (while p
-           (let ((cmd (car p)))
-             (run-hook-with-args 'eshell-pre-rewrite-command-hook cmd)
-             (setq cmd (run-hook-with-args-until-success
-                        'eshell-rewrite-command-hook cmd))
-             (let ((eshell--cmd cmd))
-               (run-hook-with-args 'eshell-post-rewrite-command-hook
-                                   'eshell--cmd)
-               (setq cmd eshell--cmd))
-             (setcar p (funcall eshell-post-rewrite-command-function cmd)))
-           (setq p (cdr p)))
-         (nconc results
-                (list
-                 (if (<= (length pieces) 1)
-                     (car pieces)
-                   (cl-assert (not eshell-in-pipeline-p))
-                   `(eshell-execute-pipeline (quote ,pieces))))))
-       (setq bp (cdr bp))))
+  (pcase-let*
+      ((`(,bigpieces . ,sep-terms)
+        (eshell-split-commands terms "\\(&&\\|||\\)" nil t))
+       (results) (final))
+    (dolist (subterms bigpieces)
+      (let* ((pieces (eshell-split-commands subterms "|"))
+             (p pieces))
+        (while p
+          (let ((cmd (car p)))
+            (run-hook-with-args 'eshell-pre-rewrite-command-hook cmd)
+            (setq cmd (run-hook-with-args-until-success
+                       'eshell-rewrite-command-hook cmd))
+            (let ((eshell--cmd cmd))
+              (run-hook-with-args 'eshell-post-rewrite-command-hook
+                                  'eshell--cmd)
+              (setq cmd eshell--cmd))
+            (setcar p (funcall eshell-post-rewrite-command-function cmd)))
+          (setq p (cdr p)))
+        (push (if (<= (length pieces) 1)
+                  (car pieces)
+                (cl-assert (not eshell-in-pipeline-p))
+                `(eshell-execute-pipeline (quote ,pieces)))
+              results)))
     ;; `results' might be empty; this happens in the case of
     ;; multi-line input
-    (setq results (cdr results)
-         results (nreverse results)
-         final (car results)
-         results (cdr results)
-         eshell--sep-terms (nreverse eshell--sep-terms))
+    (setq final (car results)
+          results (cdr results)
+          sep-terms (nreverse sep-terms))
     (while results
-      (cl-assert (car eshell--sep-terms))
+      (cl-assert (car sep-terms))
       (setq final (eshell-structure-basic-command
-                  'if (string= (car eshell--sep-terms) "&&") "if"
-                  `(eshell-protect ,(car results))
-                  `(eshell-protect ,final))
-           results (cdr results)
-           eshell--sep-terms (cdr eshell--sep-terms)))
+                   'if (string= (pop sep-terms) "&&") "if"
+                   `(eshell-protect ,(pop results))
+                   `(eshell-protect ,final))))
     final))
 
 (defun eshell-parse-subcommand-argument ()
@@ -712,6 +696,34 @@ This means an exit code of 0."
               (eshell-lisp-command (quote ,obj)))
          (ignore (goto-char here))))))
 
+(defun eshell-split-commands (terms separator &optional
+                                    reversed return-seps)
+  "Split TERMS using SEPARATOR.
+If REVERSED is non-nil, the list of separated term groups will be
+returned in reverse order.
+
+If RETURN-SEPS is nil, return just the separated terms as a list;
+otherwise, return both the separated terms and their separators
+as a pair of lists."
+  (let (sub-chains sub-terms sep-terms)
+    (dolist (term terms)
+      (if (and (eq (car-safe term) 'eshell-operator)
+               (string-match (concat "^" separator "$")
+                             (nth 1 term)))
+          (progn
+            (push (nth 1 term) sep-terms)
+            (push (nreverse sub-terms) sub-chains)
+            (setq sub-terms nil))
+        (push term sub-terms)))
+    (when sub-terms
+      (push (nreverse sub-terms) sub-chains))
+    (unless reversed
+      (setq sub-chains (nreverse sub-chains)
+            sep-terms (nreverse sep-terms)))
+    (if return-seps
+        (cons sub-chains sep-terms)
+      sub-chains)))
+
 (defun eshell-separate-commands (terms separator &optional
                                       reversed last-terms-sym)
   "Separate TERMS using SEPARATOR.
@@ -719,30 +731,14 @@ If REVERSED is non-nil, the list of separated term groups 
will be
 returned in reverse order.  If LAST-TERMS-SYM is a symbol, its value
 will be set to a list of all the separator operators found (or (nil)
 if none)."
-  (let ((sub-terms (list t))
-       (eshell-sep-terms (list t))
-       subchains)
-    (while terms
-      (if (and (consp (car terms))
-              (eq (caar terms) 'eshell-operator)
-              (string-match (concat "^" separator "$")
-                            (nth 1 (car terms))))
-         (progn
-           (nconc eshell-sep-terms (list (nth 1 (car terms))))
-           (setq subchains (cons (cdr sub-terms) subchains)
-                 sub-terms (list t)))
-       (nconc sub-terms (list (car terms))))
-      (setq terms (cdr terms)))
-    (if (> (length sub-terms) 1)
-       (setq subchains (cons (cdr sub-terms) subchains)))
-    (if reversed
-       (progn
-         (if last-terms-sym
-             (set last-terms-sym (reverse (cdr eshell-sep-terms))))
-         subchains)                    ; already reversed
-      (if last-terms-sym
-         (set last-terms-sym (cdr eshell-sep-terms)))
-      (nreverse subchains))))
+  (declare (obsolete eshell-split-commands "30.1"))
+  (let ((split-terms (eshell-split-commands terms separator reversed
+                                            last-terms-sym)))
+    (if last-terms-sym
+        (progn
+          (set last-terms-sym (cdr split-terms))
+          (car split-terms))
+      split-terms)))
 
 ;;_* Command evaluation macros
 ;;
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index cccdb49ce2a..c07f871dd37 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -423,57 +423,6 @@ If HANDLES is nil, use `eshell-current-handles'."
   (eshell-set-output-handle eshell-output-handle mode target handles)
   (eshell-copy-output-handle eshell-error-handle eshell-output-handle handles))
 
-(defun eshell-close-target (target status)
-  "Close an output TARGET, passing STATUS as the result.
-STATUS should be non-nil on successful termination of the output."
-  (cond
-   ((symbolp target) nil)
-
-   ;; If we were redirecting to a file, save the file and close the
-   ;; buffer.
-   ((markerp target)
-    (let ((buf (marker-buffer target)))
-      (when buf                         ; somebody's already killed it!
-       (save-current-buffer
-         (set-buffer buf)
-         (when eshell-output-file-buffer
-           (save-buffer)
-           (when (eq eshell-output-file-buffer t)
-             (or status (set-buffer-modified-p nil))
-             (kill-buffer buf)))))))
-
-   ;; If we're redirecting to a process (via a pipe, or process
-   ;; redirection), send it EOF so that it knows we're finished.
-   ((eshell-processp target)
-    ;; According to POSIX.1-2017, section 11.1.9, when communicating
-    ;; via terminal, sending EOF causes all bytes waiting to be read
-    ;; to be sent to the process immediately.  Thus, if there are any
-    ;; bytes waiting, we need to send EOF twice: once to flush the
-    ;; buffer, and a second time to cause the next read() to return a
-    ;; size of 0, indicating end-of-file to the reading process.
-    ;; However, some platforms (e.g. Solaris) actually require sending
-    ;; a *third* EOF.  Since sending extra EOFs while the process is
-    ;; running are a no-op, we'll just send the maximum we'd ever
-    ;; need.  See bug#56025 for further details.
-    (let ((i 0)
-          ;; Only call `process-send-eof' once if communicating via a
-          ;; pipe (in truth, this just closes the pipe).
-          (max-attempts (if (process-tty-name target 'stdin) 3 1)))
-      (while (and (<= (cl-incf i) max-attempts)
-                  (eq (process-status target) 'run))
-        (process-send-eof target))))
-
-   ;; A plain function redirection needs no additional arguments
-   ;; passed.
-   ((functionp target)
-    (funcall target status))
-
-   ;; But a more complicated function redirection (which can only
-   ;; happen with aliases at the moment) has arguments that need to be
-   ;; passed along with it.
-   ((consp target)
-    (apply (car target) status (cdr target)))))
-
 (defun eshell-kill-append (string)
   "Call `kill-append' with STRING, if it is indeed a string."
   (if (stringp string)
@@ -485,56 +434,6 @@ STATUS should be non-nil on successful termination of the 
output."
       (let ((select-enable-clipboard t))
        (kill-append string nil))))
 
-(defun eshell-get-target (target &optional mode)
-  "Convert TARGET, which is a raw argument, into a valid output target.
-MODE is either `overwrite', `append' or `insert'; if it is omitted or nil,
-it defaults to `insert'."
-  (setq mode (or mode 'insert))
-  (cond
-   ((stringp target)
-    (let ((redir (assoc target eshell-virtual-targets)))
-      (if redir
-         (if (nth 2 redir)
-             (funcall (nth 1 redir) mode)
-           (nth 1 redir))
-       (let* ((exists (get-file-buffer target))
-              (buf (find-file-noselect target t)))
-         (with-current-buffer buf
-           (if buffer-file-read-only
-               (error "Cannot write to read-only file `%s'" target))
-           (setq buffer-read-only nil)
-            (setq-local eshell-output-file-buffer
-                        (if (eq exists buf) 0 t))
-           (cond ((eq mode 'overwrite)
-                  (erase-buffer))
-                 ((eq mode 'append)
-                  (goto-char (point-max))))
-           (point-marker))))))
-
-
-   ((bufferp target)
-    (with-current-buffer target
-      (cond ((eq mode 'overwrite)
-             (erase-buffer))
-            ((eq mode 'append)
-             (goto-char (point-max))))
-      (point-marker)))
-
-   ((functionp target) nil)
-
-   ((symbolp target)
-    (if (eq mode 'overwrite)
-       (set target nil))
-    target)
-
-   ((or (eshell-processp target)
-       (markerp target))
-    target)
-
-   (t
-    (error "Invalid redirection target: %s"
-          (eshell-stringify target)))))
-
 (defun eshell-interactive-output-p (&optional index handles)
   "Return non-nil if the specified handle is bound for interactive display.
 HANDLES is the set of handles to check; if nil, use
@@ -599,55 +498,168 @@ after all printing is over with no argument."
   (eshell-print object)
   (eshell-print "\n"))
 
-(defun eshell-output-object-to-target (object target)
-  "Insert OBJECT into TARGET.
-Returns what was actually sent, or nil if nothing was sent."
-  (cond
-   ((functionp target)
-    (funcall target object))
-
-   ((symbolp target)
-    (if (eq target t)                   ; means "print to display"
-       (eshell-interactive-print (eshell-stringify object))
-      (if (not (symbol-value target))
-         (set target object)
-       (setq object (eshell-stringify object))
-       (if (not (stringp (symbol-value target)))
-           (set target (eshell-stringify
-                        (symbol-value target))))
-       (set target (concat (symbol-value target) object)))))
-
-   ((markerp target)
-    (if (buffer-live-p (marker-buffer target))
-       (with-current-buffer (marker-buffer target)
-         (let ((moving (= (point) target)))
-           (save-excursion
-             (goto-char target)
-             (unless (stringp object)
-               (setq object (eshell-stringify object)))
-             (insert-and-inherit object)
-             (set-marker target (point-marker)))
-           (if moving
-               (goto-char target))))))
-
-   ((eshell-processp target)
-    (unless (stringp object)
-      (setq object (eshell-stringify object)))
-    (condition-case err
-        (process-send-string target object)
-      (error
-       ;; If `process-send-string' raises an error and the process has
-       ;; finished, treat it as a broken pipe.  Otherwise, just
-       ;; re-throw the signal.
-       (if (memq (process-status target)
-                '(run stop open closed))
-           (signal (car err) (cdr err))
-         (signal 'eshell-pipe-broken (list target))))))
-
-   ((consp target)
-    (apply (car target) object (cdr target))))
+(cl-defstruct (eshell-virtual-target
+               (:constructor nil)
+               (:constructor eshell-virtual-target-create (output-function)))
+  "A virtual target (see `eshell-virtual-targets')."
+  output-function)
+
+(cl-defgeneric eshell-get-target (raw-target &optional _mode)
+  "Convert RAW-TARGET, which is a raw argument, into a valid output target.
+MODE is either `overwrite', `append' or `insert'; if it is omitted or nil,
+it defaults to `insert'."
+  (error "Invalid redirection target: %s" (eshell-stringify raw-target)))
+
+(cl-defmethod eshell-get-target ((raw-target string) &optional mode)
+  "Convert a string RAW-TARGET into a valid output target using MODE.
+If TARGET is a virtual target (see `eshell-virtual-targets'),
+return an `eshell-virtual-target' instance; otherwise, return a
+marker for a file named TARGET."
+  (setq mode (or mode 'insert))
+  (if-let ((redir (assoc raw-target eshell-virtual-targets)))
+      (eshell-virtual-target-create
+       (if (nth 2 redir)
+           (funcall (nth 1 redir) mode)
+         (nth 1 redir)))
+    (let ((exists (get-file-buffer raw-target))
+          (buf (find-file-noselect raw-target t)))
+      (with-current-buffer buf
+        (when buffer-file-read-only
+          (error "Cannot write to read-only file `%s'" raw-target))
+          (setq buffer-read-only nil)
+          (setq-local eshell-output-file-buffer
+                      (if (eq exists buf) 0 t))
+          (cond ((eq mode 'overwrite)
+                 (erase-buffer))
+                ((eq mode 'append)
+                 (goto-char (point-max))))
+          (point-marker)))))
+
+(cl-defmethod eshell-get-target ((raw-target buffer) &optional mode)
+  "Convert a buffer RAW-TARGET into a valid output target using MODE.
+This returns a marker for that buffer."
+  (with-current-buffer raw-target
+    (cond ((eq mode 'overwrite)
+           (erase-buffer))
+          ((eq mode 'append)
+           (goto-char (point-max))))
+    (point-marker)))
+
+(cl-defmethod eshell-get-target ((raw-target symbol) &optional mode)
+  "Convert a symbol RAW-TARGET into a valid output target using MODE.
+This returns RAW-TARGET, with its value initialized to nil if MODE is
+`overwrite'."
+  (when (eq mode 'overwrite)
+    (set raw-target nil))
+  raw-target)
+
+(cl-defmethod eshell-get-target ((raw-target process) &optional _mode)
+  "Convert a process RAW-TARGET into a valid output target.
+This just returns RAW-TARGET."
+  raw-target)
+
+(cl-defmethod eshell-get-target ((raw-target marker) &optional _mode)
+  "Convert a marker RAW-TARGET into a valid output target.
+This just returns RAW-TARGET."
+  raw-target)
+
+(cl-defgeneric eshell-close-target (target status)
+  "Close an output TARGET, passing STATUS as the result.
+STATUS should be non-nil on successful termination of the output.")
+
+(cl-defmethod eshell-close-target ((_target symbol) _status)
+  "Close a symbol TARGET."
+  nil)
+
+(cl-defmethod eshell-close-target ((target marker) status)
+  "Close a marker TARGET.
+If TARGET was created from a file name, save and kill the buffer.
+If status is nil, prompt before killing."
+  (when (buffer-live-p (marker-buffer target))
+    (with-current-buffer (marker-buffer target)
+      (when eshell-output-file-buffer
+        (save-buffer)
+        (when (eq eshell-output-file-buffer t)
+          (or status (set-buffer-modified-p nil))
+          (kill-buffer))))))
+
+(cl-defmethod eshell-close-target ((target process) _status)
+  "Close a process TARGET."
+  ;; According to POSIX.1-2017, section 11.1.9, when communicating via
+  ;; terminal, sending EOF causes all bytes waiting to be read to be
+  ;; sent to the process immediately.  Thus, if there are any bytes
+  ;; waiting, we need to send EOF twice: once to flush the buffer, and
+  ;; a second time to cause the next read() to return a size of 0,
+  ;; indicating end-of-file to the reading process.  However, some
+  ;; platforms (e.g. Solaris) actually require sending a *third* EOF.
+  ;; Since sending extra EOFs to a running process is a no-op, we'll
+  ;; just send the maximum we'd ever need.  See bug#56025 for further
+  ;; details.
+  (catch 'done
+    (dotimes (_ (if (process-tty-name target 'stdin) 3 1))
+      (unless (process-live-p target)
+        (throw 'done nil))
+      (process-send-eof target))))
+
+(cl-defmethod eshell-close-target ((_target eshell-virtual-target) _status)
+  "Close a virtual TARGET."
+  nil)
+
+(cl-defgeneric eshell-output-object-to-target (object target)
+  "Output OBJECT to TARGET.
+Returns what was actually sent, or nil if nothing was sent.")
+
+(cl-defmethod eshell-output-object-to-target (object (_target (eql t)))
+  "Output OBJECT to the display."
+  (setq object (eshell-stringify object))
+  (eshell-interactive-print object))
+
+(cl-defmethod eshell-output-object-to-target (object (target symbol))
+  "Output OBJECT to the value of the symbol TARGET."
+  (if (not (symbol-value target))
+      (set target object)
+    (setq object (eshell-stringify object))
+    (if (not (stringp (symbol-value target)))
+        (set target (eshell-stringify
+                     (symbol-value target))))
+    (set target (concat (symbol-value target) object)))
+  object)
+
+(cl-defmethod eshell-output-object-to-target (object (target marker))
+  "Output OBJECT to the marker TARGET."
+  (when (buffer-live-p (marker-buffer target))
+    (with-current-buffer (marker-buffer target)
+      (let ((moving (= (point) target)))
+        (save-excursion
+          (goto-char target)
+          (unless (stringp object)
+            (setq object (eshell-stringify object)))
+          (insert-and-inherit object)
+          (set-marker target (point-marker)))
+        (when moving
+          (goto-char target)))))
+  object)
+
+(cl-defmethod eshell-output-object-to-target (object (target process))
+  "Output OBJECT to the process TARGET."
+  (unless (stringp object)
+    (setq object (eshell-stringify object)))
+  (condition-case err
+      (process-send-string target object)
+    (error
+     ;; If `process-send-string' raises an error and the process has
+     ;; finished, treat it as a broken pipe.  Otherwise, just
+     ;; re-throw the signal.
+     (if (process-live-p target)
+         (signal (car err) (cdr err))
+       (signal 'eshell-pipe-broken (list target)))))
   object)
 
+(cl-defmethod eshell-output-object-to-target (object
+                                              (target eshell-virtual-target))
+  "Output OBJECT to the virtual TARGET."
+  (funcall (eshell-virtual-target-output-function target) object))
+
 (defun eshell-output-object (object &optional handle-index handles)
   "Insert OBJECT, using HANDLE-INDEX specifically.
 If HANDLE-INDEX is nil, output to `eshell-output-handle'.
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 00e0c8014e1..9c4036004ff 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -157,17 +157,14 @@ The signals which will cause this to happen are matched by
     (eshell-reset)))
 
 (defun eshell-wait-for-process (&rest procs)
-  "Wait until PROC has successfully completed."
-  (while procs
-    (let ((proc (car procs)))
-      (when (eshell-processp proc)
-       ;; NYI: If the process gets stopped here, that's bad.
-       (while (assq proc eshell-process-list)
-         (if (input-pending-p)
-             (discard-input))
-         (sit-for eshell-process-wait-seconds
-                  eshell-process-wait-milliseconds))))
-    (setq procs (cdr procs))))
+  "Wait until PROCS have successfully completed."
+  (dolist (proc procs)
+    (when (eshell-processp proc)
+      (while (process-live-p proc)
+        (when (input-pending-p)
+          (discard-input))
+        (sit-for eshell-process-wait-seconds
+                 eshell-process-wait-milliseconds)))))
 
 (defalias 'eshell/wait #'eshell-wait-for-process)
 
@@ -494,11 +491,11 @@ PROC is the process that's exiting.  STRING is the exit 
message."
                             (eshell-close-handles
                              status
                              (when status (list 'quote (= status 0)))
-                             handles)))))
+                             handles)
+                            (eshell-kill-process-function proc string)))))
                 (funcall finish-io))))
         (when-let ((entry (assq proc eshell-process-list)))
-          (eshell-remove-process-entry entry))
-        (eshell-kill-process-function proc string)))))
+          (eshell-remove-process-entry entry))))))
 
 (defun eshell-process-interact (func &optional all query)
   "Interact with a process, using PROMPT if more than one, via FUNC.
@@ -506,16 +503,14 @@ If ALL is non-nil, background processes will be 
interacted with as well.
 If QUERY is non-nil, query the user with QUERY before calling FUNC."
   (let (defunct result)
     (dolist (entry eshell-process-list)
-      (if (and (memq (process-status (car entry))
-                   '(run stop open closed))
+      (if (and (process-live-p (car entry))
               (or all
                   (not (cdr entry)))
               (or (not query)
                   (y-or-n-p (format-message query
                                             (process-name (car entry))))))
          (setq result (funcall func (car entry))))
-      (unless (memq (process-status (car entry))
-                   '(run stop open closed))
+      (unless (process-live-p (car entry))
        (setq defunct (cons entry defunct))))
     ;; clean up the process list; this can get dirty if an error
     ;; occurred that brought the user into the debugger, and then they
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 3608c78ba2b..87cd1f5dcb2 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -433,37 +433,21 @@ Prepend remote identification of `default-directory', if 
any."
 (defun eshell-printable-size (filesize &optional human-readable
                                       block-size use-colors)
   "Return a printable FILESIZE."
+  (when (and human-readable
+             (not (= human-readable 1000))
+             (not (= human-readable 1024)))
+    (error "human-readable must be 1000 or 1024"))
   (let ((size (float (or filesize 0))))
     (if human-readable
-       (if (< size human-readable)
-           (if (= (round size) 0)
-               "0"
-             (if block-size
-                 "1.0k"
-               (format "%.0f" size)))
-         (setq size (/ size human-readable))
-         (if (< size human-readable)
-             (if (<= size 9.94)
-                 (format "%.1fk" size)
-               (format "%.0fk" size))
-           (setq size (/ size human-readable))
-           (if (< size human-readable)
-               (let ((str (if (<= size 9.94)
-                              (format "%.1fM" size)
-                            (format "%.0fM" size))))
-                 (if use-colors
-                     (put-text-property 0 (length str)
-                                        'face 'bold str))
-                 str)
-             (setq size (/ size human-readable))
-             (if (< size human-readable)
-                 (let ((str (if (<= size 9.94)
-                                (format "%.1fG" size)
-                              (format "%.0fG" size))))
-                   (if use-colors
-                       (put-text-property 0 (length str)
-                                          'face 'bold-italic str))
-                   str)))))
+        (let* ((flavor (and (= human-readable 1000) 'si))
+               (str (file-size-human-readable size flavor)))
+          (if (not use-colors)
+              str
+            (cond ((> size (expt human-readable 3))
+                   (propertize str 'face 'bold-italic))
+                  ((> size (expt human-readable 2))
+                   (propertize str 'face 'bold))
+                  (t str))))
       (if block-size
          (setq size (/ size block-size)))
       (format "%.0f" size))))
@@ -492,16 +476,6 @@ list."
        (cadr flist)
       (cdr flist))))
 
-(defsubst eshell-redisplay ()
-  "Allow Emacs to redisplay buffers."
-  ;; for some strange reason, Emacs 21 is prone to trigger an
-  ;; "args out of range" error in `sit-for', if this function
-  ;; runs while point is in the minibuffer and the users attempt
-  ;; to use completion.  Don't ask me.
-  (condition-case nil
-      (sit-for 0)
-    (error nil)))
-
 (defun eshell-user-login-name ()
   "Return the connection-aware value of the user's login name.
 See also `user-login-name'."
@@ -628,8 +602,6 @@ See also `user-login-name'."
          (setq host-users (cdr host-users))
          (cdr (assoc user host-users))))))
 
-(autoload 'parse-time-string "parse-time")
-
 (eval-when-compile
   (require 'ange-ftp))         ; ange-ftp-parse-filename
 
@@ -813,6 +785,8 @@ If N or M is nil, it means the end of the list."
   (declare (obsolete seq-subseq "28.1"))
   (seq-subseq l n (1+ m)))
 
+(define-obsolete-function-alias 'eshell-redisplay #'redisplay "30.1")
+
 (provide 'esh-util)
 
 ;;; esh-util.el ends here
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index c7c0a21d2a9..711c35f8527 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -162,6 +162,13 @@ if they are quoted with a backslash."
     ("COLUMNS" ,(lambda () (window-body-width nil 'remap)) t t)
     ("LINES" ,(lambda () (window-body-height nil 'remap)) t t)
     ("INSIDE_EMACS" eshell-inside-emacs t)
+    ("PAGER" (,(lambda () (or comint-pager (getenv "PAGER")))
+              . ,(lambda (_ value)
+                   ;; When unsetting PAGER, be sure to clear its value
+                   ;; from `process-environment' too.
+                   (unless value (setenv "PAGER"))
+                   (setq comint-pager value)))
+     t t)
     ("UID" ,(lambda () (file-user-uid)) nil t)
     ("GID" ,(lambda () (file-group-gid)) nil t)
 
@@ -262,11 +269,13 @@ copied (a.k.a. \"exported\") to the environment of 
created subprocesses."
   ;; changing a variable will affect all of Emacs.
   (unless eshell-modify-global-environment
     (setq-local process-environment (eshell-copy-environment)))
+  (make-local-variable 'comint-pager)
   (setq-local eshell-subcommand-bindings
               (append
                '((process-environment (eshell-copy-environment))
                  (eshell-variable-aliases-list eshell-variable-aliases-list)
-                 (eshell-path-env-list eshell-path-env-list))
+                 (eshell-path-env-list eshell-path-env-list)
+                 (comint-pager comint-pager))
                eshell-subcommand-bindings))
 
   (setq-local eshell-special-chars-inside-quoting
diff --git a/lisp/face-remap.el b/lisp/face-remap.el
index 3ec271b67a4..c5f7af37406 100644
--- a/lisp/face-remap.el
+++ b/lisp/face-remap.el
@@ -70,21 +70,10 @@
    :foreground :background :stipple :overline :strike-through :box
    :font :inherit :fontset :distant-foreground :extend :vector])
 
-(defun face-remap--copy-face (val)
-  "Return a copy of the `face' property value VAL."
-  ;; A `face' property can be either a face name (a symbol), or a face
-  ;; property list like (:foreground "red" :inherit default),
-  ;; or a list of such things.
-  ;; FIXME: This should probably be shared to some extent with
-  ;; `add-face-text-property'.
-  (if (or (not (listp val)) (keywordp (car val)))
-      val
-    (copy-sequence val)))
-
 (defun face-attrs--make-indirect-safe ()
   "Deep-copy the buffer's `face-remapping-alist' upon cloning the buffer."
   (setq-local face-remapping-alist
-              (mapcar #'face-remap--copy-face face-remapping-alist)))
+              (mapcar #'copy-tree face-remapping-alist)))
 
 (add-hook 'clone-indirect-buffer-hook #'face-attrs--make-indirect-safe)
 
diff --git a/lisp/faces.el b/lisp/faces.el
index 44d64c743ba..8f93f9b2c0c 100644
--- a/lisp/faces.el
+++ b/lisp/faces.el
@@ -1118,8 +1118,7 @@ element of DEFAULT is returned.  If DEFAULT isn't a list, 
but
 MULTIPLE is non-nil, a one-element list containing DEFAULT is
 returned.  Otherwise, DEFAULT is returned verbatim."
   (let (defaults)
-    (unless (listp default)
-      (setq default (list default)))
+    (setq default (ensure-list default))
     (when default
       (setq default
             (if multiple
@@ -1340,10 +1339,11 @@ of a global face.  Value is the new attribute value."
                       (format "%s" old-value))))
             (setq new-value
                    (if (memq attribute '(:foreground :background))
-                       (let ((color
-                              (read-color
-                               (format-prompt "%s for face `%s'"
-                                              default attribute-name face))))
+                       (let* ((prompt (format-prompt
+                                       "%s for face `%s'"
+                                       default attribute-name face))
+                              (fg (eq attribute ':foreground))
+                              (color (read-color prompt nil nil nil fg face)))
                          (if (equal (string-trim color) "")
                              default
                            color))
@@ -1539,15 +1539,12 @@ argument, prompt for a regular expression using 
`read-regexp'."
 ;;; Face specifications (defface).
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-;; Parameter FRAME Is kept for call compatibility to with previous
-;; face implementation.
-
 (defun face-attr-construct (face &optional _frame)
   "Return a `defface'-style attribute list for FACE.
 Value is a property list of pairs ATTRIBUTE VALUE for all specified
 face attributes of FACE where ATTRIBUTE is the attribute name and
-VALUE is the specified value of that attribute.
-Argument FRAME is ignored and retained for compatibility."
+VALUE is the specified value of that attribute."
+  (declare (advertised-calling-convention (face) "30.1"))
   (let (result)
     (dolist (entry face-attribute-name-alist result)
       (let* ((attribute (car entry))
@@ -1849,7 +1846,6 @@ If there is neither a user setting nor a default for 
FACE, return nil."
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; Frame-type independent color support.
-;;; We keep the old x-* names as aliases for back-compatibility.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (defun defined-colors (&optional frame)
@@ -1861,7 +1857,6 @@ If FRAME is nil, that stands for the selected frame."
   (if (display-graphic-p frame)
       (xw-defined-colors frame)
     (mapcar 'car (tty-color-alist frame))))
-(defalias 'x-defined-colors 'defined-colors)
 
 (defun defined-colors-with-face-attributes (&optional frame foreground)
   "Return a list of colors supported for a particular FRAME.
@@ -1870,15 +1865,26 @@ to `defined-colors' the elements of the returned list 
are color
 strings with text properties, that make the color names render
 with the color they represent as background color (if FOREGROUND
 is nil; otherwise use the foreground color)."
-  (mapcar
-   (lambda (color-name)
-     (let ((color (copy-sequence color-name)))
-       (propertize color 'face
-                  (if foreground
-                      (list :foreground color)
-                    (list :foreground (readable-foreground-color color-name)
-                           :background color)))))
-   (defined-colors frame)))
+  (mapcar (lambda (color-name)
+            (faces--string-with-color color-name color-name foreground))
+          (defined-colors frame)))
+
+(defun faces--string-with-color (string color &optional foreground face)
+  "Return a copy of STRING with face attributes for COLOR.
+Set the :background or :foreground attribute to COLOR, depending
+on the argument FOREGROUND.
+
+The optional FACE argument determines the values of other face
+attributes."
+  (let* ((defaults (if face (list face) '()))
+         (colors (cond (foreground
+                        (list :foreground color))
+                       (face
+                        (list :background color))
+                       (t
+                        (list :foreground (readable-foreground-color color)
+                              :background color)))))
+    (propertize string 'face (cons colors defaults))))
 
 (defun readable-foreground-color (color)
   "Return a readable foreground color for background COLOR.
@@ -1934,7 +1940,6 @@ If FRAME is omitted or nil, use the selected frame."
     (if (display-graphic-p frame)
        (xw-color-defined-p color frame)
       (numberp (tty-color-translate color frame)))))
-(defalias 'x-color-defined-p 'color-defined-p)
 
 (declare-function xw-color-values "xfns.c" (color &optional frame))
 
@@ -1962,8 +1967,6 @@ return value is nil."
    (t
     (tty-color-values color frame))))
 
-(defalias 'x-color-values 'color-values)
-
 (declare-function xw-display-color-p "xfns.c" (&optional terminal))
 
 (defun display-color-p (&optional display)
@@ -1974,7 +1977,6 @@ If omitted or nil, that stands for the selected frame's 
display."
   (if (display-graphic-p display)
       (xw-display-color-p display)
     (tty-display-color-p display)))
-(defalias 'x-display-color-p 'display-color-p)
 
 (declare-function x-display-grayscale-p "xfns.c" (&optional terminal))
 
@@ -1987,7 +1989,7 @@ If omitted or nil, that stands for the selected frame's 
display."
     (> (tty-color-gray-shades display) 2)))
 
 (defun read-color (&optional prompt convert-to-RGB allow-empty-name msg
-                            foreground)
+                            foreground face)
   "Read a color name or RGB triplet.
 Completion is available for color names, but not for RGB triplets.
 
@@ -2016,17 +2018,25 @@ to enter an empty color name (the empty string).
 Interactively, or with optional arg MSG non-nil, print the
 resulting color name in the echo area.
 
-Interactively, displays a list of colored completions.  If optional
-argument FOREGROUND is non-nil, shows them as foregrounds, otherwise
-as backgrounds."
+Interactively, provides completion for selecting the color.  If
+the optional argument FOREGROUND is non-nil, shows the completion
+candidates with their foregound color changed to be the color of
+the candidate, otherwise changes the background color of the
+candidates.  The optional argument FACE determines the other
+face attributes of the candidates on display."
   (interactive "i\np\ni\np")    ; Always convert to RGB interactively.
   (let* ((completion-ignore-case t)
-        (colors (append '("foreground at point" "background at point")
-                        (if allow-empty-name '(""))
-                         (if (display-color-p)
-                             (defined-colors-with-face-attributes
-                               nil foreground)
-                           (defined-colors))))
+        (color-alist
+          `(("foreground at point" . ,(foreground-color-at-point))
+            ("background at point" . ,(background-color-at-point))
+            ,@(if allow-empty-name '(("" . unspecified)))
+            ,@(mapcar (lambda (c) (cons c c)) (defined-colors))))
+         (colors (mapcar (lambda (pair)
+                           (let* ((name (car pair))
+                                  (color (cdr pair)))
+                             (faces--string-with-color name color
+                                                       foreground face)))
+                         color-alist))
         (color (completing-read
                 (or prompt "Color (name or #RGB triplet): ")
                 ;; Completing function for reading colors, accepting
@@ -2926,7 +2936,7 @@ Note: Other faces cannot inherit from the cursor face."
     (((type haiku))
      :foreground "B_MENU_ITEM_TEXT_COLOR"
      :background "B_MENU_BACKGROUND_COLOR")
-    (((type x w32 ns pgtk) (class color))
+    (((type x w32 ns pgtk android) (class color))
      :background "grey75")
     (((type x) (class mono))
      :background "grey"))
@@ -3201,6 +3211,10 @@ also the same size as FACE on FRAME, or fail."
 
 (define-obsolete-function-alias 'face-background-pixmap #'face-stipple "29.1")
 (define-obsolete-function-alias 'set-face-background-pixmap #'set-face-stipple 
"29.1")
+(define-obsolete-function-alias 'x-defined-colors #'defined-colors "30.1")
+(define-obsolete-function-alias 'x-color-defined-p #'color-defined-p "30.1")
+(define-obsolete-function-alias 'x-color-values #'color-values "30.1")
+(define-obsolete-function-alias 'x-display-color-p #'display-color-p "30.1")
 
 (provide 'faces)
 
diff --git a/lisp/ffap.el b/lisp/ffap.el
index 57f5271708b..907f56763ff 100644
--- a/lisp/ffap.el
+++ b/lisp/ffap.el
@@ -558,10 +558,6 @@ Looks at `ffap-ftp-default-user', returns \"\" for 
\"localhost\"."
    (ffap-ftp-regexp (ffap-host-to-filename mach))
    ))
 
-(defvaralias 'ffap-newsgroup-regexp 'thing-at-point-newsgroup-regexp)
-(defvaralias 'ffap-newsgroup-heads  'thing-at-point-newsgroup-heads)
-(defalias 'ffap-newsgroup-p 'thing-at-point-newsgroup-p)
-
 (defun ffap-url-p (string)
   "If STRING looks like an URL, return it (maybe improved), else nil."
   (when (and (stringp string) ffap-url-regexp)
@@ -2144,6 +2140,10 @@ Of course if you do not like these bindings, just roll 
your own!")
   (interactive)
   (eval (cons 'progn ffap-bindings)))
 
+(define-obsolete-variable-alias 'ffap-newsgroup-regexp 
'thing-at-point-newsgroup-regexp "30.1")
+(define-obsolete-variable-alias 'ffap-newsgroup-heads  
'thing-at-point-newsgroup-heads "30.1")
+(define-obsolete-function-alias 'ffap-newsgroup-p #'thing-at-point-newsgroup-p 
"30.1")
+
 
 (provide 'ffap)
 
diff --git a/lisp/files-x.el b/lisp/files-x.el
index 9b1a7a17902..3ba7632d253 100644
--- a/lisp/files-x.el
+++ b/lisp/files-x.el
@@ -136,7 +136,10 @@ Intended to be used in the `interactive' spec of
              (eq new-value not-value)
              (not (equal old-value new-value)))
       (message "%s" (substitute-command-keys
-                    "For this change to take effect revisit file using 
\\[revert-buffer]")))))
+                     (if (and (stringp buffer-file-name)
+                              (file-exists-p buffer-file-name))
+                        "For this change to take effect revisit file using 
\\[revert-buffer]"
+                       "For this change to take effect use 
\\[normal-mode]"))))))
 
 (defun modify-file-local-variable (variable value op &optional interactive)
   "Modify file-local VARIABLE in Local Variables depending on operation OP.
diff --git a/lisp/files.el b/lisp/files.el
index 4b65ff10b68..4188615e490 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -1283,10 +1283,8 @@ Tip: You can use this expansion of remote identifier 
components
      returns a remote file name for file \"/bin/sh\" that has the
      same remote identifier as FILE but expanded; a name such as
      \"/sudo:root@myhost:/bin/sh\"."
-  (let ((handler (find-file-name-handler file 'file-remote-p)))
-    (if handler
-       (funcall handler 'file-remote-p file identification connected)
-      nil)))
+  (when-let ((handler (find-file-name-handler file 'file-remote-p)))
+    (funcall handler 'file-remote-p file identification connected)))
 
 ;; Probably this entire variable should be obsolete now, in favor of
 ;; something Tramp-related (?).  It is not used in many places.
@@ -1998,6 +1996,8 @@ INHIBIT-BUFFER-HOOKS non-nil.
 Note: Be careful with let-binding this hook considering it is
 frequently used for cleanup.")
 
+(defvar find-alternate-file-dont-kill-client nil
+  "If non-nil, `server-buffer-done' should not delete the client.")
 (defun find-alternate-file (filename &optional wildcards)
   "Find file FILENAME, select its buffer, kill previous buffer.
 If the current buffer now contains an empty file that you just visited
@@ -2044,7 +2044,8 @@ killed."
     ;; save a modified buffer visiting a file.  Rather, `kill-buffer'
     ;; asks that itself.  Thus, there's no need to temporarily do
     ;; `(set-buffer-modified-p nil)' before running this hook.
-    (run-hooks 'kill-buffer-hook)
+    (let ((find-alternate-file-dont-kill-client 'dont-kill-client))
+      (run-hooks 'kill-buffer-hook))
     ;; Okay, now we can end-of-life the old buffer.
     (if (get-buffer " **lose**")
        (kill-buffer " **lose**"))
@@ -2094,6 +2095,8 @@ Emacs treats buffers whose names begin with a space as 
internal buffers.
 To avoid confusion when visiting a file whose name begins with a space,
 this function prepends a \"|\" to the final result if necessary."
   (let* ((lastname (file-name-nondirectory (directory-file-name filename)))
+         (lastname (if (string= lastname "") ; FILENAME is a root directory
+                       filename lastname))
          (lastname (cond
                     ((not (and uniquify-trailing-separator-p
                                (file-directory-p filename)))
@@ -5793,9 +5796,14 @@ Before and after saving the buffer, this function runs
                  (run-hook-with-args-until-success 'write-file-functions)
                  ;; If a hook returned t, file is already "written".
                  ;; Otherwise, write it the usual way now.
-                 (let ((dir (file-name-directory
+                 (let ((file (buffer-file-name))
+                        (dir (file-name-directory
                              (expand-file-name buffer-file-name))))
-                   (unless (file-exists-p dir)
+                    ;; Some systems have directories (like /content on
+                    ;; Android) in which files can exist without a
+                    ;; corresponding parent directory.
+                   (unless (or (file-exists-p file)
+                                (file-exists-p dir))
                      (if (y-or-n-p
                           (format-message
                             "Directory `%s' does not exist; create? " dir))
@@ -5864,8 +5872,10 @@ Before and after saving the buffer, this function runs
                     buffer-file-name)))
                  (setq tempsetmodes t)
                (error "Attempt to save to a file that you aren't allowed to 
write"))))))
-    (or buffer-backed-up
-       (setq setmodes (backup-buffer)))
+    (with-demoted-errors
+        "Backing up buffer: %s"
+      (or buffer-backed-up
+         (setq setmodes (backup-buffer))))
     (let* ((dir (file-name-directory buffer-file-name))
            (dir-writable (file-writable-p dir)))
       (if (or (and file-precious-flag dir-writable)
@@ -6350,6 +6360,27 @@ non-nil and if FN fails due to a missing file or 
directory."
       (apply fn args)
     (file-missing (or no-such (signal (car err) (cdr err))))))
 
+(defun delete-file (filename &optional trash)
+  "Delete file named FILENAME.  If it is a symlink, remove the symlink.
+If file has multiple names, it continues to exist with the other names.
+TRASH non-nil means to trash the file instead of deleting, provided
+`delete-by-moving-to-trash' is non-nil.
+
+When called interactively, TRASH is t if no prefix argument is given.
+With a prefix argument, TRASH is nil."
+  (interactive (list (read-file-name
+                      (if (and delete-by-moving-to-trash (null 
current-prefix-arg))
+                          "Move file to trash: " "Delete file: ")
+                      nil default-directory 
(confirm-nonexistent-file-or-buffer))
+                     (null current-prefix-arg)))
+  (if (and (file-directory-p filename) (not (file-symlink-p filename)))
+      (signal 'file-error (list "Removing old name: is a directory" filename)))
+  (let* ((filename (expand-file-name filename))
+         (handler (find-file-name-handler filename 'delete-file)))
+    (cond (handler (funcall handler 'delete-file filename trash))
+          ((and delete-by-moving-to-trash trash) (move-file-to-trash filename))
+          (t (delete-file-internal filename)))))
+
 (defun delete-directory (directory &optional recursive trash)
   "Delete the directory named DIRECTORY.  Does not follow symlinks.
 If RECURSIVE is non-nil, delete files in DIRECTORY as well, with
@@ -6591,7 +6622,15 @@ into NEWNAME instead."
                                     (file-attributes directory))))
              (follow-flag (unless follow 'nofollow)))
          (if modes (set-file-modes newname modes follow-flag))
-         (if times (set-file-times newname times follow-flag)))))))
+         (when times
+            ;; When built for an Android GUI build, don't attempt to
+            ;; set file times for a file within /content, as the
+            ;; Android VFS layer does not provide means to change file
+            ;; timestamps.
+            (when (or (not (and (eq system-type 'android)
+                                (featurep 'android)))
+                      (not (string-prefix-p "/content/" newname)))
+                (set-file-times newname times follow-flag))))))))
 
 
 ;; At time of writing, only info uses this.
@@ -6732,7 +6771,10 @@ This function binds `revert-buffer-in-progress-p' 
non-nil while it operates.
 This function calls the function that `revert-buffer-function' specifies
 to do the work, with arguments IGNORE-AUTO and NOCONFIRM.
 The default function runs the hooks `before-revert-hook' and
-`after-revert-hook'
+`after-revert-hook'.
+Return value is whatever `revert-buffer-function' returns.  For historical
+reasons, that return value is non-nil when `revert-buffer-function'
+succeeds in its job and returns non-nil.
 
 Reverting a buffer will try to preserve markers in the buffer,
 but it cannot always preserve all of them.  For better results,
@@ -6749,17 +6791,20 @@ preserve markers and overlays, at the price of being 
slower."
         (revert-buffer-preserve-modes preserve-modes)
         (state (and (boundp 'read-only-mode--state)
                     (list read-only-mode--state))))
-    (funcall (or revert-buffer-function #'revert-buffer--default)
-             ignore-auto noconfirm)
-    (when state
-      (setq buffer-read-only (car state))
-      (setq-local read-only-mode--state (car state)))))
+    ;; Return whatever 'revert-buffer-function' returns.
+    (prog1 (funcall (or revert-buffer-function #'revert-buffer--default)
+                    ignore-auto noconfirm)
+      (when state
+        (setq buffer-read-only (car state))
+        (setq-local read-only-mode--state (car state))))))
 
 (defun revert-buffer--default (ignore-auto noconfirm)
   "Default function for `revert-buffer'.
 The arguments IGNORE-AUTO and NOCONFIRM are as described for `revert-buffer'.
 Runs the hooks `before-revert-hook' and `after-revert-hook' at the
 start and end.
+The function returns non-nil if it reverts the buffer; signals
+an error if the buffer is not associated with a file.
 
 Calls `revert-buffer-insert-file-contents-function' to reread the
 contents of the visited file, with two arguments: the first is the file
@@ -6867,9 +6912,9 @@ an auto-save file."
       (if revert-buffer-preserve-modes
           (let ((buffer-file-format buffer-file-format))
             (insert-file-contents file-name (not auto-save-p)
-                                  nil nil t))
+                                  nil nil 'if-regular))
         (insert-file-contents file-name (not auto-save-p)
-                              nil nil t))))))
+                              nil nil 'if-regular))))))
 
 (defvar revert-buffer-with-fine-grain-max-seconds 2.0
   "Maximum time that `revert-buffer-with-fine-grain' should use.
@@ -7713,7 +7758,6 @@ If DIR's free space cannot be obtained, this function 
returns nil."
       (if avail
           (funcall byte-count-to-string-function avail)))))
 
-;; The following expression replaces `dired-move-to-filename-regexp'.
 (defvar directory-listing-before-filename-regexp
   (let* ((l "\\([A-Za-z]\\|[^\0-\177]\\)")
         (l-or-quote "\\([A-Za-z']\\|[^\0-\177]\\)")
@@ -8518,7 +8562,7 @@ the leading `-' character."
 (defun file-modes-symbolic-to-number (modes &optional from)
   "Convert symbolic file modes to numeric file modes.
 MODES is the string to convert, it should match
-\"[ugoa]*([+-=][rwxXstugo]*)+,...\".
+\"[ugoa]*([+=-][rwxXstugo]*)+,...\".
 See Info node `(coreutils)File permissions' for more information on this
 notation.
 FROM (or 0 if nil) gives the mode bits on which to base permissions if
diff --git a/lisp/find-dired.el b/lisp/find-dired.el
index db2f5e7d026..48408d35499 100644
--- a/lisp/find-dired.el
+++ b/lisp/find-dired.el
@@ -247,8 +247,8 @@ it finishes, type \\[kill-find]."
     (erase-buffer)
     (setq default-directory dir)
     ;; Start the find process.
-    (shell-command (concat command "&") (current-buffer))
-    (let ((proc (get-buffer-process (current-buffer))))
+    (let ((proc (start-file-process-shell-command
+                 (buffer-name) (current-buffer) command)))
       ;; Initialize the process marker; it is used by the filter.
       (move-marker (process-mark proc) (point) (current-buffer))
       (set-process-filter proc #'find-dired-filter)
diff --git a/lisp/finder.el b/lisp/finder.el
index 5aec0149b89..f5bf2641537 100644
--- a/lisp/finder.el
+++ b/lisp/finder.el
@@ -2,7 +2,7 @@
 
 ;; Copyright (C) 1992-2023 Free Software Foundation, Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Created: 16 Jun 1992
 ;; Keywords: help
 
diff --git a/lisp/format.el b/lisp/format.el
index b2dba16659b..89a83c3cee2 100644
--- a/lisp/format.el
+++ b/lisp/format.el
@@ -87,11 +87,16 @@
           rot13-region rot13-region t nil)
     (duden ,(purecopy "Duden Ersatzdarstellung")
           nil
-          ,(purecopy "diac") iso-iso2duden t nil)
+          ;; FROM-FN used to call the "diac" command which is not widely
+          ;; available and apparently not under a free software license:
+          ;; https://nm.wu-wien.ac.at/nm/download/file/diac4.tar.gz
+          ;; Reliable round-trip conversion is not possible anyway
+          ;; and would be by heuristic method, so use nil for now.
+          nil iso-iso2duden t nil)
     (de646 ,(purecopy "German ASCII (ISO 646)")
           nil
-          ,(purecopy "recode -f iso646-ge:latin1")
-          ,(purecopy "recode -f latin1:iso646-ge") t nil)
+          ,(purecopy "iconv -f iso646-de -t utf-8")
+          ,(purecopy "iconv -f utf-8 -t iso646-de") t nil)
     (denet ,(purecopy "net German")
           nil
           iso-german iso-cvt-read-only t nil)
@@ -290,7 +295,7 @@ For most purposes, consider using `format-decode-region' 
instead."
                          (setq try format-alist))
                      (setq try (cdr try))))))
            ;; Deal with given format(s)
-           (or (listp format) (setq format (list format)))
+            (setq format (ensure-list format))
            (let ((do format) f)
              (while do
                (or (setq f (assq (car do) format-alist))
diff --git a/lisp/frame.el b/lisp/frame.el
index 39e8a4c88b8..ba5d1caafa2 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -1653,6 +1653,7 @@ live frame and defaults to the selected one."
 (declare-function ns-frame-geometry "nsfns.m" (&optional frame))
 (declare-function pgtk-frame-geometry "pgtkfns.c" (&optional frame))
 (declare-function haiku-frame-geometry "haikufns.c" (&optional frame))
+(declare-function android-frame-geometry "androidfns.c" (&optional frame))
 
 (defun frame-geometry (&optional frame)
   "Return geometric attributes of FRAME.
@@ -1706,6 +1707,8 @@ and width values are in pixels.
       (pgtk-frame-geometry frame))
      ((eq frame-type 'haiku)
       (haiku-frame-geometry frame))
+     ((eq frame-type 'android)
+      (android-frame-geometry frame))
      (t
       (list
        '(outer-position 0 . 0)
@@ -1832,6 +1835,7 @@ of frames like calls to map a frame or change its 
visibility."
 (declare-function ns-frame-edges "nsfns.m" (&optional frame type))
 (declare-function pgtk-frame-edges "pgtkfns.c" (&optional frame type))
 (declare-function haiku-frame-edges "haikufns.c" (&optional frame type))
+(declare-function android-frame-edges "androidfns.c" (&optional frame type))
 
 (defun frame-edges (&optional frame type)
   "Return coordinates of FRAME's edges.
@@ -1859,6 +1863,8 @@ FRAME."
       (pgtk-frame-edges frame type))
      ((eq frame-type 'haiku)
       (haiku-frame-edges frame type))
+     ((eq frame-type 'android)
+      (android-frame-edges frame type))
      (t
       (list 0 0 (frame-width frame) (frame-height frame))))))
 
@@ -1867,6 +1873,7 @@ FRAME."
 (declare-function ns-mouse-absolute-pixel-position "nsfns.m")
 (declare-function pgtk-mouse-absolute-pixel-position "pgtkfns.c")
 (declare-function haiku-mouse-absolute-pixel-position "haikufns.c")
+(declare-function android-mouse-absolute-pixel-position "androidfns.c")
 
 (defun mouse-absolute-pixel-position ()
   "Return absolute position of mouse cursor in pixels.
@@ -1885,6 +1892,8 @@ position (0, 0) of the selected frame's terminal."
       (pgtk-mouse-absolute-pixel-position))
      ((eq frame-type 'haiku)
       (haiku-mouse-absolute-pixel-position))
+     ((eq frame-type 'android)
+      (android-mouse-absolute-pixel-position))
      (t
       (cons 0 0)))))
 
@@ -1893,6 +1902,8 @@ position (0, 0) of the selected frame's terminal."
 (declare-function w32-set-mouse-absolute-pixel-position "w32fns.c" (x y))
 (declare-function x-set-mouse-absolute-pixel-position "xfns.c" (x y))
 (declare-function haiku-set-mouse-absolute-pixel-position "haikufns.c" (x y))
+(declare-function android-set-mouse-absolute-pixel-position
+                  "androidfns.c" (x y))
 
 (defun set-mouse-absolute-pixel-position (x y)
   "Move mouse pointer to absolute pixel position (X, Y).
@@ -1909,7 +1920,9 @@ position (0, 0) of the selected frame's terminal."
      ((eq frame-type 'w32)
       (w32-set-mouse-absolute-pixel-position x y))
      ((eq frame-type 'haiku)
-      (haiku-set-mouse-absolute-pixel-position x y)))))
+      (haiku-set-mouse-absolute-pixel-position x y))
+     ((eq frame-type 'android)
+      (android-set-mouse-absolute-pixel-position x y)))))
 
 (defun frame-monitor-attributes (&optional frame)
   "Return the attributes of the physical monitor dominating FRAME.
@@ -2005,6 +2018,7 @@ workarea attribute."
 ;; TODO: implement this on PGTK.
 ;; (declare-function pgtk-frame-list-z-order "pgtkfns.c" (&optional display))
 (declare-function haiku-frame-list-z-order "haikufns.c" (&optional display))
+(declare-function android-frame-list-z-order "androidfns.c" (&optional 
display))
 
 (defun frame-list-z-order (&optional display)
   "Return list of Emacs' frames, in Z (stacking) order.
@@ -2030,13 +2044,17 @@ Return nil if DISPLAY contains no Emacs frame."
       ;; (pgtk-frame-list-z-order display)
       nil)
      ((eq frame-type 'haiku)
-      (haiku-frame-list-z-order display)))))
+      (haiku-frame-list-z-order display))
+     ((eq frame-type 'android)
+      (android-frame-list-z-order display)))))
 
 (declare-function x-frame-restack "xfns.c" (frame1 frame2 &optional above))
 (declare-function w32-frame-restack "w32fns.c" (frame1 frame2 &optional above))
 (declare-function ns-frame-restack "nsfns.m" (frame1 frame2 &optional above))
 (declare-function pgtk-frame-restack "pgtkfns.c" (frame1 frame2 &optional 
above))
 (declare-function haiku-frame-restack "haikufns.c" (frame1 frame2 &optional 
above))
+(declare-function android-frame-restack "androidfns.c" (frame1 frame2
+                                                               &optional 
above))
 
 (defun frame-restack (frame1 frame2 &optional above)
   "Restack FRAME1 below FRAME2.
@@ -2070,7 +2088,9 @@ Some window managers may refuse to restack windows."
          ((eq frame-type 'haiku)
           (haiku-frame-restack frame1 frame2 above))
          ((eq frame-type 'pgtk)
-          (pgtk-frame-restack frame1 frame2 above))))
+          (pgtk-frame-restack frame1 frame2 above))
+         ((eq frame-type 'android)
+          (android-frame-restack frame1 frame2 above))))
     (error "Cannot restack frames")))
 
 (defun frame-size-changed-p (&optional frame)
@@ -2105,6 +2125,7 @@ for FRAME."
 ;; or in https://debbugs.gnu.org/cgi/bugreport.cgi?bug=35058#17.
 
 (declare-function msdos-mouse-p "dosfns.c")
+(declare-function android-detect-mouse "androidfns.c")
 
 (defun display-mouse-p (&optional display)
   "Return non-nil if DISPLAY has a mouse available.
@@ -2119,6 +2140,8 @@ frame's display)."
        (> w32-num-mouse-buttons 0)))
      ((memq frame-type '(x ns haiku pgtk))
       t)    ;; We assume X, NeXTstep, GTK, and Haiku *always* have a pointing 
device
+     ((eq frame-type 'android)
+      (android-detect-mouse))
      (t
       (or (and (featurep 'xt-mouse)
               xterm-mouse-mode)
@@ -2134,8 +2157,12 @@ frame's display)."
   "Return non-nil if popup menus are supported on DISPLAY.
 DISPLAY can be a display name, a frame, or nil (meaning the selected
 frame's display).
-Support for popup menus requires that the mouse be available."
-  (display-mouse-p display))
+Support for popup menus requires that a suitable pointing device
+be available."
+  ;; Android menus work fine with touch screens as well, and one must
+  ;; be present.
+  (or (eq (framep-on-display display) 'android)
+      (display-mouse-p display)))
 
 (defun display-graphic-p (&optional display)
   "Return non-nil if DISPLAY is a graphic display.
@@ -2144,7 +2171,8 @@ frames and several different fonts at once.  This is true 
for displays
 that use a window system such as X, and false for text-only terminals.
 DISPLAY can be a display name, a frame, or nil (meaning the selected
 frame's display)."
-  (not (null (memq (framep-on-display display) '(x w32 ns pgtk haiku)))))
+  (not (null (memq (framep-on-display display) '(x w32 ns pgtk haiku
+                                                   android)))))
 
 (defun display-images-p (&optional display)
   "Return non-nil if DISPLAY can display images.
@@ -2196,7 +2224,7 @@ frame's display)."
 This means that, for example, DISPLAY can differentiate between
 the keybinding RET and [return]."
   (let ((frame-type (framep-on-display display)))
-    (or (memq frame-type '(x w32 ns pc pgtk haiku))
+    (or (memq frame-type '(x w32 ns pc pgtk haiku android))
         ;; MS-DOS and MS-Windows terminals have built-in support for
         ;; function (symbol) keys
         (memq system-type '(ms-dos windows-nt)))))
@@ -2209,7 +2237,7 @@ DISPLAY should be either a frame or a display name (a 
string).
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-screens display))
      (t
       1))))
@@ -2229,7 +2257,7 @@ with DISPLAY.  To get information for each physical 
monitor, use
 `display-monitor-attributes-list'."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-pixel-height display))
      (t
       (frame-height (if (framep display) display (selected-frame)))))))
@@ -2249,7 +2277,7 @@ with DISPLAY.  To get information for each physical 
monitor, use
 `display-monitor-attributes-list'."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-pixel-width display))
      (t
       (frame-width (if (framep display) display (selected-frame)))))))
@@ -2287,7 +2315,7 @@ For graphical terminals, note that on \"multi-monitor\" 
setups this
 refers to the height in millimeters for all physical monitors
 associated with DISPLAY.  To get information for each physical
 monitor, use `display-monitor-attributes-list'."
-  (and (memq (framep-on-display display) '(x w32 ns haiku pgtk))
+  (and (memq (framep-on-display display) '(x w32 ns haiku pgtk android))
        (or (cddr (assoc (or display (frame-parameter nil 'display))
                        display-mm-dimensions-alist))
           (cddr (assoc t display-mm-dimensions-alist))
@@ -2308,7 +2336,7 @@ For graphical terminals, note that on \"multi-monitor\" 
setups this
 refers to the width in millimeters for all physical monitors
 associated with DISPLAY.  To get information for each physical
 monitor, use `display-monitor-attributes-list'."
-  (and (memq (framep-on-display display) '(x w32 ns haiku pgtk))
+  (and (memq (framep-on-display display) '(x w32 ns haiku pgtk android))
        (or (cadr (assoc (or display (frame-parameter nil 'display))
                        display-mm-dimensions-alist))
           (cadr (assoc t display-mm-dimensions-alist))
@@ -2326,7 +2354,7 @@ DISPLAY can be a display name or a frame.
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-backing-store display))
      (t
       'not-useful))))
@@ -2339,7 +2367,7 @@ DISPLAY can be a display name or a frame.
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-save-under display))
      (t
       'not-useful))))
@@ -2352,7 +2380,7 @@ DISPLAY can be a display name or a frame.
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-planes display))
      ((eq frame-type 'pc)
       4)
@@ -2367,7 +2395,7 @@ DISPLAY can be a display name or a frame.
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-color-cells display))
      ((eq frame-type 'pc)
       16)
@@ -2384,7 +2412,7 @@ DISPLAY can be a display name or a frame.
 If DISPLAY is omitted or nil, it defaults to the selected frame's display."
   (let ((frame-type (framep-on-display display)))
     (cond
-     ((memq frame-type '(x w32 ns haiku pgtk))
+     ((memq frame-type '(x w32 ns haiku pgtk android))
       (x-display-visual-class display))
      ((and (memq frame-type '(pc t))
           (tty-display-color-p display))
@@ -2402,6 +2430,8 @@ If DISPLAY is omitted or nil, it defaults to the selected 
frame's display."
                  (&optional terminal))
 (declare-function haiku-display-monitor-attributes-list "haikufns.c"
                  (&optional terminal))
+(declare-function android-display-monitor-attributes-list "androidfns.c"
+                  (&optional terminal))
 
 (defun display-monitor-attributes-list (&optional display)
   "Return a list of physical monitor attributes on DISPLAY.
@@ -2455,6 +2485,8 @@ monitors."
       (pgtk-display-monitor-attributes-list display))
      ((eq frame-type 'haiku)
       (haiku-display-monitor-attributes-list display))
+     ((eq frame-type 'android)
+      (android-display-monitor-attributes-list display))
      (t
       (let ((geometry (list 0 0 (display-pixel-width display)
                            (display-pixel-height display))))
@@ -2530,6 +2562,28 @@ symbols."
                'core-keyboard))))))
 
 
+;;;; On-screen keyboard management.
+
+(declare-function android-toggle-on-screen-keyboard "androidfns.c")
+
+(defun frame-toggle-on-screen-keyboard (frame hide)
+  "Display or hide the on-screen keyboard.
+On systems with an on-screen keyboard, display the on screen
+keyboard on behalf of the frame FRAME if HIDE is nil.  Else, hide
+the on screen keyboard.
+
+Return whether or not the on screen keyboard may have been
+displayed; that is, return t on systems with an on screen
+keyboard, and nil on those without.
+
+FRAME must already have the input focus for this to work
+ reliably."
+  (let ((frame-type (framep-on-display frame)))
+    (cond ((eq frame-type 'android)
+           (android-toggle-on-screen-keyboard frame hide) t)
+          (t nil))))
+
+
 ;;;; Frame geometry values
 
 (defun frame-geom-value-cons (type value &optional frame)
diff --git a/lisp/gnus/gmm-utils.el b/lisp/gnus/gmm-utils.el
index 2fc89681ba5..c0651e3dbfc 100644
--- a/lisp/gnus/gmm-utils.el
+++ b/lisp/gnus/gmm-utils.el
@@ -200,7 +200,8 @@ DEFAULT-MAP specifies the default key map for ICON-LIST."
   "Create function NAME.
 If FUNCTION exists, then NAME becomes an alias for FUNCTION.
 Otherwise, create function NAME with ARG-LIST and BODY."
-  (declare (indent defun))
+  (declare (obsolete nil "30.1")
+           (indent defun))
   (let ((defined-p (fboundp function)))
     (if defined-p
         `(defalias ',name ',function)
diff --git a/lisp/gnus/gnus-cite.el b/lisp/gnus/gnus-cite.el
index d9071491ed4..a3c24ea05d9 100644
--- a/lisp/gnus/gnus-cite.el
+++ b/lisp/gnus/gnus-cite.el
@@ -1117,12 +1117,6 @@ Returns nil if there is no such line before LIMIT, t 
otherwise."
           (setq count (1+ count)))))) ;;
   "Keywords for highlighting different levels of message citations.")
 
-(defvar font-lock-defaults-computed)
-(defvar font-lock-keywords)
-(defvar font-lock-set-defaults)
-
-(autoload 'font-lock-set-defaults "font-lock")
-
 (define-minor-mode gnus-message-citation-mode
   "Minor mode providing more font-lock support for nested citations.
 When enabled, it automatically turns on `font-lock-mode'."
diff --git a/lisp/gnus/gnus-score.el b/lisp/gnus/gnus-score.el
index 05459ffae88..62167ea9e6a 100644
--- a/lisp/gnus/gnus-score.el
+++ b/lisp/gnus/gnus-score.el
@@ -517,6 +517,35 @@ of the last successful match.")
     "t" #'gnus-score-find-trace
     "w" #'gnus-score-find-favorite-words))
 
+
+
+;; Touch screen ``character reading'' routines for
+;; `gnus-summary-increase-score' and friends.
+
+(defun gnus-read-char (prompt options)
+  "Read a character from the keyboard.
+
+On Android, if `use-dialog-box-p' returns non-nil, display a
+dialog box containing PROMPT, with buttons representing each of
+item in the list of characters OPTIONS instead.
+
+Value is the character read, as with `read-char', or nil upon
+failure."
+  (if (and (display-graphic-p) (featurep 'android)
+           (use-dialog-box-p))
+      ;; Set up the dialog box.
+      (let ((dialog (cons prompt ; Message displayed in dialog box.
+                          (mapcar (lambda (arg)
+                                    (cons (char-to-string arg)
+                                          arg))
+                                  options))))
+        ;; Display the dialog box.
+        (x-popup-dialog t dialog))
+    ;; Fall back to read-char.
+    (read-char)))
+
+
+
 ;; Summary score file commands
 
 ;; Much modification of the kill (ahem, score) code and lots of the
@@ -588,21 +617,23 @@ current score file."
                     (aref (symbol-name gnus-score-default-type) 0)))
         (pchar (and gnus-score-default-duration
                     (aref (symbol-name gnus-score-default-duration) 0)))
-        entry temporary type match extra)
+        entry temporary type match extra header-string)
 
     (unwind-protect
        (progn
-
+          (setq header-string
+                (format "%s header (%s?): " (if increase "Increase" "Lower")
+                       (mapconcat (lambda (s) (char-to-string (car s)))
+                                  char-to-header "")))
          ;; First we read the header to score.
          (while (not hchar)
            (if mimic
                (progn
                  (sit-for 1)
                  (message "%c-" prefix))
-             (message "%s header (%s?): " (if increase "Increase" "Lower")
-                      (mapconcat (lambda (s) (char-to-string (car s)))
-                                 char-to-header "")))
-           (setq hchar (read-char))
+             (message header-string))
+           (setq hchar (gnus-read-char header-string
+                                        (mapcar #'car char-to-header)))
            (when (or (= hchar ??) (= hchar ?\C-h))
              (setq hchar nil)
              (gnus-score-insert-help "Match on header" char-to-header 1)))
@@ -625,17 +656,20 @@ current score file."
                                         (nth 3 s))
                                     s nil))
                               char-to-type))))
+            (setq header-string
+                  (format "%s header `%s' with match type (%s?): "
+                         (if increase "Increase" "Lower")
+                         (nth 1 entry)
+                         (mapconcat (lambda (s) (char-to-string (car s)))
+                                    legal-types "")))
            ;; We continue reading - the type.
            (while (not tchar)
              (if mimic
                  (progn
                    (sit-for 1) (message "%c %c-" prefix hchar))
-               (message "%s header `%s' with match type (%s?): "
-                        (if increase "Increase" "Lower")
-                        (nth 1 entry)
-                        (mapconcat (lambda (s) (char-to-string (car s)))
-                                   legal-types "")))
-             (setq tchar (read-char))
+               (message header-string))
+             (setq tchar (gnus-read-char header-string
+                                          (mapcar #'car legal-types)))
              (when (or (= tchar ??) (= tchar ?\C-h))
                (setq tchar nil)
                (gnus-score-insert-help "Match type" legal-types 2)))
@@ -651,15 +685,19 @@ current score file."
              (message ""))
            (setq pchar (or pchar ?t)))
 
+          (setq header-string
+                (format "%s permanence (%s?): " (if increase "Increase" 
"Lower")
+                       (mapconcat (lambda (s) (char-to-string (car s)))
+                                  char-to-perm "")))
+
          ;; We continue reading.
          (while (not pchar)
            (if mimic
                (progn
                  (sit-for 1) (message "%c %c %c-" prefix hchar tchar))
-             (message "%s permanence (%s?): " (if increase "Increase" "Lower")
-                      (mapconcat (lambda (s) (char-to-string (car s)))
-                                 char-to-perm "")))
-           (setq pchar (read-char))
+             (message header-string))
+           (setq pchar (gnus-read-char header-string
+                                        (mapcar #'car char-to-perm)))
            (when (or (= pchar ??) (= pchar ?\C-h))
              (setq pchar nil)
              (gnus-score-insert-help "Match permanence" char-to-perm 2)))
@@ -2956,10 +2994,7 @@ The list is determined from the variable 
`gnus-score-file-alist'."
        (group (or group gnus-newsgroup-name))
        score-files)
     (when group
-      ;; Make sure funcs is a list.
-      (and funcs
-          (not (listp funcs))
-          (setq funcs (list funcs)))
+      (setq funcs (ensure-list funcs))
       (when gnus-score-use-all-scores
        ;; Get the initial score files for this group.
        (when funcs
@@ -3066,12 +3101,8 @@ The list is determined from the variable 
`gnus-score-file-alist'."
 (defun gnus-home-score-file (group &optional adapt)
   "Return the home score file for GROUP.
 If ADAPT, return the home adaptive file instead."
-  (let ((list (if adapt gnus-home-adapt-file gnus-home-score-file))
+  (let ((list (ensure-list (if adapt gnus-home-adapt-file 
gnus-home-score-file)))
        elem found)
-    ;; Make sure we have a list.
-    (unless (listp list)
-      (setq list (list list)))
-    ;; Go through the list and look for matches.
     (while (and (not found)
                (setq elem (pop list)))
       (setq found
diff --git a/lisp/gnus/gnus-search.el b/lisp/gnus/gnus-search.el
index 98a7e23428b..839e5d203ff 100644
--- a/lisp/gnus/gnus-search.el
+++ b/lisp/gnus/gnus-search.el
@@ -1434,6 +1434,9 @@ Returns a list of [group article score] vectors."
                     ""))
         (groups (mapcar #'gnus-group-short-name groups))
        artlist article group)
+    (when (>= gnus-verbose 7)
+      (gnus-message 7 "Search engine returned %d results"
+                    (car (buffer-line-statistics))))
     (goto-char (point-min))
     ;; Prep prefix, we want to at least be removing the root
     ;; filesystem separator.
@@ -1485,6 +1488,10 @@ Returns a list of [group article score] vectors."
     ;; Are we running an additional grep query?
     (when-let ((grep-reg (alist-get 'grep query)))
       (setq artlist (gnus-search-grep-search engine artlist grep-reg)))
+
+    (when (>= gnus-verbose 7)
+      (gnus-message 7 "Gnus search returning %d results"
+                    (length artlist)))
     ;; Munge into the list of vectors expected by nnselect.
     (mapcar (pcase-lambda (`(,_ ,article ,group ,score))
               (vector
diff --git a/lisp/gnus/gnus-uu.el b/lisp/gnus/gnus-uu.el
index 1846f05af2d..6ed6358e8ce 100644
--- a/lisp/gnus/gnus-uu.el
+++ b/lisp/gnus/gnus-uu.el
@@ -1371,8 +1371,7 @@ When called interactively, prompt for REGEXP."
          ;; Allow user-defined functions to be run on this file.
          (when gnus-uu-grabbed-file-functions
            (let ((funcs gnus-uu-grabbed-file-functions))
-             (unless (listp funcs)
-               (setq funcs (list funcs)))
+              (setq funcs (ensure-list funcs))
              (while funcs
                (funcall (pop funcs) result-file))))
          (setq result-file nil)
diff --git a/lisp/gnus/mail-source.el b/lisp/gnus/mail-source.el
index 582c598ac22..f870c0b8274 100644
--- a/lisp/gnus/mail-source.el
+++ b/lisp/gnus/mail-source.el
@@ -285,7 +285,7 @@ number."
   "Number of idle seconds to wait before checking for new mail."
   :type 'number)
 
-(defcustom mail-source-movemail-program "movemail"
+(defcustom mail-source-movemail-program movemail-program-name
   "If non-nil, name of program for fetching new mail."
   :version "26.2"
   :type '(choice (const nil) string))
diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el
index 45cc21701b3..7a31f86a2c4 100644
--- a/lisp/gnus/message.el
+++ b/lisp/gnus/message.el
@@ -7707,10 +7707,7 @@ the message."
                ""))
        (when message-wash-forwarded-subjects
          (setq subject (message-wash-subject subject)))
-       ;; Make sure funcs is a list.
-       (and funcs
-            (not (listp funcs))
-            (setq funcs (list funcs)))
+        (setq funcs (ensure-list funcs))
        ;; Apply funcs in order, passing subject generated by previous
        ;; func to the next one.
        (dolist (func funcs)
diff --git a/lisp/gnus/mm-view.el b/lisp/gnus/mm-view.el
index 2c407353559..47d2b5f57e6 100644
--- a/lisp/gnus/mm-view.el
+++ b/lisp/gnus/mm-view.el
@@ -487,8 +487,6 @@ after inserting the part."
             (let ((inhibit-read-only t))
               (delete-region beg end)))))))))
 
-;; Shut up byte-compiler.
-(defvar font-lock-mode-hook)
 (defun mm-display-inline-fontify (handle &optional mode)
   "Insert HANDLE inline fontifying with MODE.
 If MODE is not set, try to find mode automatically."
diff --git a/lisp/gnus/nnmail.el b/lisp/gnus/nnmail.el
index e8f157392d4..904b564409a 100644
--- a/lisp/gnus/nnmail.el
+++ b/lisp/gnus/nnmail.el
@@ -667,7 +667,9 @@ nn*-request-list should have been called before calling 
this function."
                  (cond ((symbolp group)
                         (symbol-name group))
                        ((numberp group)
-                        (number-to-string group))))
+                        (number-to-string group))
+                        ((stringp group)
+                        group)))
            (if (and (numberp (setq max (read buffer)))
                     (numberp (setq min (read buffer))))
                (push (list group (cons min max))
diff --git a/lisp/gnus/nnmairix.el b/lisp/gnus/nnmairix.el
index 72833d7bc33..deb2b669b6b 100644
--- a/lisp/gnus/nnmairix.el
+++ b/lisp/gnus/nnmairix.el
@@ -741,8 +741,7 @@ called interactively, user will be asked for parameters."
   (when (and (stringp query)
             (string-match "\\s-" query))
     (setq query (split-string query)))
-  (when (not (listp query))
-    (setq query (list query)))
+  (setq query (ensure-list query))
   (when (and server group query)
     (let ((groupname (gnus-group-prefixed-name group server))
           ) ;; info
diff --git a/lisp/gnus/nnspool.el b/lisp/gnus/nnspool.el
index 3508c388897..ca97afcb7f1 100644
--- a/lisp/gnus/nnspool.el
+++ b/lisp/gnus/nnspool.el
@@ -35,7 +35,7 @@
 ;; It's only used to init nnspool-spool-directory, so why not just
 ;; set that variable's default directly?
 (eval-and-compile
-  (defvaralias 'news-path 'news-directory)
+  (define-obsolete-variable-alias 'news-path 'news-directory "30.1")
   (defvar news-directory (if (file-exists-p "/usr/spool/news/")
                             "/usr/spool/news/"
                           "/var/spool/news/")
@@ -62,9 +62,7 @@ This is most commonly `inews' or `injnews'.")
 If you are using Cnews, you probably should set this variable to nil.")
 
 (defvoo nnspool-spool-directory
-    (file-name-as-directory (if (boundp 'news-directory)
-                               (symbol-value 'news-directory)
-                             news-path))
+    (file-name-as-directory news-directory)
   "Local news spool directory.")
 
 (defvoo nnspool-nov-directory (concat nnspool-spool-directory "over.view/")
diff --git a/lisp/gnus/spam.el b/lisp/gnus/spam.el
index 3178d9f59e6..c598b10bc08 100644
--- a/lisp/gnus/spam.el
+++ b/lisp/gnus/spam.el
@@ -1375,8 +1375,7 @@ In the case of mover backends, checks the setting of
   (when (and (car-safe groups) (listp (car-safe groups)))
     (setq groups (pop groups)))
 
-  (unless (listp groups)
-    (setq groups (list groups)))
+  (setq groups (ensure-list groups))
 
     ;; remove the current process mark
   (gnus-summary-kill-process-mark)
diff --git a/lisp/help-fns.el b/lisp/help-fns.el
index b9388b45397..609bed18f2f 100644
--- a/lisp/help-fns.el
+++ b/lisp/help-fns.el
@@ -152,6 +152,17 @@ edited even if this option is enabled."
   :group 'help
   :version "28.1")
 
+(defcustom help-display-function-type t
+  "Whether to display type specifiers of functions in \"*Help*\" buffers.
+
+The type specifier of a function is returned by `comp-function-type-spec',
+which see.  When this variable is non-nil, \\[describe-function] will \
+display the function's
+type specifier when available."
+  :type 'boolean
+  :group 'help
+  :version "30.1")
+
 (defun help--symbol-class (s)
   "Return symbol class characters for symbol S."
   (when (stringp s)
@@ -229,7 +240,11 @@ interactive command."
                (lambda (f) (if want-command
                           (commandp f)
                         (or (fboundp f) (get f 'function-documentation))))
-               'confirm nil nil
+               ;; We used `confirm' for a while because we may want to see the
+               ;; meta-info about a function even if the function itself is not
+               ;; defined, but this use case is too marginal and rarely tested,
+               ;; not worth the trouble (bug#64902).
+               t nil nil
                (and fn (symbol-name fn)))))
     (unless (equal val "")
       (setq fn (intern val)))
@@ -711,7 +726,8 @@ the C sources, too."
           (unless (and (symbolp function)
                        (get function 'reader-construct))
             (insert high-usage "\n")
-            (when-let* ((res (comp-function-type-spec function))
+            (when-let* ((gate help-display-function-type)
+                        (res (comp-function-type-spec function))
                         (type-spec (car res))
                         (kind (cdr res)))
               (insert (format
@@ -1003,7 +1019,8 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED 
REAL-DEF)."
                                               (symbol-name function)))))))
         (real-def (cond
                     ((and aliased (not (subrp def)))
-                     (car (function-alias-p real-function)))
+                     (or (car (function-alias-p real-function))
+                         real-function))
                    ((subrp def) (intern (subr-name def)))
                     (t def))))
 
@@ -1067,10 +1084,8 @@ Returns a list of the form (REAL-FUNCTION DEF ALIASED 
REAL-DEF)."
                  (concat beg "byte-compiled Lisp function"))
                  ((module-function-p def)
                   (concat beg "module function"))
-                ((eq (car-safe def) 'lambda)
+                ((memq (car-safe def) '(lambda closure))
                  (concat beg "Lisp function"))
-                ((eq (car-safe def) 'closure)
-                 (concat beg "Lisp closure"))
                 ((keymapp def)
                  (let ((is-full nil)
                        (elts (cdr-safe def)))
@@ -1734,8 +1749,7 @@ If FRAME is omitted or nil, use the selected frame."
                     (called-interactively-p 'interactive))
     (unless face
       (setq face 'default))
-    (if (not (listp face))
-        (setq face (list face)))
+    (setq face (ensure-list face))
     (with-help-window (help-buffer)
       (with-current-buffer standard-output
         (dolist (f face (buffer-string))
@@ -2066,11 +2080,9 @@ keymap value."
         (if (symbolp keymap)
             (error "Not a keymap variable: %S" keymap)
           (error "Not a keymap")))
-      (let ((sym nil))
-        (unless sym
-          (setq sym (cl-gentemp "KEYMAP OBJECT (no variable) "))
-          (setq used-gentemp t)
-          (set sym keymap))
+      (let ((sym (cl-gentemp "KEYMAP OBJECT (no variable) ")))
+        (setq used-gentemp t)
+        (set sym keymap)
         (setq keymap sym)))
     ;; Follow aliasing.
     (setq keymap (or (ignore-errors (indirect-variable keymap)) keymap))
diff --git a/lisp/help-macro.el b/lisp/help-macro.el
index 6fd0ca3bf98..fac3419f184 100644
--- a/lisp/help-macro.el
+++ b/lisp/help-macro.el
@@ -166,6 +166,7 @@ and then returns."
                      (error nil))
                    (let ((cursor-in-echo-area t)
                          (overriding-local-map local-map))
+                     (frame-toggle-on-screen-keyboard (selected-frame) nil)
                      (setq key (read-key-sequence
                                 (format "Type one of listed options%s: "
                                         (if (pos-visible-in-window-p
@@ -179,7 +180,13 @@ and then returns."
                                                    
(help--key-description-fontified (kbd "SPC"))
                                                    "/"
                                                    
(help--key-description-fontified (kbd "DEL"))
-                                                   " to scroll"))))
+                                                   " to scroll")))
+                                nil nil nil nil
+                                ;; Disable ``text conversion''.  OS
+                                ;; input methods might otherwise chose
+                                ;; to insert user input directly into
+                                ;; a buffer.
+                                t)
                            char (aref key 0)))
 
                    ;; If this is a scroll bar command, just run it.
diff --git a/lisp/help.el b/lisp/help.el
index 11e1d5e509b..614d90b84ba 100644
--- a/lisp/help.el
+++ b/lisp/help.el
@@ -1493,7 +1493,7 @@ If PREFIX is non-nil, mention only keys that start with 
PREFIX.
 If TITLE is non-nil, is a string to insert at the beginning.
 TITLE should not end with a colon or a newline; we supply that.
 
-If NOMENU is non-nil, then omit menu-bar commands.
+If NO-MENU is non-nil, then omit menu-bar commands.
 
 If TRANSL is non-nil, the definitions are actually key
 translations so print strings and vectors differently.
diff --git a/lisp/hexl.el b/lisp/hexl.el
index bb57f4ac4c3..5fa09459a46 100644
--- a/lisp/hexl.el
+++ b/lisp/hexl.el
@@ -60,7 +60,7 @@
                  (const 64))
   :version "24.3")
 
-(defcustom hexl-program "hexl"
+(defcustom hexl-program hexl-program-name
   "The program that will hexlify and dehexlify its stdin.
 `hexl-program' will always be concatenated with `hexl-options'
 and \"-de\" when dehexlifying a buffer."
diff --git a/lisp/htmlfontify.el b/lisp/htmlfontify.el
index f0e38242e48..6c303226e54 100644
--- a/lisp/htmlfontify.el
+++ b/lisp/htmlfontify.el
@@ -308,13 +308,14 @@ done;")
   :tag   "etags-cmd-alist"
   :type  '(alist :key-type (string) :value-type (string)))
 
-(defcustom hfy-etags-bin "etags"
+(defcustom hfy-etags-bin etags-program-name
   "Location of etags binary (we begin by assuming it's in your path).
 
 Note that if etags is not in your path, you will need to alter the shell
 commands in `hfy-etags-cmd-alist'."
   :tag   "etags-bin"
-  :type  '(file))
+  :type  '(file)
+  :version "30.1")
 
 (defcustom hfy-shell-file-name "/bin/sh"
   "Shell (Bourne or compatible) to invoke for complex shell operations."
diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el
index 550b5ed0e6a..37065f5d41a 100644
--- a/lisp/ibuf-ext.el
+++ b/lisp/ibuf-ext.el
@@ -143,10 +143,10 @@ Returns (OLD-FORMAT-DETECTED . 
UPDATED-SAVED-FILTERS-LIST)."
                                         (mode         . bibtex-mode)))
                                    ("web"
                                     (or (derived-mode . sgml-mode)
-                                        (derived-mode . css-mode)
-                                        (mode         . javascript-mode)
+                                        (derived-mode . css-base-mode)
+                                        (derived-mode . js-base-mode)
+                                        (derived-mode . 
typescript-ts-base-mode)
                                         (mode         . js2-mode)
-                                        (mode         . scss-mode)
                                         (derived-mode . haml-mode)
                                         (mode         . sass-mode)))
                                    ("gnus"
diff --git a/lisp/ibuf-macs.el b/lisp/ibuf-macs.el
index 2c9e9ea8bcd..c38dfefe0c5 100644
--- a/lisp/ibuf-macs.el
+++ b/lisp/ibuf-macs.el
@@ -310,7 +310,7 @@ bound to the current value of the filter.
            (,qualifier-str qualifier))
        ,(when accept-list
           `(progn
-         (unless (listp qualifier) (setq qualifier (list qualifier)))
+         (setq qualifier (ensure-list qualifier))
          ;; Reject equivalent filters: (or f1 f2) is same as (or f2 f1).
          (setq qualifier (sort (delete-dups qualifier) #'string-lessp))
          (setq ,filter (cons ',name (car qualifier)))
diff --git a/lisp/ielm.el b/lisp/ielm.el
index 01550de71b5..f7d025b8c01 100644
--- a/lisp/ielm.el
+++ b/lisp/ielm.el
@@ -148,9 +148,8 @@ such as `edebug-defun' to work with such inputs."
 This variable is buffer-local.")
 
 (defvar ielm-header
-  (substitute-command-keys
    "*** Welcome to IELM ***  Type (describe-mode) or press \
-\\[describe-mode] for help.\n")
+\\[describe-mode] for help.\n"
   "Message to display when IELM is started.")
 
 (defvaralias 'inferior-emacs-lisp-mode-map 'ielm-map)
@@ -612,7 +611,7 @@ Customized bindings may be defined in `ielm-map', which 
currently contains:
     ;; Was cat, but on non-Unix platforms that might not exist, so
     ;; use hexl instead, which is part of the Emacs distribution.
     (condition-case nil
-        (start-process "ielm" (current-buffer) "hexl")
+        (start-process "ielm" (current-buffer) hexl-program-name)
       (file-error (start-process "ielm" (current-buffer) "cat")))
     (set-process-query-on-exit-flag (ielm-process) nil)
     (goto-char (point-max))
@@ -622,7 +621,7 @@ Customized bindings may be defined in `ielm-map', which 
currently contains:
     (setq-local comint-inhibit-carriage-motion t)
 
     ;; Add a silly header
-    (insert ielm-header)
+    (insert (substitute-command-keys ielm-header))
     (ielm-set-pm (point-max))
     (unless comint-use-prompt-regexp
       (let ((inhibit-read-only t))
diff --git a/lisp/image-mode.el b/lisp/image-mode.el
index fa28c1bf7a5..713125d4e58 100644
--- a/lisp/image-mode.el
+++ b/lisp/image-mode.el
@@ -248,8 +248,9 @@ Stop if the right edge of the image is reached."
         (image-set-window-hscroll (max 0 (+ (window-hscroll) n))))
        (t
         (let* ((image (image-get-display-property))
-               (edges (window-inside-edges))
-               (win-width (- (nth 2 edges) (nth 0 edges)))
+               (edges (window-edges nil t nil t))
+               (win-width (- (/ (nth 2 edges) (frame-char-width))
+                              (/ (nth 0 edges) (frame-char-width))))
                (img-width (ceiling (car (image-display-size image)))))
           (image-set-window-hscroll (min (max 0 (- img-width win-width))
                                          (+ n (window-hscroll))))))))
diff --git a/lisp/image/image-dired-external.el 
b/lisp/image/image-dired-external.el
index 9f35e17a7e6..77352c25a3b 100644
--- a/lisp/image/image-dired-external.el
+++ b/lisp/image/image-dired-external.el
@@ -405,7 +405,8 @@ The new file will be named THUMBNAIL-FILE."
                 (not image-dired-rotate-original-ask-before-overwrite))
             (progn
               (copy-file image-dired-temp-rotate-image-file file t)
-              (image-dired-refresh-thumb))
+              (image-dired-refresh-thumb)
+              (image-dired-update-thumbnail-at-point))
           (image-dired-display-image file))))))
 
 
diff --git a/lisp/image/image-dired-util.el b/lisp/image/image-dired-util.el
index a80b3afc0f3..53a5e274175 100644
--- a/lisp/image/image-dired-util.el
+++ b/lisp/image/image-dired-util.el
@@ -30,6 +30,7 @@
 (eval-when-compile (require 'cl-lib))
 
 (defvar image-dired-dir)
+(defvar image-dired-thumb-naming)
 (defvar image-dired-thumbnail-storage)
 
 (defconst image-dired--thumbnail-standard-sizes
@@ -57,42 +58,59 @@ Create the thumbnail directory if it does not exist."
       (message "Thumbnail directory created: %s" image-dired-dir))
     image-dired-dir))
 
+(defun image-dired-contents-sha1 (filename)
+  "Compute the SHA-1 of the first 4KiB of FILENAME's contents."
+  (with-temp-buffer
+    (insert-file-contents-literally filename nil 0 4096)
+    (sha1 (current-buffer))))
+
 (defun image-dired-thumb-name (file)
   "Return absolute file name for thumbnail FILE.
-Depending on the value of `image-dired-thumbnail-storage', the
-file name of the thumbnail will vary:
-- For `use-image-dired-dir', make a SHA1-hash of the image file's
-  directory name and add that to make the thumbnail file name
-  unique.
-- For `per-directory' storage, just add a subdirectory.
-- For `standard' storage, produce the file name according to the
-  Thumbnail Managing Standard.  Among other things, an MD5-hash
-  of the image file's directory name will be added to the
-  filename.
-See also `image-dired-thumbnail-storage'."
+Depending on the value of `image-dired-thumbnail-storage' and
+`image-dired-thumb-naming', the file name of the thumbnail will
+vary:
+
+- If `image-dired-thumbnail-storage' is set to one of the value
+  of `image-dired--thumbnail-standard-sizes', produce the file
+  name according to the Thumbnail Managing Standard.  Among other
+  things, an MD5-hash of the image file's directory name will be
+  added to the file name.
+
+- Otherwise `image-dired-thumbnail-storage' is used to set the
+  directory where to store the thumbnail.  In this latter case,
+  if `image-dired-thumbnail-storage' is set to `image-dired' the
+  file name given to the thumbnail depends on the value of
+  `image-dired-thumb-naming'.
+
+See also `image-dired-thumbnail-storage' and
+`image-dired-thumb-naming'."
   (let ((file (expand-file-name file)))
-    (cond ((memq image-dired-thumbnail-storage
-                 image-dired--thumbnail-standard-sizes)
-           (let ((thumbdir (cl-case image-dired-thumbnail-storage
-                             (standard "thumbnails/normal")
-                             (standard-large "thumbnails/large")
-                             (standard-x-large "thumbnails/x-large")
-                             (standard-xx-large "thumbnails/xx-large"))))
-             (expand-file-name
-              ;; MD5 is mandated by the Thumbnail Managing Standard.
-              (concat (md5 (concat "file://" file)) ".png")
-              (expand-file-name thumbdir (xdg-cache-home)))))
-          ((or (eq 'image-dired image-dired-thumbnail-storage)
-               ;; Maintained for backwards compatibility:
-               (eq 'use-image-dired-dir image-dired-thumbnail-storage))
-           (expand-file-name (format "%s.jpg" (sha1 file))
-                             (image-dired-dir)))
-          ((eq 'per-directory image-dired-thumbnail-storage)
-           (expand-file-name (format "%s.thumb.jpg"
-                                     (file-name-nondirectory file))
-                             (expand-file-name
-                              ".image-dired"
-                              (file-name-directory file)))))))
+    (if (memq image-dired-thumbnail-storage
+              image-dired--thumbnail-standard-sizes)
+        (let ((thumbdir (cl-case image-dired-thumbnail-storage
+                          (standard "thumbnails/normal")
+                          (standard-large "thumbnails/large")
+                          (standard-x-large "thumbnails/x-large")
+                          (standard-xx-large "thumbnails/xx-large"))))
+          (expand-file-name
+           ;; MD5 and PNG is mandated by the Thumbnail Managing
+           ;; Standard.
+           (concat (md5 (concat "file://" file)) ".png")
+           (expand-file-name thumbdir (xdg-cache-home))))
+      (let ((name (if (eq 'sha1-contents image-dired-thumb-naming)
+                      (image-dired-contents-sha1 file)
+                    ;; Defaults to SHA-1 of file name
+                    (sha1 file))))
+        (cond ((or (eq 'image-dired image-dired-thumbnail-storage)
+                   ;; Maintained for backwards compatibility:
+                   (eq 'use-image-dired-dir image-dired-thumbnail-storage))
+               (expand-file-name (format "%s.jpg" name) (image-dired-dir)))
+              ((eq 'per-directory image-dired-thumbnail-storage)
+               (expand-file-name (format "%s.thumb.jpg"
+                                         (file-name-nondirectory file))
+                                 (expand-file-name
+                                  ".image-dired"
+                                  (file-name-directory file)))))))))
 
 (defvar image-dired-thumbnail-buffer "*image-dired*"
   "Image-Dired's thumbnail buffer.")
@@ -172,6 +190,23 @@ Should be used by commands in 
`image-dired-thumbnail-mode'."
   "Return non-nil if there is an `image-dired' thumbnail at point."
   (get-text-property (point) 'image-dired-thumbnail))
 
+(defun image-dired-update-thumbnail-at-point ()
+  "Update the thumbnail at point if the original image file has been modified.
+This function uncaches and removes the thumbnail file under the old name."
+  (when (image-dired-image-at-point-p)
+    (let* ((file (image-dired-original-file-name))
+           (thumb (expand-file-name (image-dired-thumb-name file)))
+           (image (get-text-property (point) 'display)))
+      (when image
+        (let ((old-thumb (plist-get (cdr image) :file)))
+          ;; When 'image-dired-thumb-naming' is set to
+          ;; 'sha1-contents', 'thumb' and 'old-thumb' could be
+          ;; different file names.  Update the thumbnail then.
+          (unless (string= thumb old-thumb)
+            (setf (plist-get (cdr image) :file) thumb)
+            (clear-image-cache old-thumb)
+            (delete-file old-thumb)))))))
+
 (defun image-dired-window-width-pixels (window)
   "Calculate WINDOW width in pixels."
   (declare (obsolete window-body-width "29.1"))
diff --git a/lisp/image/image-dired.el b/lisp/image/image-dired.el
index b13b3e08ce2..83f228b2d59 100644
--- a/lisp/image/image-dired.el
+++ b/lisp/image/image-dired.el
@@ -162,8 +162,27 @@ to use the Thumbnail Managing Standard; they will be saved 
in
 `image-dired-thumbnail-storage'."
   :type 'directory)
 
+(defcustom image-dired-thumb-naming 'sha1-filename
+  "How `image-dired' names thumbnail files.
+When set to `sha1-filename' the name of thumbnail is built by
+computing the SHA-1 of the full file name of the image.
+
+When set to `sha1-contents' the name of thumbnail is built by
+computing the SHA-1 of first 4KiB of the image contents (See
+`image-dired-contents-sha1').
+
+In both case, a \"jpg\" extension is appended to save as JPEG.
+
+The value of this option is ignored if Image-Dired is customized
+to use the Thumbnail Managing Standard or the per-directory
+thumbnails setting.  See `image-dired-thumbnail-storage'."
+  :type '(choice :tag "How to name thumbnail files"
+                 (const :tag "SHA-1 of the image file name" sha1-filename)
+                 (const :tag "SHA-1 of the image contents" sha1-contents))
+  :version "30.1")
+
 (defcustom image-dired-thumbnail-storage 'image-dired
-  "How `image-dired' stores thumbnail files.
+  "Where `image-dired' stores thumbnail files.
 There are three ways that Image-Dired can store and generate
 thumbnails:
 
@@ -189,6 +208,9 @@ thumbnails:
 
     Set this user option to `per-directory'.
 
+To control the naming of thumbnails for alternative (2) above,
+customize the value of `image-dired-thumb-naming'.
+
 To control the default size of thumbnails for alternatives (2)
 and (3) above, customize the value of `image-dired-thumb-size'.
 
@@ -197,7 +219,7 @@ format, as mandated by that standard; otherwise save them 
as JPEG.
 
 For more information on the Thumbnail Managing Standard, see:
 
https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html";
-  :type '(choice :tag "How to store thumbnail files"
+  :type '(choice :tag "Where to store thumbnail files"
                  (const :tag "Use image-dired-dir" image-dired)
                  (const :tag "Thumbnail Managing Standard (normal 128x128)"
                         standard)
@@ -568,7 +590,7 @@ used or not.  If non-nil, use `display-buffer' instead of
 `image-dired-previous-line-and-display' where we do not want the
 thumbnail buffer to be selected."
   (interactive "P" nil dired-mode)
-  (setq image-dired--generate-thumbs-start  (current-time))
+  (setq image-dired--generate-thumbs-start (current-time))
   (let ((buf (image-dired-create-thumbnail-buffer))
         files dired-buf)
     (if arg
@@ -702,21 +724,22 @@ On reaching end or beginning of buffer, stop and show a 
message."
               (not (if reverse (bobp) (eobp))))
     (forward-char (if reverse -1 1))))
 
-(defmacro image-dired--movement-command (to &optional reverse)
-  `(progn
-     (goto-char ,to)
-     (image-dired--movement-ensure-point-pos ,reverse)
-     (when image-dired-track-movement
-       (image-dired-track-original-file))
-     (image-dired--update-header-line)))
-
-(defmacro image-dired--movement-command-line (&optional reverse)
-  `(image-dired--movement-command
-     (let ((goal-column (current-column)))
-       (forward-line ,(if reverse -1 1))
-       (move-to-column goal-column)
-       (point))
-     ,reverse))
+(defun image-dired--update-after-move (reverse)
+  "Book-keeping after move."
+  (image-dired--movement-ensure-point-pos reverse)
+  (when image-dired-track-movement
+    (image-dired-track-original-file))
+  (image-dired--update-header-line))
+
+(defun image-dired--movement-command (to &optional reverse)
+  (goto-char to)
+  (image-dired--update-after-move reverse))
+
+(defun image-dired--movement-command-line (&optional reverse)
+  (let ((goal-column (current-column)))
+    (forward-line (if reverse -1 1))
+    (move-to-column goal-column)
+    (image-dired--update-after-move reverse)))
 
 (defun image-dired-next-line ()
   "Move to next line in the thumbnail buffer."
@@ -748,6 +771,21 @@ On reaching end or beginning of buffer, stop and show a 
message."
   (interactive nil image-dired-thumbnail-mode)
   (image-dired--movement-command (pos-eol) 'reverse))
 
+(defun image-dired-scroll (&optional down)
+  "Scroll in the thumbnail buffer."
+  (let ((goal-column (current-column)))
+    (if down (scroll-down) (scroll-up))
+    (move-to-column goal-column)
+    (image-dired--update-after-move down)))
+
+(defun image-dired-scroll-up ()
+  (interactive nil image-dired-thumbnail-mode)
+  (image-dired-scroll))
+
+(defun image-dired-scroll-down ()
+  (interactive nil image-dired-thumbnail-mode)
+  (image-dired-scroll 'down))
+
 
 ;;; Header line
 
@@ -958,6 +996,8 @@ You probably want to use this together with
   "<remap> <end-of-buffer>"          #'image-dired-end-of-buffer
   "<remap> <move-beginning-of-line>" #'image-dired-move-beginning-of-line
   "<remap> <move-end-of-line>"       #'image-dired-move-end-of-line
+  "<remap> <scroll-up-command>"      #'image-dired-scroll-up
+  "<remap> <scroll-down-command>"    #'image-dired-scroll-down
 
   :menu
   '("Image-Dired"
@@ -1883,8 +1923,8 @@ when using per-directory thumbnail file storage"))
     (if (file-exists-p image-dired-gallery-dir)
         (if (not (file-directory-p image-dired-gallery-dir))
             (error "Variable image-dired-gallery-dir is not a directory"))
-      ;; FIXME: Should we set umask to 077 here, as we do for thumbnails?
-      (make-directory image-dired-gallery-dir))
+      (with-file-modes #o700
+        (make-directory image-dired-gallery-dir)))
     ;; Open index file
     (with-temp-file index-file
       (if (file-exists-p index-file)
diff --git a/lisp/image/wallpaper.el b/lisp/image/wallpaper.el
index a2f175e4628..c778264d3ef 100644
--- a/lisp/image/wallpaper.el
+++ b/lisp/image/wallpaper.el
@@ -432,6 +432,8 @@ See also `wallpaper-default-width'.")
 
 ;;; wallpaper-set
 
+(declare-function x-open-connection "xfns.c")
+
 (defun wallpaper--x-monitor-name ()
   "Get the monitor name for `wallpaper-set'.
 On a graphical display, try using the same monitor as the current
diff --git a/lisp/indent-aux.el b/lisp/indent-aux.el
new file mode 100644
index 00000000000..ed41d4926f7
--- /dev/null
+++ b/lisp/indent-aux.el
@@ -0,0 +1,76 @@
+;;; indent-aux.el --- Autoloaded indentation commands for Emacs  -*- 
lexical-binding:t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Maintainer: emacs-devel@gnu.org
+;; Package: emacs
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Autoloaded commands for making and changing indentation in text and
+;; killed text
+
+;;; Code:
+
+
+
+;; Indent Filter mode.  When enabled, this minor mode filters all
+;; killed text to remove leading indentation.
+
+(defun kill-ring-deindent-buffer-substring-function (beg end delete)
+  "Save the text within BEG and END to kill-ring, decreasing indentation.
+Delete the saved text if DELETE is non-nil.
+
+In the saved copy of the text, remove some of the indentation, such
+that the buffer position at BEG will be at column zero when the text
+is yanked."
+  (let ((a beg)
+        (b end))
+    (setq beg (min a b)
+          end (max a b)))
+  (let ((indentation (save-excursion (goto-char beg)
+                                     (current-column)))
+        (text (if delete
+                  (delete-and-extract-region beg end)
+                (buffer-substring beg end))))
+    (with-temp-buffer
+      (insert text)
+      (indent-rigidly (point-min) (point-max)
+                      (- indentation))
+      (buffer-string))))
+
+;;;###autoload
+(define-minor-mode kill-ring-deindent-mode
+  "Toggle removal of indentation from text saved to the kill ring.
+
+When this minor mode is enabled, text saved into the kill ring is
+indented towards the left by the column number at the start of
+that text."
+  :global 't
+  :group 'killing
+  (if kill-ring-deindent-mode
+      (add-function :override filter-buffer-substring-function
+                    #'kill-ring-deindent-buffer-substring-function
+                    '(:depth 100))
+    (remove-function filter-buffer-substring-function
+                     #'kill-ring-deindent-buffer-substring-function)))
+
+
+
+(provide 'indent-aux)
+;;; indent-aux.el ends here.
diff --git a/lisp/info.el b/lisp/info.el
index 035dff66e75..463aea93376 100644
--- a/lisp/info.el
+++ b/lisp/info.el
@@ -1587,7 +1587,7 @@ escaped (\\\",\\\\)."
   (let ((start 0)
        (parameter-alist))
     (while (string-match
-           "\\s *\\([^=]+\\)=\\(?:\\([^\\s 
\"]+\\)\\|\\(?:\"\\(\\(?:[^\\\"]\\|\\\\[\\\"]\\)*\\)\"\\)\\)"
+           "\\s 
*\\([^=]+\\)=\\(?:\\([^\"[:space:]]+\\)\\|\\(?:\"\\(\\(?:[^\\\"]\\|\\\\[\\\"]\\)*\\)\"\\)\\)"
            parameter-string start)
       (setq start (match-end 0))
       (push (cons (match-string 1 parameter-string)
diff --git a/lisp/international/characters.el b/lisp/international/characters.el
index 9aea5e27063..1aa570ca59a 100644
--- a/lisp/international/characters.el
+++ b/lisp/international/characters.el
@@ -1394,6 +1394,174 @@ with L, LRE, or LRO Unicode bidi character type.")
   (dolist (elt l)
     (set-char-table-range char-width-table elt 2)))
 
+;; A: East Asian "Ambiguous" characters.
+(let ((l '((#x00A1 . #x00A1)
+          (#x00A4 . #x00A4)
+          (#x00A7 . #x00A8)
+          (#x00AA . #x00AA)
+          (#x00AD . #x00AE)
+          (#x00B0 . #x00B4)
+          (#x00B6 . #x00BA)
+          (#x00BC . #x00BF)
+          (#x00C6 . #x00C6)
+          (#x00D0 . #x00D0)
+          (#x00D7 . #x00D8)
+          (#x00E0 . #x00E1)
+          (#x00E6 . #x00E6)
+          (#x00E8 . #x00EA)
+          (#x00EC . #x00ED)
+          (#x00F0 . #x00F0)
+          (#x00F2 . #x00F3)
+          (#x00F7 . #x00FA)
+          (#x00FC . #x00FC)
+          (#x00FE . #x00FE)
+          (#x0101 . #x0101)
+          (#x0111 . #x0111)
+          (#x0113 . #x0113)
+          (#x011B . #x011B)
+          (#x0126 . #x0127)
+          (#x012B . #x012B)
+          (#x0131 . #x0133)
+          (#x0138 . #x0138)
+          (#x013F . #x0142)
+          (#x0144 . #x0144)
+          (#x0148 . #x014B)
+          (#x014D . #x014D)
+          (#x0152 . #x0153)
+          (#x0166 . #x0167)
+          (#x016B . #x016B)
+          (#x01CE . #x01CE)
+          (#x01D0 . #x01D0)
+          (#x01D2 . #x01D2)
+          (#x01D4 . #x01D4)
+          (#x01D6 . #x01D6)
+          (#x01D8 . #x01D8)
+          (#x01DA . #x01DA)
+          (#x01DC . #x01DC)
+          (#x0251 . #x0251)
+          (#x0261 . #x0261)
+          (#x02C4 . #x02C4)
+          (#x02C7 . #x02C7)
+          (#x02C9 . #x02CB)
+          (#x02CD . #x02CD)
+          (#x02D0 . #x02D0)
+          (#x02D8 . #x02DB)
+          (#x02DD . #x02DD)
+          (#x02DF . #x02DF)
+          (#x0300 . #x036F)
+          (#x0391 . #x03A1)
+          (#x03A3 . #x03A9)
+          (#x03B1 . #x03C1)
+          (#x03C3 . #x03C9)
+          (#x0401 . #x0401)
+          (#x0410 . #x044F)
+          (#x0451 . #x0451)
+          (#x2010 . #x2010)
+          (#x2013 . #x2016)
+          (#x2018 . #x2019)
+          (#x201C . #x201D)
+          (#x2020 . #x2022)
+          (#x2024 . #x2027)
+          (#x2030 . #x2030)
+          (#x2032 . #x2033)
+          (#x2035 . #x2035)
+          (#x203E . #x203E)
+          (#x2074 . #x2074)
+          (#x207F . #x207F)
+          (#x2081 . #x2084)
+          (#x20AC . #x20AC)
+          (#x2103 . #x2103)
+          (#x2105 . #x2105)
+          (#x2109 . #x2109)
+          (#x2113 . #x2113)
+          (#x2116 . #x2116)
+          (#x2121 . #x2122)
+          (#x2126 . #x2126)
+          (#x212B . #x212B)
+          (#x2153 . #x2154)
+          (#x215B . #x215E)
+          (#x2160 . #x216B)
+          (#x2170 . #x2179)
+          (#x2189 . #x2189)
+          (#x2190 . #x2199)
+          (#x21B8 . #x21B9)
+          (#x21D2 . #x21D2)
+          (#x21D4 . #x21D4)
+          (#x21E7 . #x21E7)
+          (#x2200 . #x2200)
+          (#x2202 . #x2203)
+          (#x2207 . #x2208)
+          (#x220B . #x220B)
+          (#x220F . #x220F)
+          (#x2211 . #x2211)
+          (#x2215 . #x2215)
+          (#x221A . #x221A)
+          (#x221D . #x2220)
+          (#x2223 . #x2223)
+          (#x2225 . #x2225)
+          (#x2227 . #x222C)
+          (#x222E . #x222E)
+          (#x2234 . #x2237)
+          (#x223C . #x223D)
+          (#x2248 . #x2248)
+          (#x224C . #x224C)
+          (#x2252 . #x2252)
+          (#x2260 . #x2261)
+          (#x2264 . #x2267)
+          (#x226A . #x226B)
+          (#x226E . #x226F)
+          (#x2282 . #x2283)
+          (#x2286 . #x2287)
+          (#x2295 . #x2295)
+          (#x2299 . #x2299)
+          (#x22A5 . #x22A5)
+          (#x22BF . #x22BF)
+          (#x2312 . #x2312)
+          (#x2460 . #x24E9)
+          (#x24EB . #x254B)
+          (#x2550 . #x2573)
+          (#x2580 . #x258F)
+          (#x2592 . #x2595)
+          (#x25A0 . #x25A1)
+          (#x25A3 . #x25A9)
+          (#x25B2 . #x25B3)
+          (#x25B6 . #x25B7)
+          (#x25BC . #x25BD)
+          (#x25C0 . #x25C1)
+          (#x25C6 . #x25C8)
+          (#x25CE . #x25D1)
+          (#x25E2 . #x25E5)
+          (#x25EF . #x25EF)
+          (#x2605 . #x2606)
+          (#x260E . #x260F)
+          (#x261C . #x261C)
+          (#x261E . #x261E)
+          (#x2640 . #x2640)
+          (#x2642 . #x2642)
+          (#x2660 . #x2661)
+          (#x2663 . #x2665)
+          (#x2667 . #x266A)
+          (#x266C . #x266D)
+          (#x266F . #x266F)
+          (#x269E . #x269F)
+          (#x26BF . #x26BF)
+          (#x26C6 . #x26CD)
+          (#x26CF . #x26D3)
+          (#x26D5 . #x26E1)
+          (#x26E3 . #x26E3)
+          (#x26E8 . #x26E9)
+          (#x26EB . #x26F1)
+          (#x26F4 . #x26F4)
+          (#x26F6 . #x26F9)
+          (#x26FB . #x26FC)
+          (#x26FE . #x26FF)
+          (#x273D . #x273D)
+          (#x2776 . #x277F)
+          (#x2B56 . #x2B59)
+          (#x3248 . #x324F))))
+  (dolist (elt l)
+    (set-char-table-range ambiguous-width-chars elt t)))
+
 ;; Other double width
 ;;(map-charset-chars
 ;; (lambda (range ignore) (set-char-table-range char-width-table range 2))
@@ -1427,6 +1595,45 @@ with L, LRE, or LRO Unicode bidi character type.")
               (chinese-cns11643-1 (#x2121 . #x427E)))
     (ko_KR nil (korean-ksc5601 (#x2121 . #x2C7E)))))
 
+(defun update-cjk-ambiguous-char-widths (locale-name)
+  "Update character widths for LOCALE-NAME using `ambiguous-width-chars'.
+LOCALE-NAME is the symbol of a CJK locale, such as \\='zh_CN."
+  (let ((slot (assq locale-name cjk-char-width-table-list)))
+    (or slot (error "Unknown locale for CJK language environment: %s"
+                   locale-name))
+    ;; Force recomputation of child table in 'use-cjk-char-width-table'.
+    (setcar (cdr slot) nil)
+    (use-cjk-char-width-table locale-name)))
+
+
+(defcustom cjk-ambiguous-chars-are-wide t
+  "Whether the \"ambiguous-width\" characters take 2 columns on display.
+
+Some of the characters are defined by Unicode as being of \"ambiguous\"
+width: the actual width, either 1 column or 2 columns, should be
+determined at display time, depending on the language context.
+If this variable is non-nil, Emacs will consider these characters as
+full-width, i.e. taking 2 columns; otherwise they are narrow characters
+taking 1 column on display.  Which value is correct depends on the
+fonts being used.  In some CJK locales the fonts are set so that
+these characters are displayed as full-width.  This setting is most
+important for text-mode frames, because there Emacs cannot access the
+metrics of the fonts used by the console or the terminal emulator.
+
+Do not set this directly via `setq'; instead, use `setopt' or the
+Customize commands.  Alternatively, call `update-cjk-ambiguous-char-widths'
+passing it the symbol of the current locale environment, after changing
+the value of the variable with `setq'."
+  :type 'boolean
+  :set (lambda (symbol value)
+         (set-default symbol value)
+         (let ((locsym (get-language-info current-language-environment
+                                          'cjk-locale-symbol)))
+           (when locsym
+             (update-cjk-ambiguous-char-widths locsym))))
+  :version "30.1"
+  :group 'display)
+
 ;; Internal use only.
 ;; Setup char-width-table appropriate for a language environment
 ;; corresponding to LOCALE-NAME (symbol).
@@ -1448,7 +1655,15 @@ with L, LRE, or LRO Unicode bidi character type.")
                                 (car code-range) (cdr code-range)))))
        (optimize-char-table table)
        (set-char-table-parent table char-width-table)
-       (setcar (cdr slot) table)))
+        (let ((tbl (make-char-table nil)))
+          (map-char-table
+           (lambda (range _val)
+             (set-char-table-range tbl range
+                                   (if cjk-ambiguous-chars-are-wide 2 1)))
+           ambiguous-width-chars)
+          (optimize-char-table tbl)
+          (set-char-table-parent tbl table)
+         (setcar (cdr slot) tbl))))
     (setq char-width-table (nth 1 slot))))
 
 (defun use-default-char-width-table ()
diff --git a/lisp/international/fontset.el b/lisp/international/fontset.el
index b72c68d9d59..d879920b1d0 100644
--- a/lisp/international/fontset.el
+++ b/lisp/international/fontset.el
@@ -200,7 +200,10 @@
        (symbol . [#x201C #x2200 #x2500])
        (braille #x2800)
        (ideographic-description #x2FF0)
-       (cjk-misc #x300E)
+        ;; Noto Sans Phags Pa is broken and reuses the CJK misc code
+        ;; points for some of its own characters.  Add one actual CJK
+        ;; character to prevent finding such broken fonts.
+       (cjk-misc #x300E #xff0c #x300a #xff09 #x5b50)
        (kana #x304B)
        (bopomofo #x3105)
        (kanbun #x319D)
@@ -685,7 +688,11 @@
          (nil . "JISX0213.2000-2")
          (nil . "JISX0213.2004-1")
          ,(font-spec :registry "iso10646-1" :lang 'ja)
-         ,(font-spec :registry "iso10646-1" :lang 'zh))
+         ,(font-spec :registry "iso10646-1" :lang 'zh)
+          ;; This is required, as otherwise many TrueType fonts with
+          ;; CJK characters but no corresponding ``design language''
+          ;; declaration can't be found.
+          ,(font-spec :registry "iso10646-1" :script 'han))
 
      (cjk-misc (nil . "GB2312.1980-0")
               (nil . "JISX0208*")
@@ -704,7 +711,11 @@
               (nil . "JISX0213.2000-1")
               (nil . "JISX0213.2000-2")
               ,(font-spec :registry "iso10646-1" :lang 'ja)
-              ,(font-spec :registry "iso10646-1" :lang 'zh))
+              ,(font-spec :registry "iso10646-1" :lang 'zh)
+               ;; This is required, as otherwise many TrueType fonts
+               ;; with CJK characters but no corresponding ``design
+               ;; language'' declaration can't be found.
+               ,(font-spec :registry "iso10646-1" :script 'cjk-misc))
 
      (hangul (nil . "KSC5601.1987-0")
             ,(font-spec :registry "iso10646-1" :lang 'ko))
@@ -1196,7 +1207,8 @@ Internal use only.  Should be called at startup time."
       (list (cons (purecopy "-cdac$")  1.3)))
 
 (defvar x-font-name-charset-alist nil
-  "This variable has no meaning now.  Just kept for backward compatibility.")
+  "This variable has no meaning starting with Emacs 22.1.")
+(make-obsolete-variable 'x-font-name-charset-alist nil "30.1")
 
 ;;; XLFD (X Logical Font Description) format handler.
 
@@ -1262,9 +1274,8 @@ Return nil if PATTERN doesn't conform to XLFD."
 (defun x-compose-font-name (fields &optional _reduce)
   "Compose X fontname from FIELDS.
 FIELDS is a vector of XLFD fields, of length 12.
-If a field is nil, wild-card letter `*' is embedded.
-Optional argument REDUCE exists just for backward compatibility,
-and is always ignored."
+If a field is nil, wild-card letter `*' is embedded."
+  (declare (advertised-calling-convention (fields) "30.1"))
   (concat "-" (mapconcat (lambda (x) (or x "*")) fields "-")))
 
 
diff --git a/lisp/international/iso-transl.el b/lisp/international/iso-transl.el
index 80f14f3e8cb..459d1ff7f97 100644
--- a/lisp/international/iso-transl.el
+++ b/lisp/international/iso-transl.el
@@ -203,11 +203,15 @@
     ("^I"   . [?Î])
     ("^O"   . [?Ô])
     ("^U"   . [?Û])
+    ("^W"   . [?Ŵ])
+    ("^Y"   . [?Ŷ])
     ("^a"   . [?â])
     ("^e"   . [?ê])
     ("^i"   . [?î])
     ("^o"   . [?ô])
     ("^u"   . [?û])
+    ("^w"   . [?ŵ])
+    ("^y"   . [?ŷ])
     ("^^A"  . [?Ǎ])
     ("^^C"  . [?Č])
     ("^^E"  . [?Ě])
diff --git a/lisp/international/mule-cmds.el b/lisp/international/mule-cmds.el
index 3d6d66970d3..c26898f7649 100644
--- a/lisp/international/mule-cmds.el
+++ b/lisp/international/mule-cmds.el
@@ -88,7 +88,8 @@
     (bindings--define-key map [separator-3] menu-bar-separator)
     (bindings--define-key map [set-terminal-coding-system]
       '(menu-item "For Terminal" set-terminal-coding-system
-        :enable (null (memq initial-window-system '(x w32 ns haiku pgtk)))
+        :enable (null (memq initial-window-system '(x w32 ns haiku pgtk
+                                                   android)))
         :help "How to encode terminal output"))
     (bindings--define-key map [set-keyboard-coding-system]
       '(menu-item "For Keyboard" set-keyboard-coding-system
@@ -867,8 +868,7 @@ overrides ACCEPT-DEFAULT-P.
 
 Kludgy feature: if FROM is a string, the string is the target text,
 and TO is ignored."
-  (if (not (listp default-coding-system))
-      (setq default-coding-system (list default-coding-system)))
+  (setq default-coding-system (ensure-list default-coding-system))
 
   (let ((no-other-defaults nil)
        auto-cs)
diff --git a/lisp/isearch.el b/lisp/isearch.el
index 3d2bbda4975..4d231fba469 100644
--- a/lisp/isearch.el
+++ b/lisp/isearch.el
@@ -244,6 +244,10 @@ If you use `add-function' to modify this variable, you can 
use the
 `isearch-message-prefix' advice property to specify the prefix string
 displayed in the search message.")
 
+(defvar isearch-text-conversion-style nil
+  "Value of `text-conversion-style' before Isearch mode
+was enabled in this buffer.")
+
 ;; Search ring.
 
 (defvar search-ring nil
@@ -1221,6 +1225,8 @@ active region is added to the search string."
 ;;                          isearch-forward-regexp isearch-backward-regexp)
 ;;  "List of commands for which isearch-mode does not recursive-edit.")
 
+(declare-function set-text-conversion-style "textconv.c")
+
 (defun isearch-mode (forward &optional regexp op-fun recursive-edit 
regexp-function)
   "Start Isearch minor mode.
 It is called by the function `isearch-forward' and other related functions.
@@ -1237,6 +1243,8 @@ does not return to the calling function until the search 
is completed.
 To behave this way it enters a recursive edit and exits it when done
 isearching.
 
+Also display the on-screen keyboard if necessary.
+
 The arg REGEXP-FUNCTION, if non-nil, should be a function.  It is
 used to set the value of `isearch-regexp-function'."
 
@@ -1332,6 +1340,25 @@ used to set the value of `isearch-regexp-function'."
   (add-hook 'mouse-leave-buffer-hook 'isearch-mouse-leave-buffer)
   (add-hook 'kbd-macro-termination-hook 'isearch-done)
 
+  ;; If the keyboard is not up and the last event did not come from
+  ;; a keyboard, bring it up so that the user can type.
+  ;;
+  ;; last-event-frame may be `macro', since people apparently make use
+  ;; of I-search in keyboard macros.  (bug#65175)
+  (when (and (not (eq last-event-frame 'macro))
+             (or (not last-event-frame)
+                 (not (eq (device-class last-event-frame
+                                        last-event-device)
+                          'keyboard))))
+    (frame-toggle-on-screen-keyboard (selected-frame) nil))
+
+  ;; Disable text conversion so that isearch can behave correctly.
+
+  (when (fboundp 'set-text-conversion-style)
+    (setq isearch-text-conversion-style
+          text-conversion-style)
+    (set-text-conversion-style nil))
+
   ;; isearch-mode can be made modal (in the sense of not returning to
   ;; the calling function until searching is completed) by entering
   ;; a recursive-edit and exiting it when done isearching.
@@ -1465,6 +1492,10 @@ NOPUSH is t and EDIT is t."
         (setq isearch-tool-bar-old-map nil))
     (kill-local-variable 'tool-bar-map))
 
+  ;; Restore the previous text conversion style.
+  (when (fboundp 'set-text-conversion-style)
+    (set-text-conversion-style isearch-text-conversion-style))
+
   (force-mode-line-update)
 
   ;; If we ended in the middle of some intangible text,
@@ -4640,6 +4671,7 @@ CASE-FOLD non-nil means the search was case-insensitive."
        isearch-message message
        isearch-case-fold-search case-fold)
   (isearch-search)
+  (isearch-push-state)
   (isearch-update))
 
 
diff --git a/lisp/jit-lock.el b/lisp/jit-lock.el
index 452cbd1ca51..d0522d6a791 100644
--- a/lisp/jit-lock.el
+++ b/lisp/jit-lock.el
@@ -1,6 +1,6 @@
 ;;; jit-lock.el --- just-in-time fontification  -*- lexical-binding: t -*-
 
-;; Copyright (C) 1998, 2000-2023 Free Software Foundation, Inc.
+;; Copyright (C) 1998-2023 Free Software Foundation, Inc.
 
 ;; Author: Gerd Moellmann <gerd@gnu.org>
 ;; Keywords: faces files
@@ -90,7 +90,8 @@ See also `jit-lock-stealth-nice'."
   :type 'boolean)
 
 
-(defvaralias 'jit-lock-defer-contextually 'jit-lock-contextually)
+(define-obsolete-variable-alias 'jit-lock-defer-contextually
+  'jit-lock-contextually "30.1")
 (defcustom jit-lock-contextually 'syntax-driven
   "If non-nil, fontification should be syntactically true.
 If nil, refontification occurs only on lines that were modified.  This
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index ccf0f966574..52ffb220d8b 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -223,7 +223,7 @@ object, using the keywords `:code', `:message' and `:data'."
              (apply #'format-message (car args) (cdr args))))
         (signal 'jsonrpc-error
                 `(,msg
-                  (jsonrpc-error-code . ,32603)
+                  (jsonrpc-error-code . -32603)
                   (jsonrpc-error-message . ,msg))))
     (cl-destructuring-bind (&key code message data) args
       (signal 'jsonrpc-error
@@ -698,7 +698,7 @@ TIMEOUT is nil)."
                              :params params)
     (puthash id
              (list (or success-fn
-                       (jsonrpc-lambda (&rest _ignored)
+                       (lambda (&rest _ignored)
                          (jsonrpc--debug
                           connection (list :message "success ignored"
                                            :id id))))
diff --git a/lisp/keymap.el b/lisp/keymap.el
index cd06b830e0a..017b2d6ead0 100644
--- a/lisp/keymap.el
+++ b/lisp/keymap.el
@@ -78,7 +78,7 @@ called from Lisp, COMMAND can be anything that `keymap-set' 
accepts
 as its DEFINITION argument.
 
 If COMMAND is a string (which can only happen when this function is
-callled from Lisp), it must satisfy `key-valid-p'.
+called from Lisp), it must satisfy `key-valid-p'.
 
 Note that if KEY has a local binding in the current buffer,
 that local binding will continue to shadow any global binding
@@ -102,7 +102,7 @@ called from Lisp, COMMAND can be anything that `keymap-set' 
accepts
 as its DEFINITION argument.
 
 If COMMAND is a string (which can only happen when this function is
-callled from Lisp), it must satisfy `key-valid-p'.
+called from Lisp), it must satisfy `key-valid-p'.
 
 The binding goes in the current buffer's local keymap, which in most
 cases is shared with all other buffers in the same major mode."
diff --git a/lisp/kmacro.el b/lisp/kmacro.el
index 7489076ea2e..588b2d14943 100644
--- a/lisp/kmacro.el
+++ b/lisp/kmacro.el
@@ -1189,7 +1189,10 @@ following additional answers: `insert', `insert-1', 
`replace', `replace-1',
        (setq act (lookup-key kmacro-step-edit-map
                              (vector (with-current-buffer (current-buffer) 
(read-event))))))))
 
-    ;; Resume macro execution and perform the action
+    ;; Resume macro execution and perform the action.
+    ;; Suffixing executing-kbd-macro with `dummy-event'
+    ;; is done when pre-command-hook must be called
+    ;; again as part of this keyboard macro's execution.
     (cond
      ((cond
        ((eq act 'act)
@@ -1220,18 +1223,21 @@ following additional answers: `insert', `insert-1', 
`replace', `replace-1',
        ((member act '(replace-1 replace))
        (setq kmacro-step-edit-inserting (if (eq act 'replace-1) 1 t))
        (if (= executing-kbd-macro-index (length executing-kbd-macro))
-           (setq executing-kbd-macro (vconcat executing-kbd-macro [nil])
+           (setq executing-kbd-macro (vconcat executing-kbd-macro
+                                               [dummy-event])
                  kmacro-step-edit-appending t))
        nil)
        ((eq act 'append)
        (setq kmacro-step-edit-inserting t)
        (if (= executing-kbd-macro-index (length executing-kbd-macro))
-           (setq executing-kbd-macro (vconcat executing-kbd-macro [nil])
+           (setq executing-kbd-macro (vconcat executing-kbd-macro
+                                               [dummy-event])
                  kmacro-step-edit-appending t))
        t)
        ((eq act 'append-end)
        (if (= executing-kbd-macro-index (length executing-kbd-macro))
-           (setq executing-kbd-macro (vconcat executing-kbd-macro [nil])
+           (setq executing-kbd-macro (vconcat executing-kbd-macro
+                                               [dummy-event])
                  kmacro-step-edit-inserting t
                  kmacro-step-edit-appending t)
          (setq kmacro-step-edit-active 'append-end))
@@ -1314,7 +1320,8 @@ following additional answers: `insert', `insert-1', 
`replace', `replace-1',
       (setq this-command #'ignore))
      ((eq kmacro-step-edit-active 'append-end)
       (if (= executing-kbd-macro-index (length executing-kbd-macro))
-         (setq executing-kbd-macro (vconcat executing-kbd-macro [nil])
+         (setq executing-kbd-macro (vconcat executing-kbd-macro
+                                             [dummy-event])
                kmacro-step-edit-inserting t
                kmacro-step-edit-appending t
                kmacro-step-edit-active t)))
diff --git a/lisp/language/chinese.el b/lisp/language/chinese.el
index 26f1194aa4c..e35f3f179ad 100644
--- a/lisp/language/chinese.el
+++ b/lisp/language/chinese.el
@@ -111,6 +111,7 @@
 (set-language-info-alist
  "Chinese-GB" '((charset chinese-gb2312 chinese-sisheng)
                (iso639-language . zh)
+                (cjk-locale-symbol . zh_CN)
                (setup-function . (lambda ()
                                    (use-cjk-char-width-table 'zh_CN)))
                (exit-function . use-default-char-width-table)
@@ -142,6 +143,7 @@
 (set-language-info-alist
  "Chinese-BIG5" '((charset chinese-big5-1 chinese-big5-2)
                  (iso639-language . zh)
+                  (cjk-locale-symbol . zh_HK)
                  (setup-function . (lambda ()
                                      (use-cjk-char-width-table 'zh_HK)))
                  (exit-function . use-default-char-width-table)
@@ -198,6 +200,7 @@
                          chinese-cns11643-5 chinese-cns11643-6
                          chinese-cns11643-7)
                 (iso639-language . zh)
+                 (cjk-locale-symbol . zh_TW)
                 (setup-function . (lambda ()
                                     (use-cjk-char-width-table 'zh_TW)))
                 (exit-function . use-default-char-width-table)
@@ -218,6 +221,7 @@ accepts Big5 for input also (which is then converted to 
CNS)."))
                             chinese-cns11643-5 chinese-cns11643-6
                             chinese-cns11643-7 chinese-big5-1 chinese-big5-2)
                    (iso639-language . zh)
+                    (cjk-locale-symbol . zh_TW)
                    (setup-function . (lambda ()
                                        (use-cjk-char-width-table 'zh_TW)))
                    (exit-function . use-default-char-width-table)
@@ -248,6 +252,7 @@ converted to CNS)."))
 (set-language-info-alist
  "Chinese-GBK" '((charset chinese-gbk)
                 (iso639-language . zh)
+                 (cjk-locale-symbol . zh_CN)
                 (setup-function . (lambda ()
                                     (use-cjk-char-width-table 'zh_CN)))
                 (exit-function . use-default-char-width-table)
diff --git a/lisp/language/japanese.el b/lisp/language/japanese.el
index 681dc9d7b92..6042ebf4511 100644
--- a/lisp/language/japanese.el
+++ b/lisp/language/japanese.el
@@ -208,6 +208,7 @@ eucJP-ms is defined in 
<http://www.opengroup.or.jp/jvc/cde/appendix.html>."
  "Japanese" '((setup-function . setup-japanese-environment-internal)
              (exit-function . use-default-char-width-table)
              (iso639-language . ja)
+              (cjk-locale-symbol . ja_JP)
              (tutorial . "TUTORIAL.ja")
              (charset japanese-jisx0208
                       japanese-jisx0212 latin-jisx0201 katakana-jisx0201
diff --git a/lisp/language/korean.el b/lisp/language/korean.el
index fef5796bc4b..ede37d5d07c 100644
--- a/lisp/language/korean.el
+++ b/lisp/language/korean.el
@@ -68,6 +68,7 @@
 (set-language-info-alist
  "Korean" '((setup-function . setup-korean-environment-internal)
            (exit-function . exit-korean-environment)
+            (cjk-locale-symbol . ko_KR)
            (iso639-language . ko)
            (tutorial . "TUTORIAL.ko")
            (charset korean-ksc5601 cp949)
diff --git a/lisp/language/tibetan.el b/lisp/language/tibetan.el
index 31ff37016fe..21b3fc03417 100644
--- a/lisp/language/tibetan.el
+++ b/lisp/language/tibetan.el
@@ -126,6 +126,7 @@
 ;;; Definitions of conversion data.
 ;;;
 
+(eval-and-compile
 
 ;;; alists for tibetan char <-> transcription conversion
 ;;; longer transcription should come first
@@ -333,6 +334,7 @@
 
 
 (defconst tibetan-subjoined-transcription-alist
+ (eval-when-compile
   (sort
    (copy-sequence
        '(("+k"  . "ྐ")
@@ -381,7 +383,7 @@
          ("+Y" . "ྻ") ;; fixed form subscribed YA
          ("+R" . "ྼ") ;; fixed form subscribed RA
          ))
-   (lambda (x y) (> (length (car x)) (length (car y))))))
+   (lambda (x y) (> (length (car x)) (length (car y)))))))
 
 ;;;
 ;;; alist for Tibetan base consonant <-> subjoined consonant conversion.
@@ -557,32 +559,34 @@
     ("སྦ" . "����")
     ("སྨ" . "����")))
 
+)   ; eval-and-compile
+
 (defconst tibetan-regexp
-  (mapconcat (lambda (x) (regexp-quote (car x)))
-             (append tibetan-precomposed-transcription-alist
-                    tibetan-consonant-transcription-alist
-                    tibetan-vowel-transcription-alist
-                    tibetan-modifier-transcription-alist
-                    tibetan-subjoined-transcription-alist)
-             "\\|")
+  (eval-when-compile
+    (regexp-opt (mapcar #'car
+                        (append tibetan-precomposed-transcription-alist
+                               tibetan-consonant-transcription-alist
+                               tibetan-vowel-transcription-alist
+                               tibetan-modifier-transcription-alist
+                               tibetan-subjoined-transcription-alist))))
   "Regexp matching a Tibetan transcription of a composable Tibetan sequence.
 The result of matching is to be used for indexing alists at conversion
 from a roman transcription to the corresponding Tibetan character.")
 
 (defvar tibetan-precomposed-regexp
   (purecopy
-   (concat "^\\("
-           (mapconcat #'car tibetan-precomposed-transcription-alist "\\|")
-           "\\)"))
+   (eval-when-compile
+     (concat "^"
+             (regexp-opt (mapcar #'car tibetan-precomposed-transcription-alist)
+                         t))))
   "Regexp string to match a romanized Tibetan complex consonant.
 The result of matching is to be used for indexing alists when the input key
 from an input method is converted to the corresponding precomposed glyph.")
 
 (defvar tibetan-precomposition-rule-regexp
   (purecopy
-   (concat "\\("
-           (mapconcat #'car tibetan-precomposition-rule-alist "\\|")
-           "\\)"))
+   (eval-when-compile
+     (regexp-opt (mapcar #'car tibetan-precomposition-rule-alist) t)))
   "Regexp string to match a sequence of Tibetan consonantic components.
 That is, one base consonant and one or more subjoined consonants.
 The result of matching is to be used for indexing alist when the component
diff --git a/lisp/language/vietnamese.el b/lisp/language/vietnamese.el
index bd0b3c5ae3e..e439a2c3851 100644
--- a/lisp/language/vietnamese.el
+++ b/lisp/language/vietnamese.el
@@ -28,8 +28,8 @@
 
 ;;; Commentary:
 
-;; For Vietnamese, the character sets VISCII, VSCII and TCVN-5712 are
-;; supported.
+;; For Vietnamese, the coding systems VISCII, VSCII-1 (TCVN-5712),
+;; VIQR and windows-1258 are supported.
 
 ;;; Code:
 
@@ -44,13 +44,16 @@
 (define-coding-system-alias 'viscii 'vietnamese-viscii)
 
 (define-coding-system 'vietnamese-vscii
-  "8-bit encoding for Vietnamese VSCII-1."
+  "8-bit encoding for Vietnamese VSCII-1 (TCVN-5712)."
   :coding-type 'charset
   :mnemonic ?v
   :charset-list '(vscii)
   :suitable-for-file-name t)
 
 (define-coding-system-alias 'vscii 'vietnamese-vscii)
+(define-coding-system-alias 'vietnamese-tcvn 'vietnamese-vscii)
+(define-coding-system-alias 'tcvn 'vietnamese-vscii)
+(define-coding-system-alias 'tcvn-5712 'vietnamese-vscii)
 
 ;; (make-coding-system
 ;;  'vietnamese-vps 4 ?p
@@ -74,7 +77,7 @@
 (set-language-info-alist
  "Vietnamese" '((charset viscii)
                (coding-system vietnamese-viscii vietnamese-vscii
-                              vietnamese-tcvn vietnamese-viqr windows-1258)
+                              vietnamese-viqr windows-1258)
                (nonascii-translation . viscii)
                (coding-priority vietnamese-viscii)
                (input-method . "vietnamese-viqr")
@@ -83,12 +86,12 @@
                (sample-text . "Vietnamese (Tiếng Việt) Chào bạn")
                (documentation . "\
 For Vietnamese, Emacs uses special charsets internally.
-They can be decoded from and encoded to VISCII, VSCII, TCVN-5712, VIQR
-and windows-1258.  VSCII is deprecated in favor of TCVN-5712.  The
-Current setting gives higher priority to the coding system VISCII than
-TCVN-5712.  If you prefer TCVN-5712, please do: (prefer-coding-system
-'vietnamese-tcvn).  There are two Vietnamese input methods: VIQR and
-Telex, VIQR is the default setting.")))
+They can be decoded from and encoded to VISCII, VSCII-1 (TCVN-5712),
+VIQR and windows-1258.  The current setting gives higher priority
+to the coding system VISCII than VSCII-1.  If you prefer VSCII-1,
+please do: (prefer-coding-system 'vietnamese-vscii).  There are
+two Vietnamese input methods: VIQR and Telex, VIQR is the default
+setting.")))
 
 (define-coding-system 'windows-1258
   "windows-1258 encoding for Vietnamese (MIME: WINDOWS-1258)"
@@ -98,15 +101,6 @@ Telex, VIQR is the default setting.")))
   :mime-charset 'windows-1258)
 (define-coding-system-alias 'cp1258 'windows-1258)
 
-(define-coding-system 'vietnamese-tcvn
-  "8-bit encoding for Vietnamese TCVN-5712"
-  :coding-type 'charset
-  :mnemonic ?t
-  :charset-list '(tcvn-5712)
-  :suitable-for-file-name t)
-(define-coding-system-alias 'tcvn 'vietnamese-tcvn)
-(define-coding-system-alias 'tcvn-5712 'vietnamese-tcvn)
-
 (provide 'vietnamese)
 
 ;;; vietnamese.el ends here
diff --git a/lisp/ldefs-boot.el b/lisp/ldefs-boot.el
index 898dcd55f58..f062f3bf8de 100644
--- a/lisp/ldefs-boot.el
+++ b/lisp/ldefs-boot.el
@@ -302,6 +302,7 @@ usage: (defadvice FUNCTION (CLASS NAME [POSITION] [ARGLIST] 
FLAG...)
 (fn FUNCTION ARGS &rest BODY)" nil t)
 (function-put 'defadvice 'doc-string-elt 3)
 (function-put 'defadvice 'lisp-indent-function 2)
+(make-obsolete 'defadvice '"use advice-add or define-advice" "30.1")
 (register-definition-prefixes "advice" '("ad-"))
 
 
@@ -2544,6 +2545,13 @@ The optional argument NEW-WINDOW is not used.
 Browse URL with the system default browser.
 Default to the URL around or before point.
 
+(fn URL &optional NEW-WINDOW)" t)
+(autoload 'browse-url-default-android-browser "browse-url" "\
+Browse URL with the system default browser.
+If `browse-url-android-share' is non-nil, try to share URL using
+an external program instead.  Default to the URL around or before
+point.
+
 (fn URL &optional NEW-WINDOW)" t)
 (autoload 'browse-url-emacs "browse-url" "\
 Ask Emacs to load URL into a buffer and show it in another window.
@@ -4645,12 +4653,6 @@ But if you just want to print something, don't call this 
directly:
 call other entry points instead, such as `cl-prin1'.
 
 (fn OBJECT STREAM)")
-(autoload 'cl-print-expand-ellipsis "cl-print" "\
-Print the expansion of an ellipsis to STREAM.
-VALUE should be the value of the `cl-print-ellipsis' text property
-which was attached to the ellipsis by `cl-prin1'.
-
-(fn VALUE STREAM)")
 (autoload 'cl-prin1 "cl-print" "\
 Print OBJECT on STREAM according to its type.
 Output is further controlled by the variables
@@ -4671,13 +4673,12 @@ characters with appropriate settings of `print-level' 
and
 the arguments VALUE and STREAM and which should respect
 `print-length' and `print-level'.  LIMIT may be nil or zero in
 which case PRINT-FUNCTION will be called with `print-level' and
-`print-length' bound to nil.
+`print-length' bound to nil, and it can also be t in which case
+PRINT-FUNCTION will be called with the current values of `print-level'
+and `print-length'.
 
 Use this function with `cl-prin1' to print an object,
-abbreviating it with ellipses to fit within a size limit.  Use
-this function with `cl-prin1-expand-ellipsis' to expand an
-ellipsis, abbreviating the expansion to stay within a size
-limit.
+abbreviating it with ellipses to fit within a size limit.
 
 (fn PRINT-FUNCTION VALUE LIMIT)")
 (register-definition-prefixes "cl-print" '("cl-print-" "help-byte-code"))
@@ -4873,6 +4874,36 @@ The files to be removed are those produced from the 
original source
 filename (including FILE).
 
 (fn FILE)")
+(autoload 'native--compile-async "comp" "\
+Compile FILES asynchronously.
+FILES is one filename or a list of filenames or directories.
+
+If optional argument RECURSIVELY is non-nil, recurse into
+subdirectories of given directories.
+
+If optional argument LOAD is non-nil, request to load the file
+after compiling.
+
+The optional argument SELECTOR has the following valid values:
+
+nil -- Select all files.
+a string -- A regular expression selecting files with matching names.
+a function -- A function selecting files with matching names.
+
+The variable `native-comp-async-jobs-number' specifies the number
+of (commands) to run simultaneously.
+
+LOAD can also be the symbol `late'.  This is used internally if
+the byte code has already been loaded when this function is
+called.  It means that we request the special kind of load
+necessary in that situation, called \"late\" loading.
+
+During a \"late\" load, instead of executing all top-level forms
+of the original files, only function definitions are
+loaded (paying attention to have these effective only if the
+bytecode definition was not changed in the meantime).
+
+(fn FILES &optional RECURSIVELY LOAD SELECTOR)")
 (autoload 'comp-lookup-eln "comp" "\
 Given a Lisp source FILENAME return the corresponding .eln file if found.
 Search happens in `native-comp-eln-load-path'.
@@ -4939,7 +4970,7 @@ inferred from the code itself by the native compiler; if 
it is
 `know', the type specifier comes from `comp-known-type-specifiers'.
 
 (fn FUNCTION)")
-(register-definition-prefixes "comp" '("comp-" "make-comp-edge" "native-" 
"no-native-compile"))
+(register-definition-prefixes "comp" '("comp-" "make-comp-edge" "native-comp" 
"no-native-compile"))
 
 
 ;;; Generated autoloads from cedet/semantic/wisent/comp.el
@@ -5638,7 +5669,7 @@ Run `perldoc' on WORD.
 (fn WORD)" t)
 (autoload 'cperl-perldoc-at-point "cperl-mode" "\
 Run a `perldoc' on the word around point." t)
-(register-definition-prefixes "cperl-mode" '("cperl-"))
+(register-definition-prefixes "cperl-mode" '("cperl-" "imenu-max-items"))
 
 
 ;;; Generated autoloads from progmodes/cpp.el
@@ -8171,7 +8202,6 @@ Switch to *dungeon* buffer and start game." t)
 
 ;;; Generated autoloads from emacs-lisp/easy-mmode.el
 
-(defalias 'easy-mmode-define-minor-mode #'define-minor-mode)
 (autoload 'define-minor-mode "easy-mmode" "\
 Define a new minor mode MODE.
 This defines the toggle command MODE and (by default) a control variable
@@ -8246,7 +8276,6 @@ INIT-VALUE LIGHTER KEYMAP.
 (fn MODE DOC [KEYWORD VAL ... &rest BODY])" nil t)
 (function-put 'define-minor-mode 'doc-string-elt 2)
 (function-put 'define-minor-mode 'lisp-indent-function 'defun)
-(defalias 'easy-mmode-define-global-mode #'define-globalized-minor-mode)
 (defalias 'define-global-minor-mode #'define-globalized-minor-mode)
 (autoload 'define-globalized-minor-mode "easy-mmode" "\
 Make a global mode GLOBAL-MODE corresponding to buffer-local minor MODE.
@@ -8254,14 +8283,17 @@ TURN-ON is a function that will be called with no args 
in every buffer
 and that should try to turn MODE on if applicable for that buffer.
 
 Each of KEY VALUE is a pair of CL-style keyword arguments.
-The :predicate argument specifies in which major modes should the
+The :predicate key specifies in which major modes should the
 globalized minor mode be switched on.  The value should be t (meaning
 switch on the minor mode in all major modes), nil (meaning don't
 switch on in any major mode), a list of modes (meaning switch on only
 in those modes and their descendants), or a list (not MODES...),
 meaning switch on in any major mode except MODES.  The value can also
 mix all of these forms, see the info node `Defining Minor Modes' for
-details.
+details.  The :predicate key causes the macro to create a user option
+named the same as MODE, but ending with \"-modes\" instead of \"-mode\".
+That user option can then be used to customize in which modes this
+globalized minor mode will be switched on.
 As the minor mode defined by this function is always global, any
 :global keyword is ignored.
 Other keywords have the same meaning as in `define-minor-mode',
@@ -8325,6 +8357,8 @@ CSS contains a list of syntax specifications of the form 
(CHAR . SYNTAX).
 (fn ST CSS DOC &rest ARGS)" nil t)
 (function-put 'easy-mmode-defsyntax 'doc-string-elt 3)
 (function-put 'easy-mmode-defsyntax 'lisp-indent-function 1)
+(define-obsolete-function-alias 'easy-mmode-define-minor-mode 
#'define-minor-mode "30.1")
+(define-obsolete-function-alias 'easy-mmode-define-global-mode 
#'define-globalized-minor-mode "30.1")
 (register-definition-prefixes "easy-mmode" '("easy-mmode-"))
 
 
@@ -8633,7 +8667,7 @@ A second call of this function without changing point 
inserts the next match.
 A call with prefix PREFIX reads the symbol to insert from the minibuffer with
 completion.
 
-(fn PREFIX)" '("P"))
+(fn PREFIX)" t)
 (autoload 'ebrowse-tags-loop-continue "ebrowse" "\
 Repeat last operation on files in tree.
 FIRST-TIME non-nil means this is not a repetition, but the first time.
@@ -10234,6 +10268,9 @@ Look at CONFIG and try to expand GROUP.
 (autoload 'erc-select-read-args "erc" "\
 Prompt the user for values of nick, server, port, and password.
 With prefix arg, also prompt for user and full name.")
+(autoload 'erc-server-select "erc" "\
+Interactively connect to a server from `erc-server-alist'." t)
+(make-obsolete 'erc-server-select 'erc-tls "30.1")
 (autoload 'erc "erc" "\
 ERC is a powerful, modular, and extensible IRC client.
 This function is the main entry point for ERC.
@@ -10259,7 +10296,7 @@ for the values of the other parameters.
 
 See `erc-tls' for the meaning of ID.
 
-(fn &key SERVER PORT NICK USER PASSWORD FULL-NAME ID)" 
'((erc-select-read-args)))
+(fn &key SERVER PORT NICK USER PASSWORD FULL-NAME ID)" t)
 (defalias 'erc-select #'erc)
 (autoload 'erc-tls "erc" "\
 ERC is a powerful, modular, and extensible IRC client.
@@ -10307,7 +10344,7 @@ See Info node `(erc) Network Identifier' for details.  
Like
 CLIENT-CERTIFICATE, this parameter cannot be specified
 interactively.
 
-(fn &key SERVER PORT NICK USER PASSWORD FULL-NAME CLIENT-CERTIFICATE ID)" 
'((let ((erc-default-port erc-default-port-tls)) (erc-select-read-args))))
+(fn &key SERVER PORT NICK USER PASSWORD FULL-NAME CLIENT-CERTIFICATE ID)" t)
 (autoload 'erc-handle-irc-url "erc" "\
 Use ERC to IRC on HOST:PORT in CHANNEL.
 If ERC is already connected to HOST:PORT, simply /join CHANNEL.
@@ -10432,11 +10469,14 @@ Return the name of the network or \"Unknown\" as a 
symbol.
 Use the server parameter NETWORK if provided, otherwise parse the
 server name and search for a match in `erc-networks-alist'.")
 (make-obsolete 'erc-determine-network '"maybe see `erc-networks--determine'" 
"29.1")
-(autoload 'erc-server-select "erc-networks" "\
-Interactively select a server to connect to using `erc-server-alist'." t)
 (register-definition-prefixes "erc-networks" '("erc-"))
 
 
+;;; Generated autoloads from erc/erc-nicks.el
+
+(register-definition-prefixes "erc-nicks" '("erc-nicks-"))
+
+
 ;;; Generated autoloads from erc/erc-notify.el
 
 (register-definition-prefixes "erc-notify" '("erc-"))
@@ -10536,7 +10576,9 @@ it has to be wrapped in `(eval (quote ...))'.
 If NAME is already defined as a test and Emacs is running
 in batch mode, an error is signaled.
 
-(fn NAME () [DOCSTRING] [:expected-result RESULT-TYPE] [:tags \\='(TAG...)] 
BODY...)" nil 'macro)
+(fn NAME () [DOCSTRING] [:expected-result RESULT-TYPE] [:tags \\='(TAG...)] 
BODY...)" nil t)
+(function-put 'ert-deftest 'doc-string-elt 3)
+(function-put 'ert-deftest 'lisp-indent-function 2)
 (autoload 'ert-run-tests-batch "ert" "\
 Run the tests specified by SELECTOR, printing results to the terminal.
 
@@ -14830,9 +14872,8 @@ to specify a command to run.
 If CONFIRM is non-nil, the user will be given an opportunity to edit the
 command before it's run.
 
-Interactively, the user can use the \\`M-c' command while entering
-the regexp to indicate whether the grep should be case sensitive
-or not.
+Interactively, the user can use 
\\<read-regexp-map>\\[read-regexp-toggle-case-fold] while entering the regexp
+to indicate whether the grep should be case sensitive or not.
 
 (fn REGEXP &optional FILES DIR CONFIRM)" t)
 (autoload 'zrgrep "grep" "\
@@ -16409,7 +16450,8 @@ inlined into the compiled format versions.  This means 
that if you
 change its definition, you should explicitly call
 `ibuffer-recompile-formats'.
 
-(fn SYMBOL (&key NAME INLINE PROPS SUMMARIZER) &rest BODY)" nil 'macro)
+(fn SYMBOL (&key NAME INLINE PROPS SUMMARIZER) &rest BODY)" nil t)
+(function-put 'define-ibuffer-column 'lisp-indent-function 'defun)
 (autoload 'define-ibuffer-sorter "ibuf-macs" "\
 Define a method of sorting named NAME.
 DOCUMENTATION is the documentation of the function, which will be called
@@ -16420,7 +16462,9 @@ For sorting, the forms in BODY will be evaluated with 
`a' bound to one
 buffer object, and `b' bound to another.  BODY should return a non-nil
 value if and only if `a' is \"less than\" `b'.
 
-(fn NAME DOCUMENTATION (&key DESCRIPTION) &rest BODY)" nil 'macro)
+(fn NAME DOCUMENTATION (&key DESCRIPTION) &rest BODY)" nil t)
+(function-put 'define-ibuffer-sorter 'lisp-indent-function 1)
+(function-put 'define-ibuffer-sorter 'doc-string-elt 2)
 (autoload 'define-ibuffer-op "ibuf-macs" "\
 Generate a function which operates on a buffer.
 OP becomes the name of the function; if it doesn't begin with
@@ -16459,7 +16503,9 @@ BODY define the operation; they are forms to evaluate 
per each
 marked buffer.  BODY is evaluated with `buf' bound to the
 buffer object.
 
-(fn OP ARGS DOCUMENTATION (&key INTERACTIVE MARK MODIFIER-P DANGEROUS OPSTRING 
ACTIVE-OPSTRING BEFORE AFTER COMPLEX) &rest BODY)" nil 'macro)
+(fn OP ARGS DOCUMENTATION (&key INTERACTIVE MARK MODIFIER-P DANGEROUS OPSTRING 
ACTIVE-OPSTRING BEFORE AFTER COMPLEX) &rest BODY)" nil t)
+(function-put 'define-ibuffer-op 'lisp-indent-function 2)
+(function-put 'define-ibuffer-op 'doc-string-elt 3)
 (autoload 'define-ibuffer-filter "ibuf-macs" "\
 Define a filter named NAME.
 DOCUMENTATION is the documentation of the function.
@@ -16474,7 +16520,9 @@ not a particular buffer should be displayed or not.  
The forms in BODY
 will be evaluated with BUF bound to the buffer object, and QUALIFIER
 bound to the current value of the filter.
 
-(fn NAME DOCUMENTATION (&key READER DESCRIPTION) &rest BODY)" nil 'macro)
+(fn NAME DOCUMENTATION (&key READER DESCRIPTION) &rest BODY)" nil t)
+(function-put 'define-ibuffer-filter 'lisp-indent-function 2)
+(function-put 'define-ibuffer-filter 'doc-string-elt 2)
 (register-definition-prefixes "ibuf-macs" '("ibuffer-"))
 
 
@@ -18954,7 +19002,7 @@ The macro is now available for use via 
\\[kmacro-call-macro],
 or it can be given a name with \\[kmacro-name-last-macro] and then invoked
 under that name.
 
-With numeric arg, repeat macro now that many times,
+With numeric ARG, repeat the macro that many times,
 counting the definition just completed as the first repetition.
 An argument of zero means repeat until error.
 
@@ -19179,7 +19227,7 @@ A major mode to edit GNU ld script files.
 (put 'less-css-input-file-name 'safe-local-variable #'stringp)
  (add-to-list 'auto-mode-alist '("\\.less\\'" . less-css-mode))
 (autoload 'less-css-mode "less-css-mode" "\
-Major mode for editing Less files (http://lesscss.org/).
+Major mode for editing Less files (https://lesscss.org/).
 Special commands:
 \\{less-css-mode-map}
 
@@ -20706,6 +20754,8 @@ Also see the `duplicate-line' command.
 (autoload 'duplicate-line "misc" "\
 Duplicate the current line N times.
 Interactively, N is the prefix numeric argument, and defaults to 1.
+The user option `duplicate-line-final-position' specifies where to
+move point after duplicating the line.
 Also see the `copy-from-above-command' command.
 
 (fn &optional N)" t)
@@ -20715,6 +20765,9 @@ If the region is inactive, duplicate the current line 
(like `duplicate-line').
 Otherwise, duplicate the region, which remains active afterwards.
 If the region is rectangular, duplicate on its right-hand side.
 Interactively, N is the prefix numeric argument, and defaults to 1.
+The variables `duplicate-line-final-position' and
+`duplicate-region-final-position' control the position of point
+and the region after the duplication.
 
 (fn &optional N)" t)
 (autoload 'zap-up-to-char "misc" "\
@@ -20766,7 +20819,7 @@ Optional argument BUFFER specifies a buffer to use, 
instead of
 The return value is always nil.
 
 (fn &optional LOADED-ONLY-P BUFFER)" t)
-(register-definition-prefixes "misc" '("list-dynamic-libraries--"))
+(register-definition-prefixes "misc" '("duplicate-" 
"list-dynamic-libraries--"))
 
 
 ;;; Generated autoloads from misearch.el
@@ -22508,7 +22561,7 @@ Coloring:
 
 ;;; Generated autoloads from org/org.el
 
-(push (purecopy '(org 9 6 6)) package--builtin-versions)
+(push (purecopy '(org 9 6 7)) package--builtin-versions)
 (autoload 'org-babel-do-load-languages "org" "\
 Load the languages defined in `org-babel-load-languages'.
 
@@ -24489,11 +24542,6 @@ they are not by default assigned to keys." t)
 (register-definition-prefixes "picture" '("picture-"))
 
 
-;;; Generated autoloads from language/pinyin.el
-
-(register-definition-prefixes "pinyin" '("pinyin-character-map"))
-
-
 ;;; Generated autoloads from textmodes/pixel-fill.el
 
 (register-definition-prefixes "pixel-fill" '("pixel-fill-"))
@@ -24527,6 +24575,18 @@ The mode's hook is called both when the mode is 
enabled and when
 it is disabled.
 
 (fn &optional ARG)" t)
+(autoload 'pixel-scroll-precision-scroll-down-page "pixel-scroll" "\
+Scroll the current window down by DELTA pixels.
+Note that this function doesn't work if DELTA is larger than
+the height of the current window.
+
+(fn DELTA)")
+(autoload 'pixel-scroll-precision-scroll-up-page "pixel-scroll" "\
+Scroll the current window up by DELTA pixels.
+Note that this function doesn't work if DELTA is larger than
+the height of the current window.
+
+(fn DELTA)")
 (defvar pixel-scroll-precision-mode nil "\
 Non-nil if Pixel-Scroll-Precision mode is enabled.
 See the `pixel-scroll-precision-mode' command
@@ -24618,8 +24678,9 @@ Use streaming commands.
 Return a string containing the pretty-printed representation of OBJECT.
 OBJECT can be any Lisp object.  Quoting characters are used as needed
 to make output that `read' can handle, whenever this is possible.
+Optional argument PP-FUNCTION overrides `pp-default-function'.
 
-(fn OBJECT)")
+(fn OBJECT &optional PP-FUNCTION)")
 (autoload 'pp-buffer "pp" "\
 Prettify the current buffer with printed representation of a Lisp object." t)
 (autoload 'pp "pp" "\
@@ -24627,11 +24688,7 @@ Output the pretty-printed representation of OBJECT, 
any Lisp object.
 Quoting characters are printed as needed to make output that `read'
 can handle, whenever this is possible.
 
-This function does not apply special formatting rules for Emacs
-Lisp code.  See `pp-emacs-lisp-code' instead.
-
-By default, this function won't limit the line length of lists
-and vectors.  Bind `pp-use-max-width' to a non-nil value to do so.
+Uses the pretty-printing code specified in `pp-default-function'.
 
 Output stream is STREAM, or value of `standard-output' (which see).
 
@@ -24669,8 +24726,10 @@ Ignores leading comment characters.
 Insert SEXP into the current buffer, formatted as Emacs Lisp code.
 Use the `pp-max-width' variable to control the desired line length.
 Note that this could be slow for large SEXPs.
+Can also be called with two arguments, in which case they're taken to be
+the bounds of a region containing Lisp code to pretty-print.
 
-(fn SEXP)")
+(fn SEXP &optional END)")
 (register-definition-prefixes "pp" '("pp-"))
 
 
@@ -25261,8 +25320,10 @@ Return the project instance in DIRECTORY, defaulting 
to `default-directory'.
 
 When no project is found in that directory, the result depends on
 the value of MAYBE-PROMPT: if it is nil or omitted, return nil,
-else ask the user for a directory in which to look for the
-project, and if no project is found there, return a \"transient\"
+else prompt the user for the project to use.  To prompt for a
+project, call the function specified by `project-prompter', which
+returns the directory in which to look for the project.  If no
+project is found in that directory, return a \"transient\"
 project instance.
 
 The \"transient\" project instance is a special kind of value
@@ -25468,6 +25529,15 @@ When called in a program, it will use the project 
corresponding
 to directory DIR.
 
 (fn DIR)" t)
+(autoload 'project-uniquify-dirname-transform "project" "\
+Uniquify name of directory DIRNAME using `project-name', if in a project.
+
+If you set `uniquify-dirname-transform' to this function,
+slash-separated components from `project-name' will be appended to
+the buffer's directory name when buffers from two different projects
+would otherwise have the same name.
+
+(fn DIRNAME)")
 (register-definition-prefixes "project" '("project-"))
 
 
@@ -25832,6 +25902,7 @@ Major mode for editing Python files, using tree-sitter 
library.
 \\{python-ts-mode-map}
 
 (fn)" t)
+(add-to-list 'auto-mode-alist '("/\\(?:Pipfile\\|\\.?flake8\\)\\'" . 
conf-mode))
 (register-definition-prefixes "python" '("inferior-python-mode" "python-" 
"run-python-internal"))
 
 
@@ -27501,8 +27572,8 @@ Currently there are `ruby-mode' and `ruby-ts-mode'.
 Major mode for editing Ruby code.
 
 (fn)" t)
-(add-to-list 'auto-mode-alist (cons (purecopy (concat "\\(?:\\.\\(?:" 
"rbw?\\|ru\\|rake\\|thor" "\\|jbuilder\\|rabl\\|gemspec\\|podspec" "\\)" "\\|/" 
"\\(?:Gem\\|Rake\\|Cap\\|Thor" "\\|Puppet\\|Berks\\|Brew" 
"\\|Vagrant\\|Guard\\|Pod\\)file" "\\)\\'")) 'ruby-mode))
-(dolist (name (list "ruby" "rbx" "jruby" "ruby1.9" "ruby1.8")) (add-to-list 
'interpreter-mode-alist (cons (purecopy name) 'ruby-mode)))
+(add-to-list 'auto-mode-alist (cons (purecopy (concat "\\(?:\\.\\(?:" 
"rbw?\\|ru\\|rake\\|thor\\|axlsx" "\\|jbuilder\\|rabl\\|gemspec\\|podspec" 
"\\)" "\\|/" "\\(?:Gem\\|Rake\\|Cap\\|Thor" "\\|Puppet\\|Berks\\|Brew\\|Fast" 
"\\|Vagrant\\|Guard\\|Pod\\)file" "\\)\\'")) 'ruby-mode))
+(dolist (name (list "ruby" "rbx" "jruby" "j?ruby\\(?:[0-9.]+\\)")) 
(add-to-list 'interpreter-mode-alist (cons (purecopy name) 'ruby-mode)))
 (register-definition-prefixes "ruby-mode" '("ruby-"))
 
 
@@ -30433,7 +30504,7 @@ this defaults to the current buffer.
 Query the user for a process and return the process object.
 
 (fn PROMPT)")
-(register-definition-prefixes "subr-x" '("emacs-etc--hide-local-variables" 
"eval-command-interactive-spec" "hash-table-" "internal--thread-argument" 
"replace-region-contents" "string-" "thread-" 
"with-buffer-unmodified-if-unchanged"))
+(register-definition-prefixes "subr-x" '("emacs-etc--hide-local-variables" 
"hash-table-" "internal--thread-argument" "replace-region-contents" "string-" 
"thread-" "with-buffer-unmodified-if-unchanged"))
 
 
 ;;; Generated autoloads from progmodes/subword.el
@@ -31307,7 +31378,7 @@ See also: variables `tar-update-datestamp' and 
`tar-anal-blocksize'.
 \\{tar-mode-map}
 
 (fn)" t)
-(register-definition-prefixes "tar-mode" '("tar-"))
+(register-definition-prefixes "tar-mode" '("pax-" "tar-"))
 
 
 ;;; Generated autoloads from progmodes/tcl.el
@@ -32670,7 +32741,7 @@ FROM-MAP must contain appropriate binding for 
`[menu-bar]' which
 holds a keymap.
 
 (fn COMMAND ICON IN-MAP &optional FROM-MAP &rest PROPS)")
-(register-definition-prefixes "tool-bar" '("toggle-tool-bar-mode-from-frame" 
"tool-bar-"))
+(register-definition-prefixes "tool-bar" '("modifier-bar-" 
"secondary-tool-bar-map" "toggle-tool-bar-mode-from-frame" "tool-bar-"))
 
 
 ;;; Generated autoloads from tooltip.el
@@ -32771,6 +32842,11 @@ Add Tramp file name handlers to 
`file-name-handler-alist' during autoload." (unl
  (tramp-register-autoload-file-name-handlers)
 (defun tramp-unload-file-name-handlers nil "\
 Unload Tramp file name handlers from `file-name-handler-alist'." (dolist (fnh 
file-name-handler-alist) (when (and (symbolp (cdr fnh)) (string-prefix-p 
"tramp-" (symbol-name (cdr fnh)))) (setq file-name-handler-alist (delq fnh 
file-name-handler-alist)))))
+(defun inhibit-remote-files nil "\
+Deactivate remote file names." (interactive) (when (fboundp 
'tramp-cleanup-all-connections) (funcall 'tramp-cleanup-all-connections)) 
(tramp-unload-file-name-handlers) (setq tramp-mode nil))
+(defmacro without-remote-files (&rest body) "\
+Deactivate remote file names temporarily.
+Run BODY." (declare (indent 0) (debug ((form body) body))) `(let 
((file-name-handler-alist (copy-tree file-name-handler-alist)) tramp-mode) 
(tramp-unload-file-name-handlers) ,@body))
 (defun tramp-unload-tramp nil "\
 Discard Tramp from loading remote files." (interactive) (ignore-errors 
(unload-feature 'tramp 'force)))
 (register-definition-prefixes "tramp" '("tramp-" "with-"))
@@ -32847,6 +32923,11 @@ Add archive file name handler to 
`file-name-handler-alist'." (when (and tramp-ar
 (register-definition-prefixes "tramp-integration" '("tramp-"))
 
 
+;;; Generated autoloads from net/tramp-message.el
+
+(register-definition-prefixes "tramp-message" '("tramp-" 
"with-tramp-debug-message"))
+
+
 ;;; Generated autoloads from net/tramp-rclone.el
 
 (register-definition-prefixes "tramp-rclone" '("tramp-rclone-"))
@@ -33245,7 +33326,7 @@ The JSX-specific faces are used when 
`treesit-font-lock-level' is
 at least 3 (which is the default value).
 
 (fn)" t)
-(register-definition-prefixes "typescript-ts-mode" '("typescript-ts-mode-"))
+(register-definition-prefixes "typescript-ts-mode" '("tsx-ts-mode--" 
"typescript-ts-mode-"))
 
 
 ;;; Generated autoloads from international/ucs-normalize.el
@@ -36636,7 +36717,7 @@ The optional ARGS are additional keyword arguments.
 Call `insert' with ARGS even if surrounding text is read only.
 
 (fn &rest ARGS)")
-(defvar widget-keymap (let ((map (make-sparse-keymap))) (define-key map "\11" 
'widget-forward) (define-key map "\33\11" 'widget-backward) (define-key map 
[(shift tab)] 'widget-backward) (put 'widget-backward :advertised-binding 
[(shift tab)]) (define-key map [backtab] 'widget-backward) (define-key map 
[down-mouse-2] 'widget-button-click) (define-key map [down-mouse-1] 
'widget-button-click) (define-key map [(control 109)] 'widget-button-press) 
map) "\
+(defvar widget-keymap (let ((map (make-sparse-keymap))) (define-key map "\11" 
'widget-forward) (define-key map "\33\11" 'widget-backward) (define-key map 
[(shift tab)] 'widget-backward) (put 'widget-backward :advertised-binding 
[(shift tab)]) (define-key map [backtab] 'widget-backward) (define-key map 
[down-mouse-2] 'widget-button-click) (define-key map [down-mouse-1] 
'widget-button-click) (define-key map [touchscreen-begin] 'widget-button-click) 
(define-key map [(control 109)] 'widget-b [...]
 Keymap containing useful binding for buffers containing widgets.
 Recommended as a parent keymap for modes using widgets.
 Note that such modes will need to require wid-edit.")
diff --git a/lisp/loadhist.el b/lisp/loadhist.el
index 1609542e5fa..3800ea70ea4 100644
--- a/lisp/loadhist.el
+++ b/lisp/loadhist.el
@@ -1,8 +1,8 @@
 ;;; loadhist.el --- lisp functions for working with feature groups  -*- 
lexical-binding: t -*-
 
-;; Copyright (C) 1995, 1998, 2000-2023 Free Software Foundation, Inc.
+;; Copyright (C) 1995-2023 Free Software Foundation, Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: internal
 
@@ -108,7 +108,8 @@ from a file."
                               features))
                      features)))))
 
-(defvaralias 'loadhist-hook-functions 'unload-feature-special-hooks)
+(define-obsolete-variable-alias 'loadhist-hook-functions
+  'unload-feature-special-hooks "30.1")
 (defvar unload-feature-special-hooks
   '(after-change-functions after-insert-file-functions
     after-make-frame-functions auto-coding-functions
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 532d2ca176d..38fb0fc1fa9 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -258,6 +258,9 @@
 (load "jit-lock")
 
 (load "mouse")
+;; This loading happens on Android despite scroll bars being
+;; unsupported, because scroll-bar-mode (the variable) must be
+;; defined.
 (if (boundp 'x-toolkit-scroll-bars)
     (load "scroll-bar"))
 (load "select")
@@ -295,6 +298,10 @@
 (if (featurep 'dynamic-setting)
     (load "dynamic-setting"))
 
+;; touch-screen.el is tiny and is used liberally throughout the button
+;; code etc, so it may as well be preloaded everywhere.
+(load "touch-screen")
+
 (if (featurep 'x)
     (progn
       (load "x-dnd")
@@ -306,6 +313,12 @@
       (load "term/common-win")
       (load "term/haiku-win")))
 
+(if (featurep 'android)
+    (progn
+      (load "ls-lisp")
+      (load "term/common-win")
+      (load "term/android-win")))
+
 (if (or (eq system-type 'windows-nt)
         (featurep 'w32))
     (progn
@@ -429,6 +442,13 @@ lost after dumping")))
       (defconst emacs-build-number
        (if versions (1+ (apply #'max versions)) 1))))
 
+;; Just set the repository branch during initial dumping on Android.
+(if (and (eq system-type 'android)
+         (not (pdumper-stats)))
+    (setq emacs-repository-version
+          (ignore-errors (emacs-repository-get-version))
+          emacs-repository-branch
+          (ignore-errors (emacs-repository-get-branch))))
 
 (message "Finding pointers to doc strings...")
 (if (and (or (and (fboundp 'dump-emacs)
@@ -546,66 +566,102 @@ lost after dumping")))
 
 
 
-(if dump-mode
-    (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp")
-                        ((equal dump-mode "dump") "emacs")
-                        ((equal dump-mode "bootstrap") "emacs")
-                        ((equal dump-mode "pbootstrap") "bootstrap-emacs.pdmp")
-                        (t (error "Unrecognized dump mode %s" dump-mode)))))
-      (when (and (featurep 'native-compile)
-                 (equal dump-mode "pdump"))
-        ;; Don't enable this before bootstrap is completed, as the
-        ;; compiler infrastructure may not be usable yet.
-        (setq native-comp-enable-subr-trampolines t))
-      (message "Dumping under the name %s" output)
-      (condition-case ()
-          (delete-file output)
-        (file-error nil))
-      ;; On MS-Windows, the current directory is not necessarily the
-      ;; same as invocation-directory.
-      (let (success)
-        (unwind-protect
-             (let ((tmp-dump-mode dump-mode)
-                   (dump-mode nil)
-                   (lexical-binding nil))
-               (if (member tmp-dump-mode '("pdump" "pbootstrap"))
-                   (dump-emacs-portable (expand-file-name output 
invocation-directory))
-                 (dump-emacs output (if (eq system-type 'ms-dos)
-                                        "temacs.exe"
-                                      "temacs"))
-                 (message "%d pure bytes used" pure-bytes-used))
-               (setq success t))
-          (unless success
-            (ignore-errors
-              (delete-file output)))))
-      ;; Recompute NAME now, so that it isn't set when we dump.
-      (if (not (or (eq system-type 'ms-dos)
-                   (eq system-type 'haiku) ;; BFS doesn't support hard links
-                   ;; Don't bother adding another name if we're just
-                   ;; building bootstrap-emacs.
-                   (member dump-mode '("pbootstrap" "bootstrap"))))
-          (let ((name (format "emacs-%s.%d" emacs-version emacs-build-number))
-                (exe (if (eq system-type 'windows-nt) ".exe" "")))
-            (while (string-match "[^-+_.a-zA-Z0-9]+" name)
-              (setq name (concat (downcase (substring name 0 (match-beginning 
0)))
-                                 "-"
-                                 (substring name (match-end 0)))))
-            (message "Adding name %s" (concat name exe))
-            ;; When this runs on Windows, invocation-directory is not
-            ;; necessarily the current directory.
-            (add-name-to-file (expand-file-name (concat "emacs" exe)
-                                                invocation-directory)
-                              (expand-file-name (concat name exe)
-                                                invocation-directory)
-                              t)
-            (when (equal dump-mode "pdump")
-              (message "Adding name %s" (concat name ".pdmp"))
-              (add-name-to-file (expand-file-name "emacs.pdmp"
+(if (and (eq system-type 'android)
+         (featurep 'android))
+    (progn
+      ;; Dumping Emacs on Android works slightly differently from
+      ;; everywhere else.  The first time Emacs starts, Emacs dumps
+      ;; itself to "emacs-%s.pdump", and then proceeds with loadup,
+      ;; where %s is replaced by the dump fingerprint.
+      ;; EmacsApplication.java removes any pdump files with a
+      ;; different build fingerprint upon being created, which happens
+      ;; the moment the Android system starts Emacs.  Then, it passes
+      ;; the appropriate "--dump-file" to libemacs.so as it starts.
+      (when (not noninteractive)
+        (let ((temp-dir (getenv "TEMP"))
+              (dump-file-name (format "%semacs-%s.pdmp"
+                                      (file-name-as-directory "~")
+                                      pdumper-fingerprint))
+              (dump-temp-file-name (format "%s~emacs-%s.pdmp"
+                                           (file-name-as-directory "~")
+                                           pdumper-fingerprint)))
+          (unless (pdumper-stats)
+            (condition-case ()
+                (progn
+                  (dump-emacs-portable dump-temp-file-name)
+                  ;; Move the dumped file to the actual dump file name.
+                  (rename-file dump-temp-file-name dump-file-name)
+                  ;; Continue with loadup.
+                  nil)
+              (error nil))))))
+  (if dump-mode
+      (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp")
+                          ((equal dump-mode "dump") "emacs")
+                          ((equal dump-mode "bootstrap") "emacs")
+                          ((equal dump-mode "pbootstrap") 
"bootstrap-emacs.pdmp")
+                          (t (error "Unrecognized dump mode %s" dump-mode)))))
+        (when (and (featurep 'native-compile)
+                   (equal dump-mode "pdump"))
+          ;; Don't enable this before bootstrap is completed, as the
+          ;; compiler infrastructure may not be usable yet.
+          (setq native-comp-enable-subr-trampolines t))
+        (message "Dumping under the name %s" output)
+        (condition-case ()
+            (delete-file output)
+          (file-error nil))
+        ;; On MS-Windows, the current directory is not necessarily the
+        ;; same as invocation-directory.
+        (let (success)
+          (unwind-protect
+              (let ((tmp-dump-mode dump-mode)
+                    (dump-mode nil)
+                    (lexical-binding nil))
+                (if (member tmp-dump-mode '("pdump" "pbootstrap"))
+                    (dump-emacs-portable (expand-file-name output 
invocation-directory))
+                  (dump-emacs output (if (eq system-type 'ms-dos)
+                                         "temacs.exe"
+                                       "temacs"))
+                  (message "%d pure bytes used" pure-bytes-used))
+                (setq success t))
+            (unless success
+              (ignore-errors
+                (delete-file output)))))
+        ;; Recompute NAME now, so that it isn't set when we dump.
+        (if (not (or (eq system-type 'ms-dos)
+                     (eq system-type 'haiku) ;; BFS doesn't support hard links
+                     ;; There's no point keeping old dumps around for
+                     ;; the binary used to build Lisp on the build
+                     ;; machine.
+                     (or (featurep 'android)
+                         ;; And if this branch is reached with
+                         ;; `system-type' set to Android, this is a
+                         ;; regular Emacs TTY build.  (bug#65339)
+                         (eq system-type 'android))
+                     ;; Don't bother adding another name if we're just
+                     ;; building bootstrap-emacs.
+                     (member dump-mode '("pbootstrap" "bootstrap"))))
+            (let ((name (format "emacs-%s.%d" emacs-version 
emacs-build-number))
+                  (exe (if (eq system-type 'windows-nt) ".exe" "")))
+              (while (string-match "[^-+_.a-zA-Z0-9]+" name)
+                (setq name (concat (downcase (substring name 0 
(match-beginning 0)))
+                                   "-"
+                                   (substring name (match-end 0)))))
+              (message "Adding name %s" (concat name exe))
+              ;; When this runs on Windows, invocation-directory is not
+              ;; necessarily the current directory.
+              (add-name-to-file (expand-file-name (concat "emacs" exe)
                                                   invocation-directory)
-                                (expand-file-name (concat name ".pdmp")
+                                (expand-file-name (concat name exe)
                                                   invocation-directory)
-                                t))))
-      (kill-emacs)))
+                                t)
+              (when (equal dump-mode "pdump")
+                (message "Adding name %s" (concat name ".pdmp"))
+                (add-name-to-file (expand-file-name "emacs.pdmp"
+                                                    invocation-directory)
+                                  (expand-file-name (concat name ".pdmp")
+                                                    invocation-directory)
+                                  t))))
+        (kill-emacs))))
 
 ;; This file must be loaded each time Emacs is run from scratch, e.g., temacs.
 ;; So run the startup code now.  First, remove `-l loadup' from args.
@@ -621,6 +677,13 @@ lost after dumping")))
 (setq load-file-name nil)
 (eval top-level t)
 
+;; loadup.el is loaded at startup, but clobbers current-load-list.
+;; Set current-load-list to a list containing no definitions and only
+;; its name, to prevent invalid entries from ending up in
+;; Vload_history when running temacs interactively.
+
+(setq current-load-list (list "loadup.el"))
+
 
 ;; Local Variables:
 ;; no-byte-compile: t
diff --git a/lisp/ls-lisp.el b/lisp/ls-lisp.el
index 485ac4476ef..b0f86839740 100644
--- a/lisp/ls-lisp.el
+++ b/lisp/ls-lisp.el
@@ -184,7 +184,7 @@ if emulation is GNU then default is `(links uid gid)'."
   :group 'ls-lisp)
 
 (defcustom ls-lisp-use-insert-directory-program
-  (not (memq system-type '(ms-dos windows-nt)))
+  (not (memq system-type '(ms-dos windows-nt android)))
   "Non-nil causes ls-lisp to revert back to using `insert-directory-program'.
 This is useful on platforms where ls-lisp is dumped into Emacs, such as
 Microsoft Windows, but you would still like to use a program to list
diff --git a/lisp/mail/emacsbug.el b/lisp/mail/emacsbug.el
index 7a66089aec9..bebaad720db 100644
--- a/lisp/mail/emacsbug.el
+++ b/lisp/mail/emacsbug.el
@@ -144,6 +144,10 @@ This requires either the macOS \"open\" command, or the 
freedesktop
                (goto-char (point-min))
                (buffer-substring (line-beginning-position)
                                  (line-end-position))))))
+        ((eq system-type 'android)
+         ;; This is a short string containing the Android version,
+         ;; build number, and window system distributor.
+         (symbol-value 'android-build-fingerprint))
         ;; TODO Cygwin, Solaris (usg-unix-v).
         (t
          (or (let ((file "/etc/os-release"))
diff --git a/lisp/mail/footnote.el b/lisp/mail/footnote.el
index b88fd405da8..4b2770ee3a1 100644
--- a/lisp/mail/footnote.el
+++ b/lisp/mail/footnote.el
@@ -347,10 +347,11 @@ Use Unicode characters for footnoting."
     ("ק" "ר" "ש" "ת" "תק" "תר" "תש" "תת" "תתק")))
 
 (defconst footnote-hebrew-numeric-regex
-  (let ((numchars (string-to-list
-                  (apply #'concat (apply #'append footnote-hebrew-numeric)))))
+  (let ((numchars
+         (delete-dups
+          (string-to-list
+           (apply #'concat (apply #'append footnote-hebrew-numeric))))))
     (rx-to-string `(1+ (in ?' ,@numchars)))))
-;; (defconst footnote-hebrew-numeric-regex 
"\\([אבגדהוזחט]'\\)?\\(ת\\)?\\(ת\\)?\\([קרשת]\\)?\\([טיכלמנסעפצ]\\)?\\([אבגדהוזחט]\\)?")
 
 (defun footnote--hebrew-numeric (n)
   "Supports 9999 footnotes, then rolls over."
diff --git a/lisp/mail/rmail.el b/lisp/mail/rmail.el
index 872299c2415..f76600000c9 100644
--- a/lisp/mail/rmail.el
+++ b/lisp/mail/rmail.el
@@ -1,7 +1,6 @@
 ;;; rmail.el --- main code of "RMAIL" mail reader for Emacs  -*- 
lexical-binding:t -*-
 
-;; Copyright (C) 1985-1988, 1993-1998, 2000-2023 Free Software
-;; Foundation, Inc.
+;; Copyright (C) 1985-2023 Free Software Foundation, Inc.
 
 ;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: mail
@@ -90,7 +89,6 @@
 its character representation and its display representation.")
 
 (defvar deleted-head)
-(defvar font-lock-fontified)
 (defvar mail-abbrev-syntax-table)
 (defvar mail-abbrevs)
 (defvar messages-head)
@@ -264,7 +262,7 @@ Otherwise, look for `movemail' in the directories in
          ;; assuming it would work.
          ;; https://lists.gnu.org/r/bug-gnu-emacs/2008-02/msg00087.html
          (let ((progname (expand-file-name
-                          (concat "movemail"
+                          (concat movemail-program-name
                                   (if (memq system-type '(ms-dos windows-nt))
                                       ".exe")) dir)))
            (when (and (not (file-directory-p progname))
@@ -2016,7 +2014,9 @@ Value is the size of the newly read mail after 
conversion."
                 (buffer-disable-undo errors)
                 (let ((args
                        (append
-                        (list (or rmail-movemail-program "movemail") nil 
errors nil)
+                        (list (or rmail-movemail-program
+                                   movemail-program-name)
+                               nil errors nil)
                         (if rmail-preserve-inbox
                             (list "-p")
                           nil)
diff --git a/lisp/mail/supercite.el b/lisp/mail/supercite.el
index 1a48c64a663..8d9cb5511ed 100644
--- a/lisp/mail/supercite.el
+++ b/lisp/mail/supercite.el
@@ -620,9 +620,6 @@ the list should be unique."
         ((setq elt (rassq char alist))
          (message "%s%s" p (car elt))
          (setq p (cdr elt)))
-        ((if (fboundp 'button-release-event-p)
-             (button-release-event-p event)) ; ignore them
-         nil)
         (t
          (message "%s%s" p (single-key-description event))
          (ding)
diff --git a/lisp/man.el b/lisp/man.el
index 479bf9f9a3c..506d6060269 100644
--- a/lisp/man.el
+++ b/lisp/man.el
@@ -315,7 +315,7 @@ If this is nil, `man' will use `locale-coding-system'."
   :type 'hook
   :group 'man)
 
-(defvar Man-name-regexp "[-[:alnum:]_­+][-[:alnum:]_.:­+]*"
+(defvar Man-name-regexp "[-[:alnum:]_­+[@][-[:alnum:]_.:­+]*"
   "Regular expression describing the name of a manpage (without section).")
 
 (defvar Man-section-regexp "[0-9][a-zA-Z0-9+]*\\|[LNln]"
@@ -937,7 +937,16 @@ foo(sec)[, bar(sec) [, ...]] [other stuff] - description"
                          "-k" (concat (when (or Man-man-k-use-anchor
                                                 (string-equal prefix ""))
                                         "^")
-                                      prefix))))
+                                      (if (string-equal prefix "")
+                                          prefix
+                                        ;; FIXME: shell-quote-argument
+                                        ;; is not entirely
+                                        ;; appropriate: we actually
+                                        ;; need to quote ERE here.
+                                        ;; But we don't have that, and
+                                        ;; shell-quote-argument does
+                                        ;; the job...
+                                        (shell-quote-argument prefix))))))
               (setq table (Man-parse-man-k)))))
        ;; Cache the table for later reuse.
         (when table
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el
index 030fe374598..5e837485db3 100644
--- a/lisp/menu-bar.el
+++ b/lisp/menu-bar.el
@@ -79,6 +79,14 @@
                   :help "Print current buffer with page headings"))
     menu))
 
+(defcustom menu-bar-close-window nil
+  "Whether or not to close the current window from the menu bar.
+If non-nil, selecting Close from the File menu or clicking Close
+in the tool bar will close the current window where possible."
+  :type 'boolean
+  :group 'menu
+  :version "30.1")
+
 (defvar menu-bar-file-menu
   (let ((menu (make-sparse-keymap "File")))
 
@@ -486,6 +494,11 @@
 (defvar menu-bar-edit-menu
   (let ((menu (make-sparse-keymap "Edit")))
 
+    (bindings--define-key menu [execute-extended-command]
+      '(menu-item "Execute Command" execute-extended-command
+                  :enable t
+                  :help "Read a command name, its arguments, then call it."))
+
     ;; ns-win.el said: Add spell for platform consistency.
     (if (featurep 'ns)
         (bindings--define-key menu [spell]
@@ -686,10 +699,10 @@ Do the same for the keys of the same name."
       menu-bar-separator)
     (bindings--define-key menu [customize-browse]
       '(menu-item "Browse Customization Groups" customize-browse
-                  :help "Browse all customization groups"))
+                  :help "Tree-like browser of all the groups of customizable 
options"))
     (bindings--define-key menu [customize]
-      '(menu-item "Top-level Customization Group" customize
-                  :help "The master group called `Emacs'"))
+      '(menu-item "Top-level Emacs Customization Group" customize
+                  :help "Top-level groups of customizable options, and their 
descriptions"))
     (bindings--define-key menu [customize-themes]
       '(menu-item "Custom Themes" customize-themes
                   :help "Choose a pre-defined customization theme"))
@@ -2213,12 +2226,19 @@ otherwise it could decide to silently do nothing."
    ;; (Bug#8184).
    ((not (menu-bar-menu-frame-live-and-visible-p)))
    ((menu-bar-non-minibuffer-window-p)
-    (kill-buffer (current-buffer)))
+    (kill-buffer (current-buffer))
+    ;; Also close the current window if `menu-bar-close-windows' is
+    ;; set.
+    (when menu-bar-close-window
+      (ignore-errors (delete-window))))
    (t
     (abort-recursive-edit))))
 
 (defun kill-this-buffer-enabled-p ()
-  "Return non-nil if the `kill-this-buffer' menu item should be enabled."
+  "Return non-nil if the `kill-this-buffer' menu item should be enabled.
+It should be enabled there is at least one non-hidden buffer, or if
+`menu-bar-close-window' is non-nil and there is more than one window on
+this frame."
   (or (not (menu-bar-non-minibuffer-window-p))
       (let (found-1)
        ;; Instead of looping over entire buffer list, stop once we've
@@ -2228,7 +2248,9 @@ otherwise it could decide to silently do nothing."
            (unless (string-match-p "^ " (buffer-name buffer))
              (if (not found-1)
                  (setq found-1 t)
-               (throw 'found-2 t))))))))
+               (throw 'found-2 t))))))
+      (and menu-bar-close-window
+           (window-parent (selected-window)))))
 
 (put 'dired 'menu-enable '(menu-bar-non-minibuffer-window-p))
 
@@ -2708,20 +2730,25 @@ FROM-MENU-BAR, if non-nil, means we are dropping one of 
menu-bar's menus."
 POSITION can be an event, a posn- value, a value having the
 form ((XOFFSET YOFFSET) WINDOW), or nil.
 If nil, the current mouse position is used, or nil if there is no mouse."
-  (pcase position
+  (cond
     ;; nil -> mouse cursor position
-    ('nil
+    ((eq position nil)
      (let ((mp (mouse-pixel-position)))
        (list (list (cadr mp) (cddr mp)) (car mp))))
     ;; Value returned from `event-end' or `posn-at-point'.
-    ((pred posnp)
+    ((posnp position)
      (let ((xy (posn-x-y position)))
        (list (list (car xy) (cdr xy))
             (posn-window position))))
+    ;; `touchscreen-begin' or `touchscreen-end' event.
+    ((or (eq (car-safe position) 'touchscreen-begin)
+         (eq (car-safe position) 'touchscreen-end))
+     position)
     ;; Event.
-    ((pred eventp)
+    ((eventp position)
      (popup-menu-normalize-position (event-end position)))
-    (_ position)))
+    ;; Some other value.
+    (t position)))
 
 (defcustom tty-menu-open-use-tmm nil
   "If non-nil, \\[menu-bar-open] on a TTY will invoke `tmm-menubar'.
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 186a4753df1..35b359a75e2 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -973,10 +973,16 @@ Intended to be called via `clear-message-function'."
     (when (overlayp minibuffer-message-overlay)
       (delete-overlay minibuffer-message-overlay)
       (setq minibuffer-message-overlay nil)))
-
-  ;; Return nil telling the caller that the message
-  ;; should be also handled by the caller.
-  nil)
+  ;; Don't clear the message if touch screen drag-to-select is in
+  ;; progress, because a preview message might currently be displayed
+  ;; in the echo area.  FIXME: find some way to place this in
+  ;; touch-screen.el.
+  (if (and touch-screen-preview-select
+           (eq (nth 3 touch-screen-current-tool) 'drag))
+      'dont-clear-message
+    ;; Return nil telling the caller that the message
+    ;; should be also handled by the caller.
+    nil))
 
 (setq clear-message-function 'clear-minibuffer-message)
 
@@ -2957,7 +2963,10 @@ For customizing this mode, it is better to use
 `minibuffer-setup-hook' and `minibuffer-exit-hook' rather than
 the mode hook of this mode."
   :syntax-table nil
-  :interactive nil)
+  :interactive nil
+  ;; Enable text conversion, but always make sure `RET' does
+  ;; something.
+  (setq text-conversion-style 'action))
 
 ;;; Completion tables.
 
@@ -4612,6 +4621,59 @@ is included in the return value."
                   default)))
    ": "))
 
+
+;;; On screen keyboard support.
+;; Try to display the on screen keyboard whenever entering the
+;; mini-buffer, and hide it whenever leaving.
+
+(defvar minibuffer-on-screen-keyboard-timer nil
+  "Timer run upon exiting the minibuffer.
+It will hide the on screen keyboard when necessary.")
+
+(defvar minibuffer-on-screen-keyboard-displayed nil
+  "Whether or not the on-screen keyboard has been displayed.
+Set inside `minibuffer-setup-on-screen-keyboard'.")
+
+(defun minibuffer-setup-on-screen-keyboard ()
+  "Maybe display the on-screen keyboard in the current frame.
+Display the on-screen keyboard in the current frame if the
+last device to have sent an input event is not a keyboard.
+This is run upon minibuffer setup."
+  ;; Don't hide the on screen keyboard later on.
+  (when minibuffer-on-screen-keyboard-timer
+    (cancel-timer minibuffer-on-screen-keyboard-timer)
+    (setq minibuffer-on-screen-keyboard-timer nil))
+  (setq minibuffer-on-screen-keyboard-displayed nil)
+  (when (and (framep last-event-frame)
+             (not (memq (device-class last-event-frame
+                                      last-event-device)
+                        '(keyboard core-keyboard))))
+    (setq minibuffer-on-screen-keyboard-displayed
+          (frame-toggle-on-screen-keyboard (selected-frame) nil))))
+
+(defun minibuffer-exit-on-screen-keyboard ()
+  "Hide the on-screen keyboard if it was displayed.
+Hide the on-screen keyboard in a timer set to run in 0.1 seconds.
+It will be cancelled if the minibuffer is displayed again within
+that timeframe.
+
+Do not hide the on screen keyboard inside a recursive edit.
+Likewise, do not hide the on screen keyboard if point in the
+window that will be selected after exiting the minibuffer is not
+on read-only text.
+
+The latter is implemented in `touch-screen.el'."
+  (unless (or (not minibuffer-on-screen-keyboard-displayed)
+              (> (recursion-depth) 1))
+    (when minibuffer-on-screen-keyboard-timer
+      (cancel-timer minibuffer-on-screen-keyboard-timer))
+    (setq minibuffer-on-screen-keyboard-timer
+          (run-with-timer 0.1 nil #'frame-toggle-on-screen-keyboard
+                          (selected-frame) t))))
+
+(add-hook 'minibuffer-setup-hook #'minibuffer-setup-on-screen-keyboard)
+(add-hook 'minibuffer-exit-hook #'minibuffer-exit-on-screen-keyboard)
+
 (provide 'minibuffer)
 
 ;;; minibuffer.el ends here
diff --git a/lisp/mouse.el b/lisp/mouse.el
index 3c30361ad7d..11a2db52eec 100644
--- a/lisp/mouse.el
+++ b/lisp/mouse.el
@@ -206,8 +206,16 @@ always return a positive integer or zero."
 
 ;; Provide a mode-specific menu on a mouse button.
 
-(defun minor-mode-menu-from-indicator (indicator)
+(defun minor-mode-menu-from-indicator (indicator &optional window event)
   "Show menu for minor mode specified by INDICATOR.
+
+INDICATOR is either a string object returned by `posn-object' or
+the car of such an object.  WINDOW may be the window whose mode
+line is being displayed.
+
+EVENT may be the mouse event that is causing this menu to be
+displayed.
+
 Interactively, INDICATOR is read using completion.
 If there is no menu defined for the minor mode, then create one with
 items `Turn Off' and `Help'."
@@ -215,7 +223,44 @@ items `Turn Off' and `Help'."
    (list (completing-read
          "Minor mode indicator: "
          (describe-minor-mode-completion-table-for-indicator))))
-  (let* ((minor-mode (lookup-minor-mode-from-indicator indicator))
+  ;; If INDICATOR is a string object, WINDOW is set, and
+  ;; `mode-line-compact' might be enabled, find a string in
+  ;; `minor-mode-alist' that is present within the INDICATOR and whose
+  ;; extents within INDICATOR contain the position of the object
+  ;; within the string.
+  (when window
+    (catch 'found
+      (with-selected-window window
+        (let ((alist minor-mode-alist) string position)
+          (when (and (consp indicator) mode-line-compact)
+            (with-temp-buffer
+              (insert (car indicator))
+              (dolist (menu alist)
+                ;; If this is a valid minor mode menu entry,
+                (when (and (consp menu)
+                           (setq string (format-mode-line (cadr menu)
+                                                          nil window))
+                           (> (length string) 0))
+                  ;; Start searching for an appearance of (cdr menu).
+                  (goto-char (point-min))
+                  (while (search-forward string nil 0)
+                    ;; If the position of the string object is
+                    ;; contained within, set indicator to the minor
+                    ;; mode in question.
+                    (setq position (1+ (cdr indicator)))
+                    (and (>= position (match-beginning 0))
+                         (<= position (match-end 0))
+                         (setq indicator (car menu))
+                         (throw 'found nil)))))))))))
+  ;; If INDICATOR is still a cons, use its car.
+  (when (consp indicator)
+    (setq indicator (car indicator)))
+  (let* ((minor-mode (if (symbolp indicator)
+                         ;; indicator being set to a symbol means that
+                         ;; the loop above has already found a
+                         ;; matching minor mode.
+                         indicator
+                       (lookup-minor-mode-from-indicator indicator)))
          (mm-fun (or (get minor-mode :minor-mode-function) minor-mode)))
     (unless minor-mode (error "Cannot find minor mode for `%s'" indicator))
     (let* ((map (cdr-safe (assq minor-mode minor-mode-map-alist)))
@@ -234,14 +279,19 @@ items `Turn Off' and `Help'."
                           ,(lambda () (interactive)
                              (describe-function mm-fun)))))))
       (if menu
-          (popup-menu menu)
+          (popup-menu menu event)
         (message "No menu available")))))
 
 (defun mouse-minor-mode-menu (event)
   "Show minor-mode menu for EVENT on minor modes area of the mode line."
   (interactive "@e")
-  (let ((indicator (car (nth 4 (car (cdr event))))))
-    (minor-mode-menu-from-indicator indicator)))
+  (let* ((posn (event-start event))
+         (indicator (posn-object posn))
+         (window (posn-window posn)))
+    (minor-mode-menu-from-indicator indicator window event)))
+
+;; See (elisp)Touchscreen Events.
+(put 'mouse-minor-mode-menu 'mouse-1-menu-command t)
 
 (defun mouse-menu-major-mode-map ()
   (run-hooks 'activate-menubar-hook 'menu-bar-update-hook)
diff --git a/lisp/mwheel.el b/lisp/mwheel.el
index caa74159ecd..86ed7393a17 100644
--- a/lisp/mwheel.el
+++ b/lisp/mwheel.el
@@ -58,7 +58,8 @@
 
 (defcustom mouse-wheel-down-event
   (if (or (featurep 'w32-win) (featurep 'ns-win)
-          (featurep 'haiku-win) (featurep 'pgtk-win))
+          (featurep 'haiku-win) (featurep 'pgtk-win)
+          (featurep 'android-win))
       'wheel-up
     'mouse-4)
   "Event used for scrolling down."
@@ -79,7 +80,8 @@
 
 (defcustom mouse-wheel-up-event
   (if (or (featurep 'w32-win) (featurep 'ns-win)
-          (featurep 'haiku-win) (featurep 'pgtk-win))
+          (featurep 'haiku-win) (featurep 'pgtk-win)
+          (featurep 'android-win))
       'wheel-down
     'mouse-5)
   "Event used for scrolling up."
@@ -254,7 +256,8 @@ Also see `mouse-wheel-tilt-scroll'."
 
 (defvar mouse-wheel-left-event
   (if (or (featurep 'w32-win) (featurep 'ns-win)
-          (featurep 'haiku-win) (featurep 'pgtk-win))
+          (featurep 'haiku-win) (featurep 'pgtk-win)
+          (featurep 'android-win))
       'wheel-left
     'mouse-6)
   "Event used for scrolling left.")
@@ -268,7 +271,8 @@ Also see `mouse-wheel-tilt-scroll'."
 
 (defvar mouse-wheel-right-event
   (if (or (featurep 'w32-win) (featurep 'ns-win)
-          (featurep 'haiku-win) (featurep 'pgtk-win))
+          (featurep 'haiku-win) (featurep 'pgtk-win)
+          (featurep 'android-win))
       'wheel-right
     'mouse-7)
   "Event used for scrolling right.")
diff --git a/lisp/net/ange-ftp.el b/lisp/net/ange-ftp.el
index 16ec33f92dc..3d64b7976b3 100644
--- a/lisp/net/ange-ftp.el
+++ b/lisp/net/ange-ftp.el
@@ -4233,7 +4233,7 @@ directory, so that Emacs will know its current contents."
         (host (nth 0 parsed))
         (user (nth 1 parsed))
         (localname (nth 2 parsed)))
-    (and (or (not connected)
+    (and (or (memq connected '(nil never))
             (let ((proc (get-process (ange-ftp-ftp-process-buffer host user))))
               (and proc (processp proc)
                    (memq (process-status proc) '(run open)))))
diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el
index 39513b8f602..daa46baf8f2 100644
--- a/lisp/net/browse-url.el
+++ b/lisp/net/browse-url.el
@@ -52,6 +52,7 @@
 ;; browse-url-xdg-open                freedesktop.org xdg-open
 ;; browse-url-kde                     KDE konqueror (kfm)
 ;; browse-url-elinks                  Elinks      Don't know (tried with 
0.12.GIT)
+;; browse-url-default-android-browser Android     2.3.3 (should work on 2.2 
too)
 ;; eww-browse-url                     Emacs Web Wowser
 
 ;; Browsers can cache web pages so it may be necessary to tell them to
@@ -173,6 +174,9 @@
     ,@(when (eq system-type 'darwin)
         (list '(function-item :tag "Default macOS browser"
                               :value browse-url-default-macosx-browser)))
+    ,@(when (eq system-type 'android)
+        (list '(function-item :tag "Default Android browser"
+                              :value browse-url-default-android-browser)))
     (function-item :tag "Default browser"
                   :value browse-url-default-browser)
     (function :tag "Your own function")
@@ -1064,6 +1068,8 @@ instead of `browse-url-new-window-flag'."
      'browse-url-default-macosx-browser)
     ((featurep 'haiku)
      'browse-url-default-haiku-browser)
+    ((eq system-type 'android)
+     'browse-url-default-android-browser)
     ((browse-url-can-use-xdg-open) 'browse-url-xdg-open)
 ;;;    ((executable-find browse-url-gnome-moz-program) 'browse-url-gnome-moz)
     ((executable-find browse-url-firefox-program) 'browse-url-firefox)
@@ -1301,6 +1307,35 @@ Default to the URL around or before point."
 (function-put 'browse-url-default-haiku-browser
               'browse-url-browser-kind 'external)
 
+(defcustom browse-url-android-share nil
+  "If non-nil, share URLs instead of opening them.
+When non-nil, `browse-url-default-android-browser' will try to
+share the URL being browsed through programs such as mail clients
+and instant messengers instead of opening it in a web browser."
+  :type 'boolean
+  :version "30.1")
+
+(declare-function android-browse-url "androidselect.c")
+
+;;;###autoload
+(defun browse-url-default-android-browser (url &optional _new-window)
+  "Browse URL with the system default browser.
+If `browse-url-android-share' is non-nil, try to share URL using
+an external program instead.  Default to the URL around or before
+point."
+  (interactive (browse-url-interactive-arg "URL: "))
+  (unless browse-url-android-share
+    ;; The URL shouldn't be encoded if it's being shared through
+    ;; another program.
+    (setq url (browse-url-encode-url url)))
+  ;; Make sure the URL starts with an appropriate scheme.
+  (unless (string-match "\\(.+\\):/" url)
+    (setq url (concat "http://"; url)))
+  (android-browse-url url browse-url-android-share))
+
+(function-put 'browse-url-default-android-browser
+              'browse-url-browser-kind 'external)
+
 ;;;###autoload
 (defun browse-url-emacs (url &optional same-window)
   "Ask Emacs to load URL into a buffer and show it in another window.
diff --git a/lisp/net/dictionary.el b/lisp/net/dictionary.el
index 8d81b3ec9d8..ca706c3c6e9 100644
--- a/lisp/net/dictionary.el
+++ b/lisp/net/dictionary.el
@@ -23,18 +23,18 @@
 ;;; Commentary:
 
 ;; dictionary allows you to interact with dictionary servers.
-;; Use M-x customize-group dictionary to modify user settings.
+;;
+;; Use `M-x customize-group RET dictionary RET' to modify user settings.
 ;;
 ;; Main commands for interaction are:
-;; M-x dictionary        - opens a new dictionary buffer
-;; M-x dictionary-search - search for the definition of a word
+;; `M-x dictionary'        - open a new dictionary buffer
+;; `M-x dictionary-search' - search for the definition of a word
 ;;
 ;; You can find more information in the README file of the GitHub
 ;; repository https://github.com/myrkr/dictionary-el
 
 ;;; Code:
 
-(require 'cl-lib)
 (require 'custom)
 (require 'dictionary-connection)
 (require 'button)
@@ -409,20 +409,10 @@ Otherwise, `dictionary-search' displays definitions in a 
*Dictionary* buffer."
   nil
   "The current network connection.")
 
-(defvar dictionary-instances
-  0
-  "The number of open dictionary buffers.")
-
 (defvar dictionary-marker
   nil
   "Stores the point position while buffer display.")
 
-(defvar dictionary-color-support
-  (condition-case nil
-      (display-color-p)
-    (error nil))
-  "Determines if the Emacs has support to display color.")
-
 (defvar dictionary-word-history
   '()
   "History list of searched word.")
@@ -434,47 +424,35 @@ Otherwise, `dictionary-search' displays definitions in a 
*Dictionary* buffer."
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 ;;;###autoload
-(defun dictionary-mode ()
-  ;; FIXME: Use define-derived-mode.
+(define-derived-mode dictionary-mode special-mode "Dictionary"
   "Mode for searching a dictionary.
+
 This is a mode for searching a dictionary server implementing the
 protocol defined in RFC 2229.
 
 This is a quick reference to this mode describing the default key bindings:
 \\<dictionary-mode-map>
-* \\[dictionary-close] close the dictionary buffer
-* \\[describe-mode] display this help information
-* \\[dictionary-search] ask for a new word to search
-* \\[dictionary-lookup-definition] search the word at point
-* \\[forward-button] or TAB place point to the next link
-* \\[backward-button] or S-TAB place point to the prev link
-
-* \\[dictionary-match-words] ask for a pattern and list all matching words.
-* \\[dictionary-select-dictionary] select the default dictionary
-* \\[dictionary-select-strategy] select the default search strategy
-
-* \\`RET' or \\`<mouse-2>' visit that link"
-
-  (unless (eq major-mode 'dictionary-mode)
-    (cl-incf dictionary-instances))
-
-  (kill-all-local-variables)
+ \\[dictionary-close]  close the dictionary buffer
+ \\[describe-mode]     display this help
+ \\[dictionary-search] ask for a new word to search
+ \\[dictionary-lookup-definition]      search for word at point
+ \\[forward-button] or \\`TAB' move point to the next link
+ \\[backward-button] or \\`S-TAB'      move point to the previous link
+
+ \\[dictionary-match-words]    ask for a pattern and list all matching words
+ \\[dictionary-select-dictionary]      select the default dictionary
+ \\[dictionary-select-strategy]        select the default search strategy
+
+ \\`RET'       visit link at point
+ \\`<mouse-2>' visit clicked link"
   (buffer-disable-undo)
-  (use-local-map dictionary-mode-map)
-  (setq major-mode 'dictionary-mode)
-  (setq mode-name "Dictionary")
-
   (setq-local dictionary-data-stack nil)
   (setq-local dictionary-position-stack nil)
-
   (make-local-variable 'dictionary-current-data)
   (make-local-variable 'dictionary-positions)
-
   (make-local-variable 'dictionary-default-dictionary)
   (make-local-variable 'dictionary-default-strategy)
-
-  (add-hook 'kill-buffer-hook #'dictionary-close t t)
-  (run-hooks 'dictionary-mode-hook))
+  (add-hook 'kill-buffer-hook #'dictionary-close t t))
 
 ;;;###autoload
 (defun dictionary ()
@@ -598,16 +576,15 @@ The connection takes the proxy setting in customization 
group
 (defun dictionary-close (&rest _ignored)
   "Close the current dictionary buffer and its connection."
   (interactive)
-  (if (eq major-mode 'dictionary-mode)
-      (progn
-       (setq major-mode nil)
-       (if (<= (cl-decf dictionary-instances) 0)
-           (dictionary-connection-close dictionary-connection))
-       (let ((configuration dictionary-window-configuration)
-             (selected-window dictionary-selected-window))
-         (kill-buffer (current-buffer))
-         (set-window-configuration configuration)
-         (select-window selected-window)))))
+  (when (derived-mode-p 'dictionary-mode)
+    (setq major-mode nil)
+    (if (<= (length (match-buffers '(derived-mode . dictionary-mode))) 0)
+        (dictionary-connection-close dictionary-connection))
+    (let ((configuration dictionary-window-configuration)
+          (selected-window dictionary-selected-window))
+      (kill-buffer (current-buffer))
+      (set-window-configuration configuration)
+      (select-window selected-window))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Helpful functions
@@ -900,7 +877,7 @@ them with buttons to perform a new search."
        (if (search-forward-regexp regexp nil t)
            (let ((match-start (match-beginning 2))
                  (match-end (match-end 2)))
-             (if dictionary-color-support
+              (if (display-color-p)
                  ;; Compensate for the replacement
                  (let ((brace-match-length (- (match-end 1)
                                               (match-beginning 1))))
@@ -1222,9 +1199,12 @@ If PATTERN is omitted, it defaults to \"[ 
\\f\\t\\n\\r\\v]+\"."
 
 ;;;###autoload
 (defun dictionary-search (word &optional dictionary)
-  "Search the WORD in DICTIONARY if given or in all if nil.
-It presents the selection or word at point as default input and
-allows editing it."
+  "Search for WORD in all the known dictionaries.
+Interactively, prompt for WORD, and offer the word at point as default.
+
+Optional argument DICTIONARY means restrict the search to only
+that one dictionary.  Interactively, with prefix argument,
+prompt for DICTIONARY."
   (interactive
    (let ((dict
           (if current-prefix-arg
@@ -1559,5 +1539,9 @@ Further arguments are currently ignored."
                             (match-string 1)
                             dictionary))))))
 
+(defvar dictionary-color-support (display-color-p)
+  "Determines if the Emacs has support to display color.")
+(make-obsolete-variable 'dictionary-color-support 'display-color-p "30.1")
+
 (provide 'dictionary)
 ;;; dictionary.el ends here
diff --git a/lisp/net/dns.el b/lisp/net/dns.el
index 1e320a2124a..42e7fb415d3 100644
--- a/lisp/net/dns.el
+++ b/lisp/net/dns.el
@@ -212,7 +212,7 @@ If TCP-P, the first two bytes of the packet will be the 
length field."
                 spec))
         (push (list 'authoritative-p (if (zerop (logand byte (ash 1 2)))
                                          nil t)) spec)
-        (push (list 'truncated-p (if (zerop (logand byte (ash 1 2))) nil t))
+        (push (list 'truncated-p (if (zerop (logand byte (ash 1 1))) nil t))
               spec)
         (push (list 'recursion-desired-p
                     (if (zerop (logand byte (ash 1 0))) nil t)) spec))
diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index 2e743751427..089e481ead2 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -249,7 +249,7 @@ parameter, and should return the (possibly) transformed 
URL."
   :version "29.1")
 
 (defface eww-form-submit
-  '((((type x w32 ns haiku pgtk) (class color))        ; Like default mode line
+  '((((type x w32 ns haiku pgtk android) (class color))        ; Like default 
mode line
      :box (:line-width 2 :style released-button)
      :background "#808080" :foreground "black"))
   "Face for eww buffer buttons."
@@ -257,7 +257,7 @@ parameter, and should return the (possibly) transformed 
URL."
   :group 'eww)
 
 (defface eww-form-file
-  '((((type x w32 ns haiku pgtk) (class color))        ; Like default mode line
+  '((((type x w32 ns haiku pgtk android) (class color))        ; Like default 
mode line
      :box (:line-width 2 :style released-button)
      :background "#808080" :foreground "black"))
   "Face for eww buffer buttons."
@@ -265,7 +265,7 @@ parameter, and should return the (possibly) transformed 
URL."
   :group 'eww)
 
 (defface eww-form-checkbox
-  '((((type x w32 ns haiku pgtk) (class color))        ; Like default mode line
+  '((((type x w32 ns haiku pgtk android) (class color))        ; Like default 
mode line
      :box (:line-width 2 :style released-button)
      :background "lightgrey" :foreground "black"))
   "Face for eww buffer buttons."
@@ -273,7 +273,7 @@ parameter, and should return the (possibly) transformed 
URL."
   :group 'eww)
 
 (defface eww-form-select
-  '((((type x w32 ns haiku pgtk) (class color))        ; Like default mode line
+  '((((type x w32 ns haiku pgtk android) (class color))        ; Like default 
mode line
      :box (:line-width 2 :style released-button)
      :background "lightgrey" :foreground "black"))
   "Face for eww buffer buttons."
@@ -542,24 +542,35 @@ for the search engine used."
           (call-interactively #'eww)))
     (call-interactively #'eww)))
 
-(defun eww-open-in-new-buffer ()
-  "Fetch link at point in a new EWW buffer."
-  (interactive)
-  (let ((url (eww-suggested-uris)))
-    (if (null url) (user-error "No link at point")
-      (when (or (eq eww-browse-url-new-window-is-tab t)
-                (and (eq eww-browse-url-new-window-is-tab 'tab-bar)
-                     tab-bar-mode))
-        (let ((tab-bar-new-tab-choice t))
-          (tab-new)))
-      ;; clone useful to keep history, but
-      ;; should not clone from non-eww buffer
-      (with-current-buffer
-          (if (eq major-mode 'eww-mode) (clone-buffer)
-            (generate-new-buffer "*eww*"))
-        (unless (equal url (eww-current-url))
-          (eww-mode)
-          (eww (if (consp url) (car url) url)))))))
+(defun eww--open-url-in-new-buffer (url)
+  "Open the URL in a new EWW buffer."
+  ;; Clone is useful to keep history, but we
+  ;; should not clone from a non-eww buffer.
+  (with-current-buffer
+      (if (eq major-mode 'eww-mode) (clone-buffer)
+        (generate-new-buffer "*eww*"))
+    (unless (equal url (eww-current-url))
+      (eww-mode)
+      (eww (if (consp url) (car url) url)))))
+
+(defun eww-open-in-new-buffer (&optional no-select url)
+  "Fetch URL (interactively, the link at point) into a new EWW buffer.
+
+NO-SELECT non-nil means do not make the new buffer the current buffer."
+  (interactive "P")
+  (if-let ((url (or url (eww-suggested-uris))))
+      (if (or (eq eww-browse-url-new-window-is-tab t)
+              (and (eq eww-browse-url-new-window-is-tab 'tab-bar)
+                   tab-bar-mode))
+          (let ((tab-bar-new-tab-choice t))
+            (tab-new)
+            (eww--open-url-in-new-buffer url)
+            (when no-select
+              (tab-bar-switch-to-recent-tab)))
+        (if no-select
+            (save-window-excursion (eww--open-url-in-new-buffer url))
+          (eww--open-url-in-new-buffer url)))
+    (user-error "No link at point")))
 
 (defun eww-html-p (content-type)
   "Return non-nil if CONTENT-TYPE designates an HTML content type.
diff --git a/lisp/net/imap.el b/lisp/net/imap.el
index cfb92674d8a..7de847e2a59 100644
--- a/lisp/net/imap.el
+++ b/lisp/net/imap.el
@@ -1833,7 +1833,7 @@ on failure."
 
 (defun imap-send-command (command &optional buffer)
   (with-current-buffer (or buffer (current-buffer))
-    (if (not (listp command)) (setq command (list command)))
+    (setq command (ensure-list command))
     (let ((tag (setq imap-tag (1+ imap-tag)))
          cmd cmdstr)
       (setq cmdstr (concat (number-to-string imap-tag) " "))
diff --git a/lisp/net/newst-backend.el b/lisp/net/newst-backend.el
index a68a6bf1a24..31dc8d8e177 100644
--- a/lisp/net/newst-backend.el
+++ b/lisp/net/newst-backend.el
@@ -81,15 +81,15 @@ considered to be running if the newsticker timer list is 
not empty."
     ("The Register"
      "https://www.theregister.co.uk/headlines.rss";)
     ("slashdot"
-     "http://rss.slashdot.org/Slashdot/slashdot";
+     "https://rss.slashdot.org/Slashdot/slashdot";
      nil
      3600)                        ;/. will ban you if under 3600 seconds!
     ("Wired News"
      "https://www.wired.com/feed/rss";)
     ("Heise News (german)"
-     "http://www.heise.de/newsticker/heise.rdf";)
+     "https://www.heise.de/newsticker/heise.rdf";)
     ("Tagesschau (german)"
-     "http://www.tagesschau.de/newsticker.rdf";
+     "https://www.tagesschau.de/newsticker.rdf";
      nil
      1800))
   "Default URL list in raw form.
@@ -1168,7 +1168,7 @@ URL 
`http://www.atompub.org/2005/08/17/draft-ietf-atompub-format-11.html'"
                       ;; allows for integrating (x)html into the atom
                       ;; structure but we need the raw html string.
                       ;; e.g. https://www.heise.de/open/news/news-atom.xml
-                      ;; http://feeds.feedburner.com/ru_nix_blogs
+                      ;; https://feeds.feedburner.com/ru_nix_blogs
                       (or (newsticker--unxml
                            (car (xml-node-children
                                  (car (xml-get-children node 'content)))))
@@ -1302,7 +1302,7 @@ For the RSS 0.92 specification see URL 
`http://backend.userland.com/rss092'."
 Return value as well as arguments NAME, TIME, and TOPNODE are the
 same as in `newsticker--parse-atom-1.0'.
 
-For the RSS 1.0 specification see URL `http://web.resource.org/rss/1.0/spec'."
+For the RSS 1.0 specification see URL `https://web.resource.org/rss/1.0/spec'."
   (newsticker--debug-msg "Parsing RSS 1.0 feed %s" name)
   (let* ((channelnode (car (xml-get-children topnode 'channel)))
          is-new-feed has-new-items)
@@ -1361,7 +1361,7 @@ For the RSS 1.0 specification see URL 
`http://web.resource.org/rss/1.0/spec'."
 Return value as well as arguments NAME, TIME, and TOPNODE are the
 same as in `newsticker--parse-atom-1.0'.
 
-For the RSS 2.0 specification see URL `http://blogs.law.harvard.edu/tech/rss'."
+For the RSS 2.0 specification see URL `https://cyber.harvard.edu/rss/'."
   (newsticker--debug-msg "Parsing RSS 2.0 feed %s" name)
   (let* ((channelnode (car (xml-get-children topnode 'channel)))
          is-new-feed has-new-items)
diff --git a/lisp/net/newst-plainview.el b/lisp/net/newst-plainview.el
index b284ca84bfc..55fa19cbf2a 100644
--- a/lisp/net/newst-plainview.el
+++ b/lisp/net/newst-plainview.el
@@ -1175,9 +1175,7 @@ The mode-line is changed accordingly."
 
 (defun newsticker--buffer-redraw ()
   "Redraw the newsticker window."
-  (if (fboundp 'force-window-update)
-      (force-window-update (current-buffer))
-    (redraw-frame))
+  (force-window-update (current-buffer))
   (run-hooks 'newsticker-buffer-change-hook)
   (sit-for 0))
 
diff --git a/lisp/net/newsticker.el b/lisp/net/newsticker.el
index 938683a12a1..c86174b6d8e 100644
--- a/lisp/net/newsticker.el
+++ b/lisp/net/newsticker.el
@@ -47,9 +47,9 @@
 ;;  * RSS 0.92
 ;;    (see http://backend.userland.com/rss092)
 ;;  * RSS 1.0
-;;    (see http://purl.org/rss/1.0/spec)
+;;    (see https://web.resource.org/rss/1.0/spec)
 ;;  * RSS 2.0
-;;    (see http://blogs.law.harvard.edu/tech/rss)
+;;    (see https://cyber.harvard.edu/rss/)
 ;; as well as the following Atom formats:
 ;;  * Atom 0.3
 ;;  * Atom 1.0
diff --git a/lisp/net/rcirc.el b/lisp/net/rcirc.el
index 0ee52d8ef6c..a6dad4b640d 100644
--- a/lisp/net/rcirc.el
+++ b/lisp/net/rcirc.el
@@ -2059,7 +2059,7 @@ connection."
                              (point-min)))
               (when (let ((then (get-text-property (point) 'rcirc-time)))
                       (and then (not (time-less-p time then))))
-                (next-single-property-change (point) 'hard)
+                (goto-char (next-single-property-change (point) 'hard))
                 (forward-char 1)
                 (throw 'exit nil))))
           (goto-char (line-beginning-position))
diff --git a/lisp/net/shr.el b/lisp/net/shr.el
index 9b19a1b3980..b5bb7b42650 100644
--- a/lisp/net/shr.el
+++ b/lisp/net/shr.el
@@ -216,15 +216,18 @@ temporarily blinks with this face."
   "Face for <h3> elements."
   :version "28.1")
 
-(defface shr-h4 nil
+(defface shr-h4
+  '((t (:inherit default)))
   "Face for <h4> elements."
   :version "28.1")
 
-(defface shr-h5 nil
+(defface shr-h5
+  '((t (:inherit default)))
   "Face for <h5> elements."
   :version "28.1")
 
-(defface shr-h6 nil
+(defface shr-h6
+  '((t (:inherit default)))
   "Face for <h6> elements."
   :version "28.1")
 
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index 2b5369ea3b5..3de4721ec77 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -209,8 +209,10 @@ It is used for TCP/IP devices."
 First arg specifies the OPERATION, second arg is a list of
 arguments to pass to the OPERATION."
   (if-let ((fn (assoc operation tramp-adb-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (tramp--with-startup
@@ -273,7 +275,7 @@ arguments to pass to the OPERATION."
   (with-current-buffer (tramp-get-buffer vec)
     (goto-char (point-min))
     (let (file-properties)
-      (while (re-search-forward tramp-adb-ls-toolbox-regexp nil t)
+      (while (search-forward-regexp tramp-adb-ls-toolbox-regexp nil t)
        (let* ((mod-string (match-string 1))
               (is-dir (eq ?d (aref mod-string 0)))
               (is-symlink (eq ?l (aref mod-string 0)))
@@ -319,7 +321,7 @@ arguments to pass to the OPERATION."
                       (tramp-shell-quote-argument localname)))
        ;; We insert also filename/. and filename/.., because "ls"
        ;; doesn't on some file systems, like "sdcard".
-       (unless (re-search-backward (rx "." eol) nil t)
+       (unless (search-backward-regexp (rx "." eol) nil t)
          (narrow-to-region (point-max) (point-max))
          (tramp-adb-send-command
           v (format "%s -d -a -l %s %s | cat"
@@ -509,9 +511,9 @@ Emacs dired can't find files."
   (with-parsed-tramp-file-name (expand-file-name filename) nil
     (with-tramp-file-property v localname "file-writable-p"
       (if (file-exists-p filename)
+         ;; Examine `file-attributes' cache to see if request can be
+         ;; satisfied without remote operation.
          (if (tramp-file-property-p v localname "file-attributes")
-             ;; Examine `file-attributes' cache to see if request can
-             ;; be satisfied without remote operation.
              (tramp-check-cached-permissions v ?w)
            (tramp-adb-send-command-and-check
             v (format "test -w %s" (tramp-shell-quote-argument localname))))
@@ -536,6 +538,8 @@ Emacs dired can't find files."
          (set-file-modes tmpfile (logior (or (file-modes tmpfile) 0) #o0600)))
        (let (create-lockfiles)
           (write-region start end tmpfile append 'no-message))
+       ;; Now, `last-coding-system-used' has the right value.  Remember it.
+       (setq coding-system-used last-coding-system-used)
        (with-tramp-progress-reporter
            v 3 (format-message
                 "Moving tmp file `%s' to `%s'" tmpfile filename)
@@ -919,7 +923,7 @@ implementation will be used."
 
            (when (string-match-p (rx multibyte) command)
              (tramp-error
-              v 'file-error "Cannot apply multi-byte command `%s'" command))
+              v 'file-error "Cannot apply multibyte command `%s'" command))
 
            (while (get-process name1)
              ;; NAME must be unique as process name.
@@ -1142,7 +1146,7 @@ error and non-nil on success."
          ;; There must be a better solution by setting the correct coding
          ;; system, but this requires changes in core Tramp.
          (goto-char (point-min))
-         (while (re-search-forward (rx (+ "\r") eol) nil t)
+         (while (search-forward-regexp (rx (+ "\r") eol) nil t)
            (replace-match "" nil nil)))))))
 
 (defun tramp-adb-send-command-and-check (vec command &optional exit-status)
@@ -1186,12 +1190,12 @@ FMT and ARGS are passed to `error'."
          (let ((inhibit-read-only t))
            (goto-char (point-min))
            ;; ADB terminal sends "^H" sequences.
-           (when (re-search-forward (rx "<" (+ "\b")) (line-end-position) t)
+           (when (search-forward-regexp (rx "<" (+ "\b")) (line-end-position) 
t)
              (forward-line 1)
              (delete-region (point-min) (point)))
            ;; Delete the prompt.
             (goto-char (point-min))
-            (when (re-search-forward prompt (line-end-position) t)
+            (when (search-forward-regexp prompt (line-end-position) t)
               (forward-line 1)
               (delete-region (point-min) (point)))
            (when (tramp-search-regexp prompt)
@@ -1211,102 +1215,106 @@ connection if a previous connection has died for some 
reason."
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  (let* ((buf (tramp-get-connection-buffer vec))
-        (p (get-buffer-process buf))
-        (host (tramp-file-name-host vec))
-        (user (tramp-file-name-user vec))
-         (device (tramp-adb-get-device vec)))
-
-    ;; Maybe we know already that "su" is not supported.  We cannot
-    ;; use a connection property, because we have not checked yet
-    ;; whether it is still the same device.
-    (when (and user (not (tramp-get-file-property vec "/" "su-command-p" t)))
-      (tramp-error vec 'file-error "Cannot switch to user `%s'" user))
-
-    (unless (process-live-p p)
-      (save-match-data
-       (when (and p (processp p)) (delete-process p))
-       (if (tramp-string-empty-or-nil-p device)
-           (tramp-error vec 'file-error "Device %s not connected" host))
-       (with-tramp-progress-reporter vec 3 "Opening adb shell connection"
-         (let* ((coding-system-for-read 'utf-8-dos) ; Is this correct?
-                (process-connection-type tramp-process-connection-type)
-                (args (tramp-expand-args
-                       vec 'tramp-login-args ?d (or device "")))
-                (p (let ((default-directory
-                           tramp-compat-temporary-file-directory))
-                     (apply #'start-process (tramp-get-connection-name vec) buf
-                            tramp-adb-program args)))
-                (prompt (md5 (concat (prin1-to-string process-environment)
-                                     (current-time-string)))))
-           ;; Wait for initial prompt.  On some devices, it needs an
-           ;; initial RET, in order to get it.
-            (sleep-for 0.1)
-           (tramp-send-string vec tramp-rsh-end-of-line)
-           (tramp-adb-wait-for-output p 30)
-           (unless (process-live-p p)
-             (tramp-error vec 'file-error "Terminated!"))
-
-           ;; Set sentinel.  Initialize variables.
-           (set-process-sentinel p #'tramp-process-sentinel)
-           (tramp-post-process-creation p vec)
-
-           ;; Set connection-local variables.
-           (tramp-set-connection-local-variables vec)
-
-           ;; Change prompt.
-           (tramp-set-connection-property
-            p "prompt" (rx "///" (literal prompt) "#$"))
-           (tramp-adb-send-command
-            vec (format "PS1=\"///\"\"%s\"\"#$\"" prompt))
-
-           ;; Disable line editing.
-           (tramp-adb-send-command
-            vec "set +o vi +o vi-esccomplete +o vi-tabcomplete +o emacs")
-
-           ;; Dump option settings in the traces.
-           (when (>= tramp-verbose 9)
-             (tramp-adb-send-command vec "set -o"))
-
-           ;; Check whether the properties have been changed.  If
-           ;; yes, this is a strong indication that we must expire all
-           ;; connection properties.  We start again.
-           (tramp-message vec 5 "Checking system information")
-           (tramp-adb-send-command
-            vec
-            (concat
-             "echo \\\"`getprop ro.product.model` "
-             "`getprop ro.product.version` "
-             "`getprop ro.build.version.release`\\\""))
-           (let ((old-getprop (tramp-get-connection-property vec "getprop"))
-                 (new-getprop
-                  (tramp-set-connection-property
-                   vec "getprop"
-                   (with-current-buffer (tramp-get-connection-buffer vec)
-                     ;; Read the expression.
-                     (goto-char (point-min))
-                     (read (current-buffer))))))
-             (when (and (stringp old-getprop)
-                        (not (string-equal old-getprop new-getprop)))
-               (tramp-message
-                vec 3
-                "Connection reset, because remote host changed from `%s' to 
`%s'"
-                old-getprop new-getprop)
-               (tramp-cleanup-connection vec t)
-               (tramp-adb-maybe-open-connection vec)))
-
-           ;; Change user if indicated.
-           (when user
-             (tramp-adb-send-command vec (format "su %s" user))
-             (unless (tramp-adb-send-command-and-check vec nil)
-               (delete-process p)
-               ;; Do not flush, we need the nil value.
-               (tramp-set-file-property vec "/" "su-command-p" nil)
-               (tramp-error
-                vec 'file-error "Cannot switch to user `%s'" user)))
-
-           ;; Mark it as connected.
-           (tramp-set-connection-property p "connected" t)))))))
+  (with-tramp-debug-message vec "Opening connection"
+    (let* ((buf (tramp-get-connection-buffer vec))
+          (p (get-buffer-process buf))
+          (host (tramp-file-name-host vec))
+          (user (tramp-file-name-user vec))
+           (device (tramp-adb-get-device vec)))
+
+      ;; Maybe we know already that "su" is not supported.  We cannot
+      ;; use a connection property, because we have not checked yet
+      ;; whether it is still the same device.
+      (when (and user (not (tramp-get-file-property vec "/" "su-command-p" t)))
+       (tramp-error vec 'file-error "Cannot switch to user `%s'" user))
+
+      (unless (process-live-p p)
+       (save-match-data
+         (when (and p (processp p)) (delete-process p))
+         (if (tramp-string-empty-or-nil-p device)
+             (tramp-error vec 'file-error "Device %s not connected" host))
+         (with-tramp-progress-reporter vec 3 "Opening adb shell connection"
+           (let* ((coding-system-for-read 'utf-8-dos) ; Is this correct?
+                  (process-connection-type tramp-process-connection-type)
+                  (args (tramp-expand-args
+                         vec 'tramp-login-args ?d (or device "")))
+                  (p (let ((default-directory
+                            tramp-compat-temporary-file-directory))
+                       (apply
+                        #'start-process (tramp-get-connection-name vec) buf
+                        tramp-adb-program args)))
+                  (prompt (md5 (concat (prin1-to-string process-environment)
+                                       (current-time-string)))))
+             ;; Wait for initial prompt.  On some devices, it needs
+             ;; an initial RET, in order to get it.
+              (sleep-for 0.1)
+             (tramp-send-string vec tramp-rsh-end-of-line)
+             (tramp-adb-wait-for-output p 30)
+             (unless (process-live-p p)
+               (tramp-error vec 'file-error "Terminated!"))
+
+             ;; Set sentinel.  Initialize variables.
+             (set-process-sentinel p #'tramp-process-sentinel)
+             (tramp-post-process-creation p vec)
+
+             ;; Set connection-local variables.
+             (tramp-set-connection-local-variables vec)
+
+             ;; Change prompt.
+             (tramp-set-connection-property
+              p "prompt" (rx "///" (literal prompt) "#$"))
+             (tramp-adb-send-command
+              vec (format "PS1=\"///\"\"%s\"\"#$\"" prompt))
+
+             ;; Disable line editing.
+             (tramp-adb-send-command
+              vec "set +o vi +o vi-esccomplete +o vi-tabcomplete +o emacs")
+
+             ;; Dump option settings in the traces.
+             (when (>= tramp-verbose 9)
+               (tramp-adb-send-command vec "set -o"))
+
+             ;; Check whether the properties have been changed.  If
+             ;; yes, this is a strong indication that we must expire
+             ;; all connection properties.  We start again.
+             (tramp-message vec 5 "Checking system information")
+             (tramp-adb-send-command
+              vec
+              (concat
+               "echo \\\"`getprop ro.product.model` "
+               "`getprop ro.product.version` "
+               "`getprop ro.build.version.release`\\\""))
+             (let ((old-getprop (tramp-get-connection-property vec "getprop"))
+                   (new-getprop
+                    (tramp-set-connection-property
+                     vec "getprop"
+                     (with-current-buffer (tramp-get-connection-buffer vec)
+                       ;; Read the expression.
+                       (goto-char (point-min))
+                       (read (current-buffer))))))
+               (when (and (stringp old-getprop)
+                          (not (string-equal old-getprop new-getprop)))
+                 (tramp-message
+                  vec 3
+                  (concat
+                   "Connection reset, because remote host changed "
+                   "from `%s' to `%s'")
+                  old-getprop new-getprop)
+                 (tramp-cleanup-connection vec t)
+                 (tramp-adb-maybe-open-connection vec)))
+
+             ;; Change user if indicated.
+             (when user
+               (tramp-adb-send-command vec (format "su %s" user))
+               (unless (tramp-adb-send-command-and-check vec nil)
+                 (delete-process p)
+                 ;; Do not flush, we need the nil value.
+                 (tramp-set-file-property vec "/" "su-command-p" nil)
+                 (tramp-error
+                  vec 'file-error "Cannot switch to user `%s'" user)))
+
+             ;; Mark it as connected.
+             (tramp-set-connection-property p "connected" t))))))))
 
 ;;; Default connection-local variables for Tramp.
 
diff --git a/lisp/net/tramp-archive.el b/lisp/net/tramp-archive.el
index 6fcb0ae5e69..590544f199f 100644
--- a/lisp/net/tramp-archive.el
+++ b/lisp/net/tramp-archive.el
@@ -263,7 +263,7 @@ It must be supported by libarchive(3).")
     (file-regular-p . tramp-handle-file-regular-p)
     ;; `file-remote-p' performed by default handler.
     (file-selinux-context . tramp-handle-file-selinux-context)
-    (file-symlink-p . tramp-handle-file-symlink-p)
+    (file-symlink-p . tramp-archive-handle-file-symlink-p)
     (file-system-info . tramp-archive-handle-file-system-info)
     (file-truename . tramp-archive-handle-file-truename)
     (file-user-uid . tramp-archive-handle-file-user-uid)
@@ -666,6 +666,10 @@ offered."
   "Like `file-readable-p' for file archives."
   (file-readable-p (tramp-archive-gvfs-file-name filename)))
 
+(defun tramp-archive-handle-file-symlink-p (filename)
+  "Like `file-symlink-p' for file archives."
+  (file-symlink-p (tramp-archive-gvfs-file-name filename)))
+
 (defun tramp-archive-handle-file-system-info (filename)
   "Like `file-system-info' for file archives."
   (with-parsed-tramp-archive-file-name filename nil
diff --git a/lisp/net/tramp-cache.el b/lisp/net/tramp-cache.el
index e0d38853956..6ecb80f09b2 100644
--- a/lisp/net/tramp-cache.el
+++ b/lisp/net/tramp-cache.el
@@ -28,8 +28,8 @@
 ;; An implementation of information caching for remote files.
 
 ;; Each connection, identified by a `tramp-file-name' structure or by
-;; a process, has a unique cache.  We distinguish 6 kind of caches,
-;; depending on the key:
+;; a process, has a unique cache.  We distinguish several kinds of
+;; caches, depending on the key:
 ;;
 ;; - localname is nil.  These are reusable properties.  Examples:
 ;;   "remote-shell" identifies the POSIX shell to be called on the
@@ -50,11 +50,14 @@
 ;;   definitions already sent to the remote shell, "last-cmd-time" is
 ;;   the timestamp a command has been sent to the remote process.
 ;;
-;; - The key is nil.  These are temporary properties related to the
-;;   local machine.  Examples: "parse-passwd" and "parse-group" keep
-;;   the results of parsing "/etc/passwd" and "/etc/group",
+;; - The key is `tramp-null-hop' or nil.  These are temporary
+;;   properties related to the local machine.  If the key is nil, it
+;;   is silently converted into `tramp-null-hop'.
+;;   Examples: "parse-passwd" and "parse-group" keep the results of
+;;   parsing "/etc/passwd" and "/etc/group",
 ;;   "{uid,gid}-{integer,string}" are the local uid and gid, and
-;;   "locale" is the used shell locale.
+;;   "locale" is the used shell locale.  "user-host-completions" keeps
+;;   the reachable hosts for the commands in tramp-container.el.
 ;;
 ;; - The key is `tramp-cache-version'.  It keeps the Tramp version the
 ;;   cache data was produced with.  If the cache is read by another
@@ -80,7 +83,6 @@
 ;;; Code:
 
 (require 'tramp-compat)
-(require 'tramp-loaddefs)
 (require 'time-stamp)
 
 ;;; -- Cache --
@@ -125,6 +127,7 @@ details see the info pages."
 If it doesn't exist yet, it is created and initialized with
 matching entries of `tramp-connection-properties'.
 If KEY is `tramp-cache-undefined', don't create anything, and return nil."
+  (declare (tramp-suppress-trace t))
   (unless (eq key tramp-cache-undefined)
     (or (gethash key tramp-cache-data)
        (let ((hash
@@ -506,6 +509,7 @@ PROPERTIES is a list of file properties (strings)."
 ;;;###tramp-autoload
 (defun tramp-cache-print (table)
   "Print hash table TABLE."
+  ;; (declare (tramp-suppress-trace t))
   (when (hash-table-p table)
     (let (result)
       (maphash
@@ -538,6 +542,11 @@ PROPERTIES is a list of file properties (strings)."
        table)
       result)))
 
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-cache-print 'tramp-suppress-trace t)
+
 ;;;###tramp-autoload
 (defun tramp-list-connections ()
   "Return all active `tramp-file-name' structs according to 
`tramp-cache-data'."
@@ -553,6 +562,7 @@ PROPERTIES is a list of file properties (strings)."
 (defun tramp-dump-connection-properties ()
   "Write persistent connection properties into file \
 `tramp-persistency-file-name'."
+  (declare (tramp-suppress-trace t))
   ;; We shouldn't fail, otherwise Emacs might not be able to be closed.
   (ignore-errors
     (when (and (hash-table-p tramp-cache-data)
@@ -561,6 +571,8 @@ PROPERTIES is a list of file properties (strings)."
               (stringp tramp-persistency-file-name))
       (let ((cache (copy-hash-table tramp-cache-data))
            print-length print-level)
+       ;; Remove `tramp-null-hop'.
+       (remhash tramp-null-hop cache)
        ;; Remove temporary data.  If there is the key "login-as", we
        ;; don't save either, because all other properties might
        ;; depend on the login name, and we want to give the
diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el
index 07f449a3a2e..8d95adb597c 100644
--- a/lisp/net/tramp-cmds.el
+++ b/lisp/net/tramp-cmds.el
@@ -52,6 +52,7 @@ SYNTAX can be one of the symbols `default' (default),
   (when syntax
     (customize-set-variable 'tramp-syntax syntax)))
 
+;; Use `match-buffers' starting with Emacs 29.1.
 ;;;###tramp-autoload
 (defun tramp-list-tramp-buffers ()
   "Return a list of all Tramp connection buffers."
@@ -63,6 +64,7 @@ SYNTAX can be one of the symbols `default' (default),
    (all-completions
     "*trace tramp" (mapcar #'list (mapcar #'buffer-name (buffer-list))))))
 
+;; Use `match-buffers' starting with Emacs 29.1.
 ;;;###tramp-autoload
 (defun tramp-list-remote-buffers ()
   "Return a list of all buffers with remote `default-directory'."
@@ -221,6 +223,7 @@ functions are called with `current-buffer' set."
 
 (defun tramp-cleanup-dired-buffer-p ()
   "Return t if current buffer runs `dired-mode'."
+  (declare (tramp-suppress-trace t))
   (derived-mode-p 'dired-mode))
 
 (add-hook 'tramp-cleanup-some-buffers-hook
@@ -231,14 +234,21 @@ functions are called with `current-buffer' set."
 
 (defun tramp-delete-tainted-remote-process-buffer-function ()
   "Delete current buffer from `tramp-tainted-remote-process-buffers'."
+  (declare (tramp-suppress-trace t))
   (setq tramp-tainted-remote-process-buffers
        (delete (current-buffer) tramp-tainted-remote-process-buffers)))
 
 ;;;###tramp-autoload
 (defun tramp-taint-remote-process-buffer (buffer)
   "Mark buffer as related to remote processes."
+  ;; (declare (tramp-suppress-trace t))
   (add-to-list 'tramp-tainted-remote-process-buffers buffer))
 
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-taint-remote-process-buffer 'tramp-suppress-trace t)
+
 (add-hook 'kill-buffer-hook
          #'tramp-delete-tainted-remote-process-buffer-function)
 (add-hook 'tramp-unload-hook
@@ -738,7 +748,7 @@ buffer in your bug report.
 
   ;; Beautify encoded values.
   (goto-char (point-min))
-  (while (re-search-forward
+  (while (search-forward-regexp
          (rx "'" (group "(decode-coding-string")) nil 'noerror)
     (replace-match "\\1"))
   (goto-char (point-max))
@@ -766,7 +776,7 @@ buffer in your bug report.
        (setq buffer-read-only nil)
        (goto-char (point-min))
        (while (not (eobp))
-         (if (re-search-forward tramp-buf-regexp (line-end-position) t)
+         (if (search-forward-regexp tramp-buf-regexp (line-end-position) t)
              (forward-line 1)
            (forward-line 0)
            (let ((start (point)))
diff --git a/lisp/net/tramp-compat.el b/lisp/net/tramp-compat.el
index bb7b266dd35..7c10c6530e9 100644
--- a/lisp/net/tramp-compat.el
+++ b/lisp/net/tramp-compat.el
@@ -29,15 +29,15 @@
 
 ;;; Code:
 
+(require 'tramp-loaddefs)
 (require 'ansi-color)
 (require 'auth-source)
 (require 'format-spec)
 (require 'parse-time)
 (require 'shell)
-(require 'subr-x)
 (require 'xdg)
 
-(declare-function tramp-error "tramp")
+(declare-function tramp-error "tramp-message")
 (declare-function tramp-tramp-file-p "tramp")
 (defvar tramp-temp-name-prefix)
 
@@ -75,7 +75,7 @@
        (prog1 (setq xdg (concat (file-name-as-directory xdg) "emacs"))
         (make-directory xdg t))
      (eval (car (get 'temporary-file-directory 'standard-value)) t)))
-  "The default value of `temporary-file-directory'.")
+  "The default value of `temporary-file-directory' for Tramp.")
 
 (defsubst tramp-compat-make-temp-name ()
   "Generate a local temporary file name (compat function)."
@@ -202,7 +202,7 @@ Add the extension of F, if existing."
        (let ((matches 0)
               (case-fold-search nil))
          (goto-char start)
-         (while (re-search-forward regexp end t)
+         (while (search-forward-regexp regexp end t)
             (replace-match replacement t)
             (setq matches (1+ matches)))
          (and (not (zerop matches))
@@ -307,7 +307,7 @@ Also see `ignore'."
     "List of characters equivalent to trailing colon in \"password\" 
prompts."))
 
 (dolist (elt (all-completions "tramp-compat-" obarray 'functionp))
-  (put (intern elt) 'tramp-suppress-trace t))
+  (function-put (intern elt) 'tramp-suppress-trace t))
 
 (add-hook 'tramp-unload-hook
          (lambda ()
diff --git a/lisp/net/tramp-container.el b/lisp/net/tramp-container.el
index 7f8d4473ad7..687092e7e95 100644
--- a/lisp/net/tramp-container.el
+++ b/lisp/net/tramp-container.el
@@ -158,26 +158,39 @@ If it is nil, the default context will be used."
   "Tramp method name to use to connect to Flatpak sandboxes.")
 
 ;;;###tramp-autoload
-(defun tramp-container--completion-function (program)
+(defun tramp-container--completion-function (method)
   "List running containers available for connection.
-PROGRAM is the program to be run for \"ps\", either
-`tramp-docker-program' or `tramp-podman-program'.
+METHOD is the Tramp method to be used for \"ps\", either
+`tramp-docker-method' or `tramp-podman-method'.
 
 This function is used by `tramp-set-completion-function', please
 see its function help for a description of the format."
-  (when-let ((default-directory tramp-compat-temporary-file-directory)
-            (raw-list (shell-command-to-string
-                       (concat program " ps --format '{{.ID}}\t{{.Names}}'")))
-             (lines (split-string raw-list "\n" 'omit))
-             (names (mapcar
-                    (lambda (line)
-                       (when (string-match
-                             (rx bol (group (1+ nonl))
-                                 "\t" (? (group (1+ nonl))) eol)
-                             line)
-                        (or (match-string 2 line) (match-string 1 line))))
-                     lines)))
-    (mapcar (lambda (name) (list nil name)) (delq nil names))))
+  (let ((default-directory
+        (or (and tramp-completion-remote-containers tramp--last-hop-directory)
+            tramp-compat-temporary-file-directory))
+       (program (tramp-get-method-parameter
+                 (make-tramp-file-name :method method) 'tramp-login-program))
+       non-essential)
+    ;; We don't use connection properties, because this information
+    ;; shouldn't be kept persistently.
+    (with-tramp-file-property
+       (when (tramp-tramp-file-p default-directory)
+         (tramp-dissect-file-name default-directory))
+       (concat "/" method ":") "user-host-completions"
+      (when-let ((raw-list
+                 (shell-command-to-string
+                  (concat program " ps --format '{{.ID}}\t{{.Names}}'")))
+                (lines (split-string raw-list "\n" 'omit))
+                (names
+                 (mapcar
+                  (lambda (line)
+                    (when (string-match
+                           (rx bol (group (1+ nonl))
+                               "\t" (? (group (1+ nonl))) eol)
+                           line)
+                      (or (match-string 2 line) (match-string 1 line))))
+                  lines)))
+       (mapcar (lambda (name) (list nil name)) (delq nil names))))))
 
 ;;;###tramp-autoload
 (defun tramp-kubernetes--completion-function (&rest _args)
@@ -382,13 +395,11 @@ see its function help for a description of the format."
 
  (tramp-set-completion-function
   tramp-docker-method
-  `((tramp-container--completion-function
-     ,(executable-find tramp-docker-program))))
+  `((tramp-container--completion-function ,tramp-docker-method)))
 
  (tramp-set-completion-function
   tramp-podman-method
-  `((tramp-container--completion-function
-     ,(executable-find tramp-podman-program))))
+  `((tramp-container--completion-function ,tramp-podman-method)))
 
  (tramp-set-completion-function
   tramp-kubernetes-method
diff --git a/lisp/net/tramp-crypt.el b/lisp/net/tramp-crypt.el
index 9d52966b817..79eafc5c12e 100644
--- a/lisp/net/tramp-crypt.el
+++ b/lisp/net/tramp-crypt.el
@@ -279,8 +279,10 @@ arguments to pass to the OPERATION."
            (apply #'tramp-crypt-file-name-for-operation operation args))
           (fn (and (tramp-crypt-file-name-p filename)
                    (assoc operation tramp-crypt-file-name-handler-alist))))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-crypt-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-crypt-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (progn (defun tramp-register-crypt-file-name-handler ()
@@ -312,73 +314,75 @@ connection if a previous connection has died for some 
reason."
   ;; For password handling, we need a process bound to the connection
   ;; buffer.  Therefore, we create a dummy process.  Maybe there is a
   ;; better solution?
-  (unless (get-buffer-process (tramp-get-connection-buffer vec))
-    (let ((p (make-network-process
-             :name (tramp-get-connection-name vec)
-             :buffer (tramp-get-connection-buffer vec)
-             :server t :host 'local :service t :noquery t)))
-      (tramp-post-process-creation p vec)))
-
-  ;; The following operations must be performed without
-  ;; `tramp-crypt-file-name-handler'.
-  (let* (tramp-crypt-enabled
-        ;; Don't check for a proper method.
-        (non-essential t)
-        (remote-config
-         (expand-file-name
-          tramp-crypt-encfs-config (tramp-crypt-get-remote-dir vec)))
-        (local-config (tramp-crypt-config-file-name vec)))
-    ;; There is no local encfs6 config file.
-    (unless (file-exists-p local-config)
-      (if (and tramp-crypt-save-encfs-config-remote
-              (file-exists-p remote-config))
-         ;; Copy remote encfs6 config file if possible.
-         (copy-file remote-config local-config 'ok 'keep)
-
-       ;; Create local encfs6 config file otherwise.
-       (let* ((default-directory tramp-compat-temporary-file-directory)
-              (tmpdir1 (file-name-as-directory
-                        (tramp-compat-make-temp-file " .crypt" 'dir-flag)))
-              (tmpdir2 (file-name-as-directory
-                        (tramp-compat-make-temp-file " .nocrypt" 'dir-flag))))
-         ;; Enable `auth-source', unless "emacs -Q" has been called.
-         (tramp-set-connection-property
-          vec "first-password-request" tramp-cache-read-persistent-data)
-         (with-temp-buffer
-           (insert
-            (tramp-read-passwd
-             (tramp-get-connection-process vec)
-             (format
-              "New EncFS Password for %s " (tramp-crypt-get-remote-dir vec))))
-           (when
-               (zerop
-                (tramp-call-process-region
-                 vec (point-min) (point-max)
-                 tramp-crypt-encfs-program nil (tramp-get-connection-buffer 
vec)
-                 nil tramp-crypt-encfs-option "--extpass=cat" tmpdir1 tmpdir2))
-             ;; Save the password.
-             (ignore-errors
-               (and (functionp tramp-password-save-function)
-                    (funcall tramp-password-save-function)))))
-
-         ;; Write local config file.  Suppress file name IV chaining mode.
-         (with-temp-file local-config
-           (insert-file-contents
-            (expand-file-name tramp-crypt-encfs-config tmpdir1))
-           (when (search-forward
-                  "<chainedNameIV>1</chainedNameIV>" nil 'noerror)
-             (replace-match "<chainedNameIV>0</chainedNameIV>")))
-
-         ;; Unmount encfs.  Delete temporary directories.
-         (tramp-call-process
-          vec tramp-crypt-encfs-program nil nil nil
-          "--unmount" tmpdir1 tmpdir2)
-         (delete-directory tmpdir1 'recursive)
-         (delete-directory tmpdir2)
-
-         ;; Copy local encfs6 config file to remote.
-         (when tramp-crypt-save-encfs-config-remote
-           (copy-file local-config remote-config 'ok 'keep)))))))
+  (with-tramp-debug-message vec "Opening connection"
+    (unless (get-buffer-process (tramp-get-connection-buffer vec))
+      (let ((p (make-network-process
+               :name (tramp-get-connection-name vec)
+               :buffer (tramp-get-connection-buffer vec)
+               :server t :host 'local :service t :noquery t)))
+       (tramp-post-process-creation p vec)))
+
+    ;; The following operations must be performed without
+    ;; `tramp-crypt-file-name-handler'.
+    (let* (tramp-crypt-enabled
+          ;; Don't check for a proper method.
+          (non-essential t)
+          (remote-config
+           (expand-file-name
+            tramp-crypt-encfs-config (tramp-crypt-get-remote-dir vec)))
+          (local-config (tramp-crypt-config-file-name vec)))
+      ;; There is no local encfs6 config file.
+      (unless (file-exists-p local-config)
+       (if (and tramp-crypt-save-encfs-config-remote
+                (file-exists-p remote-config))
+           ;; Copy remote encfs6 config file if possible.
+           (copy-file remote-config local-config 'ok 'keep)
+
+         ;; Create local encfs6 config file otherwise.
+         (let* ((default-directory tramp-compat-temporary-file-directory)
+                (tmpdir1 (file-name-as-directory
+                          (tramp-compat-make-temp-file " .crypt" 'dir-flag)))
+                (tmpdir2 (file-name-as-directory
+                          (tramp-compat-make-temp-file " .nocrypt" 
'dir-flag))))
+           ;; Enable `auth-source', unless "emacs -Q" has been called.
+           (tramp-set-connection-property
+            vec "first-password-request" tramp-cache-read-persistent-data)
+           (with-temp-buffer
+             (insert
+              (tramp-read-passwd
+               (tramp-get-connection-process vec)
+               (format
+                "New EncFS Password for %s " (tramp-crypt-get-remote-dir 
vec))))
+             (when
+                 (zerop
+                  (tramp-call-process-region
+                   vec (point-min) (point-max)
+                   tramp-crypt-encfs-program nil
+                   (tramp-get-connection-buffer vec) nil
+                   tramp-crypt-encfs-option "--extpass=cat" tmpdir1 tmpdir2))
+               ;; Save the password.
+               (ignore-errors
+                 (and (functionp tramp-password-save-function)
+                      (funcall tramp-password-save-function)))))
+
+           ;; Write local config file.  Suppress file name IV chaining mode.
+           (with-temp-file local-config
+             (insert-file-contents
+              (expand-file-name tramp-crypt-encfs-config tmpdir1))
+             (when (search-forward
+                    "<chainedNameIV>1</chainedNameIV>" nil 'noerror)
+               (replace-match "<chainedNameIV>0</chainedNameIV>")))
+
+           ;; Unmount encfs.  Delete temporary directories.
+           (tramp-call-process
+            vec tramp-crypt-encfs-program nil nil nil
+            "--unmount" tmpdir1 tmpdir2)
+           (delete-directory tmpdir1 'recursive)
+           (delete-directory tmpdir2)
+
+           ;; Copy local encfs6 config file to remote.
+           (when tramp-crypt-save-encfs-config-remote
+             (copy-file local-config remote-config 'ok 'keep))))))))
 
 (defun tramp-crypt-send-command (vec &rest args)
   "Send encfsctl command to connection VEC.
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el
index 27dbf324924..71ef8215ab0 100644
--- a/lisp/net/tramp-gvfs.el
+++ b/lisp/net/tramp-gvfs.el
@@ -895,8 +895,10 @@ arguments to pass to the OPERATION."
             (and (tramp-tramp-file-p filename)
                  (tramp-dissect-file-name filename)))
            (fn (assoc operation tramp-gvfs-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (when (featurep 'dbusbind)
@@ -949,14 +951,13 @@ Return nil for null BYTE-ARRAY."
 (defun tramp-dbus-function (vec func args)
   "Apply a D-Bus function FUNC from dbus.el.
 The call will be traced by Tramp with trace level 6."
+  (declare (tramp-suppress-trace t))
   (let (result)
     (tramp-message vec 6 "%s" (cons func args))
     (setq result (apply func args))
     (tramp-message vec 6 "%s" (tramp-gvfs-stringify-dbus-message result))
     result))
 
-(put #'tramp-dbus-function 'tramp-suppress-trace t)
-
 (defmacro with-tramp-dbus-call-method
   (vec synchronous bus service path interface method &rest args)
   "Apply a D-Bus call on bus BUS.
@@ -1308,7 +1309,7 @@ If FILE-SYSTEM is non-nil, return file system attributes."
        ;; Parse output.
        (with-current-buffer (tramp-get-connection-buffer v)
          (goto-char (point-min))
-         (while (re-search-forward
+         (while (search-forward-regexp
                  (if file-system
                      tramp-gvfs-file-system-attributes-regexp
                    tramp-gvfs-file-attributes-with-gvfs-info-regexp)
@@ -2182,137 +2183,139 @@ connection if a previous connection has died for some 
reason."
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  ;; Sanity check.
-  (let ((method (tramp-file-name-method vec)))
-    (unless (member
-            (or (assoc-default
-                 method '(("smb" . "smb-share")
-                          ("davs" . "dav")
-                          ("nextcloud" . "dav")
-                          ("afp". "afp-volume")
-                          ("gdrive" . "google-drive")))
-                method)
-            tramp-gvfs-mounttypes)
-      (tramp-error vec 'file-error "Method `%s' not supported by GVFS" 
method)))
-
-  ;; For password handling, we need a process bound to the connection
-  ;; buffer.  Therefore, we create a dummy process.  Maybe there is a
-  ;; better solution?
-  (unless (get-buffer-process (tramp-get-connection-buffer vec))
-    (let ((p (make-network-process
-             :name (tramp-get-connection-name vec)
-             :buffer (tramp-get-connection-buffer vec)
-             :server t :host 'local :service t :noquery t)))
-      (tramp-post-process-creation p vec)
-
-      ;; Set connection-local variables.
-      (tramp-set-connection-local-variables vec)))
-
-  (unless (tramp-gvfs-connection-mounted-p vec)
-    (let ((method (tramp-file-name-method vec))
-         (user (tramp-file-name-user vec))
-         (host (tramp-file-name-host vec))
-         (localname (tramp-file-name-unquote-localname vec))
-         (object-path
-          (tramp-gvfs-object-path (tramp-make-tramp-file-name vec 'noloc))))
-
-      (when (and (string-equal method "afp")
-                (string-equal localname "/"))
-       (tramp-user-error vec "Filename must contain an AFP volume"))
-
-      (when (and (string-match-p (rx "dav" (? "s")) method)
-                (string-equal localname "/"))
-       (tramp-user-error vec "Filename must contain a WebDAV share"))
-
-      (when (and (string-equal method "smb")
-                (string-equal localname "/"))
-       (tramp-user-error vec "Filename must contain a Windows share"))
-
-      (when (member method tramp-goa-methods)
-       ;; Ensure that GNOME Online Accounts are cached.
-       (tramp-get-goa-accounts vec)
-       (when (tramp-get-connection-property
-              (tramp-get-goa-account vec) "FilesDisabled" t)
-         (tramp-user-error
-          vec "There is no Online Account `%s'"
-          (tramp-make-tramp-file-name vec 'noloc))))
-
-      (with-tramp-progress-reporter
-         vec 3
-         (if (tramp-string-empty-or-nil-p user)
-             (format "Opening connection for %s using %s" host method)
-           (format "Opening connection for %s@%s using %s" user host method))
-
-       ;; Enable `auth-source'.
-       (tramp-set-connection-property
-        vec "first-password-request" tramp-cache-read-persistent-data)
-
-       ;; There will be a callback of "askPassword" when a password is needed.
-       (dbus-register-method
-        :session dbus-service-emacs object-path
-        tramp-gvfs-interface-mountoperation "askPassword"
-        #'tramp-gvfs-handler-askpassword)
-       (dbus-register-method
-        :session dbus-service-emacs object-path
-        tramp-gvfs-interface-mountoperation "AskPassword"
-        #'tramp-gvfs-handler-askpassword)
-
-       ;; There could be a callback of "askQuestion" when adding
-       ;; fingerprints or checking certificates.
-       (dbus-register-method
-        :session dbus-service-emacs object-path
-        tramp-gvfs-interface-mountoperation "askQuestion"
-        #'tramp-gvfs-handler-askquestion)
-       (dbus-register-method
-        :session dbus-service-emacs object-path
-        tramp-gvfs-interface-mountoperation "AskQuestion"
-        #'tramp-gvfs-handler-askquestion)
-
-       ;; The call must be asynchronously, because of the "askPassword"
-       ;; or "askQuestion" callbacks.
-       (if (string-match-p (rx "(so)" eol) tramp-gvfs-mountlocation-signature)
+  (with-tramp-debug-message vec "Opening connection"
+    ;; Sanity check.
+    (let ((method (tramp-file-name-method vec)))
+      (unless (member
+              (or (assoc-default
+                   method '(("smb" . "smb-share")
+                            ("davs" . "dav")
+                            ("nextcloud" . "dav")
+                            ("afp". "afp-volume")
+                            ("gdrive" . "google-drive")))
+                  method)
+              tramp-gvfs-mounttypes)
+       (tramp-error
+        vec 'file-error "Method `%s' not supported by GVFS" method)))
+
+    ;; For password handling, we need a process bound to the
+    ;; connection buffer.  Therefore, we create a dummy process.
+    ;; Maybe there is a better solution?
+    (unless (get-buffer-process (tramp-get-connection-buffer vec))
+      (let ((p (make-network-process
+               :name (tramp-get-connection-name vec)
+               :buffer (tramp-get-connection-buffer vec)
+               :server t :host 'local :service t :noquery t)))
+       (tramp-post-process-creation p vec)
+
+       ;; Set connection-local variables.
+       (tramp-set-connection-local-variables vec)))
+
+    (unless (tramp-gvfs-connection-mounted-p vec)
+      (let ((method (tramp-file-name-method vec))
+           (user (tramp-file-name-user vec))
+           (host (tramp-file-name-host vec))
+           (localname (tramp-file-name-unquote-localname vec))
+           (object-path
+            (tramp-gvfs-object-path (tramp-make-tramp-file-name vec 'noloc))))
+
+       (when (and (string-equal method "afp")
+                  (string-equal localname "/"))
+         (tramp-user-error vec "Filename must contain an AFP volume"))
+
+       (when (and (string-match-p (rx "dav" (? "s")) method)
+                  (string-equal localname "/"))
+         (tramp-user-error vec "Filename must contain a WebDAV share"))
+
+       (when (and (string-equal method "smb")
+                  (string-equal localname "/"))
+         (tramp-user-error vec "Filename must contain a Windows share"))
+
+       (when (member method tramp-goa-methods)
+         ;; Ensure that GNOME Online Accounts are cached.
+         (tramp-get-goa-accounts vec)
+         (when (tramp-get-connection-property
+                (tramp-get-goa-account vec) "FilesDisabled" t)
+           (tramp-user-error
+            vec "There is no Online Account `%s'"
+            (tramp-make-tramp-file-name vec 'noloc))))
+
+       (with-tramp-progress-reporter
+           vec 3
+           (if (tramp-string-empty-or-nil-p user)
+               (format "Opening connection for %s using %s" host method)
+             (format "Opening connection for %s@%s using %s" user host method))
+
+         ;; Enable `auth-source'.
+         (tramp-set-connection-property
+          vec "first-password-request" tramp-cache-read-persistent-data)
+
+         ;; There will be a callback of "askPassword" when a password is 
needed.
+         (dbus-register-method
+          :session dbus-service-emacs object-path
+          tramp-gvfs-interface-mountoperation "askPassword"
+          #'tramp-gvfs-handler-askpassword)
+         (dbus-register-method
+          :session dbus-service-emacs object-path
+          tramp-gvfs-interface-mountoperation "AskPassword"
+          #'tramp-gvfs-handler-askpassword)
+
+         ;; There could be a callback of "askQuestion" when adding
+         ;; fingerprints or checking certificates.
+         (dbus-register-method
+          :session dbus-service-emacs object-path
+          tramp-gvfs-interface-mountoperation "askQuestion"
+          #'tramp-gvfs-handler-askquestion)
+         (dbus-register-method
+          :session dbus-service-emacs object-path
+          tramp-gvfs-interface-mountoperation "AskQuestion"
+          #'tramp-gvfs-handler-askquestion)
+
+         ;; The call must be asynchronously, because of the
+         ;; "askPassword" or "askQuestion" callbacks.
+         (if (string-match-p (rx "(so)" eol) 
tramp-gvfs-mountlocation-signature)
+             (with-tramp-dbus-call-method vec nil
+               :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker
+               tramp-gvfs-interface-mounttracker tramp-gvfs-mountlocation
+               (tramp-gvfs-mount-spec vec)
+               `(:struct :string ,(dbus-get-unique-name :session)
+                         :object-path ,object-path))
            (with-tramp-dbus-call-method vec nil
              :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker
              tramp-gvfs-interface-mounttracker tramp-gvfs-mountlocation
              (tramp-gvfs-mount-spec vec)
-             `(:struct :string ,(dbus-get-unique-name :session)
-                       :object-path ,object-path))
-         (with-tramp-dbus-call-method vec nil
-           :session tramp-gvfs-service-daemon tramp-gvfs-path-mounttracker
-           tramp-gvfs-interface-mounttracker tramp-gvfs-mountlocation
-           (tramp-gvfs-mount-spec vec)
-           :string (dbus-get-unique-name :session) :object-path object-path))
-
-       ;; We must wait, until the mount is applied.  This will be
-       ;; indicated by the "mounted" signal, i.e. the "fuse-mountpoint"
-       ;; file property.
-       (with-timeout
-           ((or (tramp-get-method-parameter vec 'tramp-connection-timeout)
-                tramp-connection-timeout)
-            (if (tramp-string-empty-or-nil-p (tramp-file-name-user vec))
+             :string (dbus-get-unique-name :session) :object-path object-path))
+
+         ;; We must wait, until the mount is applied.  This will be
+         ;; indicated by the "mounted" signal, i.e. the
+         ;; "fuse-mountpoint" file property.
+         (with-timeout
+             ((or (tramp-get-method-parameter vec 'tramp-connection-timeout)
+                  tramp-connection-timeout)
+              (if (tramp-string-empty-or-nil-p (tramp-file-name-user vec))
+                  (tramp-error
+                   vec 'file-error
+                   "Timeout reached mounting %s using %s" host method)
                 (tramp-error
                  vec 'file-error
-                 "Timeout reached mounting %s using %s" host method)
-              (tramp-error
-               vec 'file-error
-               "Timeout reached mounting %s@%s using %s" user host method)))
-         (while (not (tramp-get-file-property vec "/" "fuse-mountpoint"))
-           (read-event nil nil 0.1)))
-
-       ;; If `tramp-gvfs-handler-askquestion' has returned "No", it
-       ;; is marked with the fuse-mountpoint "/".  We shall react.
-       (when (string-equal
-              (tramp-get-file-property vec "/" "fuse-mountpoint" "") "/")
-         (tramp-error vec 'file-error "FUSE mount denied"))
-
-       ;; Save the password.
-       (ignore-errors
-         (and (functionp tramp-password-save-function)
-              (funcall tramp-password-save-function)))
-
-       ;; Mark it as connected.
-       (tramp-set-connection-property
-        (tramp-get-connection-process vec) "connected" t)))))
+                 "Timeout reached mounting %s@%s using %s" user host method)))
+           (while (not (tramp-get-file-property vec "/" "fuse-mountpoint"))
+             (read-event nil nil 0.1)))
+
+         ;; If `tramp-gvfs-handler-askquestion' has returned "No", it
+         ;; is marked with the fuse-mountpoint "/".  We shall react.
+         (when (string-equal
+                (tramp-get-file-property vec "/" "fuse-mountpoint" "") "/")
+           (tramp-error vec 'file-error "FUSE mount denied"))
+
+         ;; Save the password.
+         (ignore-errors
+           (and (functionp tramp-password-save-function)
+                (funcall tramp-password-save-function)))
+
+         ;; Mark it as connected.
+         (tramp-set-connection-property
+          (tramp-get-connection-process vec) "connected" t))))))
 
 (defun tramp-gvfs-gio-tool-p (vec)
   "Check, whether the gio tool is available."
diff --git a/lisp/net/tramp-integration.el b/lisp/net/tramp-integration.el
index d7fcd8afefa..c73c86a9110 100644
--- a/lisp/net/tramp-integration.el
+++ b/lisp/net/tramp-integration.el
@@ -65,6 +65,7 @@
   "Set up a minibuffer for `file-name-shadow-mode'.
 Adds another overlay hiding filename parts according to Tramp's
 special handling of `substitute-in-file-name'."
+  (declare (tramp-suppress-trace t))
   (when minibuffer-completing-file-name
     (setq tramp-rfn-eshadow-overlay
          (make-overlay (minibuffer-prompt-end) (minibuffer-prompt-end)))
@@ -86,6 +87,7 @@ special handling of `substitute-in-file-name'."
 
 (defun tramp-rfn-eshadow-update-overlay-regexp ()
   "An overlay covering the shadowed part of the filename."
+  (declare (tramp-suppress-trace t))
   (rx-to-string
    `(: (* (not (any ,tramp-postfix-host-format "/~"))) (| "/" "~"))))
 
@@ -94,6 +96,7 @@ special handling of `substitute-in-file-name'."
 This is intended to be used as a minibuffer `post-command-hook' for
 `file-name-shadow-mode'; the minibuffer should have already
 been set up by `rfn-eshadow-setup-minibuffer'."
+  (declare (tramp-suppress-trace t))
   ;; In remote files name, there is a shadowing just for the local part.
   (ignore-errors
     (let ((end (or (overlay-end rfn-eshadow-overlay)
diff --git a/lisp/net/tramp-message.el b/lisp/net/tramp-message.el
new file mode 100644
index 00000000000..5b909b69ae3
--- /dev/null
+++ b/lisp/net/tramp-message.el
@@ -0,0 +1,580 @@
+;;; tramp-message.el --- Tramp messages  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Author: Michael Albinus <michael.albinus@gmx.de>
+;; Keywords: comm, processes
+;; Package: tramp
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package collects all Tramp functions to trace.  This is driven
+;; by the user option `tramp-verbose'.  The following buffers are
+;; created:
+;;
+;; - *debug tramp/method user@host*
+;;
+;;   This buffer is created when `tramp-verbose' is greater than or
+;;   equal 4.  It contains all messages with a level up to `tramp-verbose'.
+;;
+;;   When `tramp-debug-command-messages' is non-nil, the buffer
+;;   contains all messages with level 6 and the entry/exit messages of
+;;   `tramp-file-name-handler'.  This is intended to analyze which
+;;   remote commands are sent for a given file name operation.
+;;
+;; - *trace tramp/method user@host*
+;;
+;;   This buffer is created by the trace.el package when
+;;   `tramp-verbose' is is greater than or equal 11.  It traces all
+;;   functions with suffix "tramp-" except those function with the
+;;   property `tramp-suppress-trace'.
+
+;;; Code:
+
+(require 'tramp-compat)
+(require 'help-mode)
+
+(declare-function tramp-file-name-equal-p "tramp")
+(declare-function tramp-file-name-host-port "tramp")
+(declare-function tramp-file-name-user-domain "tramp")
+(declare-function tramp-get-default-directory "tramp")
+
+;;;###tramp-autoload
+(defcustom tramp-verbose 3
+  "Verbosity level for Tramp messages.
+Any level x includes messages for all levels 1 .. x-1.  The levels are
+
+ 0  silent (no tramp messages at all)
+ 1  errors
+ 2  warnings
+ 3  connection to remote hosts (default level)
+ 4  activities
+ 5  internal
+ 6  sent and received strings
+ 7  connection properties
+ 8  file caching
+ 9  test commands
+10  traces (huge)
+11  call traces (maintainer only)."
+  :group 'tramp
+  :type 'integer)
+
+(defcustom tramp-debug-to-file nil
+  "Whether Tramp debug messages shall be saved to file.
+The debug file has the same name as the debug buffer, written to
+`tramp-compat-temporary-file-directory'."
+  :group 'tramp
+  :version "28.1"
+  :type 'boolean)
+
+(defcustom tramp-debug-command-messages nil
+  "Whether to write only command messages to the debug buffer.
+This increases `tramp-verbose' to 6 if necessary."
+  :group 'tramp
+  :version "30.1"
+  :type 'boolean)
+
+(defconst tramp-debug-outline-regexp
+  (rx ;; Timestamp.
+      (+ digit) ":" (+ digit) ":" (+ digit) "." (+ digit) blank
+      ;; Thread.
+      (? (group "#<thread " (+ nonl) ">") blank)
+      ;; Function name, verbosity.
+      (group (+ (any "-" alnum))) " (" (group (+ digit)) ") #")
+  "Used for highlighting Tramp debug buffers in `outline-mode'.
+When it is used for regexp matching, the regexp groups are
+
+  1 for the thread name (optional)
+  2 for the function name
+  3 for the verbosity level.")
+
+(defconst tramp-debug-font-lock-keywords
+  ;; FIXME: Make it a function instead of an ELisp expression, so you
+  ;; can evaluate it with `funcall' rather than `eval'!
+  ;; Also, in `font-lock-defaults' you can specify a function name for
+  ;; the "KEYWORDS" part, so font-lock calls it to get the actual keywords!
+  '(list
+    (rx bol (regexp tramp-debug-outline-regexp) (+ nonl))
+    '(1 font-lock-warning-face t t)
+    '(0 (outline-font-lock-face) keep t))
+  "Used for highlighting Tramp debug buffers in `outline-mode'.")
+
+(defun tramp-debug-outline-level ()
+  "Return the depth to which a statement is nested in the outline.
+Point must be at the beginning of a header line.
+
+The outline level is equal to the verbosity of the Tramp message."
+  (declare (tramp-suppress-trace t))
+  (1+ (string-to-number (match-string 3))))
+
+;; This function takes action since Emacs 28.1, when
+;; `read-extended-command-predicate' is set to
+;; `command-completion-default-include-p'.
+(defun tramp-debug-buffer-command-completion-p (_symbol buffer)
+  "A predicate for Tramp interactive commands.
+They are completed by \"M-x TAB\" only in Tramp debug buffers."
+  (declare (tramp-suppress-trace t))
+  (with-current-buffer buffer
+    (string-equal
+     (buffer-substring (point-min) (min (+ (point-min) 10) (point-max)))
+     ";; Emacs:")))
+
+(defun tramp-setup-debug-buffer ()
+  "Function to setup debug buffers."
+  (declare (tramp-suppress-trace t))
+  ;; (declare (completion tramp-debug-buffer-command-completion-p)
+  ;;      (tramp-suppress-trace t))
+  (interactive)
+  (set-buffer-file-coding-system 'utf-8)
+  (setq buffer-undo-list t)
+  ;; Activate `outline-mode'.  This runs `text-mode-hook' and
+  ;; `outline-mode-hook'.  We must prevent that local processes die.
+  ;; Yes: I've seen `flyspell-mode', which starts "ispell".
+  ;; `(custom-declare-variable outline-minor-mode-prefix ...)'  raises
+  ;; on error in `(outline-mode)', we don't want to see it in the
+  ;; traces.
+  (let ((default-directory tramp-compat-temporary-file-directory))
+    (outline-mode))
+  (setq-local outline-level 'tramp-debug-outline-level)
+  (setq-local font-lock-keywords
+              ;; FIXME: This `(t FOO . BAR)' representation in
+              ;; `font-lock-keywords' is supposed to be an internal
+              ;; implementation "detail".  Don't abuse it here!
+              `(t (eval ,tramp-debug-font-lock-keywords t)
+                  ,(eval tramp-debug-font-lock-keywords t)))
+  ;; Do not edit the debug buffer.
+  (use-local-map special-mode-map)
+  (set-buffer-modified-p nil)
+  ;; For debugging purposes.
+  (local-set-key "\M-n" 'clone-buffer)
+  (add-hook 'clone-buffer-hook #'tramp-setup-debug-buffer nil 'local))
+
+(function-put
+ #'tramp-setup-debug-buffer 'completion-predicate
+ #'tramp-debug-buffer-command-completion-p)
+
+(defun tramp-debug-buffer-name (vec)
+  "A name for the debug buffer of VEC."
+  (declare (tramp-suppress-trace t))
+  (let ((method (tramp-file-name-method vec))
+       (user-domain (tramp-file-name-user-domain vec))
+       (host-port (tramp-file-name-host-port vec)))
+    (if (tramp-string-empty-or-nil-p user-domain)
+       (format "*debug tramp/%s %s*" method host-port)
+      (format "*debug tramp/%s %s@%s*" method user-domain host-port))))
+
+(defun tramp-get-debug-buffer (vec)
+  "Get the debug buffer of VEC."
+  (declare (tramp-suppress-trace t))
+  (with-current-buffer (get-buffer-create (tramp-debug-buffer-name vec))
+    (when (bobp)
+      (tramp-setup-debug-buffer))
+    (current-buffer)))
+
+(defun tramp-get-debug-file-name (vec)
+  "Get the debug file name for VEC."
+  (declare (tramp-suppress-trace t))
+  (expand-file-name
+   (tramp-compat-string-replace "/" " " (tramp-debug-buffer-name vec))
+   tramp-compat-temporary-file-directory))
+
+(defun tramp-trace-buffer-name (vec)
+  "A name for the trace buffer for VEC."
+  (declare (tramp-suppress-trace t))
+   (tramp-compat-string-replace "*debug" "*trace" (tramp-debug-buffer-name 
vec)))
+
+(defvar tramp-trace-functions nil
+  "A list of non-Tramp functions to be traced with `tramp-verbose' > 10.")
+
+(defun tramp-debug-message (vec fmt-string &rest arguments)
+  "Append message to debug buffer of VEC.
+Message is formatted with FMT-STRING as control string and the remaining
+ARGUMENTS to actually emit the message (if applicable)."
+  (declare (tramp-suppress-trace t))
+  (let ((inhibit-message t)
+       create-lockfiles file-name-handler-alist message-log-max
+       signal-hook-function)
+    (with-current-buffer (tramp-get-debug-buffer vec)
+      (goto-char (point-max))
+      (let ((point (point)))
+       (when (bobp)
+         ;; Headline.
+         (insert
+          (format
+           ";; Emacs: %s Tramp: %s -*- mode: outline; coding: utf-8; -*-"
+           emacs-version tramp-version))
+         (when (>= tramp-verbose 10)
+           (let ((tramp-verbose 0))
+             (insert
+              (format
+               "\n;; Location: %s Git: %s/%s"
+               (locate-library "tramp")
+               (or tramp-repository-branch "")
+               (or tramp-repository-version "")))))
+         ;; Traces.
+         (when (>= tramp-verbose 11)
+           (dolist
+               (elt
+                (append
+                 (mapcar
+                  #'intern (all-completions "tramp-" obarray #'functionp))
+                 tramp-trace-functions))
+             (unless (get elt 'tramp-suppress-trace)
+               (trace-function-background elt (tramp-trace-buffer-name vec)))))
+         ;; Delete debug file.
+         (when (and tramp-debug-to-file (tramp-get-debug-file-name vec))
+           (ignore-errors (delete-file (tramp-get-debug-file-name vec)))))
+       (unless (bolp)
+         (insert "\n"))
+       ;; Timestamp.
+       (insert (format-time-string "%T.%6N "))
+       ;; Threads.  `current-thread' might not exist when Emacs is
+       ;; configured --without-threads.
+       ;; (unless (eq (tramp-compat-funcall 'current-thread) main-thread)
+       ;;   (insert (format "%s " (tramp-compat-funcall 'current-thread))))
+       ;; Calling Tramp function.  We suppress compat and trace
+       ;; functions from being displayed.
+       (let ((frames (backtrace-frames))
+             btf fn)
+         (while (not fn)
+           (setq btf (cadadr frames))
+           (if (not btf)
+               (setq fn "")
+             (and (symbolp btf) (setq fn (symbol-name btf))
+                  (or (not (string-prefix-p "tramp" fn))
+                      (get btf 'tramp-suppress-trace))
+                  (setq fn nil))
+             (setq frames (cdr frames))))
+         ;; The following code inserts filename and line number.
+         ;; Should be inactive by default, because it is time consuming.
+         ;; (let ((ffn (find-function-noselect (intern fn))))
+         ;;   (insert
+         ;;    (format
+         ;;     "%s:%d: "
+         ;;     (file-name-nondirectory (buffer-file-name (car ffn)))
+         ;;     (with-current-buffer (car ffn)
+         ;;       (1+ (count-lines (point-min) (cdr ffn)))))))
+         (insert (format "%s " fn)))
+       ;; The message.
+       (insert (apply #'format-message fmt-string arguments))
+       (if tramp-debug-command-messages
+           ;; Add help function.
+           (tramp-debug-message-buttonize point)
+         ;; Write message to debug file.
+         (when tramp-debug-to-file
+           (ignore-errors
+             (write-region
+              point (point-max) (tramp-get-debug-file-name vec) 'append))))))))
+
+;;;###tramp-autoload
+(defun tramp-message (vec-or-proc level fmt-string &rest arguments)
+  "Emit a message depending on verbosity level.
+VEC-OR-PROC identifies the Tramp buffer to use.  It can be either a
+vector or a process.  LEVEL says to be quiet if `tramp-verbose' is
+less than LEVEL.  The message is emitted only if `tramp-verbose' is
+greater than or equal to LEVEL.
+
+The message is also logged into the debug buffer when `tramp-verbose'
+is greater than or equal 4.
+
+Calls functions `message' and `tramp-debug-message' with FMT-STRING as
+control string and the remaining ARGUMENTS to actually emit the message (if
+applicable)."
+  ;; (declare (tramp-suppress-trace t))
+  (ignore-errors
+    (let ((tramp-verbose
+          (if tramp-debug-command-messages
+              (max tramp-verbose 6) tramp-verbose)))
+      (when (<= level tramp-verbose)
+       ;; Display only when there is a minimum level, and the
+       ;; progress reporter doesn't suppress further messages.
+       (when (and (<= level 3) (null tramp-inhibit-progress-reporter))
+         (apply #'message
+                (concat
+                 (cond
+                  ((= level 0) "")
+                  ((= level 1) "")
+                  ((= level 2) "Warning: ")
+                  (t           "Tramp: "))
+                 fmt-string)
+                arguments))
+       ;; Log only when there is a minimum level.
+       (when (>= tramp-verbose 4)
+         (let ((tramp-verbose 0))
+           ;; Append connection buffer for error messages, if exists.
+           (when (= level 1)
+             (ignore-errors
+               (setq fmt-string (concat fmt-string "\n%s")
+                     arguments
+                     (append
+                      arguments
+                      `(,(tramp-get-buffer-string
+                          (if (processp vec-or-proc)
+                              (process-buffer vec-or-proc)
+                            (tramp-get-connection-buffer
+                             vec-or-proc 'dont-create))))))))
+           ;; Translate proc to vec.  Handle nil vec.
+           (when (processp vec-or-proc)
+             (setq vec-or-proc (process-get vec-or-proc 'tramp-vector)))
+           (setq vec-or-proc (tramp-file-name-unify vec-or-proc)))
+         ;; Do it.
+         (when (and (tramp-file-name-p vec-or-proc)
+                    (or (null tramp-debug-command-messages) (= level 6)))
+           (apply #'tramp-debug-message
+                  vec-or-proc
+                  (concat (format "(%d) # " level) fmt-string)
+                  arguments)))))))
+
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-message 'tramp-suppress-trace t)
+
+(defsubst tramp-backtrace (&optional vec-or-proc force)
+  "Dump a backtrace into the debug buffer.
+If VEC-OR-PROC is nil, the buffer *debug tramp* is used.  FORCE
+forces the backtrace even if `tramp-verbose' is less than 10.
+This function is meant for debugging purposes."
+  (let ((tramp-verbose (if force 10 tramp-verbose)))
+    (when (>= tramp-verbose 10)
+      (tramp-message
+       vec-or-proc 10 "\n%s" (with-output-to-string (backtrace))))))
+
+(defsubst tramp-error (vec-or-proc signal fmt-string &rest arguments)
+  "Emit an error.
+VEC-OR-PROC identifies the connection to use, SIGNAL is the
+signal identifier to be raised, remaining arguments passed to
+`tramp-message'.  Finally, signal SIGNAL is raised with
+FMT-STRING and ARGUMENTS."
+  (let (signal-hook-function)
+    (tramp-backtrace vec-or-proc)
+    (unless arguments
+      ;; FMT-STRING could be just a file name, as in
+      ;; `file-already-exists' errors.  It could contain the ?\%
+      ;; character, as in smb domain spec.
+      (setq arguments (list fmt-string)
+           fmt-string "%s"))
+    (tramp-message
+     vec-or-proc 1 "%s"
+     (error-message-string
+      (list signal
+           (get signal 'error-message)
+           (apply #'format-message fmt-string arguments))))
+    (signal signal (list (substring-no-properties
+                         (apply #'format-message fmt-string arguments))))))
+
+(defvar tramp-error-show-message-timeout 30
+  "Time to show the Tramp buffer in case of an error.
+If it is bound to nil, the buffer is not shown.  This is used in
+tramp-tests.el.")
+
+(defsubst tramp-error-with-buffer
+  (buf vec-or-proc signal fmt-string &rest arguments)
+  "Emit an error, and show BUF.
+If BUF is nil, show the connection buf.  Wait for 30\", or until
+an input event arrives.  The other arguments are passed to `tramp-error'."
+  (save-window-excursion
+    (let* ((buf (or (and (bufferp buf) buf)
+                   (and (processp vec-or-proc) (process-buffer vec-or-proc))
+                   (and (tramp-file-name-p vec-or-proc)
+                        (tramp-get-connection-buffer vec-or-proc))))
+          (vec (or (and (tramp-file-name-p vec-or-proc) vec-or-proc)
+                   (and buf (tramp-dissect-file-name
+                             (tramp-get-default-directory buf))))))
+      (unwind-protect
+         (apply #'tramp-error vec-or-proc signal fmt-string arguments)
+       ;; Save exit.
+       (when (and buf
+                  (natnump tramp-error-show-message-timeout)
+                  (not (zerop tramp-verbose))
+                  ;; Do not show when flagged from outside.
+                  (not non-essential)
+                  ;; Show only when Emacs has started already.
+                  (current-message))
+         (let ((enable-recursive-minibuffers t)
+               inhibit-message)
+           ;; `tramp-error' does not show messages.  So we must do it
+           ;; ourselves.
+           (apply #'message fmt-string arguments)
+           ;; Show buffer.
+           (pop-to-buffer buf)
+           (discard-input)
+           (sit-for tramp-error-show-message-timeout)))
+       ;; Reset timestamp.  It would be wrong after waiting for a while.
+       (when (tramp-file-name-equal-p vec (car tramp-current-connection))
+         (setcdr tramp-current-connection (current-time)))))))
+
+(defsubst tramp-user-error (vec-or-proc fmt-string &rest arguments)
+  "Signal a user error (or \"pilot error\")."
+  (unwind-protect
+      (apply #'tramp-error vec-or-proc 'user-error fmt-string arguments)
+    ;; Save exit.
+    (when (and (natnump tramp-error-show-message-timeout)
+              (not (zerop tramp-verbose))
+              ;; Do not show when flagged from outside.
+              (not non-essential)
+              ;; Show only when Emacs has started already.
+              (current-message))
+      (let ((enable-recursive-minibuffers t)
+           inhibit-message)
+       ;; `tramp-error' does not show messages.  So we must do it ourselves.
+       (apply #'message fmt-string arguments)
+       (discard-input)
+       (sit-for tramp-error-show-message-timeout)
+       ;; Reset timestamp.  It would be wrong after waiting for a while.
+       (when
+           (tramp-file-name-equal-p vec-or-proc (car tramp-current-connection))
+         (setcdr tramp-current-connection (current-time)))))))
+
+(defmacro tramp-with-demoted-errors (vec-or-proc format &rest body)
+  "Execute BODY while redirecting the error message to `tramp-message'.
+BODY is executed like wrapped by `with-demoted-errors'.  FORMAT
+is a format-string containing a %-sequence meaning to substitute
+the resulting error message."
+  (declare (indent 2) (debug (symbolp form body)))
+  (let ((err (make-symbol "err")))
+    `(condition-case-unless-debug ,err
+         (progn ,@body)
+       (error (tramp-message ,vec-or-proc 3 ,format ,err) nil))))
+
+(defun tramp-test-message (fmt-string &rest arguments)
+  "Emit a Tramp message according `default-directory'."
+  (declare (tramp-suppress-trace t))
+  (cond
+   ((tramp-tramp-file-p default-directory)
+    (apply #'tramp-message
+          (tramp-dissect-file-name default-directory) 0 fmt-string arguments))
+   ((tramp-file-name-p (car tramp-current-connection))
+    (apply #'tramp-message
+          (car tramp-current-connection) 0 fmt-string arguments))
+   (t (apply #'message fmt-string arguments))))
+
+(defun tramp-debug-button-action (button)
+  "Goto the linked message in debug buffer at place."
+  (declare (tramp-suppress-trace t))
+  (when (mouse-event-p last-input-event) (mouse-set-point last-input-event))
+  (when-let ((point (button-get button 'position)))
+    (goto-char point)))
+
+(define-button-type 'tramp-debug-button-type
+  'follow-link t
+  'mouse-face 'highlight
+  'action #'tramp-debug-button-action)
+
+(defun tramp-debug-link-messages (pos1 pos2)
+  "Set links for two messages in current buffer.
+The link buttons are in the verbositiy level substrings."
+  (declare (tramp-suppress-trace t))
+  (save-excursion
+    (let (beg1 end1 beg2 end2)
+      (goto-char pos1)
+      ;; Find positions.
+      (while (not (search-forward-regexp
+                  tramp-debug-outline-regexp (line-end-position) t))
+       (forward-line))
+      (setq beg1 (1- (match-beginning 3)) end1 (1+ (match-end 3)))
+      (goto-char pos2)
+      (while (not (search-forward-regexp
+                  tramp-debug-outline-regexp (line-end-position) t))
+       (forward-line))
+      (setq beg2 (1- (match-beginning 3)) end2 (1+ (match-end 3)))
+      ;; Create text buttons.
+      (make-text-button
+       beg1 end1 :type 'tramp-debug-button-type
+       'position (set-marker (make-marker) beg2)
+       'help-echo "mouse-2, RET: goto exit message")
+      (make-text-button
+       beg2 end2 :type 'tramp-debug-button-type
+       'position (set-marker (make-marker) beg1)
+       'help-echo "mouse-2, RET: goto entry message"))))
+
+(defvar tramp-debug-nesting ""
+  "Indicator for debug messages nested level.
+This shouldn't be changed globally, but let-bind where needed.")
+
+(defvar tramp-debug-message-fnh-function nil
+  "The used file name handler operation.
+Bound in `tramp-*-file-name-handler' functions.")
+
+(defun tramp-debug-message-buttonize (position)
+  "Buttonize function in current buffer, at next line starting after POSTION."
+  (declare (tramp-suppress-trace t))
+  (save-excursion
+    (goto-char position)
+    (while (not (search-forward-regexp
+                tramp-debug-outline-regexp (line-end-position) t))
+      (forward-line))
+    (let ((fun (intern (match-string 2))))
+      (make-text-button
+       (match-beginning 2) (match-end 2)
+       :type 'help-function-def
+       'help-args (list fun (symbol-file fun))))))
+
+;; This is used in `tramp-file-name-handler' and 
`tramp-*-maybe-open-connection'.
+(defmacro with-tramp-debug-message (vec message &rest body)
+  "Execute BODY, embedded with MESSAGE in the debug buffer of VEC.
+If BODY does not raise a debug message, MESSAGE is ignored."
+  (declare (indent 2) (debug t))
+  (let ((result (make-symbol "result")))
+    `(if tramp-debug-command-messages
+        (save-match-data
+          (let ((tramp-debug-nesting
+                 (concat tramp-debug-nesting "#"))
+                (buf (tramp-get-debug-buffer ,vec))
+                beg end ,result)
+            ;; Insert entry message.
+            (with-current-buffer buf
+              (setq beg (point))
+              (tramp-debug-message
+               ,vec "(4) %s %s ..." tramp-debug-nesting ,message)
+              (setq end (point)))
+            (unwind-protect
+                ;; Run BODY.
+                (setq tramp-debug-message-fnh-function nil
+                      ,result (progn ,@body))
+              (with-current-buffer buf
+                (if (= end (point-max))
+                    (progn
+                      (delete-region beg end)
+                      (when (bobp) (kill-buffer)))
+                  ;; Insert exit message.
+                  (tramp-debug-message
+                   ,vec "(5) %s %s ... %s" tramp-debug-nesting ,message 
,result)
+                  ;; Adapt file name handler function.
+                  (dolist (pos (list (point-max) end))
+                    (goto-char pos)
+                    (when (and tramp-debug-message-fnh-function
+                               (search-backward
+                                "tramp-file-name-handler"
+                                (line-beginning-position) t))
+                      (replace-match
+                       (symbol-name tramp-debug-message-fnh-function))
+                      (tramp-debug-message-buttonize
+                       (line-beginning-position))))
+                  ;; Link related messages.
+                  (goto-char (point-max))
+                  (tramp-debug-link-messages beg 
(line-beginning-position)))))))
+
+       ;; No special messages.
+       ,@body)))
+
+(add-hook 'tramp-unload-hook
+         (lambda ()
+           (unload-feature 'tramp-message 'force)))
+
+(provide 'tramp-message)
diff --git a/lisp/net/tramp-rclone.el b/lisp/net/tramp-rclone.el
index df46bd5e20e..c2b84845f68 100644
--- a/lisp/net/tramp-rclone.el
+++ b/lisp/net/tramp-rclone.el
@@ -175,8 +175,10 @@ Operations not mentioned here will be handled by the 
default Emacs primitives.")
 First arg specifies the OPERATION, second arg is a list of
 arguments to pass to the OPERATION."
   (if-let ((fn (assoc operation tramp-rclone-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (tramp--with-startup
@@ -377,53 +379,55 @@ connection if a previous connection has died for some 
reason."
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  (let ((host (tramp-file-name-host vec)))
-    (when (rassoc `(,host) (tramp-rclone-parse-device-names nil))
-      (if (tramp-string-empty-or-nil-p host)
-         (tramp-error vec 'file-error "Storage %s not connected" host))
-      ;; We need a process bound to the connection buffer.  Therefore,
-      ;; we create a dummy process.  Maybe there is a better solution?
-      (unless (get-buffer-process (tramp-get-connection-buffer vec))
-       (let ((p (make-network-process
-                 :name (tramp-get-connection-name vec)
-                 :buffer (tramp-get-connection-buffer vec)
-                 :server t :host 'local :service t :noquery t)))
-         (tramp-post-process-creation p vec)
-
-         ;; Set connection-local variables.
-         (tramp-set-connection-local-variables vec)))
-
-      ;; Create directory.
-      (unless (file-directory-p (tramp-fuse-mount-point vec))
-       (make-directory (tramp-fuse-mount-point vec) 'parents))
-
-      ;; Mount.  This command does not return, so we use 0 as
-      ;; DESTINATION of `tramp-call-process'.
-      (unless (tramp-fuse-mounted-p vec)
-       (apply
-        #'tramp-call-process
-        vec tramp-rclone-program nil 0 nil
-        "mount" (tramp-fuse-mount-spec vec)
-        (tramp-fuse-mount-point vec)
-        (tramp-get-method-parameter vec 'tramp-mount-args))
-       (while (not (file-exists-p (tramp-make-tramp-file-name vec 'noloc)))
-         (tramp-cleanup-connection vec 'keep-debug 'keep-password))
-
-       ;; Mark it as connected.
-       (add-to-list 'tramp-fuse-mount-points (tramp-file-name-unify vec))
-       (tramp-set-connection-property
-        (tramp-get-connection-process vec) "connected" t))))
-
-  ;; In `tramp-check-cached-permissions', the connection properties
-  ;; "{uid,gid}-{integer,string}" are used.  We set them to proper values.
-  (with-tramp-connection-property
-      vec "uid-integer" (tramp-get-local-uid 'integer))
-  (with-tramp-connection-property
-      vec "gid-integer" (tramp-get-local-gid 'integer))
-  (with-tramp-connection-property
-      vec "uid-string" (tramp-get-local-uid 'string))
-  (with-tramp-connection-property
-      vec "gid-string" (tramp-get-local-gid 'string)))
+  (with-tramp-debug-message vec "Opening connection"
+    (let ((host (tramp-file-name-host vec)))
+      (when (rassoc `(,host) (tramp-rclone-parse-device-names nil))
+       (if (tramp-string-empty-or-nil-p host)
+           (tramp-error vec 'file-error "Storage %s not connected" host))
+       ;; We need a process bound to the connection buffer.
+       ;; Therefore, we create a dummy process.  Maybe there is a
+       ;; better solution?
+       (unless (get-buffer-process (tramp-get-connection-buffer vec))
+         (let ((p (make-network-process
+                   :name (tramp-get-connection-name vec)
+                   :buffer (tramp-get-connection-buffer vec)
+                   :server t :host 'local :service t :noquery t)))
+           (tramp-post-process-creation p vec)
+
+           ;; Set connection-local variables.
+           (tramp-set-connection-local-variables vec)))
+
+       ;; Create directory.
+       (unless (file-directory-p (tramp-fuse-mount-point vec))
+         (make-directory (tramp-fuse-mount-point vec) 'parents))
+
+       ;; Mount.  This command does not return, so we use 0 as
+       ;; DESTINATION of `tramp-call-process'.
+       (unless (tramp-fuse-mounted-p vec)
+         (apply
+          #'tramp-call-process
+          vec tramp-rclone-program nil 0 nil
+          "mount" (tramp-fuse-mount-spec vec)
+          (tramp-fuse-mount-point vec)
+          (tramp-get-method-parameter vec 'tramp-mount-args))
+         (while (not (file-exists-p (tramp-make-tramp-file-name vec 'noloc)))
+           (tramp-cleanup-connection vec 'keep-debug 'keep-password))
+
+         ;; Mark it as connected.
+         (add-to-list 'tramp-fuse-mount-points (tramp-file-name-unify vec))
+         (tramp-set-connection-property
+          (tramp-get-connection-process vec) "connected" t))))
+
+    ;; In `tramp-check-cached-permissions', the connection properties
+    ;; "{uid,gid}-{integer,string}" are used.  We set them to proper values.
+    (with-tramp-connection-property
+       vec "uid-integer" (tramp-get-local-uid 'integer))
+    (with-tramp-connection-property
+       vec "gid-integer" (tramp-get-local-gid 'integer))
+    (with-tramp-connection-property
+       vec "uid-string" (tramp-get-local-uid 'string))
+    (with-tramp-connection-property
+       vec "gid-string" (tramp-get-local-gid 'string))))
 
 (defun tramp-rclone-send-command (vec &rest args)
   "Send a command to connection VEC.
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index f2cbb74acd2..0599f89655c 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -32,6 +32,7 @@
 ;;; Code:
 
 (eval-when-compile (require 'cl-lib))
+(require 'cl-seq)
 (require 'tramp)
 
 ;; `dired-*' declarations can be removed, starting with Emacs 29.1.
@@ -616,6 +617,13 @@ if (!$result) {
     $result = File::Spec->catpath($vol, File::Spec->catdir(@dirs), \"\");
 }
 
+if (-l $ARGV[0]) {
+    print \"t\\n\";
+    }
+else {
+    print \"nil\\n\";
+    }
+
 $result =~ s/\"/\\\\\"/g;
 print \"\\\"$result\\\"\\n\";
 ' \"$1\" %n"
@@ -626,16 +634,20 @@ characters need to be doubled.")
 
 (defconst tramp-perl-file-name-all-completions
   "%p -e '
-opendir(d, $ARGV[0]) || die(\"$ARGV[0]: $!\\nfail\\n\");
+($dir = $ARGV[0]) =~ s#/+$##;
+opendir(d, $dir) || die(\"$dir: $!\\nfail\\n\");
 @files = readdir(d); closedir(d);
+print \"(\\n\";
 foreach $f (@files) {
- if (-d \"$ARGV[0]/$f\") {
-  print \"$f/\\n\";
- }
- else {
-  print \"$f\\n\";
- }
+  ($p = $f) =~ s/\\\"/\\\\\\\"/g;
+  ($q = \"$dir/$f\") =~ s/\\\"/\\\\\\\"/g;
+  print \"(\",
+    ((-d \"$q\") ? \"\\\"$p/\\\" \\\"$q\\\" t\" : \"\\\"$p\\\" \\\"$q\\\" 
nil\"),
+    ((-e \"$q\") ? \" t\" : \" nil\"),
+    ((-r \"$q\") ? \" t\" : \" nil\"),
+    \")\\n\";
 }
+print \")\\n\";
 ' \"$1\" %n"
   "Perl script to produce output suitable for use with
 `file-name-all-completions' on the remote file system.
@@ -699,16 +711,35 @@ characters need to be doubled.")
     " '((%s%%%%N%s) %%%%h (%s%%%%U%s . %%%%u) (%s%%%%G%s . %%%%g)"
     " %%%%X %%%%Y %%%%Z %%%%s %s%%%%A%s t %%%%i -1)' \"$1\" %%n || echo nil) |"
     " sed -e 's/\"/\\\\\"/g' -e 's/%s/\"/g'")
-    tramp-stat-marker tramp-stat-marker ; %%N
-    tramp-stat-marker tramp-stat-marker ; %%U
-    tramp-stat-marker tramp-stat-marker ; %%G
-    tramp-stat-marker tramp-stat-marker ; %%A
-    tramp-stat-quoted-marker)
+   tramp-stat-marker tramp-stat-marker ; %%N
+   tramp-stat-marker tramp-stat-marker ; %%U
+   tramp-stat-marker tramp-stat-marker ; %%G
+   tramp-stat-marker tramp-stat-marker ; %%A
+   tramp-stat-quoted-marker)
   "Shell function to produce output suitable for use with `file-attributes'
 on the remote file system.
 Format specifiers are replaced by `tramp-expand-script', percent
 characters need to be doubled.")
 
+(defconst tramp-stat-file-attributes-with-selinux
+  (format
+   (concat
+    "(%%s -c"
+    " '((%s%%%%N%s) %%%%h (%s%%%%U%s . %%%%u) (%s%%%%G%s . %%%%g)"
+    " %%%%X %%%%Y %%%%Z %%%%s %s%%%%A%s t %%%%i -1 %s%%%%C%s)'"
+    " \"$1\" %%n || echo nil) |"
+    " sed -e 's/\"/\\\\\"/g' -e 's/%s/\"/g'")
+   tramp-stat-marker tramp-stat-marker ; %%N
+   tramp-stat-marker tramp-stat-marker ; %%U
+   tramp-stat-marker tramp-stat-marker ; %%G
+   tramp-stat-marker tramp-stat-marker ; %%A
+   tramp-stat-marker tramp-stat-marker ; %%C
+   tramp-stat-quoted-marker)
+  "Shell function to produce output suitable for use with `file-attributes'
+on the remote file system, including SELinux context.
+Format specifiers are replaced by `tramp-expand-script', percent
+characters need to be doubled.")
+
 (defconst tramp-perl-directory-files-and-attributes
   "%p -e '
 chdir($ARGV[0]) or printf(\"\\\"Cannot change to $ARGV[0]: $''!''\\\"\\n\"), 
exit();
@@ -787,6 +818,33 @@ characters need to be doubled.")
 Format specifiers are replaced by `tramp-expand-script', percent
 characters need to be doubled.")
 
+(defconst tramp-stat-directory-files-and-attributes-with-selinux
+  (format
+   (concat
+    ;; We must care about file names with spaces, or starting with
+    ;; "-"; this would confuse xargs.  "ls -aQ" might be a solution,
+    ;; but it does not work on all remote systems.  Therefore, we use
+    ;; \000 as file separator.  `tramp-sh--quoting-style-options' do
+    ;; not work for file names with spaces piped to "xargs".
+    ;; Apostrophes in the stat output are masked as
+    ;; `tramp-stat-marker', in order to make a proper shell escape of
+    ;; them in file names.
+    "cd \"$1\" && echo \"(\"; (%%l -a | tr '\\n\\r' '\\000\\000' |"
+    " xargs -0 %%s -c"
+    " '(%s%%%%n%s (%s%%%%N%s) %%%%h (%s%%%%U%s . %%%%u) (%s%%%%G%s . %%%%g) 
%%%%X %%%%Y %%%%Z %%%%s %s%%%%A%s t %%%%i -1 %s%%%%C%s)'"
+    " -- %%n | sed -e 's/\"/\\\\\"/g' -e 's/%s/\"/g'); echo \")\"")
+   tramp-stat-marker tramp-stat-marker ; %n
+   tramp-stat-marker tramp-stat-marker ; %N
+   tramp-stat-marker tramp-stat-marker ; %U
+   tramp-stat-marker tramp-stat-marker ; %G
+   tramp-stat-marker tramp-stat-marker ; %A
+   tramp-stat-marker tramp-stat-marker ; %C
+   tramp-stat-quoted-marker)
+  "Shell function implementing `directory-files-and-attributes' as Lisp
+`read'able output, including SELinux context.
+Format specifiers are replaced by `tramp-expand-script', percent
+characters need to be doubled.")
+
 (defconst tramp-perl-id
   "%p -e '
 use strict;
@@ -1015,28 +1073,23 @@ BEGIN {
 Format specifiers are replaced by `tramp-expand-script', percent
 characters need to be doubled.")
 
-(defconst tramp-vc-registered-read-file-names
+(defconst tramp-bundle-read-file-names
   "echo \"(\"
 while read file; do
     quoted=`echo \"$file\" | sed -e \"s/\\\"/\\\\\\\\\\\\\\\\\\\"/\"`
-    if %s \"$file\"; then
-       echo \"(\\\"$quoted\\\" \\\"file-exists-p\\\" t)\"
-    else
-       echo \"(\\\"$quoted\\\" \\\"file-exists-p\\\" nil)\"
-    fi
-    if %s \"$file\"; then
-       echo \"(\\\"$quoted\\\" \\\"file-readable-p\\\" t)\"
-    else
-       echo \"(\\\"$quoted\\\" \\\"file-readable-p\\\" nil)\"
-    fi
+    printf \"(%%b\" \"\\\"$quoted\\\"\"
+    if %s \"$file\"; then printf \" %%b\" t; else printf \" %%b\" nil; fi
+    if %s \"$file\"; then printf \" %%b\" t; else printf \" %%b\" nil; fi
+    if %s \"$file\"; then printf \" %%b)\n\" t; else printf \" %%b)\n\" nil; fi
 done
 echo \")\""
-  "Script to check existence of VC related files.
-It must be send formatted with two strings; the tests for file
-existence, and file readability.  Input shall be read via
-here-document, otherwise the command could exceed maximum length
-of command line.
-Format specifiers \"%s\" are replaced before the script is used.")
+  "Script to check file attributes of a bundle of files.
+It must be sent formatted with three strings; the tests for file
+existence, file readability, and file directory.  Input shall be
+read via here-document, otherwise the command could exceed
+maximum length of command line.
+Format specifiers \"%s\" are replaced before the script is used,
+percent characters need to be doubled.")
 
 ;; New handlers should be added here.
 ;;;###tramp-autoload
@@ -1145,19 +1198,17 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
        (concat "Making a symbolic link: "
               "ln(1) does not exist on the remote host"))))
 
-  (tramp-skeleton-handle-make-symbolic-link target linkname 
ok-if-already-exists
-    (and (tramp-send-command-and-check
-         v (format
-            "cd %s"
-            (tramp-shell-quote-argument (file-name-directory localname))))
-         (tramp-send-command-and-check
-         v (format
-            "%s -sf %s %s" (tramp-get-remote-ln v)
-            (tramp-shell-quote-argument target)
-            ;; The command could exceed PATH_MAX, so we use relative
-            ;; file names.
-            (tramp-shell-quote-argument
-              (concat "./" (file-name-nondirectory localname))))))))
+  (tramp-skeleton-make-symbolic-link target linkname ok-if-already-exists
+    (tramp-send-command-and-check
+     v (format
+       "cd %s && %s -sf %s %s"
+       (tramp-shell-quote-argument (file-name-directory localname))
+       (tramp-get-remote-ln v)
+       (tramp-shell-quote-argument target)
+       ;; The command could exceed PATH_MAX, so we use relative
+       ;; file names.
+       (tramp-shell-quote-argument
+         (concat "./" (file-name-nondirectory localname)))))))
 
 (defun tramp-sh-handle-file-truename (filename)
   "Like `file-truename' for Tramp files."
@@ -1166,12 +1217,20 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
      ;; Use GNU readlink --canonicalize-missing where available.
      ((tramp-get-remote-readlink v)
       (tramp-send-command-and-check
-       v (format "%s --canonicalize-missing %s"
-                (tramp-get-remote-readlink v)
-                (tramp-shell-quote-argument localname)))
+       v (format
+         (concat
+          "(if %s -h \"%s\"; then echo t; else echo nil; fi) && "
+          "%s --canonicalize-missing %s")
+         (tramp-get-test-command v)
+         (tramp-shell-quote-argument localname)
+         (tramp-get-remote-readlink v)
+         (tramp-shell-quote-argument localname)))
       (with-current-buffer (tramp-get-connection-buffer v)
        (goto-char (point-min))
-       (buffer-substring (point-min) (line-end-position))))
+       (tramp-set-file-property v localname "file-symlink-marker" (read 
(current-buffer)))
+       ;; We cannote call `read', the file name isn't quoted.
+       (forward-line)
+       (buffer-substring (point) (line-end-position))))
 
      ;; Use Perl implementation.
      ((and (tramp-get-remote-perl v)
@@ -1179,9 +1238,13 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
           (tramp-get-connection-property v "perl-cwd-realpath"))
       (tramp-maybe-send-script
        v tramp-perl-file-truename "tramp_perl_file_truename")
-      (tramp-send-command-and-read
+      (tramp-send-command-and-check
        v (format "tramp_perl_file_truename %s"
-                (tramp-shell-quote-argument localname))))
+                (tramp-shell-quote-argument localname)))
+      (with-current-buffer (tramp-get-connection-buffer v)
+        (goto-char (point-min))
+       (tramp-set-file-property v localname "file-symlink-marker" (read 
(current-buffer)))
+       (read (current-buffer))))
 
      ;; Do it yourself.
      (t (tramp-file-local-name
@@ -1232,10 +1295,10 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
   (let (symlinkp dirp
        res-inode res-filemodes res-numlinks
        res-uid-string res-gid-string res-uid-integer res-gid-integer
-       res-size res-symlink-target)
+       res-size res-symlink-target res-context)
     (tramp-message vec 5 "file attributes with ls: %s" localname)
-    ;; We cannot send all three commands combined, it could exceed
-    ;; NAME_MAX or PATH_MAX.  Happened on macOS, for example.
+    ;; We cannot send both commands combined, it could exceed NAME_MAX
+    ;; or PATH_MAX.  Happened on macOS, for example.
     (when (tramp-send-command-and-check
            vec
            (format "cd %s && (%s %s || %s -h %s)"
@@ -1254,13 +1317,14 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
                      (file-name-nondirectory localname)))))
       (tramp-send-command
        vec
-       (format "%s -ild %s %s; %s -lnd %s %s"
+       (format "%s -ild %s %s; %s -lnd%s %s %s"
                (tramp-get-ls-command vec)
                ;; On systems which have no quoting style, file names
                ;; with special characters could fail.
                (tramp-sh--quoting-style-options vec)
                (tramp-shell-quote-argument localname)
                (tramp-get-ls-command vec)
+              (if (tramp-remote-selinux-p vec) "Z" "")
                ;; On systems which have no quoting style, file names
                ;; with special characters could fail.
                (tramp-sh--quoting-style-options vec)
@@ -1310,6 +1374,10 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
            (setq res-uid-integer tramp-unknown-id-integer))
           (unless (numberp res-gid-integer)
            (setq res-gid-integer tramp-unknown-id-integer))
+         ;; ... SELinux context
+         (when (tramp-remote-selinux-p vec)
+           (setq res-context (read (current-buffer))
+                 res-context (symbol-name res-context)))
 
          ;; Return data gathered.
           (list
@@ -1336,7 +1404,10 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
            ;; 10. Inode number.
            res-inode
            ;; 11. Device number.  Will be replaced by a virtual device number.
-           -1))))))
+           -1
+          ;; 12. SELinux context.  Will be extracted in
+          ;; `tramp-convert-file-attributes'.
+          res-context))))))
 
 (defun tramp-do-file-attributes-with-perl (vec localname)
   "Implement `file-attributes' for Tramp files using a Perl script."
@@ -1350,11 +1421,20 @@ Operations not mentioned here will be handled by the 
normal Emacs functions.")
 (defun tramp-do-file-attributes-with-stat (vec localname)
   "Implement `file-attributes' for Tramp files using stat(1) command."
   (tramp-message vec 5 "file attributes with stat: %s" localname)
-  (tramp-maybe-send-script
-   vec tramp-stat-file-attributes "tramp_stat_file_attributes")
-  (tramp-send-command-and-read
-   vec (format "tramp_stat_file_attributes %s"
-              (tramp-shell-quote-argument localname))))
+  (cond
+   ((tramp-remote-selinux-p vec)
+    (tramp-maybe-send-script
+     vec tramp-stat-file-attributes-with-selinux
+     "tramp_stat_file_attributes_with_selinux")
+    (tramp-send-command-and-read
+     vec (format "tramp_stat_file_attributes_with_selinux %s"
+                (tramp-shell-quote-argument localname))))
+   (t
+    (tramp-maybe-send-script
+     vec tramp-stat-file-attributes "tramp_stat_file_attributes")
+    (tramp-send-command-and-read
+     vec (format "tramp_stat_file_attributes %s"
+                (tramp-shell-quote-argument localname))))))
 
 (defun tramp-sh-handle-set-visited-file-modtime (&optional time-list)
   "Like `set-visited-file-modtime' for Tramp files."
@@ -1549,7 +1629,7 @@ ID-FORMAT valid values are `string' and `integer'."
              (tramp-shell-quote-argument localname))))))))
 
 (defun tramp-remote-selinux-p (vec)
-  "Check, whether SELINUX is enabled on the remote host."
+  "Check, whether SELinux is enabled on the remote host."
   (with-tramp-connection-property (tramp-get-process vec) "selinux-p"
     (tramp-send-command-and-check vec "selinuxenabled")))
 
@@ -1571,7 +1651,7 @@ ID-FORMAT valid values are `string' and `integer'."
                       (tramp-shell-quote-argument localname))))
          (with-current-buffer (tramp-get-connection-buffer v)
            (goto-char (point-min))
-           (when (re-search-forward regexp (line-end-position) t)
+           (when (search-forward-regexp regexp (line-end-position) t)
              (setq context (list (match-string 1) (match-string 2)
                                  (match-string 3) (match-string 4))))))
        ;; Return the context.
@@ -1675,8 +1755,8 @@ ID-FORMAT valid values are `string' and `integer'."
        (with-tramp-file-property v localname "file-directory-p"
          (if-let
              ((truename (tramp-get-file-property v localname "file-truename"))
-              (attr-p (tramp-file-property-p
-                       v (tramp-file-local-name truename) "file-attributes")))
+              ((tramp-file-property-p
+                v (tramp-file-local-name truename) "file-attributes")))
              (eq (file-attribute-type
                   (tramp-get-file-property
                    v (tramp-file-local-name truename) "file-attributes"))
@@ -1688,9 +1768,9 @@ ID-FORMAT valid values are `string' and `integer'."
   (with-parsed-tramp-file-name (expand-file-name filename) nil
     (with-tramp-file-property v localname "file-writable-p"
       (if (file-exists-p filename)
+         ;; Examine `file-attributes' cache to see if request can be
+         ;; satisfied without remote operation.
          (if (tramp-file-property-p v localname "file-attributes")
-             ;; Examine `file-attributes' cache to see if request can
-             ;; be satisfied without remote operation.
              (tramp-check-cached-permissions v ?w)
            (tramp-run-test v "-w" localname))
        ;; If file doesn't exist, check if directory is writable.
@@ -1752,12 +1832,21 @@ ID-FORMAT valid values are `string' and `integer'."
 (defun tramp-do-directory-files-and-attributes-with-stat (vec localname)
   "Implement `directory-files-and-attributes' for Tramp files with stat(1) 
command."
   (tramp-message vec 5 "directory-files-and-attributes with stat: %s" 
localname)
-  (tramp-maybe-send-script
-   vec tramp-stat-directory-files-and-attributes
-   "tramp_stat_directory_files_and_attributes")
-  (tramp-send-command-and-read
-   vec (format "tramp_stat_directory_files_and_attributes %s"
-              (tramp-shell-quote-argument localname))))
+  (cond
+   ((tramp-remote-selinux-p vec)
+    (tramp-maybe-send-script
+     vec tramp-stat-directory-files-and-attributes-with-selinux
+     "tramp_stat_directory_files_and_attributes_with_selinux")
+    (tramp-send-command-and-read
+     vec (format "tramp_stat_directory_files_and_attributes_with_selinux %s"
+                (tramp-shell-quote-argument localname))))
+   (t
+    (tramp-maybe-send-script
+     vec tramp-stat-directory-files-and-attributes
+     "tramp_stat_directory_files_and_attributes")
+    (tramp-send-command-and-read
+     vec (format "tramp_stat_directory_files_and_attributes %s"
+                (tramp-shell-quote-argument localname))))))
 
 ;; This function should return "foo/" for directories and "bar" for
 ;; files.
@@ -1775,34 +1864,48 @@ ID-FORMAT valid values are `string' and `integer'."
             ;; Get a list of directories and files, including
             ;; reliably tagging the directories with a trailing "/".
             ;; Because I rock.  --daniel@danann.net
-            (when (tramp-send-command-and-check
-                   v
-                   (if (tramp-get-remote-perl v)
-                       (progn
-                         (tramp-maybe-send-script
-                          v tramp-perl-file-name-all-completions
-                          "tramp_perl_file_name_all_completions")
-                         (format "tramp_perl_file_name_all_completions %s"
-                                 (tramp-shell-quote-argument localname)))
-
-                     (format (concat
-                              "cd %s 2>&1 && %s -a 2>%s"
-                              " | while IFS= read f; do"
-                              " if %s -d \"$f\" 2>%s;"
-                              " then \\echo \"$f/\"; else \\echo \"$f\"; fi;"
-                              " done")
-                             (tramp-shell-quote-argument localname)
-                             (tramp-get-ls-command v)
-                             (tramp-get-remote-null-device v)
-                             (tramp-get-test-command v)
-                             (tramp-get-remote-null-device v))))
-
-              ;; Now grab the output.
-              (with-current-buffer (tramp-get-buffer v)
-                (goto-char (point-max))
-                (while (zerop (forward-line -1))
-                  (push (buffer-substring (point) (line-end-position)) 
result)))
-              result)))))))))
+            (if (tramp-get-remote-perl v)
+                (progn
+                  (tramp-maybe-send-script
+                   v tramp-perl-file-name-all-completions
+                   "tramp_perl_file_name_all_completions")
+                  (setq result
+                        (tramp-send-command-and-read
+                         v (format "tramp_perl_file_name_all_completions %s"
+                                   (tramp-shell-quote-argument localname))
+                         'noerror))
+                  ;; Cached values.
+                  (dolist (elt result)
+                    (tramp-set-file-property
+                     v (cadr elt) "file-directory-p" (nth 2 elt))
+                    (tramp-set-file-property
+                     v (cadr elt) "file-exists-p" (nth 3 elt))
+                    (tramp-set-file-property
+                     v (cadr elt) "file-readable-p" (nth 4 elt)))
+                  ;; Result.
+                  (mapcar #'car result))
+
+              ;; Do it with ls.
+              (when (tramp-send-command-and-check
+                     v (format (concat
+                                "cd %s 2>&1 && %s -a 2>%s"
+                                " | while IFS= read f; do"
+                                " if %s -d \"$f\" 2>%s;"
+                                " then echo \"$f/\"; else echo \"$f\"; fi;"
+                                " done")
+                               (tramp-shell-quote-argument localname)
+                               (tramp-get-ls-command v)
+                               (tramp-get-remote-null-device v)
+                               (tramp-get-test-command v)
+                               (tramp-get-remote-null-device v)))
+
+                ;; Now grab the output.
+                (with-current-buffer (tramp-get-buffer v)
+                  (goto-char (point-max))
+                  (while (zerop (forward-line -1))
+                    (push
+                     (buffer-substring (point) (line-end-position)) result)))
+                result))))))))))
 
 ;; cp, mv and ln
 
@@ -1943,7 +2046,7 @@ OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists 
already.
 KEEP-DATE means to make sure that NEWNAME has the same timestamp
 as FILENAME.  PRESERVE-UID-GID, when non-nil, instructs to keep
 the uid and gid if both files are on the same host.
-PRESERVE-EXTENDED-ATTRIBUTES activates selinux and acl commands.
+PRESERVE-EXTENDED-ATTRIBUTES activates SELinux and ACL commands.
 
 This function is invoked by `tramp-sh-handle-copy-file' and
 `tramp-sh-handle-rename-file'.  It is an error if OP is neither
@@ -2152,7 +2255,7 @@ the uid and gid from FILENAME."
                (or
                 (and keep-date
                      ;; Mask cp -f error.
-                     (re-search-forward
+                     (search-forward-regexp
                       tramp-operation-not-permitted-regexp nil t))
                 cmd-result)
              (tramp-error-with-buffer
@@ -2612,7 +2715,7 @@ The method used must be an out-of-band method."
        (save-restriction
          (narrow-to-region beg-marker end-marker)
          ;; Check for "--dired" output.
-         (when (re-search-backward
+         (when (search-backward-regexp
                 (rx bol "//DIRED//" (+ blank) (group (+ nonl)) eol)
                 nil 'noerror)
            (let ((beg (match-beginning 1))
@@ -2627,7 +2730,7 @@ The method used must be an out-of-band method."
                      (put-text-property start end 'dired-filename t))))))
          ;; Remove trailing lines.
          (goto-char (point-max))
-         (while (re-search-backward (rx bol "//") nil 'noerror)
+         (while (search-backward-regexp (rx bol "//") nil 'noerror)
            (forward-line 1)
            (delete-region (match-beginning 0) (point))))
        ;; Reset multibyte if needed.
@@ -2639,7 +2742,7 @@ The method used must be an out-of-band method."
          (unless (tramp-compat-string-search
                   "color" (tramp-get-connection-property v "ls" ""))
            (goto-char (point-min))
-           (while (re-search-forward ansi-color-control-seq-regexp nil t)
+           (while (search-forward-regexp ansi-color-control-seq-regexp nil t)
              (replace-match "")))
 
           ;; Now decode what read if necessary.  Stolen from 
`insert-directory'.
@@ -2686,7 +2789,8 @@ The method used must be an out-of-band method."
          ;; Try to insert the amount of free space.
          (goto-char (point-min))
          ;; First find the line to put it on.
-         (when (and (re-search-forward (rx bol (group (* blank) "total")) nil 
t)
+         (when (and (search-forward-regexp
+                     (rx bol (group (* blank) "total")) nil t)
                     ;; Emacs 29.1 or later.
                     (not (fboundp 'dired--insert-disk-space)))
            (when-let ((available (get-free-disk-space ".")))
@@ -2715,7 +2819,8 @@ the result will be a local, non-Tramp, file name."
   ;; there could be the false positive "/:".
   (if (or (and (eq system-type 'windows-nt)
               (string-match-p
-               (rx bol (| (: alpha ":") (: (literal null-device) eol))) name))
+               (rx bol (| (: alpha ":") (: (literal (or null-device "")) eol)))
+               name))
          (and (not (tramp-tramp-file-p name))
               (not (tramp-tramp-file-p dir))))
       (tramp-run-real-handler #'expand-file-name (list name dir))
@@ -2745,7 +2850,8 @@ the result will be a local, non-Tramp, file name."
            ;; appropriate either, because ssh and companions might
            ;; use a user name from the config file.
            (when (and (tramp-string-empty-or-nil-p uname)
-                      (string-match-p (rx bos "su" (? "do") eos) method))
+                      (string-match-p
+                       (rx bos (| "su" "sudo" "doas" "ksu") eos) method))
              (setq uname user))
            (when (setq hname (tramp-get-home-directory v uname))
              (setq localname (concat hname fname)))))
@@ -3345,15 +3451,6 @@ implementation will be used."
 
        (let* ((modes (tramp-default-file-modes
                       filename (and (eq mustbenew 'excl) 'nofollow)))
-              ;; We use this to save the value of
-              ;; `last-coding-system-used' after writing the tmp
-              ;; file.  At the end of the function, we set
-              ;; `last-coding-system-used' to this saved value.  This
-              ;; way, any intermediary coding systems used while
-              ;; talking to the remote shell or suchlike won't hose
-              ;; this variable.  This approach was snarfed from
-              ;; ange-ftp.el.
-              coding-system-used
               ;; Write region into a tmp file.  This isn't really
               ;; needed if we use an encoding function, but currently
               ;; we use it always because this makes the logic
@@ -3383,11 +3480,11 @@ implementation will be used."
              ((error quit)
               (setq tramp-temp-buffer-file-name nil)
               (delete-file tmpfile)
-              (signal (car err) (cdr err))))
+              (signal (car err) (cdr err)))))
 
-           ;; Now, `last-coding-system-used' has the right value.
-           ;; Remember it.
-           (setq coding-system-used last-coding-system-used))
+         ;; Now, `last-coding-system-used' has the right value.
+         ;; Remember it.
+         (setq coding-system-used last-coding-system-used)
 
          ;; The permissions of the temporary file should be set.  If
          ;; FILENAME does not exist (eq modes nil) it has been
@@ -3517,11 +3614,41 @@ implementation will be used."
               v 'file-error
               (concat "Method `%s' should specify both encoding and "
                       "decoding command or an scp program")
-              method))))
+              method)))))))))
+
+(defun tramp-bundle-read-file-names (vec files)
+  "Read file attributes of FILES and with one command fill the cache.
+FILES must be the local names only.  The cache attributes to be
+filled are described in `tramp-bundle-read-file-names'."
+  (when files
+    (tramp-maybe-send-script
+     vec
+     (format tramp-bundle-read-file-names
+            (tramp-get-file-exists-command vec)
+            (format "%s -r" (tramp-get-test-command vec))
+            (format "%s -d" (tramp-get-test-command vec)))
+     "tramp_bundle_read_file_names")
+
+    (dolist
+       (elt
+        (ignore-errors
+          ;; We cannot use `tramp-send-command-and-read', because
+          ;; this does not cooperate well with heredoc documents.
+          (tramp-send-command
+           vec
+           (format
+            "tramp_bundle_read_file_names <<'%s'\n%s\n%s\n"
+            tramp-end-of-heredoc
+            (mapconcat #'tramp-shell-quote-argument files "\n")
+            tramp-end-of-heredoc))
+          (with-current-buffer (tramp-get-connection-buffer vec)
+            ;; Read the expression.
+            (goto-char (point-min))
+            (read (current-buffer)))))
 
-         ;; Make `last-coding-system-used' have the right value.
-         (when coding-system-used
-           (setq last-coding-system-used coding-system-used)))))))
+      (tramp-set-file-property vec (car elt) "file-exists-p" (nth 1 elt))
+      (tramp-set-file-property vec (car elt) "file-readable-p" (nth 2 elt))
+      (tramp-set-file-property vec (car elt) "file-directory-p" (nth 3 elt)))))
 
 (defvar tramp-vc-registered-file-names nil
   "List used to collect file names, which are checked during `vc-registered'.")
@@ -3568,36 +3695,7 @@ implementation will be used."
              (tramp-message v 10 "\n%s" tramp-vc-registered-file-names)
 
              ;; Send just one command, in order to fill the cache.
-             (when tramp-vc-registered-file-names
-               (tramp-maybe-send-script
-                v
-                (format tramp-vc-registered-read-file-names
-                        (tramp-get-file-exists-command v)
-                        (format "%s -r" (tramp-get-test-command v)))
-                "tramp_vc_registered_read_file_names")
-
-               (dolist
-                   (elt
-                    (ignore-errors
-                      ;; We cannot use `tramp-send-command-and-read',
-                      ;; because this does not cooperate well with
-                      ;; heredoc documents.
-                      (tramp-send-command
-                       v
-                       (format
-                        "tramp_vc_registered_read_file_names <<'%s'\n%s\n%s\n"
-                        tramp-end-of-heredoc
-                        (mapconcat #'tramp-shell-quote-argument
-                                   tramp-vc-registered-file-names
-                                   "\n")
-                        tramp-end-of-heredoc))
-                      (with-current-buffer (tramp-get-connection-buffer v)
-                        ;; Read the expression.
-                        (goto-char (point-min))
-                        (read (current-buffer)))))
-
-                 (tramp-set-file-property
-                  v (car elt) (cadr elt) (cadr (cdr elt))))))
+             (tramp-bundle-read-file-names v tramp-vc-registered-file-names))
 
            ;; Second run.  Now all `file-exists-p' or `file-readable-p'
            ;; calls shall be answered from the file cache.  We unset
@@ -3638,8 +3736,10 @@ implementation will be used."
   "Invoke remote-shell Tramp file name handler.
 Fall back to normal file name handler if no Tramp handler exists."
   (if-let ((fn (assoc operation tramp-sh-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (defun tramp-sh-file-name-handler-p (vec)
@@ -4156,14 +4256,17 @@ file exists and nonzero exit status otherwise."
                     vec (format "%s %s" result existing))
                    (not (tramp-send-command-and-check
                          vec (format "%s %s" result nonexistent)))))
+            ;; We cannot use `tramp-get-ls-command', this results in an 
infloop.
+            ;; (Bug#65321)
             (ignore-errors
-              (and (setq result (format "%s -d" (tramp-get-ls-command vec)))
+              (and (setq result (format "ls -d >%s" 
(tramp-get-remote-null-device vec)))
                    (tramp-send-command-and-check
                     vec (format "%s %s" result existing))
                    (not (tramp-send-command-and-check
                          vec (format "%s %s" result nonexistent))))))
       (tramp-error
        vec 'file-error "Couldn't find command to check if file exists"))
+    (tramp-set-file-property vec existing "file-exists-p" t)
     result))
 
 (defun tramp-get-sh-extra-args (shell)
@@ -4250,6 +4353,8 @@ file exists and nonzero exit status otherwise."
        "`tramp-histfile-override' uses invalid file `%s'"
        tramp-histfile-override))
 
+    (tramp-flush-connection-property
+     (tramp-get-connection-process vec) "scripts")
     (tramp-set-connection-property
      (tramp-get-connection-process vec) "remote-shell" shell)))
 
@@ -4331,12 +4436,10 @@ process to set up.  VEC specifies the connection."
     (tramp-open-shell vec (tramp-get-method-parameter vec 'tramp-remote-shell))
     (tramp-message vec 5 "Setting up remote shell environment")
 
-    ;; Disable line editing.
-    (tramp-send-command vec "set +o vi +o emacs" t)
-
-    ;; Dump option settings in the traces.
-    (when (>= tramp-verbose 9)
-      (tramp-send-command vec "set -o" t))
+    ;; Disable line editing.  Dump option settings in the traces.
+    (tramp-send-command
+     vec
+     (if (>= tramp-verbose 9) "set +o vi +o emacs -o" "set +o vi +o emacs") t)
 
     ;; Disable echo expansion.
     (tramp-send-command
@@ -4867,12 +4970,17 @@ Goes through the list `tramp-inline-compress-commands'."
          " -o ControlPath="
          (if (eq tramp-use-connection-share 'suppress)
              "none"
-           ;; Hashed tokens are introduced in OpenSSH 6.7.
-          (expand-file-name
-           (if (tramp-ssh-option-exists-p vec "ControlPath=tramp.%C")
-               "tramp.%%C" "tramp.%%r@%%h:%%p")
-           (or small-temporary-file-directory
-               tramp-compat-temporary-file-directory)))
+           ;; Hashed tokens are introduced in OpenSSH 6.7.  On macOS
+           ;; we cannot use an absolute file name, it is too long.
+           ;; See Bug#19702.
+          (if (eq system-type 'darwin)
+              (if (tramp-ssh-option-exists-p vec "ControlPath=tramp.%C")
+                  "tramp.%%C" "tramp.%%r@%%h:%%p")
+            (expand-file-name
+             (if (tramp-ssh-option-exists-p vec "ControlPath=tramp.%C")
+                 "tramp.%%C" "tramp.%%r@%%h:%%p")
+             (or small-temporary-file-directory
+                 tramp-compat-temporary-file-directory))))
 
          ;; ControlPersist option is introduced in OpenSSH 5.6.
         (when (and (not (eq tramp-use-connection-share 'suppress))
@@ -5032,235 +5140,240 @@ connection if a previous connection has died for some 
reason."
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  (let ((p (tramp-get-connection-process vec))
-       (process-name (tramp-get-connection-property vec "process-name"))
-       (process-environment (copy-sequence process-environment))
-       (pos (with-current-buffer (tramp-get-connection-buffer vec) (point))))
-
-    ;; If Tramp opens the same connection within a short time frame,
-    ;; there is a problem.  We shall signal this.
-    (unless (or (process-live-p p)
-                (and (processp p) (not non-essential))
-               (not (tramp-file-name-equal-p
-                     vec (car tramp-current-connection)))
-               (time-less-p
-                (time-since (cdr tramp-current-connection))
-                (or tramp-connection-min-time-diff 0)))
-      (throw 'suppress 'suppress))
-
-    ;; If too much time has passed since last command was sent, look
-    ;; whether process is still alive.  If it isn't, kill it.  When
-    ;; using ssh, it can sometimes happen that the remote end has hung
-    ;; up but the local ssh client doesn't recognize this until it
-    ;; tries to send some data to the remote end.  So that's why we
-    ;; try to send a command from time to time, then look again
-    ;; whether the process is really alive.
-    (condition-case nil
-       (when (and (time-less-p
-                   60 (time-since
-                       (tramp-get-connection-property p "last-cmd-time" 0)))
-                  (process-live-p p))
-         (tramp-send-command vec "echo are you awake" t t)
-         (unless (and (process-live-p p)
-                      (tramp-wait-for-output p 10))
-           ;; The error will be caught locally.
-           (tramp-error vec 'file-error "Awake did fail")))
-      (file-error
-       (tramp-cleanup-connection vec t)
-       (setq p nil)))
-
-    ;; New connection must be opened.
-    (condition-case err
-       (unless (process-live-p p)
-         (with-tramp-progress-reporter
-             vec 3
-             (if (tramp-string-empty-or-nil-p (tramp-file-name-user vec))
-                 (format "Opening connection %s for %s using %s"
+  (with-tramp-debug-message vec "Opening connection"
+    (let ((p (tramp-get-connection-process vec))
+         (process-name (tramp-get-connection-property vec "process-name"))
+         (process-environment (copy-sequence process-environment))
+         (pos (with-current-buffer (tramp-get-connection-buffer vec) (point))))
+
+      ;; If Tramp opens the same connection within a short time frame,
+      ;; there is a problem.  We shall signal this.
+      (unless (or (process-live-p p)
+                  (and (processp p) (not non-essential))
+                 (not (tramp-file-name-equal-p
+                       vec (car tramp-current-connection)))
+                 (time-less-p
+                  (time-since (cdr tramp-current-connection))
+                  (or tramp-connection-min-time-diff 0)))
+       (throw 'suppress 'suppress))
+
+      ;; If too much time has passed since last command was sent, look
+      ;; whether process is still alive.  If it isn't, kill it.  When
+      ;; using ssh, it can sometimes happen that the remote end has
+      ;; hung up but the local ssh client doesn't recognize this until
+      ;; it tries to send some data to the remote end.  So that's why
+      ;; we try to send a command from time to time, then look again
+      ;; whether the process is really alive.
+      (condition-case nil
+         (when (and (time-less-p
+                     60 (time-since
+                         (tramp-get-connection-property p "last-cmd-time" 0)))
+                    (process-live-p p))
+           (tramp-send-command vec "echo are you awake" t t)
+           (unless (and (process-live-p p)
+                        (tramp-wait-for-output p 10))
+             ;; The error will be caught locally.
+             (tramp-error vec 'file-error "Awake did fail")))
+       (file-error
+        (tramp-cleanup-connection vec t)
+        (setq p nil)))
+
+      ;; New connection must be opened.
+      (condition-case err
+         (unless (process-live-p p)
+           (with-tramp-progress-reporter
+               vec 3
+               (if (tramp-string-empty-or-nil-p (tramp-file-name-user vec))
+                   (format "Opening connection %s for %s using %s"
+                           process-name
+                           (tramp-file-name-host vec)
+                           (tramp-file-name-method vec))
+                 (format "Opening connection %s for %s@%s using %s"
                          process-name
+                         (tramp-file-name-user vec)
                          (tramp-file-name-host vec)
-                         (tramp-file-name-method vec))
-               (format "Opening connection %s for %s@%s using %s"
-                       process-name
-                       (tramp-file-name-user vec)
-                       (tramp-file-name-host vec)
-                       (tramp-file-name-method vec)))
-
-           (catch 'uname-changed
-             ;; Start new process.
-             (when (and p (processp p))
-               (delete-process p))
-             (setenv "TERM" tramp-terminal-type)
-             (setenv "LC_ALL" (tramp-get-local-locale vec))
-             (if (stringp tramp-histfile-override)
-                 (setenv "HISTFILE" tramp-histfile-override)
-               (if tramp-histfile-override
-                   (progn
-                     (setenv "HISTFILE")
-                     (setenv "HISTFILESIZE" "0")
-                     (setenv "HISTSIZE" "0"))))
-             (setenv "PROMPT_COMMAND")
-             (setenv "PS1" tramp-initial-end-of-output)
-              (unless (stringp tramp-encoding-shell)
-                (tramp-error vec 'file-error "`tramp-encoding-shell' not set"))
-             (let* ((current-host tramp-system-name)
-                    (target-alist (tramp-compute-multi-hops vec))
-                    (previous-hop tramp-null-hop)
-                    ;; We will apply `tramp-ssh-controlmaster-options'
-                    ;; only for the first hop.
-                    (options (tramp-ssh-controlmaster-options vec))
-                    (process-connection-type tramp-process-connection-type)
-                    (process-adaptive-read-buffering nil)
-                    ;; There are unfortunate settings for "cmdproxy" on
-                    ;; W32 systems.
-                    (process-coding-system-alist nil)
-                    (coding-system-for-read nil)
-                    (extra-args (tramp-get-sh-extra-args tramp-encoding-shell))
-                    ;; This must be done in order to avoid our file
-                    ;; name handler.
-                    (p (let ((default-directory
-                               tramp-compat-temporary-file-directory))
-                         (apply
-                          #'start-process
-                          (tramp-get-connection-name vec)
-                          (tramp-get-connection-buffer vec)
-                          (append
-                           (list tramp-encoding-shell)
-                           (and extra-args (split-string extra-args))
-                           (and tramp-encoding-command-interactive
-                                (list tramp-encoding-command-interactive)))))))
-
-               ;; This is neded for ssh or PuTTY based processes, and
-               ;; only if the respective options are set.  Perhaps,
-               ;; the setting could be more fine-grained.
-               ;; (process-put p 'tramp-shared-socket t)
-               ;; Set sentinel.  Initialize variables.
-               (set-process-sentinel p #'tramp-process-sentinel)
-               (tramp-post-process-creation p vec)
-               (setq tramp-current-connection (cons vec (current-time)))
-
-               ;; Set connection-local variables.
-               (tramp-set-connection-local-variables vec)
-
-               ;; Check whether process is alive.
-               (tramp-barf-if-no-shell-prompt
-                p 10
-                "Couldn't find local shell prompt for %s" tramp-encoding-shell)
-
-               ;; Now do all the connections as specified.
-               (while target-alist
-                 (let* ((hop (car target-alist))
-                        (l-method (tramp-file-name-method hop))
-                        (l-user (tramp-file-name-user hop))
-                        (l-domain (tramp-file-name-domain hop))
-                        (l-host (tramp-file-name-host hop))
-                        (l-port (tramp-file-name-port hop))
-                        (remote-shell
-                         (tramp-get-method-parameter hop 'tramp-remote-shell))
-                        (extra-args (tramp-get-sh-extra-args remote-shell))
-                        (async-args
-                         (flatten-tree
-                          (tramp-get-method-parameter hop 'tramp-async-args)))
-                        (connection-timeout
-                         (tramp-get-method-parameter
-                          hop 'tramp-connection-timeout))
-                        (command
-                         (tramp-get-method-parameter hop 'tramp-login-program))
-                        ;; We don't create the temporary file.  In
-                        ;; fact, it is just a prefix for the
-                        ;; ControlPath option of ssh; the real
-                        ;; temporary file has another name, and it is
-                        ;; created and protected by ssh.  It is also
-                        ;; removed by ssh when the connection is
-                        ;; closed.  The temporary file name is cached
-                        ;; in the main connection process, therefore
-                        ;; we cannot use `tramp-get-connection-process'.
-                        (tmpfile
-                         (with-tramp-connection-property
-                             (tramp-get-process vec) "temp-file"
-                           (tramp-compat-make-temp-name)))
-                        r-shell)
-
-                   ;; Check, whether there is a restricted shell.
-                   (dolist (elt tramp-restricted-shell-hosts-alist)
-                     (when (string-match-p elt current-host)
-                       (setq r-shell t)))
-                   (setq current-host l-host)
-
-                   ;; Set password prompt vector.
-                   (tramp-set-connection-property
-                    p "password-vector"
-                    (if (tramp-get-method-parameter
-                         hop 'tramp-password-previous-hop)
-                        (let ((pv (copy-tramp-file-name previous-hop)))
-                          (setf (tramp-file-name-method pv) l-method)
-                          pv)
-                      (make-tramp-file-name
-                       :method l-method :user l-user :domain l-domain
-                       :host l-host :port l-port)))
-
-                   ;; Set session timeout.
-                   (when (tramp-get-method-parameter
-                          hop 'tramp-session-timeout)
+                         (tramp-file-name-method vec)))
+
+             (catch 'uname-changed
+               ;; Start new process.
+               (when (and p (processp p))
+                 (delete-process p))
+               (setenv "TERM" tramp-terminal-type)
+               (setenv "LC_ALL" (tramp-get-local-locale vec))
+               (if (stringp tramp-histfile-override)
+                   (setenv "HISTFILE" tramp-histfile-override)
+                 (if tramp-histfile-override
+                     (progn
+                       (setenv "HISTFILE")
+                       (setenv "HISTFILESIZE" "0")
+                       (setenv "HISTSIZE" "0"))))
+               (setenv "PROMPT_COMMAND")
+               (setenv "PS1" tramp-initial-end-of-output)
+               (unless (stringp tramp-encoding-shell)
+                  (tramp-error vec 'file-error "`tramp-encoding-shell' not 
set"))
+               (let* ((current-host tramp-system-name)
+                      (target-alist (tramp-compute-multi-hops vec))
+                      (previous-hop tramp-null-hop)
+                      ;; We will apply `tramp-ssh-controlmaster-options'
+                      ;; only for the first hop.
+                      (options (tramp-ssh-controlmaster-options vec))
+                      (process-connection-type tramp-process-connection-type)
+                      (process-adaptive-read-buffering nil)
+                      ;; There are unfortunate settings for
+                      ;; "cmdproxy" on W32 systems.
+                      (process-coding-system-alist nil)
+                      (coding-system-for-read nil)
+                      (extra-args
+                       (tramp-get-sh-extra-args tramp-encoding-shell))
+                      ;; This must be done in order to avoid our file
+                      ;; name handler.
+                      (p (let ((default-directory
+                                tramp-compat-temporary-file-directory))
+                           (apply
+                            #'start-process
+                            (tramp-get-connection-name vec)
+                            (tramp-get-connection-buffer vec)
+                            (append
+                             `(,tramp-encoding-shell)
+                             (and extra-args (split-string extra-args))
+                             (and tramp-encoding-command-interactive
+                                  `(,tramp-encoding-command-interactive)))))))
+
+                 ;; This is neded for ssh or PuTTY based processes,
+                 ;; and only if the respective options are set.
+                 ;; Perhaps, the setting could be more fine-grained.
+                 ;; (process-put p 'tramp-shared-socket t)
+                 ;; Set sentinel.  Initialize variables.
+                 (set-process-sentinel p #'tramp-process-sentinel)
+                 (tramp-post-process-creation p vec)
+                 (setq tramp-current-connection (cons vec (current-time)))
+
+                 ;; Set connection-local variables.
+                 (tramp-set-connection-local-variables vec)
+
+                 ;; Check whether process is alive.
+                 (tramp-barf-if-no-shell-prompt
+                  p 10
+                  "Couldn't find local shell prompt for %s"
+                  tramp-encoding-shell)
+
+                 ;; Now do all the connections as specified.
+                 (while target-alist
+                   (let* ((hop (car target-alist))
+                          (l-method (tramp-file-name-method hop))
+                          (l-user (tramp-file-name-user hop))
+                          (l-domain (tramp-file-name-domain hop))
+                          (l-host (tramp-file-name-host hop))
+                          (l-port (tramp-file-name-port hop))
+                          (remote-shell
+                           (tramp-get-method-parameter hop 
'tramp-remote-shell))
+                          (extra-args (tramp-get-sh-extra-args remote-shell))
+                          (async-args
+                           (flatten-tree
+                            (tramp-get-method-parameter hop 
'tramp-async-args)))
+                          (connection-timeout
+                           (tramp-get-method-parameter
+                            hop 'tramp-connection-timeout))
+                          (command
+                           (tramp-get-method-parameter
+                            hop 'tramp-login-program))
+                          ;; We don't create the temporary file.  In
+                          ;; fact, it is just a prefix for the
+                          ;; ControlPath option of ssh; the real
+                          ;; temporary file has another name, and it
+                          ;; is created and protected by ssh.  It is
+                          ;; also removed by ssh when the connection
+                          ;; is closed.  The temporary file name is
+                          ;; cached in the main connection process,
+                          ;; therefore we cannot use
+                          ;; `tramp-get-connection-process'.
+                          (tmpfile
+                           (with-tramp-connection-property
+                               (tramp-get-process vec) "temp-file"
+                             (tramp-compat-make-temp-name)))
+                          r-shell)
+
+                     ;; Check, whether there is a restricted shell.
+                     (dolist (elt tramp-restricted-shell-hosts-alist)
+                       (when (string-match-p elt current-host)
+                         (setq r-shell t)))
+                     (setq current-host l-host)
+
+                     ;; Set password prompt vector.
                      (tramp-set-connection-property
-                      p "session-timeout"
-                      (tramp-get-method-parameter
-                       hop 'tramp-session-timeout)))
-
-                   ;; Replace `login-args' place holders.
-                   (setq
-                    command
-                    (string-join
-                     (append
-                      ;; We do not want to see the trailing local
-                      ;; prompt in `start-file-process'.
-                      (unless r-shell '("exec"))
-                      `(,command)
-                      ;; Add arguments for asynchronous processes.
-                      (when process-name async-args)
-                      (tramp-expand-args
-                       hop 'tramp-login-args
-                       ?h (or l-host "") ?u (or l-user "") ?p (or l-port "")
-                       ?c (format-spec options (format-spec-make ?t tmpfile))
-                       ?n (concat
-                           "2>" (tramp-get-remote-null-device previous-hop))
-                       ?l (concat remote-shell " " extra-args " -i"))
-                      ;; A restricted shell does not allow "exec".
-                      (when r-shell '("&&" "exit" "||" "exit")))
-                     " "))
-
-                   ;; Send the command.
-                   (tramp-message vec 3 "Sending command `%s'" command)
-                   (tramp-send-command vec command t t)
-                   (tramp-process-actions
-                    p vec
-                    (min
-                     pos (with-current-buffer (process-buffer p) (point-max)))
-                    tramp-actions-before-shell
-                    (or connection-timeout tramp-connection-timeout))
-                   (tramp-message
-                    vec 3 "Found remote shell prompt on `%s'" l-host)
-
-                   ;; Next hop.
-                   (setq options ""
-                         target-alist (cdr target-alist)
-                         previous-hop hop)))
-
-               ;; Activate session timeout.
-               (when (tramp-get-connection-property p "session-timeout")
-                 (run-at-time
-                  (tramp-get-connection-property p "session-timeout") nil
-                  #'tramp-timeout-session vec))
-
-               ;; Make initial shell settings.
-               (tramp-open-connection-setup-interactive-shell p vec)
-
-               ;; Mark it as connected.
-               (tramp-set-connection-property p "connected" t)))))
-
-      ;; Cleanup, and propagate the signal.
-      ((error quit)
-       (tramp-cleanup-connection vec t)
-       (signal (car err) (cdr err))))))
+                      p "password-vector"
+                      (if (tramp-get-method-parameter
+                           hop 'tramp-password-previous-hop)
+                          (let ((pv (copy-tramp-file-name previous-hop)))
+                            (setf (tramp-file-name-method pv) l-method)
+                            pv)
+                        (make-tramp-file-name
+                         :method l-method :user l-user :domain l-domain
+                         :host l-host :port l-port)))
+
+                     ;; Set session timeout.
+                     (when (tramp-get-method-parameter
+                            hop 'tramp-session-timeout)
+                       (tramp-set-connection-property
+                        p "session-timeout"
+                        (tramp-get-method-parameter
+                         hop 'tramp-session-timeout)))
+
+                     ;; Replace `login-args' place holders.
+                     (setq
+                      command
+                      (string-join
+                       (append
+                        ;; We do not want to see the trailing local
+                        ;; prompt in `start-file-process'.
+                        (unless r-shell '("exec"))
+                        `(,command)
+                        ;; Add arguments for asynchronous processes.
+                        (when process-name async-args)
+                        (tramp-expand-args
+                         hop 'tramp-login-args
+                         ?h (or l-host "") ?u (or l-user "") ?p (or l-port "")
+                         ?c (format-spec options (format-spec-make ?t tmpfile))
+                         ?n (concat
+                             "2>" (tramp-get-remote-null-device previous-hop))
+                         ?l (concat remote-shell " " extra-args " -i"))
+                        ;; A restricted shell does not allow "exec".
+                        (when r-shell '("&&" "exit" "||" "exit")))
+                       " "))
+
+                     ;; Send the command.
+                     (tramp-message vec 3 "Sending command `%s'" command)
+                     (tramp-send-command vec command t t)
+                     (tramp-process-actions
+                      p vec
+                      (min
+                       pos (with-current-buffer (process-buffer p) 
(point-max)))
+                      tramp-actions-before-shell
+                      (or connection-timeout tramp-connection-timeout))
+                     (tramp-message
+                      vec 3 "Found remote shell prompt on `%s'" l-host)
+
+                     ;; Next hop.
+                     (setq options ""
+                           target-alist (cdr target-alist)
+                           previous-hop hop)))
+
+                 ;; Activate session timeout.
+                 (when (tramp-get-connection-property p "session-timeout")
+                   (run-at-time
+                    (tramp-get-connection-property p "session-timeout") nil
+                    #'tramp-timeout-session vec))
+
+                 ;; Make initial shell settings.
+                 (tramp-open-connection-setup-interactive-shell p vec)
+
+                 ;; Mark it as connected.
+                 (tramp-set-connection-property p "connected" t)))))
+
+       ;; Cleanup, and propagate the signal.
+       ((error quit)
+        (tramp-cleanup-connection vec t)
+        (signal (car err) (cdr err)))))))
 
 (defun tramp-send-command (vec command &optional neveropen nooutput)
   "Send the COMMAND to connection VEC.
@@ -5316,7 +5429,7 @@ function waits for output unless NOOUTPUT is set."
            ;; A simple-minded busybox has sent " ^H" sequences.
            ;; Delete them.
            (goto-char (point-min))
-           (when (re-search-forward
+           (when (search-forward-regexp
                   (rx bol (+ nonl "\b") eol) (line-end-position) t)
              (forward-line 1)
              (delete-region (point-min) (point)))
@@ -5398,7 +5511,7 @@ raises an error."
       ;; Read the marker.
       (when (stringp marker)
        (condition-case nil
-           (re-search-forward marker)
+           (search-forward-regexp marker)
          (error (unless noerror
                   (tramp-error
                    vec 'file-error
@@ -5411,7 +5524,7 @@ raises an error."
                     (unless noerror signal-hook-function)))
                (read (current-buffer)))
            ;; Error handling.
-           (when (re-search-forward (rx (not blank)) (line-end-position) t)
+           (when (search-forward-regexp (rx (not blank)) (line-end-position) t)
              (error nil)))
        (error (unless noerror
                 (tramp-error
@@ -5540,22 +5653,16 @@ Nonexistent directories are removed from spec."
          (setq remote-path (delq 'tramp-own-remote-path remote-path)))
 
        ;; Remove double entries.
-       (setq elt1 remote-path)
-       (while (consp elt1)
-         (while (and (car elt1) (setq elt2 (member (car elt1) (cdr elt1))))
-           (setcar elt2 nil))
-         (setq elt1 (cdr elt1)))
+       (setq remote-path
+             (cl-remove-duplicates
+              remote-path :test #'string-equal :from-end t))
 
        ;; Remove non-existing directories.
-       (delq
-        nil
-        (mapcar
-         (lambda (x)
-           (and
-            (stringp x)
-            (file-directory-p (tramp-make-tramp-file-name vec x))
-            x))
-         remote-path))))))
+       (let (remote-file-name-inhibit-cache)
+         (tramp-bundle-read-file-names vec remote-path)
+         (cl-remove-if
+          (lambda (x) (not (tramp-get-file-property vec x "file-directory-p")))
+          remote-path))))))
 
 (defun tramp-get-remote-locale (vec)
   "Determine remote locale, supporting UTF8 if possible."
diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el
index dab85c5160e..f3f2c40e62c 100644
--- a/lisp/net/tramp-smb.el
+++ b/lisp/net/tramp-smb.el
@@ -68,8 +68,8 @@
 (defcustom tramp-smb-acl-program "smbcacls"
   "Name of SMB acls to run."
   :group 'tramp
-  :type 'string
-  :version "24.4")
+  :version "24.4"
+  :type 'string)
 
 (defcustom tramp-smb-conf null-device
   "Path of the \"smb.conf\" file.
@@ -85,8 +85,8 @@ They are added to the `tramp-smb-program' call via \"--option 
'...'\".
 For example, if the deprecated SMB1 protocol shall be used, add to
 this variable \"client min protocol=NT1\"."
   :group 'tramp
-  :type '(repeat string)
-  :version "28.1")
+  :version "28.1"
+  :type '(repeat string))
 
 (defvar tramp-smb-version nil
   "Version string of the SMB client.")
@@ -318,22 +318,22 @@ Operations not mentioned here will be handled by the 
default Emacs primitives.")
 If it isn't found in the local $PATH, the absolute path of winexe
 shall be given.  This is needed for remote processes."
   :group 'tramp
-  :type 'string
-  :version "24.3")
+  :version "24.3"
+  :type 'string)
 
 (defcustom tramp-smb-winexe-shell-command "powershell.exe"
   "Shell to be used for processes on remote machines.
 This must be Powershell V2 compatible."
   :group 'tramp
-  :type 'string
-  :version "24.3")
+  :version "24.3"
+  :type 'string)
 
 (defcustom tramp-smb-winexe-shell-command-switch "-file -"
   "Command switch used together with `tramp-smb-winexe-shell-command'.
 This can be used to disable echo etc."
   :group 'tramp
-  :type 'string
-  :version "24.3")
+  :version "24.3"
+  :type 'string)
 
 ;; It must be a `defsubst' in order to push the whole code into
 ;; tramp-loaddefs.el.  Otherwise, there would be recursive autoloading.
@@ -349,8 +349,10 @@ This can be used to disable echo etc."
 First arg specifies the OPERATION, second arg is a list of
 arguments to pass to the OPERATION."
   (if-let ((fn (assoc operation tramp-smb-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (unless (memq system-type '(cygwin windows-nt))
@@ -867,7 +869,7 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are 
completely ignored."
       ;; Loop the listing.
       (with-current-buffer (tramp-get-connection-buffer vec)
        (goto-char (point-min))
-       (unless (re-search-forward tramp-smb-errors nil t)
+       (unless (search-forward-regexp tramp-smb-errors nil t)
          (while (not (eobp))
            (cond
             ((looking-at
@@ -1174,7 +1176,7 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are 
completely ignored."
     (unless (tramp-smb-get-cifs-capabilities v)
       (tramp-error v 'file-error "make-symbolic-link not supported")))
 
-  (tramp-skeleton-handle-make-symbolic-link target linkname 
ok-if-already-exists
+  (tramp-skeleton-make-symbolic-link target linkname ok-if-already-exists
     (unless (tramp-smb-send-command
             v (format "symlink %s %s"
                       (tramp-smb-shell-quote-argument target)
@@ -1535,6 +1537,8 @@ VEC or USER, or if there is no home directory, return 
nil."
       ;; `set-visited-file-modtime' ourselves later on.
       (let (create-lockfiles)
         (write-region start end tmpfile append 'no-message))
+      ;; Now, `last-coding-system-used' has the right value.  Remember it.
+      (setq coding-system-used last-coding-system-used)
 
       (with-tramp-progress-reporter
          v 3 (format "Moving tmp file %s to %s" tmpfile filename)
@@ -1618,7 +1622,7 @@ Result is a list of (LOCALNAME MODE SIZE MONTH DAY TIME 
YEAR)."
          ;; Loop the listing.
          (with-current-buffer (tramp-get-connection-buffer v)
            (goto-char (point-min))
-           (if (re-search-forward tramp-smb-errors nil t)
+           (if (search-forward-regexp tramp-smb-errors nil t)
                (tramp-error v 'file-error "%s `%s'" (match-string 0) directory)
              (while (not (eobp))
                (setq entry (tramp-smb-read-file-entry share))
@@ -1809,8 +1813,8 @@ are listed.  Result is the list (LOCALNAME MODE SIZE 
MTIME)."
          (when (tramp-smb-send-command vec "posix")
            (with-current-buffer (tramp-get-connection-buffer vec)
              (goto-char (point-min))
-             (when
-                 (re-search-forward "Server supports CIFS capabilities" nil t)
+             (when (search-forward-regexp
+                    "Server supports CIFS capabilities" nil t)
                (member
                 "pathnames"
                 (split-string
@@ -1846,153 +1850,152 @@ If ARGUMENT is non-nil, use it as argument for
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  (let* ((share (tramp-smb-get-share vec))
-        (buf (tramp-get-connection-buffer vec))
-        (p (get-buffer-process buf)))
+  (with-tramp-debug-message vec "Opening connection"
+    (let* ((share (tramp-smb-get-share vec))
+          (buf (tramp-get-connection-buffer vec))
+          (p (get-buffer-process buf)))
+
+      ;; Check whether we still have the same smbclient version.
+      ;; Otherwise, we must delete the connection cache, because
+      ;; capabilities might have changed.
+      (unless (or argument (processp p))
+       (let ((default-directory tramp-compat-temporary-file-directory)
+             (command (concat tramp-smb-program " -V")))
+
+         (unless tramp-smb-version
+           (unless (executable-find tramp-smb-program)
+             (tramp-error
+              vec 'file-error
+              "Cannot find command %s in %s" tramp-smb-program exec-path))
+           (setq tramp-smb-version (shell-command-to-string command))
+           (tramp-message vec 6 command)
+           (tramp-message vec 6 "\n%s" tramp-smb-version)
+           (if (string-match (rx (+ (any " \t\r\n")) eos) tramp-smb-version)
+               (setq tramp-smb-version
+                     (replace-match "" nil nil tramp-smb-version))))
+
+         (unless (string-equal
+                  tramp-smb-version
+                  (tramp-get-connection-property
+                   vec "smbclient-version" tramp-smb-version))
+           (tramp-flush-directory-properties vec "/")
+           (tramp-flush-connection-properties vec))
+
+         (tramp-set-connection-property
+          vec "smbclient-version" tramp-smb-version)))
+
+      ;; If too much time has passed since last command was sent, look
+      ;; whether there has been an error message; maybe due to
+      ;; connection timeout.
+      (with-current-buffer buf
+       (goto-char (point-min))
+       (when (and (time-less-p
+                   60 (time-since
+                       (tramp-get-connection-property p "last-cmd-time" 0)))
+                  (process-live-p p)
+                  (search-forward-regexp tramp-smb-errors nil t))
+         (delete-process p)
+         (setq p nil)))
+
+      ;; Check whether it is still the same share.
+      (unless (and (process-live-p p)
+                  (or argument
+                      (string-equal
+                       share
+                       (tramp-get-connection-property p "smb-share" ""))))
+       (save-match-data
+         ;; There might be unread output from checking for share names.
+         (when buf (with-current-buffer buf (erase-buffer)))
+         (when (and p (processp p)) (delete-process p))
 
-    ;; Check whether we still have the same smbclient version.
-    ;; Otherwise, we must delete the connection cache, because
-    ;; capabilities might have changed.
-    (unless (or argument (processp p))
-      (let ((default-directory tramp-compat-temporary-file-directory)
-           (command (concat tramp-smb-program " -V")))
+         (let* ((user   (tramp-file-name-user vec))
+                (host   (tramp-file-name-host vec))
+                (domain (tramp-file-name-domain vec))
+                (port   (tramp-file-name-port vec))
+                (options tramp-smb-options)
+                args)
 
-       (unless tramp-smb-version
-         (unless (executable-find tramp-smb-program)
-           (tramp-error
-            vec 'file-error
-            "Cannot find command %s in %s" tramp-smb-program exec-path))
-         (setq tramp-smb-version (shell-command-to-string command))
-         (tramp-message vec 6 command)
-         (tramp-message vec 6 "\n%s" tramp-smb-version)
-         (if (string-match (rx (+ (any " \t\r\n")) eos) tramp-smb-version)
-             (setq tramp-smb-version
-                   (replace-match "" nil nil tramp-smb-version))))
-
-       (unless (string-equal
-                tramp-smb-version
-                (tramp-get-connection-property
-                 vec "smbclient-version" tramp-smb-version))
-         (tramp-flush-directory-properties vec "/")
-         (tramp-flush-connection-properties vec))
-
-       (tramp-set-connection-property
-        vec "smbclient-version" tramp-smb-version)))
-
-    ;; If too much time has passed since last command was sent, look
-    ;; whether there has been an error message; maybe due to
-    ;; connection timeout.
-    (with-current-buffer buf
-      (goto-char (point-min))
-      (when (and (time-less-p
-                 60 (time-since
-                     (tramp-get-connection-property p "last-cmd-time" 0)))
-                (process-live-p p)
-                (re-search-forward tramp-smb-errors nil t))
-       (delete-process p)
-       (setq p nil)))
-
-    ;; Check whether it is still the same share.
-    (unless (and (process-live-p p)
-                (or argument
-                    (string-equal
-                     share
-                     (tramp-get-connection-property p "smb-share" ""))))
-      (save-match-data
-       ;; There might be unread output from checking for share names.
-       (when buf (with-current-buffer buf (erase-buffer)))
-       (when (and p (processp p)) (delete-process p))
-
-       (let* ((user   (tramp-file-name-user vec))
-              (host   (tramp-file-name-host vec))
-              (domain (tramp-file-name-domain vec))
-              (port   (tramp-file-name-port vec))
-              (options tramp-smb-options)
-              args)
-
-         (cond
-          (argument
-           (setq args (list (concat "//" host))))
-          (share
-           (setq args (list (concat "//" host "/" share))))
-          (t
-           (setq args (list "-g" "-L" host ))))
+           (cond
+            (argument (setq args (list (concat "//" host))))
+            (share    (setq args (list (concat "//" host "/" share))))
+            (t        (setq args (list "-g" "-L" host ))))
 
-         (if (tramp-string-empty-or-nil-p user)
-             (setq args (append args (list "-N")))
-           (setq args (append args (list "-U" user))))
+           (if (tramp-string-empty-or-nil-p user)
+               (setq args (append args (list "-N")))
+             (setq args (append args (list "-U" user))))
 
-         (when domain (setq args (append args (list "-W" domain))))
-         (when port   (setq args (append args (list "-p" port))))
-         (when tramp-smb-conf
-           (setq args (append args (list "-s" tramp-smb-conf))))
-         (dolist (option options)
-           (setq args (append args (list "--option" option))))
-         (when argument
-           (setq args (append args (list argument))))
-
-         ;; OK, let's go.
-         (with-tramp-progress-reporter
-             vec 3
-             (format "Opening connection for //%s%s/%s"
-                     (if (tramp-string-empty-or-nil-p user)
-                         "" (concat user "@"))
-                     host (or share ""))
-
-           (let* (coding-system-for-read
-                  (process-connection-type tramp-process-connection-type)
-                  (p (let ((default-directory
-                             tramp-compat-temporary-file-directory)
-                           (process-environment
-                            (cons (concat "TERM=" tramp-terminal-type)
-                                  process-environment)))
-                       (apply #'start-process
-                              (tramp-get-connection-name vec)
-                              (tramp-get-connection-buffer vec)
-                              (if argument
-                                  tramp-smb-winexe-program tramp-smb-program)
-                              args))))
-             (tramp-post-process-creation p vec)
-
-             ;; Set connection-local variables.
-             (tramp-set-connection-local-variables vec)
-
-             (condition-case err
-                 (let ((inhibit-message t))
-                   ;; Play login scenario.
-                   (tramp-process-actions
-                    p vec nil
-                    (if (or argument share)
-                        tramp-smb-actions-with-share
-                      tramp-smb-actions-without-share))
-
-                   ;; Set chunksize to 1.  smbclient reads its input
-                   ;; character by character; if we send the string
-                   ;; at once, it is read painfully slow.
-                   (tramp-set-connection-property p "smb-share" share)
-                   (tramp-set-connection-property p "chunksize" 1)
-
-                   ;; Mark it as connected.
-                   (tramp-set-connection-property p "connected" t))
-
-               ;; Check for the error reason.  If it was due to wrong
-               ;; password, reestablish the connection.  We cannot
-               ;; handle this in `tramp-process-actions', because
-               ;; smbclient does not ask for the password, again.
-               (error
-                (with-current-buffer (tramp-get-connection-buffer vec)
-                  (goto-char (point-min))
-                  (if (and (bound-and-true-p auth-sources)
-                           (search-forward-regexp
-                            tramp-smb-wrong-passwd-regexp nil t))
-                      ;; Disable `auth-source' and `password-cache'.
-                      (let (auth-sources)
-                        (tramp-message
-                         vec 3 "Retry connection with new password")
-                        (tramp-cleanup-connection vec t)
-                        (tramp-smb-maybe-open-connection vec argument))
-                    ;; Propagate the error.
-                    (signal (car err) (cdr err)))))))))))))
+           (when domain (setq args (append args (list "-W" domain))))
+           (when port   (setq args (append args (list "-p" port))))
+           (when tramp-smb-conf
+             (setq args (append args (list "-s" tramp-smb-conf))))
+           (dolist (option options)
+             (setq args (append args (list "--option" option))))
+           (when argument
+             (setq args (append args (list argument))))
+
+           ;; OK, let's go.
+           (with-tramp-progress-reporter
+               vec 3
+               (format "Opening connection for //%s%s/%s"
+                       (if (tramp-string-empty-or-nil-p user)
+                           "" (concat user "@"))
+                       host (or share ""))
+
+             (let* (coding-system-for-read
+                    (process-connection-type tramp-process-connection-type)
+                    (p (let ((default-directory
+                              tramp-compat-temporary-file-directory)
+                             (process-environment
+                              (cons (concat "TERM=" tramp-terminal-type)
+                                    process-environment)))
+                         (apply #'start-process
+                                (tramp-get-connection-name vec)
+                                (tramp-get-connection-buffer vec)
+                                (if argument
+                                    tramp-smb-winexe-program tramp-smb-program)
+                                args))))
+               (tramp-post-process-creation p vec)
+
+               ;; Set connection-local variables.
+               (tramp-set-connection-local-variables vec)
+
+               (condition-case err
+                   (let ((inhibit-message t))
+                     ;; Play login scenario.
+                     (tramp-process-actions
+                      p vec nil
+                      (if (or argument share)
+                          tramp-smb-actions-with-share
+                        tramp-smb-actions-without-share))
+
+                     ;; Set chunksize to 1.  smbclient reads its
+                     ;; input character by character; if we send the
+                     ;; string at once, it is read painfully slow.
+                     (tramp-set-connection-property p "smb-share" share)
+                     (tramp-set-connection-property p "chunksize" 1)
+
+                     ;; Mark it as connected.
+                     (tramp-set-connection-property p "connected" t))
+
+                 ;; Check for the error reason.  If it was due to
+                 ;; wrong password, reestablish the connection.  We
+                 ;; cannot handle this in `tramp-process-actions',
+                 ;; because smbclient does not ask for the password,
+                 ;; again.
+                 (error
+                  (with-current-buffer (tramp-get-connection-buffer vec)
+                    (goto-char (point-min))
+                    (if (and (bound-and-true-p auth-sources)
+                             (search-forward-regexp
+                              tramp-smb-wrong-passwd-regexp nil t))
+                        ;; Disable `auth-source' and `password-cache'.
+                        (let (auth-sources)
+                          (tramp-message
+                           vec 3 "Retry connection with new password")
+                          (tramp-cleanup-connection vec t)
+                          (tramp-smb-maybe-open-connection vec argument))
+                      ;; Propagate the error.
+                      (signal (car err) (cdr err))))))))))))))
 
 ;; We don't use timeouts.  If needed, the caller shall wrap around.
 (defun tramp-smb-wait-for-output (vec)
@@ -2003,21 +2006,21 @@ Removes smb prompt.  Returns nil if an error message 
has appeared."
          (inhibit-read-only t))
 
       ;; Read pending output.
-      (while (not (re-search-forward tramp-smb-prompt nil t))
+      (while (not (search-forward-regexp tramp-smb-prompt nil t))
        (while (tramp-accept-process-output p))
        (goto-char (point-min)))
       (tramp-message vec 6 "\n%s" (buffer-string))
 
       ;; Remove prompt.
       (goto-char (point-min))
-      (when (re-search-forward tramp-smb-prompt nil t)
+      (when (search-forward-regexp tramp-smb-prompt nil t)
        (goto-char (point-max))
-       (re-search-backward tramp-smb-prompt nil t)
+       (search-backward-regexp tramp-smb-prompt nil t)
        (delete-region (point) (point-max)))
 
       ;; Return value is whether no error message has appeared.
       (goto-char (point-min))
-      (not (re-search-forward tramp-smb-errors nil t)))))
+      (not (search-forward-regexp tramp-smb-errors nil t)))))
 
 (defun tramp-smb-kill-winexe-function ()
   "Send SIGKILL to the winexe process."
diff --git a/lisp/net/tramp-sshfs.el b/lisp/net/tramp-sshfs.el
index e3c9e0b53b2..131f632a0fe 100644
--- a/lisp/net/tramp-sshfs.el
+++ b/lisp/net/tramp-sshfs.el
@@ -181,8 +181,10 @@ Operations not mentioned here will be handled by the 
default Emacs primitives.")
 First arg specifies the OPERATION, second arg is a list of
 arguments to pass to the OPERATION."
   (if-let ((fn (assoc operation tramp-sshfs-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (tramp--with-startup
@@ -380,7 +382,9 @@ arguments to pass to the OPERATION."
   (tramp-skeleton-write-region start end filename append visit lockname 
mustbenew
     (let (create-lockfiles)
       (write-region
-       start end (tramp-fuse-local-file-name filename) append 'nomessage))))
+       start end (tramp-fuse-local-file-name filename) append 'nomessage))
+    ;; Now, `last-coding-system-used' has the right value.  Remember it.
+    (setq coding-system-used last-coding-system-used)))
 
 
 ;; File name conversions.
@@ -393,52 +397,53 @@ connection if a previous connection has died for some 
reason."
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  ;; We need a process bound to the connection buffer.  Therefore, we
-  ;; create a dummy process.  Maybe there is a better solution?
-  (unless (get-buffer-process (tramp-get-connection-buffer vec))
-    (let ((p (make-network-process
-             :name (tramp-get-connection-name vec)
-             :buffer (tramp-get-connection-buffer vec)
-             :server t :host 'local :service t :noquery t)))
-      (tramp-post-process-creation p vec)
-
-      ;; Set connection-local variables.
-      (tramp-set-connection-local-variables vec)))
-
-  ;; Create directory.
-  (unless (file-directory-p (tramp-fuse-mount-point vec))
-    (make-directory (tramp-fuse-mount-point vec) 'parents))
-
-  (unless
-      (or (tramp-fuse-mounted-p vec)
-         (with-temp-buffer
-           (zerop
-            (apply
-             #'tramp-call-process
-             vec tramp-sshfs-program nil t nil
-             (tramp-fuse-mount-spec vec)
-             (tramp-fuse-mount-point vec)
-             (tramp-expand-args
-              vec 'tramp-mount-args
-              ?p (or (tramp-file-name-port vec) ""))))))
-    (tramp-error
-     vec 'file-error "Error mounting %s" (tramp-fuse-mount-spec vec)))
-
-  ;; Mark it as connected.
-  (add-to-list 'tramp-fuse-mount-points (tramp-file-name-unify vec))
-  (tramp-set-connection-property
-   (tramp-get-connection-process vec) "connected" t)
-
-  ;; In `tramp-check-cached-permissions', the connection properties
-  ;; "{uid,gid}-{integer,string}" are used.  We set them to proper values.
-  (with-tramp-connection-property
-      vec "uid-integer" (tramp-get-local-uid 'integer))
-  (with-tramp-connection-property
-      vec "gid-integer" (tramp-get-local-gid 'integer))
-  (with-tramp-connection-property
-      vec "uid-string" (tramp-get-local-uid 'string))
-  (with-tramp-connection-property
-      vec "gid-string" (tramp-get-local-gid 'string)))
+  (with-tramp-debug-message vec "Opening connection"
+    ;; We need a process bound to the connection buffer.  Therefore,
+    ;; we create a dummy process.  Maybe there is a better solution?
+    (unless (get-buffer-process (tramp-get-connection-buffer vec))
+      (let ((p (make-network-process
+               :name (tramp-get-connection-name vec)
+               :buffer (tramp-get-connection-buffer vec)
+               :server t :host 'local :service t :noquery t)))
+       (tramp-post-process-creation p vec)
+
+       ;; Set connection-local variables.
+       (tramp-set-connection-local-variables vec)))
+
+    ;; Create directory.
+    (unless (file-directory-p (tramp-fuse-mount-point vec))
+      (make-directory (tramp-fuse-mount-point vec) 'parents))
+
+    (unless
+       (or (tramp-fuse-mounted-p vec)
+           (with-temp-buffer
+             (zerop
+              (apply
+               #'tramp-call-process
+               vec tramp-sshfs-program nil t nil
+               (tramp-fuse-mount-spec vec)
+               (tramp-fuse-mount-point vec)
+               (tramp-expand-args
+                vec 'tramp-mount-args
+                ?p (or (tramp-file-name-port vec) ""))))))
+      (tramp-error
+       vec 'file-error "Error mounting %s" (tramp-fuse-mount-spec vec)))
+
+    ;; Mark it as connected.
+    (add-to-list 'tramp-fuse-mount-points (tramp-file-name-unify vec))
+    (tramp-set-connection-property
+     (tramp-get-connection-process vec) "connected" t)
+
+    ;; In `tramp-check-cached-permissions', the connection properties
+    ;; "{uid,gid}-{integer,string}" are used.  We set them to proper values.
+    (with-tramp-connection-property
+       vec "uid-integer" (tramp-get-local-uid 'integer))
+    (with-tramp-connection-property
+       vec "gid-integer" (tramp-get-local-gid 'integer))
+    (with-tramp-connection-property
+       vec "uid-string" (tramp-get-local-uid 'string))
+    (with-tramp-connection-property
+       vec "gid-string" (tramp-get-local-gid 'string))))
 
 ;; `shell-mode' tries to open remote files like "/sshfs:user@host:~/.history".
 ;; This fails, because the tilde cannot be expanded.  Tell
diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el
index e41a4a590e2..3d6e1d92d0b 100644
--- a/lisp/net/tramp-sudoedit.el
+++ b/lisp/net/tramp-sudoedit.el
@@ -170,8 +170,10 @@ See `tramp-actions-before-shell' for more info.")
 First arg specifies the OPERATION, second arg is a list of
 arguments to pass to the OPERATION."
   (if-let ((fn (assoc operation tramp-sudoedit-file-name-handler-alist)))
-      (save-match-data (apply (cdr fn) args))
-    (tramp-run-real-handler operation args)))
+      (prog1 (save-match-data (apply (cdr fn) args))
+       (setq tramp-debug-message-fnh-function (cdr fn)))
+    (prog1 (tramp-run-real-handler operation args)
+      (setq tramp-debug-message-fnh-function operation))))
 
 ;;;###tramp-autoload
 (tramp--with-startup
@@ -232,7 +234,7 @@ OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists 
already.
 KEEP-DATE means to make sure that NEWNAME has the same timestamp
 as FILENAME.  PRESERVE-UID-GID, when non-nil, instructs to keep
 the uid and gid if both files are on the same host.
-PRESERVE-EXTENDED-ATTRIBUTES activates selinux and acl commands.
+PRESERVE-EXTENDED-ATTRIBUTES activates SELinux and ACL commands.
 
 This function is invoked by `tramp-sudoedit-handle-copy-file' and
 `tramp-sudoedit-handle-rename-file'.  It is an error if OP is
@@ -432,14 +434,37 @@ the result will be a local, non-Tramp, file name."
   "stat format string to produce output suitable for use with
 `file-attributes' on the remote file system.")
 
+(defconst tramp-sudoedit-file-attributes-with-selinux
+  (format
+   ;; Apostrophes in the stat output are masked as
+   ;; `tramp-stat-marker', in order to make a proper shell escape of
+   ;; them in file names.  They are replaced in
+   ;; `tramp-sudoedit-send-command-and-read'.
+   (concat "((%s%%N%s) %%h (%s%%U%s . %%u) (%s%%G%s . %%g)"
+          " %%X %%Y %%Z %%s %s%%A%s t %%i -1 %s%%C%s)")
+   tramp-stat-marker tramp-stat-marker  ; %%N
+   tramp-stat-marker tramp-stat-marker  ; %%U
+   tramp-stat-marker tramp-stat-marker  ; %%G
+   tramp-stat-marker tramp-stat-marker  ; %%A
+   tramp-stat-marker tramp-stat-marker) ; %%C
+  "stat format string to produce output suitable for use with
+`file-attributes' on the remote file system, including SELinux context.")
+
 (defun tramp-sudoedit-handle-file-attributes (filename &optional id-format)
   "Like `file-attributes' for Tramp files."
   ;; The result is cached in `tramp-convert-file-attributes'.
   (with-parsed-tramp-file-name (expand-file-name filename) nil
     (tramp-convert-file-attributes v localname id-format
-      (tramp-sudoedit-send-command-and-read
-       v "env" "QUOTING_STYLE=locale" "stat" "-c"
-       tramp-sudoedit-file-attributes (file-name-unquote localname)))))
+      (cond
+       ((tramp-sudoedit-remote-selinux-p v)
+       (tramp-sudoedit-send-command-and-read
+        v "env" "QUOTING_STYLE=locale" "stat" "-c"
+        tramp-sudoedit-file-attributes-with-selinux
+        (file-name-unquote localname)))
+       (t
+       (tramp-sudoedit-send-command-and-read
+        v "env" "QUOTING_STYLE=locale" "stat" "-c"
+        tramp-sudoedit-file-attributes (file-name-unquote localname)))))))
 
 (defun tramp-sudoedit-handle-file-executable-p (filename)
   "Like `file-executable-p' for Tramp files."
@@ -505,7 +530,7 @@ the result will be a local, non-Tramp, file name."
         v 'file-error "Error while changing file's mode %s" filename)))))
 
 (defun tramp-sudoedit-remote-selinux-p (vec)
-  "Check, whether SELINUX is enabled on the remote host."
+  "Check, whether SELinux is enabled on the remote host."
   (with-tramp-connection-property (tramp-get-process vec) "selinux-p"
     (zerop (tramp-call-process vec "selinuxenabled"))))
 
@@ -524,7 +549,7 @@ the result will be a local, non-Tramp, file name."
                    v "ls" "-d" "-Z" (file-name-unquote localname)))
          (with-current-buffer (tramp-get-connection-buffer v)
            (goto-char (point-min))
-           (when (re-search-forward regexp (line-end-position) t)
+           (when (search-forward-regexp regexp (line-end-position) t)
              (setq context (list (match-string 1) (match-string 2)
                                  (match-string 3) (match-string 4))))))
        ;; Return the context.
@@ -572,9 +597,9 @@ the result will be a local, non-Tramp, file name."
   (with-parsed-tramp-file-name (expand-file-name filename) nil
     (with-tramp-file-property v localname "file-writable-p"
       (if (file-exists-p filename)
+         ;; Examine `file-attributes' cache to see if request can be
+         ;; satisfied without remote operation.
          (if (tramp-file-property-p v localname "file-attributes")
-             ;; Examine `file-attributes' cache to see if request can
-             ;; be satisfied without remote operation.
              (tramp-check-cached-permissions v ?w)
            (tramp-sudoedit-send-command
             v "test" "-w" (file-name-unquote localname)))
@@ -594,7 +619,7 @@ the result will be a local, non-Tramp, file name."
 (defun tramp-sudoedit-handle-make-symbolic-link
     (target linkname &optional ok-if-already-exists)
   "Like `make-symbolic-link' for Tramp files."
-  (tramp-skeleton-handle-make-symbolic-link target linkname 
ok-if-already-exists
+  (tramp-skeleton-make-symbolic-link target linkname ok-if-already-exists
     (tramp-sudoedit-send-command
      v "ln" "-sf"
      (file-name-unquote target)
@@ -714,20 +739,21 @@ connection if a previous connection has died for some 
reason."
   (unless (tramp-connectable-p vec)
     (throw 'non-essential 'non-essential))
 
-  ;; We need a process bound to the connection buffer.  Therefore, we
-  ;; create a dummy process.  Maybe there is a better solution?
-  (unless (tramp-get-connection-process vec)
-    (let ((p (make-network-process
-             :name (tramp-get-connection-name vec)
-             :buffer (tramp-get-connection-buffer vec)
-             :server t :host 'local :service t :noquery t)))
-      (tramp-post-process-creation p vec)
+  (with-tramp-debug-message vec "Opening connection"
+    ;; We need a process bound to the connection buffer.  Therefore,
+    ;; we create a dummy process.  Maybe there is a better solution?
+    (unless (tramp-get-connection-process vec)
+      (let ((p (make-network-process
+               :name (tramp-get-connection-name vec)
+               :buffer (tramp-get-connection-buffer vec)
+               :server t :host 'local :service t :noquery t)))
+       (tramp-post-process-creation p vec)
 
-      ;; Set connection-local variables.
-      (tramp-set-connection-local-variables vec)
+       ;; Set connection-local variables.
+       (tramp-set-connection-local-variables vec)
 
-      ;; Mark it as connected.
-      (tramp-set-connection-property p "connected" t))))
+       ;; Mark it as connected.
+       (tramp-set-connection-property p "connected" t)))))
 
 (defun tramp-sudoedit-send-command (vec &rest args)
   "Send commands ARGS to connection VEC.
@@ -785,7 +811,7 @@ In case there is no valid Lisp expression, it raises an 
error."
       (condition-case nil
          (prog1 (read (current-buffer))
            ;; Error handling.
-           (when (re-search-forward (rx (not blank)) (line-end-position) t)
+           (when (search-forward-regexp (rx (not blank)) (line-end-position) t)
              (error nil)))
        (error (tramp-error
                vec 'file-error
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index 55e1ca932e4..b34d3ff6695 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -55,6 +55,7 @@
 ;;; Code:
 
 (require 'tramp-compat)
+(require 'tramp-message)
 (require 'tramp-integration)
 (require 'trampver)
 
@@ -86,15 +87,6 @@
 ;;;###autoload (when (featurep 'tramp-compat)
 ;;;###autoload   (load "tramp-compat" 'noerror 'nomessage))
 
-;;; User Customizable Internal Variables:
-
-(defgroup tramp nil
-  "Edit remote files with a combination of ssh, scp, etc."
-  :group 'files
-  :group 'comm
-  :link '(custom-manual "(tramp)Top")
-  :version "22.1")
-
 ;;;###tramp-autoload
 (progn
   (defvar tramp--startup-hook nil
@@ -104,9 +96,26 @@
 
   (defmacro tramp--with-startup (&rest body)
     "Schedule BODY to be executed at the end of tramp.el."
-    `(add-hook 'tramp--startup-hook (lambda () ,@body))))
+    `(add-hook 'tramp--startup-hook (lambda () ,@body)))
+
+  (eval-and-compile
+    (defalias 'tramp-byte-run--set-suppress-trace
+      #'(lambda (f _args val)
+         (list 'function-put (list 'quote f)
+               ''tramp-suppress-trace val)))
 
-(require 'tramp-loaddefs)
+    (add-to-list
+     'defun-declarations-alist
+     (list 'tramp-suppress-trace #'tramp-byte-run--set-suppress-trace))))
+
+;;; User Customizable Internal Variables:
+
+(defgroup tramp nil
+  "Edit remote files with a combination of ssh, scp, etc."
+  :group 'files
+  :group 'comm
+  :version "22.1"
+  :link '(custom-manual "(tramp)Top"))
 
 ;; Maybe we need once a real Tramp mode, with key bindings etc.
 ;;;###autoload
@@ -115,32 +124,6 @@
 If it is set to nil, all remote file names are used literally."
   :type 'boolean)
 
-;;;###tramp-autoload
-(defcustom tramp-verbose 3
-  "Verbosity level for Tramp messages.
-Any level x includes messages for all levels 1 .. x-1.  The levels are
-
- 0  silent (no tramp messages at all)
- 1  errors
- 2  warnings
- 3  connection to remote hosts (default level)
- 4  activities
- 5  internal
- 6  sent and received strings
- 7  connection properties
- 8  file caching
- 9  test commands
-10  traces (huge)
-11  call traces (maintainer only)."
-  :type 'integer)
-
-(defcustom tramp-debug-to-file nil
-  "Whether Tramp debug messages shall be saved to file.
-The debug file has the same name as the debug buffer, written to
-`tramp-compat-temporary-file-directory'."
-  :version "28.1"
-  :type 'boolean)
-
 (defcustom tramp-backup-directory-alist nil
   "Alist of filename patterns and backup directory names.
 Each element looks like (REGEXP . DIRECTORY), with the same meaning like
@@ -165,7 +148,12 @@ This setting has precedence over 
`auto-save-file-name-transforms'."
 ;; Suppress `shell-file-name' for w32 systems.
 (defcustom tramp-encoding-shell
   (let (shell-file-name)
-    (or (tramp-compat-funcall 'w32-shell-name) "/bin/sh"))
+    (or (tramp-compat-funcall 'w32-shell-name)
+        (if (eq system-type 'android)
+            ;; The shell is located at /system/bin/sh on Android
+            ;; systems.
+            "/system/bin/sh"
+          "/bin/sh")))
   "Use this program for encoding and decoding commands on the local host.
 This shell is used to execute the encoding and decoding command on the
 local host, so if you want to use \"~\" in those commands, you should
@@ -1202,7 +1190,7 @@ The `ftp' syntax does not support methods.")
      ;; We cannot use `tramp-prefix-regexp', because it starts with `bol'.
      (literal tramp-prefix-format)
 
-     ;; Optional multi hops.
+     ;; Optional multi-hops.
      (* (regexp tramp-remote-file-name-spec-regexp)
         (regexp tramp-postfix-hop-regexp))
 
@@ -1267,7 +1255,7 @@ checked via the following code:
             (process-send-eof proc)
             (process-send-eof proc))
           (while (not (progn (goto-char (point-min))
-                             (re-search-forward \"\\\\w+\" (point-max) t)))
+                             (search-forward-regexp \"\\\\w+\" (point-max) t)))
             (accept-process-output proc 1))
           (when (process-live-p proc)
             (setq received (string-to-number (match-string 0)))
@@ -1397,12 +1385,12 @@ The TERM environment variable should be set via 
`tramp-terminal-type'.
 
 The INSIDE_EMACS environment variable will automatically be set
 based on the Tramp and Emacs versions, and should not be set here."
-  :group 'tramp
   :version "26.1"
   :type '(repeat string))
 
 ;;; Internal Variables:
 
+;;;###tramp-autoload
 (defvar tramp-current-connection nil
   "Last connection timestamp.
 It is a cons cell of the actual `tramp-file-name-structure', and
@@ -1422,6 +1410,7 @@ during direct remote copying with scp.")
 
 (defconst tramp-completion-file-name-handler-alist
   '((expand-file-name . tramp-completion-handle-expand-file-name)
+    (file-directory-p . tramp-completion-handle-file-directory-p)
     (file-exists-p . tramp-completion-handle-file-exists-p)
     (file-name-all-completions
      . tramp-completion-handle-file-name-all-completions)
@@ -1460,57 +1449,60 @@ calling HANDLER.")
   (cl-defstruct (tramp-file-name (:type list) :named)
     method user domain host port localname hop))
 
-(put #'tramp-file-name-method 'tramp-suppress-trace t)
-(put #'tramp-file-name-user 'tramp-suppress-trace t)
-(put #'tramp-file-name-domain 'tramp-suppress-trace t)
-(put #'tramp-file-name-host 'tramp-suppress-trace t)
-(put #'tramp-file-name-port 'tramp-suppress-trace t)
-(put #'tramp-file-name-localname 'tramp-suppress-trace t)
-(put #'tramp-file-name-hop 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-method 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-user 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-domain 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-host 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-port 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-localname 'tramp-suppress-trace t)
+(function-put #'tramp-file-name-hop 'tramp-suppress-trace t)
 
-;; Needed for `tramp-read-passwd' and `tramp-get-remote-null-device'.
+;;;###tramp-autoload
 (defconst tramp-null-hop
-  (make-tramp-file-name :user (user-login-name) :host tramp-system-name)
-"Connection hop which identifies the virtual hop before the first one.")
+  (make-tramp-file-name
+   :method "local" :user (user-login-name) :host tramp-system-name)
+  "Connection hop which identifies the virtual hop before the first one.
+Used also for caching properties of the local machine.")
 
 (defun tramp-file-name-user-domain (vec)
   "Return user and domain components of VEC."
+  (declare (tramp-suppress-trace t))
   (when (or (tramp-file-name-user vec) (tramp-file-name-domain vec))
     (concat (tramp-file-name-user vec)
            (and (tramp-file-name-domain vec)
                 tramp-prefix-domain-format)
            (tramp-file-name-domain vec))))
 
-(put #'tramp-file-name-user-domain 'tramp-suppress-trace t)
-
 (defun tramp-file-name-host-port (vec)
   "Return host and port components of VEC."
+  (declare (tramp-suppress-trace t))
   (when (or (tramp-file-name-host vec) (tramp-file-name-port vec))
     (concat (tramp-file-name-host vec)
            (and (tramp-file-name-port vec)
                 tramp-prefix-port-format)
            (tramp-file-name-port vec))))
 
-(put #'tramp-file-name-host-port 'tramp-suppress-trace t)
-
 (defun tramp-file-name-port-or-default (vec)
   "Return port component of VEC.
 If nil, return `tramp-default-port'."
+  (declare (tramp-suppress-trace t))
   (or (tramp-file-name-port vec)
       (tramp-get-method-parameter vec 'tramp-default-port)))
 
-(put #'tramp-file-name-port-or-default 'tramp-suppress-trace t)
-
 ;;;###tramp-autoload
 (defun tramp-file-name-unify (vec &optional localname)
   "Unify VEC by removing localname and hop from `tramp-file-name' structure.
-If LOCALNAME is an absolute file name, set it as localname.  If
-LOCALNAME is a relative file name, return `tramp-cache-undefined'.
-Objects returned by this function compare `equal' if they refer to the
-same connection.  Make a copy in order to avoid side effects."
+IF VEC is nil, set it to `tramp-null-hop'.
+If LOCALNAME is an absolute file name, set it as localname.
+If LOCALNAME is a relative file name, return `tramp-cache-undefined'.
+Objects returned by this function compare `equal' if they refer
+to the same connection.  Make a copy in order to avoid side
+effects."
+  ;; (declare (tramp-suppress-trace t))
   (if (and (stringp localname)
           (not (file-name-absolute-p localname)))
       (setq vec tramp-cache-undefined)
+    (unless vec (setq vec tramp-null-hop))
     (when (tramp-file-name-p vec)
       (setq vec (copy-tramp-file-name vec))
       (setf (tramp-file-name-localname vec)
@@ -1519,12 +1511,16 @@ same connection.  Make a copy in order to avoid side 
effects."
            (tramp-file-name-hop vec) nil))
     vec))
 
-(put #'tramp-file-name-unify 'tramp-suppress-trace t)
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-file-name-unify 'tramp-suppress-trace t)
 
 ;; Comparison of file names is performed by `tramp-equal-remote'.
 (defun tramp-file-name-equal-p (vec1 vec2)
   "Check, whether VEC1 and VEC2 denote the same `tramp-file-name'.
 LOCALNAME and HOP do not count."
+  (declare (tramp-suppress-trace t))
   (and (tramp-file-name-p vec1) (tramp-file-name-p vec2)
        (equal (tramp-file-name-unify vec1)
              (tramp-file-name-unify vec2))))
@@ -1553,6 +1549,7 @@ entry does not exist, return nil."
 ;;;###tramp-autoload
 (defun tramp-tramp-file-p (name)
   "Return t if NAME is a string with Tramp file name syntax."
+  ;; (declare (tramp-suppress-trace t))
   (and tramp-mode (stringp name)
        ;; No "/:" and "/c:".  This is not covered by `tramp-file-name-regexp'.
        (not (string-match-p (rx bos "/" (? alpha) ":") name))
@@ -1562,7 +1559,10 @@ entry does not exist, return nil."
        (string-match-p tramp-file-name-regexp name)
        t))
 
-(put #'tramp-tramp-file-p 'tramp-suppress-trace t)
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-tramp-file-p 'tramp-suppress-trace t)
 
 ;; This function bypasses the file name handler approach.  It is NOT
 ;; recommended to use it in any package if not absolutely necessary.
@@ -1592,6 +1592,7 @@ of `process-file', `start-file-process', or 
`shell-command'."
   "Return the right method string to use depending on USER and HOST.
 This is METHOD, if non-nil.  Otherwise, do a lookup in
 `tramp-default-method-alist' and `tramp-default-method'."
+  (declare (tramp-suppress-trace t))
   (when (and method
             (or (string-empty-p method)
                 (string-equal method tramp-default-method-marker)))
@@ -1613,12 +1614,11 @@ This is METHOD, if non-nil.  Otherwise, do a lookup in
        result
       (propertize result 'tramp-default t))))
 
-(put #'tramp-find-method 'tramp-suppress-trace t)
-
 (defun tramp-find-user (method user host)
   "Return the right user string to use depending on METHOD and HOST.
 This is USER, if non-nil.  Otherwise, do a lookup in
 `tramp-default-user-alist' and `tramp-default-user'."
+  (declare (tramp-suppress-trace t))
   (let ((result
         (or user
             (let ((choices tramp-default-user-alist)
@@ -1636,12 +1636,11 @@ This is USER, if non-nil.  Otherwise, do a lookup in
        result
       (propertize result 'tramp-default t))))
 
-(put #'tramp-find-user 'tramp-suppress-trace t)
-
 (defun tramp-find-host (method user host)
   "Return the right host string to use depending on METHOD and USER.
 This is HOST, if non-nil.  Otherwise, do a lookup in
 `tramp-default-host-alist' and `tramp-default-host'."
+  (declare (tramp-suppress-trace t))
   (let ((result
         (or (and (tramp-compat-length> host 0) host)
             (let ((choices tramp-default-host-alist)
@@ -1659,8 +1658,6 @@ This is HOST, if non-nil.  Otherwise, do a lookup in
        result
       (propertize result 'tramp-default t))))
 
-(put #'tramp-find-host 'tramp-suppress-trace t)
-
 ;;;###tramp-autoload
 (defun tramp-dissect-file-name (name &optional nodefault)
   "Return a `tramp-file-name' structure of NAME, a remote file name.
@@ -1670,6 +1667,7 @@ localname (file name on remote host), and hop.
 Unless NODEFAULT is non-nil, method, user and host are expanded
 to their default values.  For the other file name parts, no
 default values are used."
+  ;; (declare (tramp-suppress-trace t))
   (save-match-data
     (unless (tramp-tramp-file-p name)
       (tramp-user-error nil "Not a Tramp file name: \"%s\"" name))
@@ -1726,7 +1724,10 @@ default values are used."
            (tramp-user-error
             v "Method `%s' is not supported for multi-hops" method)))))))
 
-(put #'tramp-dissect-file-name 'tramp-suppress-trace t)
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-dissect-file-name 'tramp-suppress-trace t)
 
 ;;;###tramp-autoload
 (defun tramp-ensure-dissected-file-name (vec-or-filename)
@@ -1734,16 +1735,21 @@ default values are used."
 
 VEC-OR-FILENAME may be either a string or a `tramp-file-name'.
 If it's not a Tramp filename, return nil."
+  ;; (declare (tramp-suppress-trace t))
   (cond
    ((tramp-file-name-p vec-or-filename) vec-or-filename)
    ((tramp-tramp-file-p vec-or-filename)
     (tramp-dissect-file-name vec-or-filename))))
 
-(put #'tramp-ensure-dissected-file-name 'tramp-suppress-trace t)
+;; We cannot use the `declare' form for `tramp-suppress-trace' in
+;; autoloaded functions, because the tramp-loaddefs.el generation
+;; would fail.
+(function-put #'tramp-ensure-dissected-file-name 'tramp-suppress-trace t)
 
 (defun tramp-dissect-hop-name (name &optional nodefault)
   "Return a `tramp-file-name' structure of `hop' part of NAME.
 See `tramp-dissect-file-name' for details."
+  (declare (tramp-suppress-trace t))
   (let ((v (tramp-dissect-file-name
            (concat tramp-prefix-format
                    (replace-regexp-in-string
@@ -1758,14 +1764,14 @@ See `tramp-dissect-file-name' for details."
     ;; Return result.
     v))
 
-(put #'tramp-dissect-hop-name 'tramp-suppress-trace t)
-
+;;;###tramp-autoload
 (defsubst tramp-string-empty-or-nil-p (string)
   "Check whether STRING is empty or nil."
   (or (null string) (string= string "")))
 
 (defun tramp-buffer-name (vec)
   "A name for the connection buffer VEC."
+  (declare (tramp-suppress-trace t))
   (let ((method (tramp-file-name-method vec))
        (user-domain (tramp-file-name-user-domain vec))
        (host-port (tramp-file-name-host-port vec)))
@@ -1773,20 +1779,13 @@ See `tramp-dissect-file-name' for details."
        (format "*tramp/%s %s*" method host-port)
       (format "*tramp/%s %s@%s*" method user-domain host-port))))
 
-(put #'tramp-buffer-name 'tramp-suppress-trace t)
-
 ;;;###tramp-autoload
 (defun tramp-make-tramp-file-name (&rest args)
   "Construct a Tramp file name from ARGS.
-
-ARGS could have two different signatures.  The first one is of
-type (VEC &optional LOCALNAME).
 If LOCALNAME is nil, the value in VEC is used.  If it is a
 symbol, a null localname will be used.  Otherwise, LOCALNAME is
-expected to be a string, which will be used.
-
-The other signature exists for backward compatibility.  It has
-the form (METHOD USER DOMAIN HOST PORT LOCALNAME &optional HOP)."
+expected to be a string, which will be used."
+  (declare (advertised-calling-convention (vec &optional localname) "29.1"))
   (let (method user domain host port localname hop)
     (cond
      ((tramp-file-name-p (car args))
@@ -1839,9 +1838,6 @@ the form (METHOD USER DOMAIN HOST PORT LOCALNAME 
&optional HOP)."
            tramp-postfix-host-format
            localname)))
 
-(set-advertised-calling-convention
- #'tramp-make-tramp-file-name '(vec &optional localname) "29.1")
-
 (defun tramp-make-tramp-hop-name (vec)
   "Construct a Tramp hop name from VEC."
   (concat
@@ -1955,371 +1951,6 @@ of `current-buffer'."
          buffer (current-buffer))
     (substring-no-properties (buffer-string))))
 
-(defun tramp-debug-buffer-name (vec)
-  "A name for the debug buffer for VEC."
-  (let ((method (tramp-file-name-method vec))
-       (user-domain (tramp-file-name-user-domain vec))
-       (host-port (tramp-file-name-host-port vec)))
-    (if (tramp-string-empty-or-nil-p user-domain)
-       (format "*debug tramp/%s %s*" method host-port)
-      (format "*debug tramp/%s %s@%s*" method user-domain host-port))))
-
-(put #'tramp-debug-buffer-name 'tramp-suppress-trace t)
-
-(defconst tramp-debug-outline-regexp
-  (rx ;; Timestamp.
-      (+ digit) ":" (+ digit) ":" (+ digit) "." (+ digit) blank
-      ;; Thread.
-      (? (group "#<thread " (+ nonl) ">") blank)
-      ;; Function name, verbosity.
-      (+ (any "-" alnum)) " (" (group (+ digit)) ") #")
-  "Used for highlighting Tramp debug buffers in `outline-mode'.")
-
-(defconst tramp-debug-font-lock-keywords
-  ;; FIXME: Make it a function instead of an ELisp expression, so you
-  ;; can evaluate it with `funcall' rather than `eval'!
-  ;; Also, in `font-lock-defaults' you can specify a function name for
-  ;; the "KEYWORDS" part, so font-lock calls it to get the actual keywords!
-  '(list
-    (rx bol (regexp tramp-debug-outline-regexp) (+ nonl))
-    '(1 font-lock-warning-face t t)
-    '(0 (outline-font-lock-face) keep t))
-  "Used for highlighting Tramp debug buffers in `outline-mode'.")
-
-(defun tramp-debug-outline-level ()
-  "Return the depth to which a statement is nested in the outline.
-Point must be at the beginning of a header line.
-
-The outline level is equal to the verbosity of the Tramp message."
-  (1+ (string-to-number (match-string 2))))
-
-(put #'tramp-debug-outline-level 'tramp-suppress-trace t)
-
-;; This function takes action since Emacs 28.1, when
-;; `read-extended-command-predicate' is set to
-;; `command-completion-default-include-p'.
-(defun tramp-debug-buffer-command-completion-p (_symbol buffer)
-  "A predicate for Tramp interactive commands.
-They are completed by \"M-x TAB\" only in Tramp debug buffers."
-  (with-current-buffer buffer
-    (string-equal
-     (buffer-substring (point-min) (min (+ (point-min) 10) (point-max)))
-     ";; Emacs:")))
-
-(put #'tramp-debug-buffer-command-completion-p 'tramp-suppress-trace t)
-
-(defun tramp-setup-debug-buffer ()
-  "Function to setup debug buffers."
-  ;; (declare (completion tramp-debug-buffer-command-completion-p))
-  (interactive)
-  (set-buffer-file-coding-system 'utf-8)
-  (setq buffer-undo-list t)
-  ;; Activate `outline-mode'.  This runs `text-mode-hook' and
-  ;; `outline-mode-hook'.  We must prevent that local processes die.
-  ;; Yes: I've seen `flyspell-mode', which starts "ispell".
-  ;; `(custom-declare-variable outline-minor-mode-prefix ...)'  raises
-  ;; on error in `(outline-mode)', we don't want to see it in the
-  ;; traces.
-  (let ((default-directory tramp-compat-temporary-file-directory))
-    (outline-mode))
-  (setq-local outline-level 'tramp-debug-outline-level)
-  (setq-local font-lock-keywords
-              ;; FIXME: This `(t FOO . BAR)' representation in
-              ;; `font-lock-keywords' is supposed to be an internal
-              ;; implementation "detail".  Don't abuse it here!
-              `(t (eval ,tramp-debug-font-lock-keywords t)
-                  ,(eval tramp-debug-font-lock-keywords t)))
-  ;; Do not edit the debug buffer.
-  (use-local-map special-mode-map)
-  (set-buffer-modified-p nil)
-  ;; For debugging purposes.
-  (local-set-key "\M-n" 'clone-buffer)
-  (add-hook 'clone-buffer-hook #'tramp-setup-debug-buffer nil 'local))
-
-(put #'tramp-setup-debug-buffer 'tramp-suppress-trace t)
-
-(function-put
- #'tramp-setup-debug-buffer 'completion-predicate
- #'tramp-debug-buffer-command-completion-p)
-
-(defun tramp-get-debug-buffer (vec)
-  "Get the debug buffer for VEC."
-  (with-current-buffer (get-buffer-create (tramp-debug-buffer-name vec))
-    (when (bobp)
-      (tramp-setup-debug-buffer))
-    (current-buffer)))
-
-(put #'tramp-get-debug-buffer 'tramp-suppress-trace t)
-
-(defun tramp-get-debug-file-name (vec)
-  "Get the debug file name for VEC."
-  (expand-file-name
-   (tramp-compat-string-replace "/" " " (tramp-debug-buffer-name vec))
-   tramp-compat-temporary-file-directory))
-
-(put #'tramp-get-debug-file-name 'tramp-suppress-trace t)
-
-(defun tramp-trace-buffer-name (vec)
-  "A name for the trace buffer for VEC."
-  (tramp-compat-string-replace "debug" "trace" (tramp-debug-buffer-name vec)))
-
-(put #'tramp-trace-buffer-name 'tramp-suppress-trace t)
-
-(defvar tramp-trace-functions nil
-  "A list of non-Tramp functions to be traced with `tramp-verbose' > 10.")
-
-;;;###tramp-autoload
-(defun tramp-debug-message (vec fmt-string &rest arguments)
-  "Append message to debug buffer of VEC.
-Message is formatted with FMT-STRING as control string and the remaining
-ARGUMENTS to actually emit the message (if applicable)."
-  (let ((inhibit-message t)
-       create-lockfiles file-name-handler-alist message-log-max
-       signal-hook-function)
-    (with-current-buffer (tramp-get-debug-buffer vec)
-      (goto-char (point-max))
-      (let ((point (point)))
-       (when (bobp)
-         ;; Headline.
-         (insert
-          (format
-           ";; Emacs: %s Tramp: %s -*- mode: outline; coding: utf-8; -*-"
-           emacs-version tramp-version))
-         (when (>= tramp-verbose 10)
-           (let ((tramp-verbose 0))
-             (insert
-              (format
-               "\n;; Location: %s Git: %s/%s"
-               (locate-library "tramp")
-               (or tramp-repository-branch "")
-               (or tramp-repository-version "")))))
-         ;; Traces.
-         (when (>= tramp-verbose 11)
-           (dolist
-               (elt
-                (append
-                 (mapcar
-                  #'intern (all-completions "tramp-" obarray #'functionp))
-                 tramp-trace-functions))
-             (unless (get elt 'tramp-suppress-trace)
-               (trace-function-background elt))))
-         ;; Delete debug file.
-         (when (and tramp-debug-to-file (tramp-get-debug-file-name vec))
-           (ignore-errors (delete-file (tramp-get-debug-file-name vec)))))
-       (unless (bolp)
-         (insert "\n"))
-       ;; Timestamp.
-       (insert (format-time-string "%T.%6N "))
-       ;; Threads.  `current-thread' might not exist when Emacs is
-       ;; configured --without-threads.
-       ;; (unless (eq (tramp-compat-funcall 'current-thread) main-thread)
-       ;;   (insert (format "%s " (tramp-compat-funcall 'current-thread))))
-       ;; Calling Tramp function.  We suppress compat and trace
-       ;; functions from being displayed.
-       (let ((frames (backtrace-frames))
-             btf fn)
-         (while (not fn)
-           (setq btf (cadadr frames))
-           (if (not btf)
-               (setq fn "")
-             (and (symbolp btf) (setq fn (symbol-name btf))
-                  (or (not (string-prefix-p "tramp" fn))
-                      (get btf 'tramp-suppress-trace))
-                  (setq fn nil))
-             (setq frames (cdr frames))))
-         ;; The following code inserts filename and line number.
-         ;; Should be inactive by default, because it is time consuming.
-         ;; (let ((ffn (find-function-noselect (intern fn))))
-         ;;   (insert
-         ;;    (format
-         ;;     "%s:%d: "
-         ;;     (file-name-nondirectory (buffer-file-name (car ffn)))
-         ;;     (with-current-buffer (car ffn)
-         ;;       (1+ (count-lines (point-min) (cdr ffn)))))))
-         (insert (format "%s " fn)))
-       ;; The message.
-       (insert (apply #'format-message fmt-string arguments))
-       ;; Write message to debug file.
-       (when tramp-debug-to-file
-         (ignore-errors
-           (write-region
-            point (point-max) (tramp-get-debug-file-name vec) 'append)))))))
-
-(put #'tramp-debug-message 'tramp-suppress-trace t)
-
-;;;###tramp-autoload
-(defvar tramp-inhibit-progress-reporter nil
-  "Show Tramp progress reporter in the minibuffer.
-This variable is used to disable concurrent progress reporter messages.")
-
-;;;###tramp-autoload
-(defsubst tramp-message (vec-or-proc level fmt-string &rest arguments)
-  "Emit a message depending on verbosity level.
-VEC-OR-PROC identifies the Tramp buffer to use.  It can be either a
-vector or a process.  LEVEL says to be quiet if `tramp-verbose' is
-less than LEVEL.  The message is emitted only if `tramp-verbose' is
-greater than or equal to LEVEL.
-
-The message is also logged into the debug buffer when `tramp-verbose'
-is greater than or equal 4.
-
-Calls functions `message' and `tramp-debug-message' with FMT-STRING as
-control string and the remaining ARGUMENTS to actually emit the message (if
-applicable)."
-  (ignore-errors
-    (when (<= level tramp-verbose)
-      ;; Display only when there is a minimum level, and the progress
-      ;; reporter doesn't suppress further messages.
-      (when (and (<= level 3) (null tramp-inhibit-progress-reporter))
-       (apply #'message
-              (concat
-               (cond
-                ((= level 0) "")
-                ((= level 1) "")
-                ((= level 2) "Warning: ")
-                (t           "Tramp: "))
-               fmt-string)
-              arguments))
-      ;; Log only when there is a minimum level.
-      (when (>= tramp-verbose 4)
-       (let ((tramp-verbose 0))
-         ;; Append connection buffer for error messages, if exists.
-         (when (= level 1)
-           (ignore-errors
-             (setq fmt-string (concat fmt-string "\n%s")
-                   arguments
-                   (append
-                    arguments
-                    `(,(tramp-get-buffer-string
-                        (if (processp vec-or-proc)
-                            (process-buffer vec-or-proc)
-                          (tramp-get-connection-buffer
-                           vec-or-proc 'dont-create))))))))
-         ;; Translate proc to vec.
-         (when (processp vec-or-proc)
-           (setq vec-or-proc (process-get vec-or-proc 'tramp-vector))))
-       ;; Do it.
-       (when (tramp-file-name-p vec-or-proc)
-         (apply #'tramp-debug-message
-                vec-or-proc
-                (concat (format "(%d) # " level) fmt-string)
-                arguments))))))
-
-(defsubst tramp-backtrace (&optional vec-or-proc force)
-  "Dump a backtrace into the debug buffer.
-If VEC-OR-PROC is nil, the buffer *debug tramp* is used.  FORCE
-forces the backtrace even if `tramp-verbose' is less than 10.
-This function is meant for debugging purposes."
-  (let ((tramp-verbose (if force 10 tramp-verbose)))
-    (when (>= tramp-verbose 10)
-      (if vec-or-proc
-         (tramp-message
-          vec-or-proc 10 "\n%s" (with-output-to-string (backtrace)))
-       (with-output-to-temp-buffer "*debug tramp*" (backtrace))))))
-
-(defun tramp-error (vec-or-proc signal fmt-string &rest arguments)
-  "Emit an error.
-VEC-OR-PROC identifies the connection to use, SIGNAL is the
-signal identifier to be raised, remaining arguments passed to
-`tramp-message'.  Finally, signal SIGNAL is raised with
-FMT-STRING and ARGUMENTS."
-  (let (signal-hook-function)
-    (tramp-backtrace vec-or-proc)
-    (unless arguments
-      ;; FMT-STRING could be just a file name, as in
-      ;; `file-already-exists' errors.  It could contain the ?\%
-      ;; character, as in smb domain spec.
-      (setq arguments (list fmt-string)
-           fmt-string "%s"))
-    (when vec-or-proc
-      (tramp-message
-       vec-or-proc 1 "%s"
-       (error-message-string
-       (list signal
-             (get signal 'error-message)
-             (apply #'format-message fmt-string arguments)))))
-    (signal signal (list (substring-no-properties
-                         (apply #'format-message fmt-string arguments))))))
-
-(put #'tramp-error 'tramp-suppress-trace t)
-
-(defvar tramp-error-show-message-timeout 30
-  "Time to show the Tramp buffer in case of an error.
-If it is bound to nil, the buffer is not shown.  This is used in
-tramp-tests.el.")
-
-(defsubst tramp-error-with-buffer
-  (buf vec-or-proc signal fmt-string &rest arguments)
-  "Emit an error, and show BUF.
-If BUF is nil, show the connection buf.  Wait for 30\", or until
-an input event arrives.  The other arguments are passed to `tramp-error'."
-  (save-window-excursion
-    (let* ((buf (or (and (bufferp buf) buf)
-                   (and (processp vec-or-proc) (process-buffer vec-or-proc))
-                   (and (tramp-file-name-p vec-or-proc)
-                        (tramp-get-connection-buffer vec-or-proc))))
-          (vec (or (and (tramp-file-name-p vec-or-proc) vec-or-proc)
-                   (and buf (tramp-dissect-file-name
-                             (tramp-get-default-directory buf))))))
-      (unwind-protect
-         (apply #'tramp-error vec-or-proc signal fmt-string arguments)
-       ;; Save exit.
-       (when (and buf
-                  (natnump tramp-error-show-message-timeout)
-                  (not (zerop tramp-verbose))
-                  ;; Do not show when flagged from outside.
-                  (not non-essential)
-                  ;; Show only when Emacs has started already.
-                  (current-message))
-         (let ((enable-recursive-minibuffers t)
-               inhibit-message)
-           ;; `tramp-error' does not show messages.  So we must do it
-           ;; ourselves.
-           (apply #'message fmt-string arguments)
-           ;; Show buffer.
-           (pop-to-buffer buf)
-           (discard-input)
-           (sit-for tramp-error-show-message-timeout)))
-       ;; Reset timestamp.  It would be wrong after waiting for a while.
-       (when (tramp-file-name-equal-p vec (car tramp-current-connection))
-         (setcdr tramp-current-connection (current-time)))))))
-
-;; We must make it a defun, because it is used earlier already.
-(defun tramp-user-error (vec-or-proc fmt-string &rest arguments)
-  "Signal a user error (or \"pilot error\")."
-  (unwind-protect
-      (apply #'tramp-error vec-or-proc 'user-error fmt-string arguments)
-    ;; Save exit.
-    (when (and (natnump tramp-error-show-message-timeout)
-              (not (zerop tramp-verbose))
-              ;; Do not show when flagged from outside.
-              (not non-essential)
-              ;; Show only when Emacs has started already.
-              (current-message))
-      (let ((enable-recursive-minibuffers t)
-           inhibit-message)
-       ;; `tramp-error' does not show messages.  So we must do it ourselves.
-       (apply #'message fmt-string arguments)
-       (discard-input)
-       (sit-for tramp-error-show-message-timeout)
-       ;; Reset timestamp.  It would be wrong after waiting for a while.
-       (when
-           (tramp-file-name-equal-p vec-or-proc (car tramp-current-connection))
-         (setcdr tramp-current-connection (current-time)))))))
-
-(put #'tramp-user-error 'tramp-suppress-trace t)
-
-(defmacro tramp-with-demoted-errors (vec-or-proc format &rest body)
-  "Execute BODY while redirecting the error message to `tramp-message'.
-BODY is executed like wrapped by `with-demoted-errors'.  FORMAT
-is a format-string containing a %-sequence meaning to substitute
-the resulting error message."
-  (declare (indent 2) (debug (symbolp form body)))
-  (let ((err (make-symbol "err")))
-    `(condition-case-unless-debug ,err
-         (progn ,@body)
-       (error (tramp-message ,vec-or-proc 3 ,format ,err) nil))))
-
 ;; This macro shall optimize the cases where a `file-exists-p' call is
 ;; invoked first.  Often, the file exists, so the remote command is
 ;; superfluous.
@@ -2336,33 +1967,19 @@ does not exist, otherwise propagate the error."
            (tramp-error ,vec 'file-missing ,filename)
          (signal (car ,err) (cdr ,err)))))))
 
-(defun tramp-test-message (fmt-string &rest arguments)
-  "Emit a Tramp message according `default-directory'."
-  (cond
-   ((tramp-tramp-file-p default-directory)
-    (apply #'tramp-message
-          (tramp-dissect-file-name default-directory) 0 fmt-string arguments))
-   ((tramp-file-name-p (car tramp-current-connection))
-    (apply #'tramp-message
-          (car tramp-current-connection) 0 fmt-string arguments))
-   (t (apply #'message fmt-string arguments))))
-
-(put #'tramp-test-message 'tramp-suppress-trace t)
-
 ;; This function provides traces in case of errors not triggered by
 ;; Tramp functions.
 (defun tramp-signal-hook-function (error-symbol data)
   "Function to be called via `signal-hook-function'."
   ;; `custom-initialize-*' functions provoke `void-variable' errors.
   ;; We don't want to see them in the backtrace.
+  (declare (tramp-suppress-trace t))
   (unless (eq error-symbol 'void-variable)
     (let ((inhibit-message t))
       (tramp-error
        (car tramp-current-connection) error-symbol
        (mapconcat (lambda (x) (format "%s" x)) data " ")))))
 
-(put #'tramp-signal-hook-function 'tramp-suppress-trace t)
-
 (defmacro with-parsed-tramp-file-name (filename var &rest body)
   "Parse a Tramp filename and make components available in the body.
 
@@ -2402,6 +2019,11 @@ If VAR is nil, then we bind `v' to the structure and 
`method', `user',
     (when (tramp-compat-string-search message (or (current-message) ""))
       (progress-reporter-update reporter value suffix))))
 
+;;;###tramp-autoload
+(defvar tramp-inhibit-progress-reporter nil
+  "Show Tramp progress reporter in the minibuffer.
+This variable is used to disable concurrent progress reporter messages.")
+
 (defmacro with-tramp-progress-reporter (vec level message &rest body)
   "Execute BODY, spinning a progress reporter with MESSAGE in interactive mode.
 If LEVEL does not fit for visible messages, there are only traces
@@ -2517,6 +2139,8 @@ Example:
                    ;; DNS-SD service type.
                    ((string-match-p
                      tramp-dns-sd-service-regexp (nth 1 (car v))))
+                   ;; Method.
+                   ((string-equal method (nth 1 (car v))))
                    ;; Configuration file or empty string.
                    (t (file-exists-p (nth 1 (car v))))))
        (setq r (delete (car v) r)))
@@ -2762,22 +2386,20 @@ Fall back to normal file name handler if no Tramp file 
name handler exists."
                                   tramp-compat-temporary-file-directory)
                                file-name-handler-alist)
                            (autoload-do-load sf foreign)))
-                        ;; (tramp-message
-                        ;;  v 4 "Running `%s'..." (cons operation args))
-                        ;; If `non-essential' is non-nil, Tramp shall
-                       ;; not open a new connection.
-                       ;; If Tramp detects that it shouldn't continue
-                       ;; to work, it throws the `suppress' event.
-                       ;; This could happen for example, when Tramp
-                       ;; tries to open the same connection twice in
-                       ;; a short time frame.
-                       ;; In both cases, we try the default handler then.
-                       (setq result
-                             (catch 'non-essential
-                               (catch 'suppress
-                                 (apply foreign operation args))))
-                        ;; (tramp-message
-                        ;;  v 4 "Running `%s'...`%s'" (cons operation args) 
result)
+                       (with-tramp-debug-message
+                           v (format "Running `%S'" (cons operation args))
+                          ;; If `non-essential' is non-nil, Tramp shall
+                         ;; not open a new connection.
+                         ;; If Tramp detects that it shouldn't continue
+                         ;; to work, it throws the `suppress' event.
+                         ;; This could happen for example, when Tramp
+                         ;; tries to open the same connection twice in
+                         ;; a short time frame.
+                         ;; In both cases, we try the default handler then.
+                         (setq result
+                               (catch 'non-essential
+                                 (catch 'suppress
+                                   (apply foreign operation args)))))
                        (cond
                         ((eq result 'non-essential)
                          (tramp-message
@@ -2967,6 +2589,25 @@ whether HANDLER is to be called.  Add operations defined 
in
 (put #'tramp-unload-file-name-handlers 'tramp-autoload t)
 (add-hook 'tramp-unload-hook #'tramp-unload-file-name-handlers)
 
+;;;###autoload
+(progn (defun inhibit-remote-files ()
+  "Deactivate remote file names."
+  (interactive)
+  (when (fboundp 'tramp-cleanup-all-connections)
+    (funcall 'tramp-cleanup-all-connections))
+  (tramp-unload-file-name-handlers)
+  (setq tramp-mode nil)))
+
+;;;###autoload
+(progn (defmacro without-remote-files (&rest body)
+  "Deactivate remote file names temporarily.
+Run BODY."
+  (declare (indent 0) (debug ((form body) body)))
+  `(let ((file-name-handler-alist (copy-tree file-name-handler-alist))
+         tramp-mode)
+     (tramp-unload-file-name-handlers)
+     ,@body)))
+
 ;;; File name handler functions for completion mode:
 
 ;; This function takes action since Emacs 28.1, when
@@ -3010,6 +2651,29 @@ not in completion mode."
       (concat dir filename))
      (t (tramp-run-real-handler #'expand-file-name (list filename 
directory))))))
 
+;; This is needed in pcomplete.el.
+(defun tramp-completion-handle-file-directory-p (filename)
+  "Like `file-directory-p' for partial Tramp files."
+  ;; We need special handling only when a method is needed.  Then we
+  ;; regard all files "/method:" or "/[method/" as existent, if
+  ;; "method" is a valid Tramp method.
+  (or (string-equal filename "/")
+      (and ;; Is it a valid method?
+           (not (string-empty-p tramp-postfix-method-format))
+           (string-match
+           (rx
+            (regexp tramp-prefix-regexp)
+            (* (regexp tramp-remote-file-name-spec-regexp)
+               (regexp tramp-postfix-hop-regexp))
+            (group-n 9 (regexp tramp-method-regexp))
+            (? (regexp tramp-postfix-method-regexp))
+             eos)
+            filename)
+          (assoc (match-string 9 filename) tramp-methods)
+          t)
+
+      (tramp-run-real-handler #'file-directory-p (list filename))))
+
 (defun tramp-completion-handle-file-exists-p (filename)
   "Like `file-exists-p' for partial Tramp files."
   ;; We need special handling only when a method is needed.  Then we
@@ -3017,7 +2681,7 @@ not in completion mode."
   ;; "method" is a valid Tramp method.  And we regard all files
   ;; "/method:user@", "/user@" or "/[method/user@" as existent, if
   ;; "user@" is a valid file name completion.  Host completion is
-  ;; performed in the respective backen operation.
+  ;; performed in the respective backend operation.
   (or (and (cond
             ;; Completion styles like `flex' and `substring' check for
             ;; the file name "/".  This does exist.
@@ -3056,6 +2720,9 @@ not in completion mode."
 
       (tramp-run-real-handler #'file-exists-p (list filename))))
 
+(defvar tramp--last-hop-directory nil
+  "Tracks the directory from which to run login programs.")
+
 ;; Method, host name and user name completion.
 ;; `tramp-completion-dissect-file-name' returns a list of
 ;; `tramp-file-name' structures.  For all of them we return possible
@@ -3064,16 +2731,8 @@ not in completion mode."
   "Like `file-name-all-completions' for partial Tramp files."
   (let ((fullname
         (tramp-drop-volume-letter (expand-file-name filename directory)))
-       ;; When `tramp-syntax' is `simplified', we need a default method.
-       (tramp-default-method
-        (and (string-empty-p tramp-postfix-method-format)
-             tramp-default-method))
-       (tramp-default-method-alist
-        (and (string-empty-p tramp-postfix-method-format)
-             tramp-default-method-alist))
-       tramp-default-user tramp-default-user-alist
-       tramp-default-host tramp-default-host-alist
-       hop result result1)
+       (directory (tramp-drop-volume-letter directory))
+       tramp--last-hop-directory hop result result1)
 
     ;; Suppress hop from completion.
     (when (string-match
@@ -3083,58 +2742,68 @@ not in completion mode."
                      (regexp tramp-postfix-hop-regexp))))
           fullname)
       (setq hop (match-string 1 fullname)
-           fullname (replace-match "" nil nil fullname 1)))
-
-    ;; Possible completion structures.
-    (dolist (elt (tramp-completion-dissect-file-name fullname))
-      (let* ((method (tramp-file-name-method elt))
-            (user (tramp-file-name-user elt))
-            (host (tramp-file-name-host elt))
-            (localname (tramp-file-name-localname elt))
-            (m (tramp-find-method method user host))
-            all-user-hosts)
-
-       (unless localname ;; Nothing to complete.
-
-         (if (or user host)
-
-             ;; Method dependent user / host combinations.
-             (progn
-               (mapc
-                (lambda (x)
-                  (setq all-user-hosts
-                        (append all-user-hosts
-                                (funcall (nth 0 x) (nth 1 x)))))
-                (tramp-get-completion-function m))
-
-               (setq result
-                     (append result
-                             (mapcar
-                              (lambda (x)
-                                (tramp-get-completion-user-host
-                                 method user host (nth 0 x) (nth 1 x)))
-                              (delq nil all-user-hosts)))))
-
-           ;; Possible methods.
-           (setq result
-                 (append result (tramp-get-completion-methods m)))))))
-
-    ;; Unify list, add hop, remove nil elements.
-    (dolist (elt result)
-      (when elt
-       (string-match tramp-prefix-regexp elt)
-       (setq elt (replace-match (concat tramp-prefix-format hop) nil nil elt))
-       (push
-        (substring elt (length (tramp-drop-volume-letter directory)))
-        result1)))
-
-    ;; Complete local parts.
-    (delete-dups
-     (append
-      result1
-      (ignore-errors
-        (tramp-run-real-handler
-        #'file-name-all-completions (list filename directory)))))))
+           fullname (replace-match "" nil nil fullname 1)
+           tramp--last-hop-directory
+           (tramp-make-tramp-file-name (tramp-dissect-hop-name hop))))
+
+    (let (;; When `tramp-syntax' is `simplified', we need a default method.
+         (tramp-default-method
+          (and (string-empty-p tramp-postfix-method-format)
+               tramp-default-method))
+         (tramp-default-method-alist
+          (and (string-empty-p tramp-postfix-method-format)
+               tramp-default-method-alist))
+         tramp-default-user tramp-default-user-alist
+         tramp-default-host tramp-default-host-alist)
+
+      ;; Possible completion structures.
+      (dolist (elt (tramp-completion-dissect-file-name fullname))
+       (let* ((method (tramp-file-name-method elt))
+              (user (tramp-file-name-user elt))
+              (host (tramp-file-name-host elt))
+              (localname (tramp-file-name-localname elt))
+              (m (tramp-find-method method user host))
+              all-user-hosts)
+
+         (unless localname ;; Nothing to complete.
+
+           (if (or user host)
+
+               ;; Method dependent user / host combinations.
+               (progn
+                 (mapc
+                  (lambda (x)
+                    (setq all-user-hosts
+                          (append all-user-hosts
+                                  (funcall (nth 0 x) (nth 1 x)))))
+                  (tramp-get-completion-function m))
+
+                 (setq result
+                       (append result
+                               (mapcar
+                                (lambda (x)
+                                  (tramp-get-completion-user-host
+                                   method user host (nth 0 x) (nth 1 x)))
+                                (delq nil all-user-hosts)))))
+
+             ;; Possible methods.
+             (setq result
+                   (append result (tramp-get-completion-methods m hop)))))))
+
+      ;; Unify list, add hop, remove nil elements.
+      (dolist (elt result)
+        (when elt
+         (setq elt (replace-regexp-in-string
+                    tramp-prefix-regexp (concat tramp-prefix-format hop) elt))
+         (push (substring elt (length directory)) result1)))
+
+      ;; Complete local parts.
+      (delete-dups
+       (append
+        result1
+        (ignore-errors
+          (tramp-run-real-handler
+          #'file-name-all-completions (list filename directory))))))))
 
 ;; Method, host name and user name completion for a file.
 (defun tramp-completion-handle-file-name-completion
@@ -3254,11 +2923,14 @@ remote host and localname (filename on remote host)."
 
 ;; This function returns all possible method completions, adding the
 ;; trailing method delimiter.
-(defun tramp-get-completion-methods (partial-method)
-  "Return all method completions for PARTIAL-METHOD."
+(defun tramp-get-completion-methods (partial-method multi-hop)
+  "Return all method completions for PARTIAL-METHOD.
+If MULTI-HOP is non-nil, return only multi-hop capable methods."
   (mapcar
    (lambda (method)
      (and method (string-prefix-p (or partial-method "") method)
+         (or (not multi-hop)
+             (tramp-multi-hop-p (make-tramp-file-name :method method)))
          (tramp-completion-make-tramp-file-name method nil nil nil)))
    (mapcar #'car tramp-methods)))
 
@@ -3337,6 +3009,11 @@ This function is added always in 
`tramp-get-completion-function'
 for all methods.  Resulting data are derived from default settings."
   `((,(tramp-find-user method nil nil) ,(tramp-find-host method nil nil))))
 
+(defcustom tramp-completion-remote-containers nil
+  "Whether container hosts in multi-hop paths should be queried for 
completions."
+  :version "30.1"
+  :type 'boolean)
+
 (defcustom tramp-completion-use-auth-sources auth-source-do-cache
   "Whether to use `auth-source-search' for completion of user and host names.
 This could be disturbing, if it requires a password / passphrase,
@@ -3359,7 +3036,7 @@ for all methods.  Resulting data are derived from default 
settings."
    "Return a (user host) tuple allowed to access.
 User is always nil."
    (let (result)
-     (when (re-search-forward regexp (line-end-position) t)
+     (when (search-forward-regexp regexp (line-end-position) t)
        (setq result (list nil (match-string match-level))))
      (or
       (> (skip-chars-forward skip-chars) 0)
@@ -3392,7 +3069,7 @@ Either user or host may be nil."
          (rx
           bol (group (regexp tramp-host-regexp))
           (? (+ blank) (group (regexp tramp-user-regexp))))))
-     (when (re-search-forward regexp (line-end-position) t)
+     (when (search-forward-regexp regexp (line-end-position) t)
        (setq result (append (list (match-string 2) (match-string 1)))))
      (forward-line 1)
      result))
@@ -3480,7 +3157,7 @@ Host is always \"localhost\"."
 Host is always \"localhost\"."
    (let (result
         (regexp (rx bol (group (regexp tramp-user-regexp)) ":")))
-     (when (re-search-forward regexp (line-end-position) t)
+     (when (search-forward-regexp regexp (line-end-position) t)
        (setq result (list (match-string 1) "localhost")))
      (forward-line 1)
      result))
@@ -3537,7 +3214,7 @@ User is always nil."
 User is always nil."
   (let (result
        (regexp (rx (literal registry) "\\" (group (+ nonl)))))
-    (when (re-search-forward regexp (line-end-position) t)
+    (when (search-forward-regexp regexp (line-end-position) t)
       (setq result (list nil (match-string 1))))
     (forward-line 1)
     result))
@@ -3686,6 +3363,8 @@ BODY is the backend specific code."
        (when (tramp-connectable-p ,filename)
         (with-parsed-tramp-file-name (expand-file-name ,filename) nil
           (with-tramp-file-property v localname "file-exists-p"
+            ;; Examine `file-attributes' cache to see if request can
+            ;; be satisfied without remote operation.
             (if (tramp-file-property-p v localname "file-attributes")
                 (not
                  (null (tramp-get-file-property v localname 
"file-attributes")))
@@ -3756,7 +3435,7 @@ BODY is the backend specific code."
         ,@body
         nil))))
 
-(defmacro tramp-skeleton-handle-make-symbolic-link
+(defmacro tramp-skeleton-make-symbolic-link
   (target linkname &optional ok-if-already-exists &rest body)
   "Skeleton for `tramp-*-handle-make-symbolic-link'.
 BODY is the backend specific code.
@@ -3832,7 +3511,15 @@ BODY is the backend specific code."
                                 tramp-crypt-file-name-handler
                                 . inhibit-file-name-handlers))
                              (inhibit-file-name-operation 'write-region))
-                         (find-file-name-handler ,visit 'write-region)))))
+                         (find-file-name-handler ,visit 'write-region))))
+         ;; We use this to save the value of
+         ;; `last-coding-system-used' after writing the tmp file.  At
+         ;; the end of the function, we set `last-coding-system-used'
+         ;; to this saved value.  This way, any intermediary coding
+         ;; systems used while talking to the remote shell or
+         ;; suchlike won't hose this variable.  This approach was
+         ;; snarfed from ange-ftp.el.
+         coding-system-used)
      (with-parsed-tramp-file-name filename nil
        (if handler
           (progn
@@ -3879,8 +3566,6 @@ BODY is the backend specific code."
           ;; likely that it is needed shortly after `write-region'.
           (tramp-set-file-property v localname "file-exists-p" t)
 
-          ;; We must protect `last-coding-system-used', now we have
-          ;; set it to its correct value.
           (let (last-coding-system-used (need-chown t))
             ;; Set file modification time.
             (when (or (eq ,visit t) (stringp ,visit))
@@ -3897,29 +3582,33 @@ BODY is the backend specific code."
 
             ;; Set the ownership.
              (when need-chown
-               (tramp-set-file-uid-gid filename uid gid)))
-
-          ;; Set extended attributes.  We ignore possible errors,
-          ;; because ACL strings could be incompatible.
-          (when attributes
-            (ignore-errors
-              (set-file-extended-attributes filename attributes)))
-
-          ;; Unlock file.
-          (when file-locked
-            ;; `unlock-file' exists since Emacs 28.1.
-            (tramp-compat-funcall 'unlock-file lockname))
-
-          ;; Sanity check.
-          (unless (equal curbuf (current-buffer))
-            (tramp-error
-             v 'file-error
-             "Buffer has changed from `%s' to `%s'" curbuf (current-buffer)))
-
-          (when (and (null noninteractive)
-                     (or (eq ,visit t) (string-or-null-p ,visit)))
-            (tramp-message v 0 "Wrote %s" filename))
-          (run-hooks 'tramp-handle-write-region-hook))))))
+               (tramp-set-file-uid-gid filename uid gid))
+
+            ;; Set extended attributes.  We ignore possible errors,
+            ;; because ACL strings or SELinux contexts could be incompatible.
+            (when attributes
+              (ignore-errors
+                (set-file-extended-attributes filename attributes)))
+
+            ;; Unlock file.
+            (when file-locked
+              ;; `unlock-file' exists since Emacs 28.1.
+              (tramp-compat-funcall 'unlock-file lockname))
+
+            ;; Sanity check.
+            (unless (equal curbuf (current-buffer))
+              (tramp-error
+               v 'file-error
+               "Buffer has changed from `%s' to `%s'" curbuf (current-buffer)))
+
+            (when (and (null noninteractive)
+                       (or (eq ,visit t) (string-or-null-p ,visit)))
+              (tramp-message v 0 "Wrote %s" filename))
+            (run-hooks 'tramp-handle-write-region-hook))))
+
+       ;; Make `last-coding-system-used' have the right value.
+       (when coding-system-used
+        (setq last-coding-system-used coding-system-used)))))
 
 ;;; Common file name handler functions for different backends:
 
@@ -4361,8 +4050,14 @@ Let-bind it when necessary.")
 
 (defun tramp-handle-file-symlink-p (filename)
   "Like `file-symlink-p' for Tramp files."
-  (let ((x (file-attribute-type (file-attributes filename))))
-    (and (stringp x) x)))
+  (with-parsed-tramp-file-name (expand-file-name filename) nil
+    ;; Some operations, like `file-truename', set the file property
+    ;; "file-symlink-marker".  We can use it as indicator, and avoid a
+    ;; possible call of `file-attributes'.
+    (when (or (tramp-get-file-property v localname "file-symlink-marker")
+             (not (tramp-file-property-p v localname "file-symlink-marker")))
+      (let ((x (file-attribute-type (file-attributes filename))))
+       (and (stringp x) x)))))
 
 (defun tramp-handle-file-truename (filename)
   "Like `file-truename' for Tramp files."
@@ -4890,8 +4585,9 @@ Do not set it manually, it is used buffer-local in 
`tramp-get-lock-pid'.")
 
 (defun tramp-multi-hop-p (vec)
   "Whether the method of VEC is capable of multi-hops."
-  (and (tramp-sh-file-name-handler-p vec)
-       (not (tramp-get-method-parameter vec 'tramp-copy-program))))
+  (let ((tramp-verbose 0))
+    (and (tramp-sh-file-name-handler-p vec)
+        (not (tramp-get-method-parameter vec 'tramp-copy-program)))))
 
 (defun tramp-add-hops (vec)
   "Add ad-hoc proxy definitions to `tramp-default-proxies-alist'."
@@ -5030,13 +4726,13 @@ a connection-local variable."
 
 (defun tramp-post-process-creation (proc vec)
   "Apply actions after creation of process PROC."
+  (declare (tramp-suppress-trace t))
   (process-put proc 'tramp-vector vec)
   (process-put proc 'adjust-window-size-function #'ignore)
   (set-process-query-on-exit-flag proc nil)
   (tramp-taint-remote-process-buffer (process-buffer proc))
-  (tramp-message vec 6 "%s" (string-join (process-command proc) " ")))
-
-(put #'tramp-post-process-creation 'tramp-suppress-trace t)
+  (when (process-command proc)
+    (tramp-message vec 6 "%s" (string-join (process-command proc) " "))))
 
 (defun tramp-direct-async-process-p (&rest args)
   "Whether direct async `make-process' can be called."
@@ -5221,25 +4917,25 @@ support symbolic links."
        ((zerop (process-file "cat" nil '(t) nil "/proc/meminfo"))
         (goto-char (point-min))
         (when
-            (re-search-forward
+            (search-forward-regexp
              (rx bol "MemTotal:" (* space) (group (+ digit)) (* space) "kB" 
eol)
              nil 'noerror)
           (setcar (nthcdr 0 result) (string-to-number (match-string 1))))
         (goto-char (point-min))
         (when
-            (re-search-forward
+            (search-forward-regexp
              (rx bol "MemFree:" (* space) (group (+ digit)) (* space) "kB" eol)
              nil 'noerror)
           (setcar (nthcdr 1 result) (string-to-number (match-string 1))))
         (goto-char (point-min))
         (when
-            (re-search-forward
+            (search-forward-regexp
              (rx bol "SwapTotal:" (* space) (group (+ digit)) (* space) "kB" 
eol)
              nil 'noerror)
           (setcar (nthcdr 2 result) (string-to-number (match-string 1))))
         (goto-char (point-min))
         (when
-            (re-search-forward
+            (search-forward-regexp
              (rx bol "SwapFree:" (* space) (group (+ digit)) (* space) "kB" 
eol)
              nil 'noerror)
           (setcar (nthcdr 3 result) (string-to-number (match-string 1)))))
@@ -5249,13 +4945,13 @@ support symbolic links."
        ((zerop (process-file "sysctl" nil '(t) nil "-a"))
         (goto-char (point-min))
         (when
-            (re-search-forward
+            (search-forward-regexp
              (rx bol "hw.pagesize:" (* space) (group (+ digit)) eol)
              nil 'noerror)
           (let ((pagesize (string-to-number (match-string 1))))
             (goto-char (point-min))
             (when
-                (re-search-forward
+                (search-forward-regexp
                  (rx bol "vm.stats.vm.v_page_count:" (* space)
                      (group (+ digit)) eol)
                  nil 'noerror)
@@ -5264,7 +4960,7 @@ support symbolic links."
                (/ (* (string-to-number (match-string 1)) pagesize) 1024)))
             (goto-char (point-min))
             (when
-                (re-search-forward
+                (search-forward-regexp
                  (rx bol "vm.stats.vm.v_free_count:" (* space)
                      (group (+ digit)) eol)
                  nil 'noerror)
@@ -5275,7 +4971,7 @@ support symbolic links."
         (when (zerop (process-file "swapctl" nil '(t) nil "-sk"))
           (goto-char (point-min))
           (when
-              (re-search-forward
+              (search-forward-regexp
                (rx bol "Total:" (* space)
                    (group (+ digit)) (* space) (group (+ digit)) eol)
                nil 'noerror)
@@ -5561,6 +5257,8 @@ of."
       ;; `set-visited-file-modtime' ourselves later on.
       (let (create-lockfiles)
         (write-region start end tmpfile append 'no-message))
+      ;; Now, `last-coding-system-used' has the right value.  Remember it.
+      (setq coding-system-used last-coding-system-used)
       (condition-case nil
          (rename-file tmpfile filename 'ok-if-already-exists)
        (error
@@ -5766,7 +5464,7 @@ Wait, until the connection buffer changes."
         ;; This can be ignored.
         (with-current-buffer (process-buffer proc)
           (goto-char (point-min))
-          (if (re-search-forward tramp-operation-not-permitted-regexp nil t)
+          (if (search-forward-regexp tramp-operation-not-permitted-regexp nil 
t)
               (progn
                 (tramp-message vec 5 "'set mode' error ignored.")
                 (tramp-message vec 3 "Process has finished.")
@@ -5789,7 +5487,7 @@ See `tramp-process-actions' for the format of ACTIONS."
       ;; Remove ANSI control escape sequences.
       (with-current-buffer (tramp-get-connection-buffer vec)
        (goto-char (point-min))
-       (while (re-search-forward ansi-color-control-seq-regexp nil t)
+       (while (search-forward-regexp ansi-color-control-seq-regexp nil t)
          (replace-match "")))
       (setq todo actions)
       (while todo
@@ -5917,7 +5615,8 @@ If the user quits via `C-g', it is propagated up to 
`tramp-file-name-handler'."
             (v (process-get proc 'tramp-vector)))
     (dolist (p (delq proc (process-list)))
       (when (tramp-file-name-equal-p v (process-get p 'tramp-vector))
-       (with-local-quit (accept-process-output p 0 nil t)))))
+       (with-tramp-suspended-timers
+         (with-local-quit (accept-process-output p 0 nil t))))))
 
   (with-current-buffer (process-buffer proc)
     (let ((inhibit-read-only t)
@@ -5944,7 +5643,7 @@ Otherwise, return nil."
   ;; We restrict ourselves to the last 256 characters.  There were
   ;; reports of a shell command "git ls-files -zco --exclude-standard"
   ;; with 85k files involved, which has blocked Tramp forever.
-  (re-search-backward regexp (max (point-min) (- (point) 256)) 'noerror))
+  (search-backward-regexp regexp (max (point-min) (- (point) 256)) 'noerror))
 
 (defun tramp-check-for-regexp (proc regexp)
   "Check, whether REGEXP is contained in process buffer of PROC.
@@ -5956,12 +5655,12 @@ Erase echoed commands if exists."
     ;; the echo mark regexp is taken for search.  We restrict the
     ;; search for the second echo mark to PIPE_BUF characters.
     (when (and (tramp-get-connection-property proc "check-remote-echo")
-              (re-search-forward
+              (search-forward-regexp
                tramp-echoed-echo-mark-regexp
                (+ (point) (* 5 tramp-echo-mark-marker-length)) t))
       (let ((begin (match-beginning 0)))
        (when
-           (re-search-forward
+           (search-forward-regexp
             tramp-echoed-echo-mark-regexp
             (+ (point) (tramp-get-connection-property proc "pipe-buf" 4096)) t)
          ;; Discard echo from remote output.
@@ -6419,6 +6118,13 @@ to cache the result.  Return the modified ATTR."
                 ;; Set virtual device number.
                 (setcar (nthcdr 11 attr)
                         (tramp-get-device ,vec))
+                ;; Set SELinux context.
+                (when (stringp (nth 12 attr))
+                  (tramp-set-file-property
+                   ,vec ,localname  "file-selinux-context"
+                   (split-string (nth 12 attr) ":" 'omit)))
+                ;; Remove optional entries.
+                (setcdr (nthcdr 11 attr) nil)
                 attr)))))
 
        ;; Return normalized result.
@@ -6473,19 +6179,19 @@ Set connection properties 
\"{uid,gid,groups}-{integer,string}\"."
          groups-integer groups-string)
       (goto-char (point-min))
       ;; Read uid.
-      (when (re-search-forward
+      (when (search-forward-regexp
             (rx "uid=" (group (+ digit)) "(" (group (+ (any "_" word))) ")")
             nil 'noerror)
        (setq uid-integer (string-to-number (match-string 1))
              uid-string (match-string 2)))
       ;; Read gid.
-      (when (re-search-forward
+      (when (search-forward-regexp
             (rx "gid=" (group (+ digit)) "(" (group (+ (any "_" word))) ")")
             nil 'noerror)
        (setq gid-integer (string-to-number (match-string 1))
              gid-string (match-string 2)))
       ;; Read groups.
-      (when (re-search-forward (rx "groups=") nil 'noerror)
+      (when (search-forward-regexp (rx "groups=") nil 'noerror)
        (while (looking-at
                (rx (group (+ digit)) "(" (group (+ (any "_" word))) ")"))
          (setq groups-integer (cons (string-to-number (match-string 1))
@@ -6558,6 +6264,7 @@ Return the local name of the temporary file."
 
 (defun tramp-delete-temp-file-function ()
   "Remove temporary files related to current buffer."
+  (declare (tramp-suppress-trace t))
   (when (stringp tramp-temp-buffer-file-name)
     (ignore-errors (delete-file tramp-temp-buffer-file-name))))
 
@@ -6695,6 +6402,7 @@ are written with verbosity of 6."
        (temporary-file-directory tramp-compat-temporary-file-directory)
        (process-environment (default-toplevel-value 'process-environment))
        (buffer (if (eq buffer t) (current-buffer) buffer))
+       (vec (or vec (car tramp-current-connection)))
        result)
     (tramp-message
      vec 6 "`%s %s' %s %s %s %s"
@@ -6757,6 +6465,7 @@ verbosity of 6."
 (defun tramp-read-passwd (proc &optional prompt)
   "Read a password from user (compat function).
 Consults the auth-source package."
+  (declare (tramp-suppress-trace t))
   (let* (;; If `auth-sources' contains "~/.authinfo.gpg", and
         ;; `exec-path' contains a relative file name like ".", it
         ;; could happen that the "gpg" command is not found.  So we
@@ -6819,11 +6528,10 @@ Consults the auth-source package."
        (setq tramp-password-save-function nil))
       (tramp-set-connection-property vec "first-password-request" nil))))
 
-(put #'tramp-read-passwd 'tramp-suppress-trace t)
-
 (defun tramp-read-passwd-without-cache (proc &optional prompt)
   "Read a password from user (compat function)."
   ;; We suspend the timers while reading the password.
+  (declare (tramp-suppress-trace t))
   (let (tramp-dont-suspend-timers)
     (with-tramp-suspended-timers
       (password-read
@@ -6832,10 +6540,9 @@ Consults the auth-source package."
             (tramp-check-for-regexp proc tramp-password-prompt-regexp)
             (match-string 0)))))))
 
-(put #'tramp-read-passwd-without-cache 'tramp-suppress-trace t)
-
 (defun tramp-clear-passwd (vec)
   "Clear password cache for connection related to VEC."
+  (declare (tramp-suppress-trace t))
   (let ((method (tramp-file-name-method vec))
        (user-domain (tramp-file-name-user-domain vec))
        (host-port (tramp-file-name-host-port vec))
@@ -6848,11 +6555,10 @@ Consults the auth-source package."
        :host ,host-port :port ,method))
     (password-cache-remove (tramp-make-tramp-file-name vec 'noloc))))
 
-(put #'tramp-clear-passwd 'tramp-suppress-trace t)
-
 (defun tramp-time-diff (t1 t2)
   "Return the difference between the two times, in seconds.
 T1 and T2 are time values (as returned by `current-time' for example)."
+  (declare (tramp-suppress-trace t))
   (float-time (time-subtract t1 t2)))
 
 (defun tramp-unquote-shell-quote-argument (s)
@@ -7030,5 +6736,7 @@ If VEC is `tramp-null-hop', return local null device."
 ;;   "/ssh:user1@host:~user2".
 ;;
 ;; * Implement file name abbreviation for user and host names.
+;;
+;; * Implement user and host name completion for multi-hops.
 
 ;;; tramp.el ends here
diff --git a/lisp/net/trampver.el b/lisp/net/trampver.el
index ad7bf94cdcd..4d56cf367e3 100644
--- a/lisp/net/trampver.el
+++ b/lisp/net/trampver.el
@@ -47,6 +47,7 @@
 (defconst tramp-bug-report-address "tramp-devel@gnu.org"
   "Email address to send bug reports to.")
 
+;;;###tramp-autoload
 (defconst tramp-repository-branch
   (ignore-errors
     ;; Suppress message from `emacs-repository-get-branch'.  We must
@@ -60,6 +61,7 @@
           (emacs-repository-get-branch dir))))
   "The repository branch of the Tramp sources.")
 
+;;;###tramp-autoload
 (defconst tramp-repository-version
   (ignore-errors
     ;; Suppress message from `emacs-repository-get-version'.  We must
diff --git a/lisp/notifications.el b/lisp/notifications.el
index 984ddbec5e9..a694b38e52e 100644
--- a/lisp/notifications.el
+++ b/lisp/notifications.el
@@ -137,6 +137,12 @@ Various PARAMS can be set:
  :app-icon       The notification icon.
                  Default is `notifications-application-icon'.
                  Set to nil if you do not want any icon displayed.
+                 If the value is a string, the function
+                 interprets it as a file name and converts to
+                 absolute by using `expand-file-name'; if it is a
+                 symbol, the function will use its name (which is
+                 useful when using the Icon Naming
+                 Specification).
  :actions        A list of actions in the form:
                    (KEY TITLE KEY TITLE ...)
                  where KEY and TITLE are both strings.
@@ -304,7 +310,10 @@ of another `notifications-notify' call."
                                          notifications-application-name)
                              :uint32 (or replaces-id 0)
                              :string (if app-icon
-                                         (expand-file-name app-icon)
+                                         (if (stringp app-icon)
+                                              (expand-file-name app-icon)
+                                            ;; Convert symbol to string
+                                            (symbol-name app-icon))
                                        ;; If app-icon is nil because user
                                        ;; requested it to be so, send the
                                        ;; empty string
diff --git a/lisp/nxml/nxml-maint.el b/lisp/nxml/nxml-maint.el
index 24b38ed0635..24eb711eb99 100644
--- a/lisp/nxml/nxml-maint.el
+++ b/lisp/nxml/nxml-maint.el
@@ -26,7 +26,7 @@
 
 ;;; Parsing target repertoire files from ucs-fonts.
 ;; This is for converting the TARGET? files in
-;; http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts.tar.gz
+;; https://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts.tar.gz
 ;; into a glyph set.
 
 (defun nxml-insert-target-repertoire-glyph-set (file var)
diff --git a/lisp/nxml/nxml-mode.el b/lisp/nxml/nxml-mode.el
index 3869d0327fd..67d136b5a66 100644
--- a/lisp/nxml/nxml-mode.el
+++ b/lisp/nxml/nxml-mode.el
@@ -151,17 +151,17 @@ This is not used directly, but only via inheritance by 
other faces."
 This is not used directly, but only via inheritance by other faces."
   :group 'nxml-faces)
 
+(defface nxml-text
+  '((t (:inherit default)))
+  "Face used to highlight text."
+  :group 'nxml-faces)
+
 (defface nxml-delimiter
-  nil
+  '((t (:inherit nxml-text)))
   "Face used to highlight delimiters.
 This is not used directly, but only via inheritance by other faces."
   :group 'nxml-faces)
 
-(defface nxml-text
-  nil
-  "Face used to highlight text."
-  :group 'nxml-faces)
-
 (defface nxml-processing-instruction-delimiter
   '((t (:inherit nxml-delimiter)))
   "Face used for the delimiters of processing instructions, i.e., <? and ?>."
@@ -230,7 +230,7 @@ This includes the `x' in hex references."
   :group 'nxml-faces)
 
 (defface nxml-element-colon
-  nil
+  '((t (:inherit nxml-delimiter)))
   "Face used for the colon in element names."
   :group 'nxml-faces)
 
diff --git a/lisp/nxml/rng-util.el b/lisp/nxml/rng-util.el
index 27c924d960f..4f49885fc45 100644
--- a/lisp/nxml/rng-util.el
+++ b/lisp/nxml/rng-util.el
@@ -24,6 +24,8 @@
 
 ;;; Code:
 
+(require 'cl-lib)
+
 (defun rng-make-datatypes-uri (uri)
   (if (string-equal uri "")
       ;; The spec doesn't say to do this, but it's perfectly conformant
@@ -39,26 +41,7 @@
 (defun rng-substq (new old list)
   "Replace first member of LIST (if any) that is `eq' to OLD by NEW.
 LIST is not modified."
-  (cond ((null list) nil)
-       ((eq (car list) old)
-        (cons new (cdr list)))
-       (t
-        (let ((tail (cons (car list)
-                          nil))
-              (rest (cdr list)))
-          (setq list tail)
-          (while rest
-            (let ((item (car rest)))
-              (setq rest (cdr rest))
-              (cond ((eq item old)
-                     (setcdr tail
-                             (cons new rest))
-                     (setq rest nil))
-                    (t
-                     (setq tail
-                           (setcdr tail
-                                   (cons item nil))))))))
-        list)))
+  (cl-substitute new old list :count 1 :test #'eq))
 
 (defun rng-escape-string (s)
   (replace-regexp-in-string "[&\"<>]"
diff --git a/lisp/obsolete/iswitchb.el b/lisp/obsolete/iswitchb.el
index 6d316fdf1cc..59e6c0403a2 100644
--- a/lisp/obsolete/iswitchb.el
+++ b/lisp/obsolete/iswitchb.el
@@ -251,7 +251,7 @@
   :group 'convenience
   :group 'completion
   :link '(emacs-commentary-link :tag "Commentary" "iswitchb.el")
-  :link '(url-link "http://www.anc.ed.ac.uk/~stephen/emacs/";)
+  :link '(url-link "https://www.anc.ed.ac.uk/~stephen/emacs/";)
   :link '(emacs-library-link :tag "Lisp File" "iswitchb.el"))
 
 (defcustom iswitchb-case case-fold-search
diff --git a/lisp/obsolete/terminal.el b/lisp/obsolete/terminal.el
index 4a2ef680b09..4e23fc3c710 100644
--- a/lisp/obsolete/terminal.el
+++ b/lisp/obsolete/terminal.el
@@ -1095,7 +1095,9 @@ subprocess started."
                    (or explicit-shell-file-name
                        (getenv "ESHELL")
                        (getenv "SHELL")
-                       "/bin/sh"))
+                       (if (eq system-type 'android)
+                           "/system/bin/sh"
+                         "/bin/sh")))
                  (s (read-string
                      (format "Run program in emulator (default %s): "
                              default-s))))
diff --git a/lisp/org/ob-C.el b/lisp/org/ob-C.el
index 3a6e99623f5..7763c4c07c1 100644
--- a/lisp/org/ob-C.el
+++ b/lisp/org/ob-C.el
@@ -339,7 +339,7 @@ FORMAT can be either a format string or a function which is 
called with VAL."
         (type
          (pcase basetype
            (`integerp '("int" "%d"))
-           (`floatp '("double" "%f"))
+           (`floatp '("double" "%s")) ;; %f rounds, use %s to print the float 
literally
            (`stringp
             (list
              (if (eq org-babel-c-variant 'd) "string" "const char*")
diff --git a/lisp/org/ob-tangle.el b/lisp/org/ob-tangle.el
index 980d4a12054..a833037ca2b 100644
--- a/lisp/org/ob-tangle.el
+++ b/lisp/org/ob-tangle.el
@@ -357,7 +357,7 @@ Did you give the decimal value %1$d by mistake?" mode)))
     (error "File mode %S not recognized as a valid format." mode))
    ((string-match-p "^o0?[0-7][0-7][0-7]$" mode)
     (string-to-number (replace-regexp-in-string "^o" "" mode) 8))
-   ((string-match-p 
"^[ugoa]*\\(?:[+-=][rwxXstugo]*\\)+\\(,[ugoa]*\\(?:[+-=][rwxXstugo]*\\)+\\)*$" 
mode)
+   ((string-match-p 
"^[ugoa]*\\(?:[+=-][rwxXstugo]*\\)+\\(,[ugoa]*\\(?:[+=-][rwxXstugo]*\\)+\\)*$" 
mode)
     ;; Match regexp taken from `file-modes-symbolic-to-number'.
     (file-modes-symbolic-to-number mode org-babel-tangle-default-file-mode))
    ((string-match-p "^[r-][w-][xs-][r-][w-][xs-][r-][w-][x-]$" mode)
@@ -513,6 +513,7 @@ The PARAMS are the 3rd element of the info for the same src 
block."
                                            (cdr (assq :tangle params)))))
             bare))))))
 
+(defvar org-outline-regexp) ; defined in lisp/org.el
 (defun org-babel-tangle-single-block (block-counter &optional only-this-block)
   "Collect the tangled source for current block.
 Return the list of block attributes needed by
@@ -570,8 +571,8 @@ non-nil, return the full association list to be used by
             (buffer-substring
              (max (condition-case nil
                       (save-excursion
-                        (org-back-to-heading t) ; Sets match data
-                        (match-end 0))
+                        (org-back-to-heading t)
+                        (re-search-forward org-outline-regexp))
                     (error (point-min)))
                   (save-excursion
                     (if (re-search-backward
diff --git a/lisp/org/ol-bibtex.el b/lisp/org/ol-bibtex.el
index fd9517233e0..a16a4b39277 100644
--- a/lisp/org/ol-bibtex.el
+++ b/lisp/org/ol-bibtex.el
@@ -761,7 +761,10 @@ drawer."
   "If kill ring holds a bibtex entry yank it as an Org headline."
   (interactive)
   (let (entry)
-    (with-temp-buffer (yank 1) (setf entry (org-bibtex-read)))
+    (with-temp-buffer
+      (yank 1)
+      (bibtex-mode)
+      (setf entry (org-bibtex-read)))
     (if entry
        (org-bibtex-write)
       (error "Yanked text does not appear to contain a BibTeX entry"))))
diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el
index d7fe14cd5e1..6ab313e1218 100644
--- a/lisp/org/org-clock.el
+++ b/lisp/org/org-clock.el
@@ -51,6 +51,8 @@
 (declare-function org-dynamic-block-define "org" (type func))
 (declare-function w32-notification-notify "w32fns.c" (&rest params))
 (declare-function w32-notification-close "w32fns.c" (&rest params))
+(declare-function haiku-notifications-notify "haikuselect.c")
+(declare-function android-notifications-notify "androidselect.c")
 
 (defvar org-frame-title-format-backup nil)
 (defvar org-state)
@@ -855,6 +857,18 @@ use libnotify if available, or fall back on a message."
        ((stringp org-show-notification-handler)
         (start-process "emacs-timer-notification" nil
                        org-show-notification-handler notification))
+        ((fboundp 'haiku-notifications-notify)
+         ;; N.B. timeouts are not available under Haiku.
+         (haiku-notifications-notify :title "Org mode message"
+                                     :body notification
+                                     :urgency 'low))
+        ((fboundp 'android-notifications-notify)
+         ;; N.B. timeouts are not available under Haiku or Android.
+         (android-notifications-notify :title "Org mode message"
+                                       :body notification
+                                       ;; Low urgency notifications
+                                       ;; are by default hidden.
+                                       :urgency 'normal))
        ((fboundp 'w32-notification-notify)
         (let ((id (w32-notification-notify
                    :title "Org mode message"
@@ -2069,6 +2083,7 @@ Use `\\[org-clock-remove-overlays]' to remove the subtree 
times."
               h m))))
 
 (defvar-local org-clock-overlays nil)
+(put 'org-clock-overlays 'permanent-local t)
 
 (defun org-clock-put-overlay (time)
   "Put an overlay on the headline at point, displaying TIME.
diff --git a/lisp/org/org-colview.el b/lisp/org/org-colview.el
index 92a3b473d15..28cfd0d910c 100644
--- a/lisp/org/org-colview.el
+++ b/lisp/org/org-colview.el
@@ -116,6 +116,7 @@ in `org-columns-summary-types-default', which see."
 
 (defvar-local org-columns-overlays nil
   "Holds the list of current column overlays.")
+(put 'org-columns-overlays 'permanent-local t)
 
 (defvar-local org-columns-current-fmt nil
   "Local variable, holds the currently active column format.")
diff --git a/lisp/org/org-ctags.el b/lisp/org/org-ctags.el
index 5dd2bfd59cd..990214f4117 100644
--- a/lisp/org/org-ctags.el
+++ b/lisp/org/org-ctags.el
@@ -156,7 +156,9 @@ Format is: /REGEXP/TAGNAME/FLAGS,TAGTYPE/
 See the ctags documentation for more information.")
 
 (defcustom org-ctags-path-to-ctags
-  (if (executable-find "ctags-exuberant") "ctags-exuberant" "ctags")
+  (if (executable-find "ctags-exuberant")
+      "ctags-exuberant"
+    ctags-program-name)
   "Name of the ctags executable file."
   :version "24.1"
   :type 'file)
diff --git a/lisp/org/org-element.el b/lisp/org/org-element.el
index 517d45b0224..296468eed1a 100644
--- a/lisp/org/org-element.el
+++ b/lisp/org/org-element.el
@@ -6567,7 +6567,9 @@ If you observe Emacs hangs frequently, please report this 
to Org mode mailing li
                  ;; Make sure that we return referenced element in cache
                  ;; that can be altered directly.
                  (if element
-                     (setq element (or (org-element--cache-put element) 
element))
+                     (progn
+                       (org-element-put-property element :granularity 'element)
+                       (setq element (or (org-element--cache-put element) 
element)))
                    ;; Nothing to parse (i.e. empty file).
                    (throw 'exit parent))
                  (unless (or (not (org-element--cache-active-p)) parent)
@@ -6942,12 +6944,13 @@ known element in cache (it may start after END)."
                           (let ((current (org-with-point-at 
(org-element-property :begin up)
                                            (org-element-with-disabled-cache
                                              (and (looking-at-p 
org-element-headline-re)
-                                                  
(org-element-headline-parser))))))
+                                                  (org-element-headline-parser 
nil 'fast))))))
                             (when (eq 'headline (org-element-type current))
                               (org-element--cache-log-message
                                "Found non-robust headline that can be updated 
individually: %S"
                                (org-element--format-element current))
                               (org-element-set-element up current)
+                              (org-element-put-property up :granularity 
'element)
                               t)))
                      ;; If UP is org-data, the situation is similar to
                      ;; headline case.  We just need to re-parse the
@@ -7734,7 +7737,8 @@ the cache."
                               ;; it to real beginning then despite
                               ;; START being larger.
                               (setq start nil)
-                              (move-start-to-next-match nil)
+                              (let ((data nil)) ; data may not be valid. 
ignore it.
+                                (move-start-to-next-match nil))
                               ;; The new element may now start before
                               ;; or at already processed position.
                               ;; Make sure that we continue from an
diff --git a/lisp/org/org-faces.el b/lisp/org/org-faces.el
index 594c9a6e738..bb0958033fe 100644
--- a/lisp/org/org-faces.el
+++ b/lisp/org/org-faces.el
@@ -108,7 +108,7 @@ color of the frame."
   "Face used for drawers."
   :group 'org-faces)
 
-(defface org-property-value nil
+(defface org-property-value '((t :inherit default))
   "Face used for the value of a property."
   :group 'org-faces)
 
diff --git a/lisp/org/org-fold-core.el b/lisp/org/org-fold-core.el
index 027ff921581..6c17b00faf8 100644
--- a/lisp/org/org-fold-core.el
+++ b/lisp/org/org-fold-core.el
@@ -502,26 +502,34 @@ hanging around."
          ;; different buffer.  This can happen, for example, when
          ;; org-capture copies local variables into *Capture* buffer.
          (setq buffers (list (current-buffer)))
-       (dolist (buf (cons (or (buffer-base-buffer) (current-buffer))
-                          (buffer-local-value 'org-fold-core--indirect-buffers 
(or (buffer-base-buffer) (current-buffer)))))
-         (if (buffer-live-p buf)
-             (push buf buffers)
-           (dolist (spec (org-fold-core-folding-spec-list))
-             (when (and (not (org-fold-core-get-folding-spec-property spec 
:global))
-                        (gethash (cons buf spec) 
org-fold-core--property-symbol-cache))
-               ;; Make sure that dead-properties variable can be passed
-               ;; as argument to `remove-text-properties'.
-               (push t dead-properties)
-               (push (gethash (cons buf spec) 
org-fold-core--property-symbol-cache)
-                     dead-properties))))))
+       (let ((all-buffers (buffer-local-value
+                           'org-fold-core--indirect-buffers
+                           (or (buffer-base-buffer) (current-buffer)))))
+         (dolist (buf (cons (or (buffer-base-buffer) (current-buffer))
+                            (buffer-local-value 
'org-fold-core--indirect-buffers (or (buffer-base-buffer) (current-buffer)))))
+           (if (buffer-live-p buf)
+               (push buf buffers)
+             (dolist (spec (org-fold-core-folding-spec-list))
+               (when (and (not (org-fold-core-get-folding-spec-property spec 
:global))
+                          (gethash (cons buf spec) 
org-fold-core--property-symbol-cache))
+                 ;; Make sure that dead-properties variable can be passed
+                 ;; as argument to `remove-text-properties'.
+                 (push t dead-properties)
+                 (push (gethash (cons buf spec) 
org-fold-core--property-symbol-cache)
+                       dead-properties)))))
+         (when dead-properties
+           (with-current-buffer (or (buffer-base-buffer) (current-buffer))
+             (setq-local org-fold-core--indirect-buffers
+                         (seq-filter #'buffer-live-p all-buffers))))))
      (dolist (buf buffers)
        (with-current-buffer buf
-         (with-silent-modifications
-           (save-restriction
-             (widen)
-             (remove-text-properties
-              (point-min) (point-max)
-              dead-properties)))
+         (when dead-properties
+           (with-silent-modifications
+             (save-restriction
+               (widen)
+               (remove-text-properties
+                (point-min) (point-max)
+                dead-properties))))
          ,@body))))
 
 ;; This is the core function used to fold text in buffers.  We use
@@ -1277,19 +1285,19 @@ to :front-sticky/:rear-sticky folding spec property.
 If the folded region is folded with a spec with non-nil :fragile
 property, unfold the region if the :fragile function returns non-nil."
   ;; If no insertions or deletions in buffer, skip all the checks.
-  (unless (or (eq org-fold-core--last-buffer-chars-modified-tick 
(buffer-chars-modified-tick))
-              org-fold-core--ignore-modifications
+  (unless (or org-fold-core--ignore-modifications
+              (eq org-fold-core--last-buffer-chars-modified-tick 
(buffer-chars-modified-tick))
               (memql 'ignore-modification-checks 
org-fold-core--optimise-for-huge-buffers))
     ;; Store the new buffer modification state.
     (setq org-fold-core--last-buffer-chars-modified-tick 
(buffer-chars-modified-tick))
     (save-match-data
       ;; Handle changes in all the indirect buffers and in the base
       ;; buffer.  Work around Emacs bug#46982.
-      (when (eq org-fold-core-style 'text-properties)
-        (org-fold-core-cycle-over-indirect-buffers
-          ;; Re-hide text inserted in the middle/front/back of a folded
-          ;; region.
-          (unless (equal from to) ; Ignore deletions.
+      ;; Re-hide text inserted in the middle/front/back of a folded
+      ;; region.
+      (unless (equal from to) ; Ignore deletions.
+        (when (eq org-fold-core-style 'text-properties)
+          (org-fold-core-cycle-over-indirect-buffers
            (dolist (spec (org-fold-core-folding-spec-list))
               ;; Reveal fully invisible text inserted in the middle
               ;; of visible portion of the buffer.  This is needed,
diff --git a/lisp/org/org-mouse.el b/lisp/org/org-mouse.el
index 3b2c4cba624..9c9dfee51a1 100644
--- a/lisp/org/org-mouse.el
+++ b/lisp/org/org-mouse.el
@@ -859,6 +859,10 @@ This means, between the beginning of line and the point."
               (org-mouse-in-region-p (posn-point (event-start event))))
     (mouse-drag-region event)))
 
+;; This function conflicts with touch screen gestures as it relays
+;; events to `mouse-drag-region'.
+(put 'org-mouse-down-mouse 'ignored-mouse-command t)
+
 (add-hook 'org-mode-hook
           (lambda ()
             (setq org-mouse-context-menu-function #'org-mouse-context-menu)
@@ -975,7 +979,7 @@ This means, between the beginning of line and the point."
   (interactive)
   (org-back-to-heading)
   (let ((minlevel 1000)
-       (replace-text (concat (match-string 0) "* ")))
+       (replace-text (concat (make-string (org-current-level) ?*) "* ")))
     (beginning-of-line 2)
     (save-excursion
       (while (not (or (eobp) (looking-at org-outline-regexp)))
diff --git a/lisp/org/org-num.el b/lisp/org/org-num.el
index cbe5e455ea6..807fa66223b 100644
--- a/lisp/org/org-num.el
+++ b/lisp/org/org-num.el
@@ -156,6 +156,7 @@ control tag inheritance."
 
 (defvar-local org-num--overlays nil
   "Ordered list of overlays used for numbering outlines.")
+(put 'org-num--overlays 'permanent-local t)
 
 (defvar-local org-num--skip-level nil
   "Level below which headlines from current tree are not numbered.
diff --git a/lisp/org/org-table.el b/lisp/org/org-table.el
index 9a72eb5f314..221497f53b7 100644
--- a/lisp/org/org-table.el
+++ b/lisp/org/org-table.el
@@ -477,6 +477,7 @@ This may be useful when columns have been shrunk."
       (format "|%s" (mapconcat #'identity (reverse str) "")))))
 
 (defvar-local org-table-header-overlay nil)
+(put 'org-table-header-overlay 'permanent-local t)
 (defun org-table-header-set-header ()
   "Display the header of the table at point."
   (let ((gcol temporary-goal-column))
@@ -3812,6 +3813,7 @@ FACE, when non-nil, for the highlight."
 
 (defvar-local org-table-coordinate-overlays nil
   "Collects the coordinate grid overlays, so that they can be removed.")
+(put 'org-table-coordinate-overlays 'permanent-local t)
 
 (defun org-table-overlay-coordinates ()
   "Add overlays to the table at point, to show row/column coordinates."
diff --git a/lisp/org/org-version.el b/lisp/org/org-version.el
index 8d93af2c20d..57e406b24fc 100644
--- a/lisp/org/org-version.el
+++ b/lisp/org/org-version.el
@@ -5,13 +5,13 @@
 (defun org-release ()
   "The release version of Org.
 Inserted by installing Org mode or when a release is made."
-   (let ((org-release "9.6.6"))
+   (let ((org-release "9.6.7"))
      org-release))
 ;;;###autoload
 (defun org-git-version ()
   "The Git version of Org mode.
 Inserted by installing Org or when a release is made."
-   (let ((org-git-version "release_9.6.6"))
+   (let ((org-git-version "release_9.6.7-13-g99cc96"))
      org-git-version))
 
 (provide 'org-version)
diff --git a/lisp/org/org.el b/lisp/org/org.el
index b81630fdc07..f56aa4f6f69 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -9,7 +9,7 @@
 ;; URL: https://orgmode.org
 ;; Package-Requires: ((emacs "26.1"))
 
-;; Version: 9.6.6
+;; Version: 9.6.7
 
 ;; This file is part of GNU Emacs.
 ;;
@@ -3760,7 +3760,6 @@ This is needed for font-lock setup.")
 (defvar calc-embedded-close-formula)
 (defvar calc-embedded-open-formula)
 (defvar calc-embedded-open-mode)
-(defvar font-lock-unfontify-region-function)
 (defvar org-agenda-tags-todo-honor-ignore-options)
 (defvar remember-data-file)
 (defvar texmathp-why)
@@ -5691,7 +5690,7 @@ highlighting was done, nil otherwise."
 
 If this is called at a normal headline, the level is the number
 of stars.  Use `org-reduced-level' to remove the effect of
-`org-odd-levels'.  Unlike to `org-current-level', this function
+`org-odd-levels-only'.  Unlike `org-current-level', this function
 takes into consideration inlinetasks."
   (org-with-wide-buffer
    (end-of-line)
@@ -5893,6 +5892,8 @@ needs to be inserted at a specific position in the 
font-lock sequence.")
 
 (defvar-local org-custom-properties-overlays nil
   "List of overlays used for custom properties.")
+;; Preserve when switching modes or when restarting Org.
+(put 'org-custom-properties-overlays 'permanent-local t)
 
 (defun org-toggle-custom-properties-visibility ()
   "Display or hide properties in `org-custom-properties'."
@@ -6621,7 +6622,7 @@ headings in the region."
   "Return the level of the current entry, or nil if before the first headline.
 The level is the number of stars at the beginning of the
 headline.  Use `org-reduced-level' to remove the effect of
-`org-odd-levels'.  Unlike to `org-outline-level', this function
+`org-odd-levels-only'.  Unlike `org-outline-level', this function
 ignores inlinetasks."
   (let ((level (org-with-limited-levels (org-outline-level))))
     (and (> level 0) level)))
@@ -9709,7 +9710,7 @@ when there is a statistics cookie in the headline!
 
  (defun org-summary-todo (n-done n-not-done)
    \"Switch entry to DONE when all subentries are done, to TODO otherwise.\"
-   (let (org-log-done org-log-states)   ; turn off logging
+   (let (org-log-done org-todo-log-states)   ; turn off logging
      (org-todo (if (= n-not-done 0) \"DONE\" \"TODO\"))))")
 
 (defvar org-todo-statistics-hook nil
@@ -10673,6 +10674,7 @@ D      Show deadlines and scheduled items between a 
date range."
 
 (defvar-local org-occur-highlights nil
   "List of overlays used for occur matches.")
+(put 'org-occur-highlights 'permanent-local t)
 (defvar-local org-occur-parameters nil
   "Parameters of the active org-occur calls.
 This is a list, each call to org-occur pushes as cons cell,
@@ -16159,6 +16161,10 @@ SNIPPETS-P indicates if this is run to create snippet 
images for HTML."
 ;; Image display
 
 (defvar-local org-inline-image-overlays nil)
+;; Preserve when switching modes or when restarting Org.
+;; If we clear the overlay list and later enable Or mode, the existing
+;; image overlays will never be cleared by `org-toggle-inline-images'.
+(put 'org-inline-image-overlays 'permanent-local t)
 
 (defun org--inline-image-overlays (&optional beg end)
   "Return image overlays between BEG and END."
@@ -16253,7 +16259,8 @@ conventions:
 
   2. Its description consists in a single link of the previous
      type.  In this case, that link must be a well-formed plain
-     or angle link, i.e., it must have an explicit \"file\" type.
+     or angle link, i.e., it must have an explicit \"file\" or
+     \"attachment\" type.
 
 Equip each image with the key-map `image-map'.
 
@@ -16284,7 +16291,7 @@ buffer boundaries with possible narrowing."
               ;; "file:" links.  Also check link abbreviations since
               ;; some might expand to "file" links.
               (file-types-re
-               (format 
"\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?file:\\)"
+               (format 
"\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\(<?\\(?:file\\|attachment\\):\\)"
                        (if (not link-abbrevs) ""
                          (concat "\\|" (regexp-opt link-abbrevs))))))
          (while (re-search-forward file-types-re end t)
@@ -16323,7 +16330,9 @@ buffer boundaries with possible narrowing."
                             ;; description.
                             (= (org-element-property :contents-end link)
                                (match-end 0))
-                            (match-string 2)))))))
+                            (progn
+                               (setq linktype (match-string 1))
+                               (match-string 2))))))))
              (when (and path (string-match-p file-extension-re path))
                (let ((file (if (equal "attachment" linktype)
                                (progn
@@ -20656,7 +20665,7 @@ non-nil it will also look at invisible ones."
        (if backward? (goto-char (point-min)) (outline-next-heading))
       (org-back-to-heading invisible-ok)
       (unless backward? (end-of-line)) ;do not match current headline
-      (let ((level (- (match-end 0) (match-beginning 0) 1))
+      (let ((level (org-current-level))
            (f (if backward? #'re-search-backward #'re-search-forward))
            (count (if arg (abs arg) 1))
            (result (point)))
diff --git a/lisp/org/ox-beamer.el b/lisp/org/ox-beamer.el
index 689bf1559b4..2590bd5fa72 100644
--- a/lisp/org/ox-beamer.el
+++ b/lisp/org/ox-beamer.el
@@ -924,11 +924,10 @@ holding export options."
   "Support for editing Beamer oriented Org mode files."
   :lighter " Bm")
 
-(when (fboundp 'font-lock-add-keywords)
-  (font-lock-add-keywords
-   'org-mode
-   '((":\\(B_[a-z]+\\|BMCOL\\):" 1 'org-beamer-tag prepend))
-   'prepend))
+(font-lock-add-keywords
+ 'org-mode
+ '((":\\(B_[a-z]+\\|BMCOL\\):" 1 'org-beamer-tag prepend))
+ 'prepend)
 
 (defface org-beamer-tag '((t (:box (:line-width 1 :color grey40))))
   "The special face for beamer tags."
diff --git a/lisp/org/ox-publish.el b/lisp/org/ox-publish.el
index f9c3877d7df..cff34f05882 100644
--- a/lisp/org/ox-publish.el
+++ b/lisp/org/ox-publish.el
@@ -1183,7 +1183,8 @@ references with `org-export-get-reference'."
                     (org-link-search search nil t)
                   (error
                    (signal 'org-link-broken (cdr err)))))
-              (and (org-at-heading-p)
+              (and (derived-mode-p 'org-mode)
+                    (org-at-heading-p)
                    (org-string-nw-p (org-entry-get (point) "CUSTOM_ID"))))))))
    ((not org-publish-cache)
     (progn
diff --git a/lisp/org/ox-texinfo.el b/lisp/org/ox-texinfo.el
index f822f3d110c..5befcac1c39 100644
--- a/lisp/org/ox-texinfo.el
+++ b/lisp/org/ox-texinfo.el
@@ -1965,9 +1965,6 @@ EXT-PLIST, when provided, is a property list with external
 parameters overriding Org default settings, but still inferior to
 file-local settings.
 
-When optional argument PUB-DIR is set, use it as the publishing
-directory.
-
 Return INFO file's name."
   (interactive)
   (let ((outfile (org-export-output-file-name ".texi" subtreep))
diff --git a/lisp/org/ox.el b/lisp/org/ox.el
index 6f819def93a..94cc5a22881 100644
--- a/lisp/org/ox.el
+++ b/lisp/org/ox.el
@@ -5684,11 +5684,8 @@ transcoding it."
      (primary-closing
       :utf-8 " »" :html "&nbsp;&raquo;" :latex "\\fg{}"
       :texinfo "@tie{}@guillemetright{}")
-     (secondary-opening
-      :utf-8 "« " :html "&laquo;&nbsp;" :latex "\\og "
-      :texinfo "@guillemetleft{}@tie{}")
-     (secondary-closing :utf-8 " »" :html "&nbsp;&raquo;" :latex "\\fg{}"
-                       :texinfo "@tie{}@guillemetright{}")
+     (secondary-opening :utf-8 "“" :html "&ldquo;" :latex "``" :texinfo "``")
+     (secondary-closing :utf-8 "”" :html "&rdquo;" :latex "''" :texinfo "''")
      (apostrophe :utf-8 "’" :html "&rsquo;"))
     ("is"
      (primary-opening
diff --git a/lisp/pcmpl-unix.el b/lisp/pcmpl-unix.el
index 1a77e3b248f..e6b67256a4c 100644
--- a/lisp/pcmpl-unix.el
+++ b/lisp/pcmpl-unix.el
@@ -227,7 +227,7 @@ documentation), this function returns nil."
 ;;;###autoload(defalias 'pcomplete/sha224sum 'pcomplete/md5sum)
 ;;;###autoload(defalias 'pcomplete/sha256sum 'pcomplete/md5sum)
 ;;;###autoload(defalias 'pcomplete/sha384sum 'pcomplete/md5sum)
-;;;###autoload(defalias 'pcomplete/sha521sum 'pcomplete/md5sum)
+;;;###autoload(defalias 'pcomplete/sha512sum 'pcomplete/md5sum)
 
 ;;;###autoload
 (defun pcomplete/sort ()
diff --git a/lisp/pcomplete.el b/lisp/pcomplete.el
index 36f68f1af57..151611f94b7 100644
--- a/lisp/pcomplete.el
+++ b/lisp/pcomplete.el
@@ -138,6 +138,11 @@
   "A regexp of names to be disregarded during directory completion."
   :type '(choice regexp (const :tag "None" nil)))
 
+(defcustom pcomplete-remote-file-ignore nil
+  "Whether to ignore remote file names."
+  :version "30.1"
+  :type 'boolean)
+
 (define-obsolete-variable-alias 'pcomplete-ignore-case 'completion-ignore-case
   "28.1")
 
@@ -924,7 +929,10 @@ this is `comint-dynamic-complete-functions'."
                            (sort comps pcomplete-compare-entry-function)))
                      ,@(cdr (completion-file-name-table s p a)))
         (let ((completion-ignored-extensions nil)
-             (completion-ignore-case completion-ignore-case))
+             (completion-ignore-case completion-ignore-case)
+              (tramp-mode (and tramp-mode (not pcomplete-remote-file-ignore)))
+              (non-essential (not (file-remote-p s)))
+              (minibuffer-completing-file-name (not (file-remote-p s))))
           (completion-table-with-predicate
            #'comint-completion-file-name-table pred 'strict s p a))))))
 
@@ -1318,11 +1326,12 @@ If specific documentation can't be given, be generic."
 
 ;; general utilities
 
-(defun pcomplete-uniquify-list (l)
-  "Sort and remove multiples in L."
-  (setq l (sort l #'string-lessp))
-  (seq-uniq l))
-(define-obsolete-function-alias 'pcomplete-uniqify-list 
#'pcomplete-uniquify-list "27.1")
+(defun pcomplete-uniquify-list (sequence)
+  "Sort and remove multiples in SEQUENCE.
+Sequence should be a vector or list of strings."
+  (sort (seq-uniq sequence) #'string-lessp))
+(define-obsolete-function-alias
+  'pcomplete-uniqify-list #'pcomplete-uniquify-list "27.1")
 
 (defun pcomplete-process-result (cmd &rest args)
   "Call CMD using `call-process' and return the simplest result."
diff --git a/lisp/pixel-scroll.el b/lisp/pixel-scroll.el
index 5f412bf418a..488f6781254 100644
--- a/lisp/pixel-scroll.el
+++ b/lisp/pixel-scroll.el
@@ -504,6 +504,7 @@ Otherwise, redisplay will reset the window's vscroll."
   (set-window-start nil (pixel-point-at-unseen-line) t)
   (set-window-vscroll nil vscroll t))
 
+;;;###autoload
 (defun pixel-scroll-precision-scroll-down-page (delta)
   "Scroll the current window down by DELTA pixels.
 Note that this function doesn't work if DELTA is larger than
@@ -518,38 +519,41 @@ the height of the current window."
         (desired-vscroll (if start-posn
                               (- delta (cdr (posn-x-y start-posn)))
                             (+ current-vs delta)))
-         (edges (window-edges nil t))
-         (usable-height (- (nth 3 edges)
-                           (nth 1 edges)))
-         (next-pos (save-excursion
-                     (goto-char desired-start)
-                     (when (zerop (vertical-motion (1+ scroll-margin)))
-                       (set-window-start nil desired-start)
-                       (signal 'end-of-buffer nil))
-                     (while (when-let ((posn (posn-at-point)))
-                              (< (cdr (posn-x-y posn)) delta))
-                       (when (zerop (vertical-motion 1))
-                         (set-window-start nil desired-start)
-                         (signal 'end-of-buffer nil)))
-                     (point)))
          (scroll-preserve-screen-position nil)
-         (auto-window-vscroll nil))
-    (when (and (or (< (point) next-pos))
-               (let ((pos-visibility (pos-visible-in-window-p next-pos nil t)))
-                 (and pos-visibility
-                      (or (eq (length pos-visibility) 2)
-                          (when-let* ((posn (posn-at-point next-pos)))
-                            (> (cdr (posn-object-width-height posn))
-                               usable-height))))))
-      (goto-char next-pos))
-    (set-window-start nil (if (zerop (window-hscroll))
-                              desired-start
-                            (save-excursion
-                              (goto-char desired-start)
-                              (beginning-of-visual-line)
-                              (point)))
-                      t)
-    (set-window-vscroll nil desired-vscroll t t)))
+         (auto-window-vscroll nil)
+         (new-start-position (if (zerop (window-hscroll))
+                                 desired-start
+                               (save-excursion
+                                 (goto-char desired-start)
+                                 (beginning-of-visual-line)
+                                 (point)))))
+    (set-window-start nil new-start-position
+                      (not (zerop desired-vscroll)))
+    (set-window-vscroll nil desired-vscroll t t)
+    ;; Constrain point to a location that will not result in
+    ;; recentering, if it is no longer completely visible.
+    (unless (pos-visible-in-window-p (point))
+      ;; If desired-vscroll is 0, target the window start itself.  But
+      ;; in any other case, target the line immediately below the
+      ;; window start, unless that line is itself invisible.  This
+      ;; improves the appearance of the window by maintaining the
+      ;; cursor row in a fully visible state.
+      (if (zerop desired-vscroll)
+          (goto-char new-start-position)
+        (let ((line-after (save-excursion
+                            (goto-char new-start-position)
+                            (if (zerop (vertical-motion 1))
+                                (progn
+                                  (set-window-vscroll nil 0 t t)
+                                  nil) ; nil means move to new-start-position.
+                              (point)))))
+          (if (not line-after)
+              (progn
+                (goto-char new-start-position)
+                (signal 'end-of-buffer nil))
+            (if (pos-visible-in-window-p line-after nil t)
+                (goto-char line-after)
+              (goto-char new-start-position))))))))
 
 (defun pixel-scroll-precision-scroll-down (delta)
   "Scroll the current window down by DELTA pixels."
@@ -560,6 +564,7 @@ the height of the current window."
       (setq delta (- delta max-height)))
     (pixel-scroll-precision-scroll-down-page delta)))
 
+;;;###autoload
 (defun pixel-scroll-precision-scroll-up-page (delta)
   "Scroll the current window up by DELTA pixels.
 Note that this function doesn't work if DELTA is larger than
@@ -567,27 +572,12 @@ the height of the current window."
   (let* ((edges (window-edges nil t nil t))
          (max-y (- (nth 3 edges)
                    (nth 1 edges)))
-         (usable-height max-y)
          (posn (posn-at-x-y 0 (+ (window-tab-line-height)
                                  (window-header-line-height)
                                  (- max-y delta))))
-         (point (posn-point posn))
-         (up-point (and point
-                        (save-excursion
-                          (goto-char point)
-                          (vertical-motion (- (1+ scroll-margin)))
-                          (point)))))
-    (when (and point (> (point) up-point))
-      (when (let ((pos-visible (pos-visible-in-window-p up-point nil t)))
-              (or (eq (length pos-visible) 2)
-                  (when-let* ((posn (posn-at-point up-point))
-                              (edges (window-edges nil t))
-                              (usable-height (- (nth 3 edges)
-                                                (nth 1 edges))))
-                    (> (cdr (posn-object-width-height posn))
-                       usable-height))))
-        (goto-char up-point)))
-    (let ((current-vscroll (window-vscroll nil t)))
+         (point (posn-point posn)))
+    (let ((current-vscroll (window-vscroll nil t))
+          (wanted-pos (window-start)))
       (setq delta (- delta current-vscroll))
       (set-window-vscroll nil 0 t t)
       (when (> delta 0)
@@ -596,16 +586,25 @@ the height of the current window."
                                              start nil nil nil t))
                (height (nth 1 dims))
                (position (nth 2 dims)))
-          (set-window-start nil position t)
-          ;; If the line above is taller than the window height (i.e. there's
-          ;; a very tall image), keep point on it.
-          (when (> height usable-height)
-            (goto-char position))
+          (setq wanted-pos position)
           (when (or (not position) (eq position start))
             (signal 'beginning-of-buffer nil))
           (setq delta (- delta height))))
+      (set-window-start nil wanted-pos
+                        (not (zerop delta)))
       (when (< delta 0)
-        (set-window-vscroll nil (- delta) t t)))))
+        (set-window-vscroll nil (- delta) t t))
+      ;; vscroll and the window start are now set.  Move point to a
+      ;; position where redisplay will not recenter, if it is now
+      ;; outside the window.
+      (unless (pos-visible-in-window-p (point))
+        (let ((up-pos (save-excursion
+                        (goto-char point)
+                        (vertical-motion -1)
+                        (point))))
+          (if (pos-visible-in-window-p up-pos nil t)
+              (goto-char up-pos)
+            (goto-char (window-start))))))))
 
 (defun pixel-scroll-precision-interpolate (delta &optional old-window factor)
   "Interpolate a scroll of DELTA pixels.
@@ -858,7 +857,9 @@ precisely, according to the turning of the mouse wheel."
   :group 'mouse
   :keymap pixel-scroll-precision-mode-map
   (setq mwheel-coalesce-scroll-events
-        (not pixel-scroll-precision-mode)))
+        (not pixel-scroll-precision-mode))
+  (setq-default make-cursor-line-fully-visible
+                (not pixel-scroll-precision-mode)))
 
 (provide 'pixel-scroll)
 ;;; pixel-scroll.el ends here
diff --git a/lisp/play/cookie1.el b/lisp/play/cookie1.el
index 559b74084e1..e5c0d6a14d2 100644
--- a/lisp/play/cookie1.el
+++ b/lisp/play/cookie1.el
@@ -2,7 +2,7 @@
 
 ;; Copyright (C) 1993, 2001-2023 Free Software Foundation, Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: games, extensions
 ;; Created: Mon Mar 22 17:06:26 1993
diff --git a/lisp/play/doctor.el b/lisp/play/doctor.el
index dcf36c5c330..891274448d3 100644
--- a/lisp/play/doctor.el
+++ b/lisp/play/doctor.el
@@ -129,6 +129,9 @@
   "C-j" #'doctor-read-print
   "RET" #'doctor-ret-or-read)
 
+;; Actually defined in textconv.c.
+(defvar text-conversion-style)
+
 (define-derived-mode doctor-mode text-mode "Doctor"
   "Major mode for running the Doctor (Eliza) program.
 Like Text mode with Auto Fill mode
@@ -137,6 +140,8 @@ reads the sentence before point, and prints the Doctor's 
answer."
   :interactive nil
   (doctor-make-variables)
   (turn-on-auto-fill)
+  ;; Make sure RET is processed by Emacs.
+  (setq text-conversion-style 'action)
   (doctor-type '(i am the psychotherapist \.
                 (doc$ doctor--please) (doc$ doctor--describe) your (doc$ 
doctor--problems) \.
                 each time you are finished talking\, type \R\E\T twice \.))
diff --git a/lisp/play/dunnet.el b/lisp/play/dunnet.el
index 837508779e7..e290a9d73ec 100644
--- a/lisp/play/dunnet.el
+++ b/lisp/play/dunnet.el
@@ -1132,9 +1132,14 @@ treasures for points?" "4" "four")
 
 ;;;; Mode definitions for interactive mode
 
+;; Actually defined in textconv.c.
+(defvar text-conversion-style)
+
 (define-derived-mode dun-mode text-mode "Dungeon"
   "Major mode for running dunnet."
   :interactive nil
+  ;; Make sure RET is processed by Emacs.
+  (setq text-conversion-style 'action)
   (setq-local scroll-step 2))
 
 (defun dun-parse (_arg)
diff --git a/lisp/play/gamegrid.el b/lisp/play/gamegrid.el
index 54df983740e..bf195d6a0ec 100644
--- a/lisp/play/gamegrid.el
+++ b/lisp/play/gamegrid.el
@@ -411,7 +411,9 @@ convert to an Emacs image-spec instead")
                pixel-size (floor (* resy (/ point-size 72.27)))
                point-size (* (/ pixel-size resy) 72.27))
          (face-spec-set gamegrid-face
-                        `((t :height ,(floor (* point-size 10))))))))))
+                         ;; With some very high resolution displays,
+                         ;; point-size floored can be zero.
+                        `((t :height ,(max 8 (floor (* point-size 10)))))))))))
 
 (defun gamegrid-initialize-display ()
   (setq gamegrid-display-mode (gamegrid-display-type))
diff --git a/lisp/printing.el b/lisp/printing.el
index 4a6d14260a0..8aea58e157b 100644
--- a/lisp/printing.el
+++ b/lisp/printing.el
@@ -1148,8 +1148,7 @@ Used by `pr-menu-bind' and `pr-update-menus'.")
 
 (defun pr-menu-get-item (name-list)
   ;; NAME-LIST is a string or a list of strings.
-  (or (listp name-list)
-      (setq name-list (list name-list)))
+  (setq name-list (ensure-list name-list))
   (and name-list
        (let* ((reversed (reverse name-list))
              (name (easy-menu-intern (car reversed)))
diff --git a/lisp/proced.el b/lisp/proced.el
index 03a7f1bebdf..47de74b0ecb 100644
--- a/lisp/proced.el
+++ b/lisp/proced.el
@@ -152,7 +152,7 @@ the external command (usually \"kill\")."
     (pri     "Pr"      "%d" right proced-< t (pri pid) (nil t t))
     (nice    "Ni"      "%3d" 3 proced-< t (nice pid) (t t nil))
     (thcount "THCount" "%d" right proced-< t (thcount pid) (nil t t))
-    (start   "Start"   proced-format-start 6 proced-time-lessp nil (start pid)
+    (start   "Start"   proced-format-start left proced-time-lessp nil (start 
pid)
                        (t t nil))
     (vsize   "VSize"   proced-format-memory right proced-< t (vsize pid)
                        (nil t t))
@@ -776,12 +776,12 @@ of the process.  A value of nil indicates that there are 
no active refinements."
        (while (string-match "[ \t\n]+" hl pos)
          (setq pos (match-end 0))
          (put-text-property (match-beginning 0) pos 'display
-                            `(space :align-to ,(+ pos base))
+                            `(space :align-to (,(+ pos base) . width))
                             hl)))
       (setq hl (replace-regexp-in-string ;; preserve text properties
                "\\(%\\)" "\\1\\1"
                hl)))
-    (list (propertize " " 'display `(space :align-to ,base))
+    (list (propertize " " 'display `(space :align-to (,base . width)))
           hl)))
 
 (defun proced-pid-at-point ()
@@ -894,6 +894,8 @@ normal hook `proced-post-display-hook'.
   (setq-local font-lock-defaults
               '(proced-font-lock-keywords t nil nil beginning-of-line))
   (setq-local switch-to-buffer-preserve-window-point nil)
+  ;; So that the heading scales together with the body of the table.
+  (setq-local text-scale-remap-header-line t)
   (if (and (not proced-auto-update-timer) proced-auto-update-interval)
       (setq proced-auto-update-timer
             (run-at-time t proced-auto-update-interval
@@ -1597,8 +1599,7 @@ Prefix ARG controls sort order, see 
`proced-sort-interactive'."
            (format "%02d%s%02d" minutes colon seconds)))))
 
 (defun proced-format-start (start)
-  "Format time START.
-The return string is always 6 characters wide."
+  "Format time START."
   (let ((d-start (decode-time start))
         (d-current (decode-time))
         (colon (if proced-enable-color-flag
diff --git a/lisp/progmodes/asm-mode.el b/lisp/progmodes/asm-mode.el
index 2a5105fe164..0f5af9803a5 100644
--- a/lisp/progmodes/asm-mode.el
+++ b/lisp/progmodes/asm-mode.el
@@ -2,7 +2,7 @@
 
 ;; Copyright (C) 1991, 2001-2023 Free Software Foundation, Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: languages
 
@@ -23,7 +23,7 @@
 
 ;;; Commentary:
 
-;; This mode was written by Eric S. Raymond <esr@snark.thyrsus.com>,
+;; This mode was written by Eric S. Raymond <esr@thyrsus.com>,
 ;; inspired by an earlier `asm-mode' by Martin Neitzel.
 
 ;; This major mode is based on `prog-mode'.  It defines a private
diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el
index f16e06942b9..b3c48eb2c65 100644
--- a/lisp/progmodes/c-ts-mode.el
+++ b/lisp/progmodes/c-ts-mode.el
@@ -574,9 +574,7 @@ MODE is either `c' or `cpp'."
    :feature 'constant
    `((true) @font-lock-constant-face
      (false) @font-lock-constant-face
-     (null) @font-lock-constant-face
-     ,@(when (eq mode 'cpp)
-         '((nullptr) @font-lock-constant-face)))
+     (null) @font-lock-constant-face)
 
    :language mode
    :feature 'keyword
@@ -882,29 +880,36 @@ Return nil if NODE is not a defun node or doesn't have a 
name."
 (defun c-ts-mode--defun-valid-p (node)
   "Return non-nil if NODE is a valid defun node.
 Ie, NODE is not nested."
-  (or (c-ts-mode--emacs-defun-p node)
-      (not (or (and (member (treesit-node-type node)
-                            '("struct_specifier"
-                              "enum_specifier"
-                              "union_specifier"
-                              "declaration"))
-                    ;; If NODE's type is one of the above, make sure it is
-                    ;; top-level.
-                    (treesit-node-top-level
-                     node (rx (or "function_definition"
-                                  "type_definition"
-                                  "struct_specifier"
-                                  "enum_specifier"
-                                  "union_specifier"
-                                  "declaration"))))
-
-               (and (equal (treesit-node-type node) "declaration")
-                    ;; If NODE is a declaration, make sure it is not a
-                    ;; function declaration.
-                    (equal (treesit-node-type
-                            (treesit-node-child-by-field-name
-                             node "declarator"))
-                           "function_declarator"))))))
+  (let ((top-level-p (lambda (node)
+                       (not (treesit-node-top-level
+                             node (rx (or "function_definition"
+                                          "type_definition"
+                                          "struct_specifier"
+                                          "enum_specifier"
+                                          "union_specifier"
+                                          "declaration")))))))
+    (pcase (treesit-node-type node)
+      ;; The declaration part of a DEFUN.
+      ("expression_statement" (c-ts-mode--emacs-defun-p node))
+      ;; The body of a DEFUN.
+      ("compound_statement" (c-ts-mode--emacs-defun-body-p node))
+      ;; If NODE's type is one of these three, make sure it is
+      ;; top-level.
+      ((or "struct_specifier"
+           "enum_specifier"
+           "union_specifier")
+       (funcall top-level-p node))
+      ;; If NODE is a declaration, make sure it's not a function
+      ;; declaration (we only want function_definition) and is a
+      ;; top-level declaration.
+      ("declaration"
+       (and (not (equal (treesit-node-type
+                         (treesit-node-child-by-field-name
+                          node "declarator"))
+                        "function_declarator"))
+            (funcall top-level-p node)))
+      ;; Other types don't need further verification.
+      (_ t))))
 
 (defun c-ts-mode--defun-for-class-in-imenu-p (node)
   "Check if NODE is a valid entry for the Class subindex.
@@ -957,6 +962,11 @@ files using the DEFUN macro."
                t)
               "DEFUN")))
 
+(defun c-ts-mode--emacs-defun-body-p (node)
+  "Return non-nil if NODE is the function body of a DEFUN."
+  (and (equal (treesit-node-type node) "compound_statement")
+       (c-ts-mode--emacs-defun-p (treesit-node-prev-sibling node))))
+
 (defun c-ts-mode--emacs-defun-at-point (&optional range)
   "Return the defun node at point.
 
@@ -971,31 +981,18 @@ function returns the declaration node.
 If RANGE is non-nil, return (BEG . END) where BEG end END
 encloses the whole defun.  This is for when the entire defun
 is required, not just the declaration part for DEFUN."
-  (or (when-let ((node (treesit-defun-at-point)))
-        (if range
-            (cons (treesit-node-start node)
-                (treesit-node-end node))
-            node))
-      (and c-ts-mode-emacs-sources-support
-           (let ((candidate-1 ; For when point is in the DEFUN statement.
-                  (treesit-node-prev-sibling
-                   (treesit-node-top-level
-                    (treesit-node-at (point))
-                    "compound_statement")))
-                 (candidate-2 ; For when point is in the body.
-                  (treesit-node-top-level
-                   (treesit-node-at (point))
-                   "expression_statement")))
-             (when-let
-                 ((node (or (and (c-ts-mode--emacs-defun-p candidate-1)
-                                 candidate-1)
-                            (and (c-ts-mode--emacs-defun-p candidate-2)
-                                 candidate-2))))
-               (if range
-                   (cons (treesit-node-start node)
-                       (treesit-node-end
-                        (treesit-node-next-sibling node)))
-                   node))))))
+  (when-let* ((node (treesit-defun-at-point))
+              (defun-range (cons (treesit-node-start node)
+                                 (treesit-node-end node))))
+    ;; Make some adjustment for DEFUN.
+    (when c-ts-mode-emacs-sources-support
+      (cond ((c-ts-mode--emacs-defun-body-p node)
+             (setq node (treesit-node-prev-sibling node))
+             (setcar defun-range (treesit-node-start node)))
+            ((c-ts-mode--emacs-defun-p node)
+             (setcdr defun-range (treesit-node-end
+                                  (treesit-node-next-sibling node))))))
+    (if range defun-range node)))
 
 (defun c-ts-mode-indent-defun ()
   "Indent the current top-level declaration syntactically.
@@ -1113,13 +1110,19 @@ BEG and END are described in `treesit-range-rules'."
 
   ;; Navigation.
   (setq-local treesit-defun-type-regexp
-              (cons (regexp-opt '("function_definition"
-                                  "type_definition"
-                                  "struct_specifier"
-                                  "enum_specifier"
-                                  "union_specifier"
-                                  "class_specifier"
-                                  "namespace_definition"))
+              (cons (regexp-opt (append
+                                 '("function_definition"
+                                   "type_definition"
+                                   "struct_specifier"
+                                   "enum_specifier"
+                                   "union_specifier"
+                                   "class_specifier"
+                                   "namespace_definition")
+                                 (and c-ts-mode-emacs-sources-support
+                                      '(;; DEFUN.
+                                        "expression_statement"
+                                        ;; DEFUN body.
+                                        "compound_statement"))))
                     #'c-ts-mode--defun-valid-p))
   (setq-local treesit-defun-skipper #'c-ts-mode--defun-skipper)
   (setq-local treesit-defun-name-function #'c-ts-mode--defun-name)
diff --git a/lisp/progmodes/cc-cmds.el b/lisp/progmodes/cc-cmds.el
index 4c2340bfc2c..15b103a081f 100644
--- a/lisp/progmodes/cc-cmds.el
+++ b/lisp/progmodes/cc-cmds.el
@@ -5145,6 +5145,41 @@ details."
       (delete-char 1))))
 
 
+
+;; Text conversion support.
+
+(defun c-post-text-conversion ()
+  "Notice that the character `last-command-event' has been inserted.
+If said character is an electric character such as `*' or `{', delete
+it, then call the appropriate CC Mode function to electrically insert
+it again."
+  (cond ((eq last-command-event ?#)
+        (delete-char -1)
+        (c-electric-pound nil) t)
+       ((memq last-command-event '(?{ ?}))
+        (delete-char -1)
+        (c-electric-brace nil) t)
+       ((memq last-command-event '(?\( ?\)))
+        (delete-char -1)
+        (c-electric-paren nil) t)
+       ((eq last-command-event ?*)
+        (delete-char -1)
+        (c-electric-star nil) t)
+       ((eq last-command-event ?/)
+        (delete-char -1)
+        (c-electric-slash nil) t)
+       ((memq last-command-event '(?\; ?,))
+        (delete-char -1)
+        (c-electric-semi&comma nil) t)
+       ((eq last-command-event ?:)
+        (delete-char -1)
+        (c-electric-colon nil) t)
+       ((memq last-command-event '(?> ?<))
+        (delete-char -1)
+        (c-electric-lt-gt nil) t)))
+
+
+
 (cc-provide 'cc-cmds)
 
 ;; Local Variables:
diff --git a/lisp/progmodes/cc-defs.el b/lisp/progmodes/cc-defs.el
index 1d7f90ed428..2cbe9ca7e92 100644
--- a/lisp/progmodes/cc-defs.el
+++ b/lisp/progmodes/cc-defs.el
@@ -425,11 +425,14 @@ to it is returned.  This function does not modify the 
point or the mark."
 (defvar lookup-syntax-properties)       ;XEmacs.
 
 (defmacro c-is-escaped (pos)
-  ;; Are there an odd number of backslashes before POS?
+  ;; Is the character following POS escaped?
   (declare (debug t))
   `(save-excursion
      (goto-char ,pos)
-     (not (zerop (logand (skip-chars-backward "\\\\") 1)))))
+     (if (and c-escaped-newline-takes-precedence
+             (memq (char-after) '(?\n ?\r)))
+        (eq (char-before) ?\\)
+       (not (zerop (logand (skip-chars-backward "\\\\") 1))))))
 
 (defmacro c-will-be-escaped (pos beg end)
   ;; Will the character after POS be escaped after the removal of (BEG END)?
@@ -437,13 +440,23 @@ to it is returned.  This function does not modify the 
point or the mark."
   (declare (debug t))
   `(save-excursion
      (let ((-end- ,end)
+          (-pos- ,pos)
           count)
-       (goto-char ,pos)
-       (setq count (skip-chars-backward "\\\\" -end-))
-       (when (eq (point) -end-)
-        (goto-char ,beg)
-        (setq count (+ count (skip-chars-backward "\\\\"))))
-       (not (zerop (logand count 1))))))
+       (if (and c-escaped-newline-takes-precedence
+               (memq (char-after -pos-) '(?\n ?\r)))
+          (eq (char-before (if (eq -pos- -end-)
+                               ,beg
+                             -pos-))
+              ?\\)
+        (goto-char -pos-)
+        (setq count
+              (if (> -pos- -end-)
+                  (skip-chars-backward "\\\\" -end-)
+                0))
+        (when (eq (point) -end-)
+          (goto-char ,beg)
+          (setq count (+ count (skip-chars-backward "\\\\"))))
+        (not (zerop (logand count 1)))))))
 
 (defmacro c-will-be-unescaped (beg)
   ;; Would the character after BEG be unescaped?
diff --git a/lisp/progmodes/cc-langs.el b/lisp/progmodes/cc-langs.el
index 3d0ad9984fa..ef7f27dc435 100644
--- a/lisp/progmodes/cc-langs.el
+++ b/lisp/progmodes/cc-langs.el
@@ -1071,14 +1071,6 @@ Currently (2022-09) just C++ Mode uses this."
   ;; matched.
   t nil)
 
-(c-lang-defconst c-string-escaped-newlines
-  "Set if the language support backslash escaped newlines inside string
-literals."
-  t nil
-  (c c++ objc pike) t)
-(c-lang-defvar c-string-escaped-newlines
-  (c-lang-const c-string-escaped-newlines))
-
 (c-lang-defconst c-multiline-string-start-char
   "Set if the language supports multiline string literals without escaped
 newlines.  If t, all string literals are multiline.  If a character,
@@ -1095,6 +1087,18 @@ further directions."
 (c-lang-defvar c-multiline-string-start-char
   (c-lang-const c-multiline-string-start-char))
 
+(c-lang-defconst c-escaped-newline-takes-precedence
+  "Set if the language resolves escaped newlines first.
+This makes a difference in a string like \"...\\\\\n\".  When
+this variable is nil, the first backslash escapes the second,
+leaving an unterminated string.  When it's non-nil, the string is
+continued onto the next line, and the first backslash escapes
+whatever begins that next line."
+  t nil
+  (c c++ objc pike) t)
+(c-lang-defvar c-escaped-newline-takes-precedence
+  (c-lang-const c-escaped-newline-takes-precedence))
+
 (c-lang-defconst c-string-innards-re-alist
   ;; An alist of regexps matching the innards of a string, the key being the
   ;; string's delimiter.
@@ -1105,9 +1109,12 @@ further directions."
   t (mapcar (lambda (delim)
              (cons
               delim
-              (concat "\\(\\\\\\(.\\|\n\\)\\|[^\\\n\r"
-                      (string delim)
-                      "]\\)*")))
+              (concat
+               (if (c-lang-const c-escaped-newline-takes-precedence)
+                   "\\(\\\\\\(\\\\?\n\\|.\\)\\|[^\\\n\r"
+                 "\\(\\\\\\(\n\\|.\\)\\|[^\\\n\r")
+               (string delim)
+               "]\\)*")))
            (and
             (or (null (c-lang-const c-multiline-string-start-char))
                 (c-characterp (c-lang-const c-multiline-string-start-char)))
diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el
index dd699b9a119..1dbe91b5633 100644
--- a/lisp/progmodes/cc-mode.el
+++ b/lisp/progmodes/cc-mode.el
@@ -255,6 +255,10 @@ control).  See \"cc-mode.el\" for more info."
        ;; Will try initialization hooks again if they failed.
        (put 'c-initialize-cc-mode initprop c-initialization-ok))))
 
+  ;; Set up text conversion, for Emacs >= 30.0
+  (when (boundp 'post-text-conversion-hook)
+    (add-hook 'post-text-conversion-hook #'c-post-text-conversion))
+
   (unless new-style-init
     (c-init-language-vars-for 'c-mode)))
 
@@ -2723,18 +2727,18 @@ This function is called from `c-common-init', once per 
mode initialization."
 ;; Emacs < 22 and XEmacs
 (defmacro c-advise-fl-for-region (function)
   (declare (debug t))
-  `(defadvice ,function (before get-awk-region activate)
-     ;; Make sure that any string/regexp is completely font-locked.
-     (when c-buffer-is-cc-mode
-       (save-excursion
-        (ad-set-arg 1 c-new-END)   ; end
-        (ad-set-arg 0 c-new-BEG)))))   ; beg
-
-(unless (boundp 'font-lock-extend-after-change-region-function)
-  (c-advise-fl-for-region font-lock-after-change-function)
-  (c-advise-fl-for-region jit-lock-after-change)
-  (c-advise-fl-for-region lazy-lock-defer-rest-after-change)
-  (c-advise-fl-for-region lazy-lock-defer-line-after-change))
+  (unless (boundp 'font-lock-extend-after-change-region-function)
+    `(defadvice ,function (before get-awk-region activate)
+       ;; Make sure that any string/regexp is completely font-locked.
+       (when c-buffer-is-cc-mode
+        (save-excursion
+          (ad-set-arg 1 c-new-END)   ; end
+          (ad-set-arg 0 c-new-BEG))))))        ; beg
+
+(c-advise-fl-for-region font-lock-after-change-function)
+(c-advise-fl-for-region jit-lock-after-change)
+(c-advise-fl-for-region lazy-lock-defer-rest-after-change)
+(c-advise-fl-for-region lazy-lock-defer-line-after-change)
 
 ;; Connect up to `electric-indent-mode' (Emacs 24.4 and later).
 (defun c-electric-indent-mode-hook ()
diff --git a/lisp/progmodes/cmake-ts-mode.el b/lisp/progmodes/cmake-ts-mode.el
index 9d35d8077bd..53d471c381a 100644
--- a/lisp/progmodes/cmake-ts-mode.el
+++ b/lisp/progmodes/cmake-ts-mode.el
@@ -31,6 +31,7 @@
 (eval-when-compile (require 'rx))
 
 (declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-query-capture "treesit.c")
 (declare-function treesit-induce-sparse-tree "treesit.c")
 (declare-function treesit-node-child "treesit.c")
 (declare-function treesit-node-start "treesit.c")
@@ -87,6 +88,42 @@
     "VERSION_GREATER_EQUAL" "VERSION_LESS" "VERSION_LESS_EQUAL")
   "CMake if conditions for tree-sitter font-locking.")
 
+(defun cmake-ts-mode--font-lock-compatibility-fe9b5e0 ()
+  "Indent rules helper, to handle different releases of tree-sitter-cmake.
+Check if a node type is available, then return the right indent rules."
+  ;; handle commit fe9b5e0
+  (condition-case nil
+      (progn (treesit-query-capture 'cmake '((argument_list) @capture))
+             `(((foreach_command
+                 ((argument_list) @font-lock-constant-face
+                  (:match ,(rx-to-string
+                            `(seq bol
+                                  (or ,@cmake-ts-mode--foreach-options)
+                                  eol))
+                          @font-lock-constant-face))))
+               ((if_command
+                 ((argument_list) @font-lock-constant-face
+                  (:match ,(rx-to-string
+                            `(seq bol
+                                  (or ,@cmake-ts-mode--if-conditions)
+                                  eol))
+                          @font-lock-constant-face))))))
+    (error
+     `(((foreach_command
+         ((argument) @font-lock-constant-face
+          (:match ,(rx-to-string
+                    `(seq bol
+                          (or ,@cmake-ts-mode--foreach-options)
+                          eol))
+                  @font-lock-constant-face))))
+       ((if_command
+         ((argument) @font-lock-constant-face
+          (:match ,(rx-to-string
+                    `(seq bol
+                          (or ,@cmake-ts-mode--if-conditions)
+                          eol))
+                  @font-lock-constant-face))))))))
+
 (defvar cmake-ts-mode--font-lock-settings
   (treesit-font-lock-rules
    :language 'cmake
@@ -95,20 +132,7 @@
 
    :language 'cmake
    :feature 'builtin
-   `(((foreach_command
-       ((argument) @font-lock-constant-face
-        (:match ,(rx-to-string
-                  `(seq bol
-                        (or ,@cmake-ts-mode--foreach-options)
-                        eol))
-                @font-lock-constant-face))))
-     ((if_command
-       ((argument) @font-lock-constant-face
-        (:match ,(rx-to-string
-                  `(seq bol
-                        (or ,@cmake-ts-mode--if-conditions)
-                        eol))
-                @font-lock-constant-face)))))
+   (cmake-ts-mode--font-lock-compatibility-fe9b5e0)
 
    :language 'cmake
    :feature 'comment
diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el
index a68f76608d9..2b0519c314d 100644
--- a/lisp/progmodes/compile.el
+++ b/lisp/progmodes/compile.el
@@ -1866,6 +1866,9 @@ process from additional information inserted by Emacs."
     (apply #'insert args)
     (put-text-property start (point) 'compilation-annotation t)))
 
+(defvar-local compilation--start-time nil
+  "The time when the compilation started as returned by `float-time'.")
+
 ;;;###autoload
 (defun compilation-start (command &optional mode name-function highlight-regexp
                                   continue)
@@ -1997,6 +2000,7 @@ Returns the compilation buffer created."
                  mode-name
                 (substring (current-time-string) 0 19))
         command "\n")
+        (setq compilation--start-time (float-time))
        (setq thisdir default-directory))
       (set-buffer-modified-p nil))
     ;; Pop up the compilation buffer.
@@ -2484,7 +2488,14 @@ commands of Compilation major mode are available.  See
        (message "%s" (cdr status)))
     (if (bolp)
        (forward-char -1))
-    (compilation-insert-annotation " at " (substring (current-time-string) 0 
19))
+    (compilation-insert-annotation
+     " at "
+     (substring (current-time-string) 0 19)
+     ", duration "
+     (let ((elapsed (- (float-time) compilation--start-time)))
+       (cond ((< elapsed 10) (format "%.2f s" elapsed))
+             ((< elapsed 60) (format "%.1f s" elapsed))
+             (t (format-seconds "%h:%02m:%02s" elapsed)))))
     (goto-char (point-max))
     ;; Prevent that message from being recognized as a compilation error.
     (add-text-properties omax (point)
diff --git a/lisp/progmodes/cperl-mode.el b/lisp/progmodes/cperl-mode.el
index c0e9cfde8e7..98943ebda3f 100644
--- a/lisp/progmodes/cperl-mode.el
+++ b/lisp/progmodes/cperl-mode.el
@@ -5875,6 +5875,14 @@ default function."
     cperl-here-face)
    (t (funcall (default-value 'font-lock-syntactic-face-function) state))))
 
+(defface cperl-method-call
+  '((t (:inherit 'default )))
+  "Font Lock mode face for method calls.
+Usually, method calls are not fontified.
+We use this face to prevent calls to methods which look like
+builtin functions to be fontified like, well, builtin
+functions (which they are not).  Inherits from `default'.")
+
 (defun cperl-init-faces ()
   (condition-case errs
       (progn
@@ -5885,8 +5893,59 @@ default function."
             ;; -------- trailing spaces -> use invalid-face as a warning
             ;; (matcher subexp facespec)
            `("[ \t]+$" 0 ',cperl-invalid-face t)
+            ;; -------- function definition _and_ declaration
+            ;; (matcher (subexp facespec))
+            ;; facespec is evaluated depending on whether the
+            ;; statement ends in a "{" (definition) or ";"
+            ;; (declaration without body)
+           (list (concat "\\<" cperl-sub-regexp
+                          ;; group 1: optional subroutine name
+                          (rx
+                           (sequence (eval cperl--ws+-rx)
+                                     (group (optional
+                                             (eval 
cperl--normal-identifier-rx)))))
+                          ;; "fontified" elsewhere: Prototype
+                          (rx (optional
+                               (sequence (eval cperl--ws*-rx)
+                                         (eval cperl--prototype-rx))))
+                          ;; fontified elsewhere: Attributes
+                          (rx (optional (sequence (eval cperl--ws*-rx)
+                                                  (eval 
cperl--attribute-list-rx))))
+                          (rx (eval cperl--ws*-rx))
+                          ;; group 2: Identifies the start of the anchor
+                          (rx (group
+                               (or (group-n 3 ";") ; Either a declaration...
+                                   "{"             ; ... or a code block
+                                   ;; ... or a complete signature
+                                   (sequence (eval cperl--signature-rx)
+                                             (eval cperl--ws*-rx))
+                                   ;; ... or the start of a "sloppy" signature
+                                   (sequence (eval cperl--sloppy-signature-rx)
+                                             ;; arbtrarily continue "a few 
lines"
+                                             (repeat 0 200 (not (in "{"))))
+                                   ;; make sure we have a reasonably
+                                   ;; short match for an incomplete sub
+                                   (not (in ";{("))
+                                   buffer-end))))
+                 '(1 (if (match-beginning 3)
+                         'font-lock-variable-name-face
+                       'font-lock-function-name-face)
+                      nil ; override
+                      t)  ; laxmatch in case of anonymous subroutines
+                  ;; -------- anchored: Signature
+                  `(,(rx (sequence (in "(,")
+                                   (eval cperl--ws*-rx)
+                                   (group (eval cperl--basic-variable-rx))))
+                    (progn
+                      (goto-char (match-beginning 2)) ; pre-match: Back to sig
+                      (match-end 2))
+                    nil
+                    (1 font-lock-variable-name-face)))
             ;; -------- flow control
             ;; (matcher . subexp) font-lock-keyword-face by default
+           ;; This highlights declarations and definitions differently.
+           ;; We do not try to highlight in the case of attributes:
+           ;; it is already done by `cperl-find-pods-heres'
            (cons
             (concat
              "\\(^\\|[^$@%&\\]\\)\\<\\("
@@ -5910,6 +5969,11 @@ default function."
              "\\)\\>") 2)              ; was "\\)[ \n\t;():,|&]"
                                        ; In what follows we use `type' style
                                        ; for overwritable builtins
+            ;; -------- avoid method calls being fontified as keywords
+            ;; (matcher (subexp facespec))
+            (list
+             (rx "->" (* space) (group-n 1(eval cperl--basic-identifier-rx)))
+             1 ''cperl-method-call)
             ;; -------- builtin functions
             ;; (matcher subexp facespec)
            (list
@@ -5982,57 +6046,6 @@ default function."
             ;; (matcher subexp facespec)
            '("-[rwxoRWXOezsfdlpSbctugkTBMAC]\\>\\([ \t]+_\\>\\)?" 0
              font-lock-function-name-face keep) ; Not very good, triggers at 
"[a-z]"
-           ;; This highlights declarations and definitions differently.
-           ;; We do not try to highlight in the case of attributes:
-           ;; it is already done by `cperl-find-pods-heres'
-            ;; -------- function definition _and_ declaration
-            ;; (matcher (subexp facespec))
-            ;; facespec is evaluated depending on whether the
-            ;; statement ends in a "{" (definition) or ";"
-            ;; (declaration without body)
-           (list (concat "\\<" cperl-sub-regexp
-                          ;; group 1: optional subroutine name
-                          (rx
-                           (sequence (eval cperl--ws+-rx)
-                                     (group (optional
-                                             (eval 
cperl--normal-identifier-rx)))))
-                          ;; "fontified" elsewhere: Prototype
-                          (rx (optional
-                               (sequence (eval cperl--ws*-rx)
-                                         (eval cperl--prototype-rx))))
-                          ;; fontified elsewhere: Attributes
-                          (rx (optional (sequence (eval cperl--ws*-rx)
-                                                  (eval 
cperl--attribute-list-rx))))
-                          (rx (eval cperl--ws*-rx))
-                          ;; group 2: Identifies the start of the anchor
-                          (rx (group
-                               (or (group-n 3 ";") ; Either a declaration...
-                                   "{"             ; ... or a code block
-                                   ;; ... or a complete signature
-                                   (sequence (eval cperl--signature-rx)
-                                             (eval cperl--ws*-rx))
-                                   ;; ... or the start of a "sloppy" signature
-                                   (sequence (eval cperl--sloppy-signature-rx)
-                                             ;; arbtrarily continue "a few 
lines"
-                                             (repeat 0 200 (not (in "{"))))
-                                   ;; make sure we have a reasonably
-                                   ;; short match for an incomplete sub
-                                   (not (in ";{("))
-                                   buffer-end))))
-                 '(1 (if (match-beginning 3)
-                         'font-lock-variable-name-face
-                       'font-lock-function-name-face)
-                      t  ;; override
-                      t) ;; laxmatch in case of anonymous subroutines
-                  ;; -------- anchored: Signature
-                  `(,(rx (sequence (in "(,")
-                                   (eval cperl--ws*-rx)
-                                   (group (eval cperl--basic-variable-rx))))
-                    (progn
-                      (goto-char (match-beginning 2)) ; pre-match: Back to sig
-                      (match-end 2))
-                    nil
-                    (1 font-lock-variable-name-face)))
             ;; -------- various stuff calling for a package name
             ;; (matcher (subexp facespec) (subexp facespec))
             `(,(rx (sequence
@@ -6795,7 +6808,7 @@ in subdirectories too."
   ;; of etags has been commented out in the menu since ... well,
   ;; forever.  So, let's just stick to ASCII here. -- haj, 2021-09-14
   (interactive)
-  (let ((cmd "etags")
+  (let ((cmd etags-program-name)
        (args `("-l" "none" "-r"
                ;;                        1=fullname  2=package?             
3=name                       4=proto?             5=attrs? (VERY APPROX!)
                ,(concat
@@ -7361,6 +7374,9 @@ One may build such TAGS files from CPerl mode menu."
                            (nreverse list2))
                      list1)))))
 
+(defvar imenu-max-items nil
+  "Max items in an imenu list.  Defined in imenu.el.")
+
 (defun cperl-menu-to-keymap (menu)
   (let (list)
     (cons 'keymap
@@ -7750,10 +7766,27 @@ $~      The name of the current report format.
 ... >> ...     Bitwise shift right.
 ... >>= ...    Bitwise shift right assignment.
 ... ? ... : ...        Condition=if-then-else operator.
+... | ...      Bitwise or.
+... || ...     Logical or.
+... // ...      Defined-or.
+~ ...          Unary bitwise complement.
+... and ...            Low-precedence synonym for &&.
+... cmp ...    String compare.
+... eq ...     String equality.
+... ge ...     String greater than or equal.
+... gt ...     String greater than.
+... le ...     String less than or equal.
+... lt ...     String less than.
+... ne ...     String inequality.
+not ...                Low-precedence synonym for ! - negation.
+... or ...             Low-precedence synonym for ||.
+... x ...      Repeat string or array.
+x= ... Repetition assignment.
+... xor ...            Low-precedence synonym for exclusive or.
 @ARGV  Command line arguments (not including the command name - see $0).
 @INC   List of places to look for perl scripts during do/include/use.
 @_    Parameter array for subroutines; result of split() unless in list 
context.
-\\  Creates reference to what follows, like \\$var, or quotes non-\\w in 
strings.
+\\  Creates reference to what follows, like \\$var.  Quotes non-\\w in strings.
 \\0    Octal char, e.g. \\033.
 \\E    Case modification terminator.  See \\Q, \\L, and \\U.
 \\L    Lowercase until \\E .  See also \\l, lc.
@@ -7771,12 +7804,8 @@ $~       The name of the current report format.
 \\u    Upcase the next character.  See also \\U and \\l, ucfirst.
 \\x    Hex character, e.g. \\x1b.
 ... ^ ...      Bitwise exclusive or.
-__END__        Ends program source.
 __DATA__       Ends program source.
-__FILE__       Current (source) filename.
-__LINE__       Current line in current source.
-__PACKAGE__    Current package.
-__SUB__        Current sub.
+__END__        Ends program source.
 ADJUST {...}   Callback for object creation
 ARGV   Default multi-file input filehandle.  <ARGV> is a synonym for <>.
 ARGVOUT        Output filehandle with -i flag.
@@ -7786,267 +7815,252 @@ CHECK { ... } Pseudo-subroutine executed after the 
script is compiled.
 UNITCHECK { ... }
 INIT { ... }   Pseudo-subroutine executed before the script starts running.
 DATA   Input filehandle for what follows after __END__ or __DATA__.
-accept(NEWSOCKET,GENERICSOCKET)
-alarm(SECONDS)
+abs [ EXPR ]   absolute value function
+accept(NEWSOCKET,GENERICSOCKET)        accept an incoming socket connect
+alarm(SECONDS) schedule a SIGALRM
 async(SUB NAME {}|SUB {})      Mark function as potentially asynchronous
-atan2(X,Y)
+atan2(X,Y)     arctangent of Y/X in the range -PI to PI
 await(ASYNCEXPR)       Yield result of Future
-bind(SOCKET,NAME)
-binmode(FILEHANDLE)
+bind(SOCKET,NAME)      binds an address to a socket
+binmode(FILEHANDLE)    prepare binary files for I/O
+bless REFERENCE [, PACKAGE]    Makes reference into an object of a package.
 break  Break out of a given/when statement
-caller[(LEVEL)]
-chdir(EXPR)
-chmod(LIST)
-chop[(LIST|VAR)]
-chown(LIST)
-chroot(FILENAME)
-class NAME     Introduce a class.
-close(FILEHANDLE)
-closedir(DIRHANDLE)
-... cmp ...    String compare.
-connect(SOCKET,NAME)
+caller[(LEVEL)]        get context of the current subroutine call
+chdir(EXPR)    change your current working directory
+chmod(LIST)    changes the permissions on a list of files
+chomp [LIST]   Strips $/ off LIST/$_.  Returns count.
+chop[(LIST|VAR)]       remove the last character from a string
+chown(LIST)    change the ownership on a list of files
+chr [NUMBER]   Converts a number to char with the same ordinal.
+chroot(FILENAME)       make directory new root for path lookups
+class NAME     Introduce an object class.
+close(FILEHANDLE)      close file (or pipe or socket) handle
+closedir(DIRHANDLE)    close directory handle
+connect(SOCKET,NAME)   connect to a remote socket
 continue of { block } continue { block }.  Is executed after `next' or at end.
-cos(EXPR)
-crypt(PLAINTEXT,SALT)
-dbmclose(%HASH)
-dbmopen(%HASH,DBNAME,MODE)
-default { ... } default case for given/when block
-defer { ... }  run this block after the containing block.
-defined(EXPR)
-delete($HASH{KEY})
-die(LIST)
+cos(EXPR)      cosine function
+crypt(PLAINTEXT,SALT)  one-way passwd-style encryption
+dbmclose(%HASH)        breaks binding on a tied dbm file
+dbmopen(%HASH,DBNAME,MODE)     create binding on a tied dbm file
+defined(EXPR)  test whether a value, variable, or function is defined
+delete($HASH{KEY})     deletes a value from a hash
+die(LIST)      raise an exception or bail out
 do { ... }|SUBR while|until EXPR       executes at least once
 do(EXPR|SUBR([LIST]))  (with while|until executes at least once)
-dump LABEL
-each(%HASH)
-endgrent
-endhostent
-endnetent
-endprotoent
-endpwent
-endservent
-eof[([FILEHANDLE])]
-... eq ...     String equality.
-eval(EXPR) or eval { BLOCK }
+dump LABEL     create an immediate core dump
+each(%HASH)    retrieve the next key/value pair from a hash
+endgrent       be done using group file
+endhostent     be done using hosts file
+endnetent      be done using networks file
+endprotoent    be done using protocols file
+endpwent       be done using passwd file
+endservent     be done using services file
+eof[([FILEHANDLE])]    test a filehandle for its end
+eval(EXPR) or eval { BLOCK }   catch exceptions or compile and run code
 evalbytes   See eval.
 exec([TRUENAME] ARGV0, ARGVs)     or     exec(SHELL_COMMAND_LINE)
-exit(EXPR)
-exp(EXPR)
+exists $HASH{KEY}      True if the key exists.
+exit(EXPR)     terminate this program
+exp(EXPR)      raise e to a power
+fc EXPR    Returns the casefolded version of EXPR.
 fcntl(FILEHANDLE,FUNCTION,SCALAR)
 field VAR  [:param[(NAME)]] [=EXPR]    declare an object attribute
-fileno(FILEHANDLE)
-flock(FILEHANDLE,OPERATION)
-for (EXPR;EXPR;EXPR) { ... }
-foreach [VAR] (@ARRAY) { ... }
-fork
-... ge ...     String greater than or equal.
-getc[(FILEHANDLE)]
-getgrent
-getgrgid(GID)
-getgrnam(NAME)
-gethostbyaddr(ADDR,ADDRTYPE)
-gethostbyname(NAME)
-gethostent
-getlogin
-getnetbyaddr(ADDR,ADDRTYPE)
-getnetbyname(NAME)
-getnetent
-getpeername(SOCKET)
-getpgrp(PID)
-getppid
-getpriority(WHICH,WHO)
-getprotobyname(NAME)
-getprotobynumber(NUMBER)
-getprotoent
-getpwent
-getpwnam(NAME)
-getpwuid(UID)
-getservbyname(NAME,PROTO)
-getservbyport(PORT,PROTO)
-getservent
-getsockname(SOCKET)
-getsockopt(SOCKET,LEVEL,OPTNAME)
-given (EXPR) { [ when (EXPR) { ... } ]+ [ default { ... } ]? }
-gmtime(EXPR)
-goto LABEL
-... gt ...     String greater than.
-hex(EXPR)
-if (EXPR) { ... } [ elsif (EXPR) { ... } ... ] [ else { ... } ] or EXPR if EXPR
-index(STR,SUBSTR[,OFFSET])
-int(EXPR)
-ioctl(FILEHANDLE,FUNCTION,SCALA)R
-join(EXPR,LIST)
-keys(%HASH)
-kill(LIST)
-last [LABEL]
-... le ...     String less than or equal.
-length(EXPR)
-link(OLDFILE,NEWFILE)
-listen(SOCKET,QUEUESIZE)
-local(LIST)
-localtime(EXPR)
-log(EXPR)
-lstat(EXPR|FILEHANDLE|VAR)
-... lt ...     String less than.
-m/PATTERN/iogsmx
+__FILE__       Current (source) filename.
+fileno(FILEHANDLE)     return file descriptor from filehandle
+flock(FILEHANDLE,OPERATION)    lock an entire file with an advisory lock
+fork   create a new process just like this one
+format [NAME] =         Start of output format.  Ended by a single dot (.) on 
a line.
+formline PICTURE, LIST Backdoor into \"format\" processing.
+getc[(FILEHANDLE)]     get the next character from the filehandle
+getgrent       get group record given group user ID
+getgrgid(GID)  get group record given group user ID
+getgrnam(NAME) get group record given group name
+gethostbyaddr(ADDR,ADDRTYPE)   get host record given name
+gethostbyname(NAME)    get host record given name
+gethostent     get next hosts record
+getlogin       return who logged in at this tty
+getnetbyaddr(ADDR,ADDRTYPE)    get network record given its address
+getnetbyname(NAME)     get networks record given name
+getnetent      get next networks record
+getpeername(SOCKET)    find the other end of a socket connection
+getpgrp(PID)   get process group
+getppid        get parent process ID
+getpriority(WHICH,WHO) get current nice value
+getprotobyname(NAME)   get protocol record given name
+getprotobynumber(NUMBER)       get protocol record numeric protocol
+getprotoent    get next protocols record
+getpwent       get next passwd record
+getpwnam(NAME) get passwd record given user login name
+getpwuid(UID)  get passwd record given user ID
+getservbyname(NAME,PROTO)      get services record given its name
+getservbyport(PORT,PROTO)      get services record given numeric port
+getservent     get next services record
+getsockname(SOCKET)    retrieve the sockaddr for a given socket
+getsockopt(SOCKET,LEVEL,OPTNAME)       get socket options on a given socket
+glob EXPR      expand filenames using wildcards.  Synonym of <EXPR>.
+gmtime(EXPR)   convert UNIX time into record or string using Greenwich time
+goto LABEL     create spaghetti code
+grep EXPR,LIST  or grep {BLOCK} LIST   Filters LIST via EXPR/BLOCK.
+hex(EXPR)      convert a hexadecimal string to a number
+import patch a module's namespace into your own
+index(STR,SUBSTR[,OFFSET])     find a substring within a string
+int(EXPR)      get the integer portion of a number
+ioctl(FILEHANDLE,FUNCTION,SCALAR)      device control system call
+join(EXPR,LIST)        join a list into a string using a separator
+keys(%HASH)    retrieve list of indices from a hash
+kill(LIST)     send a signal to a process or process group
+last [LABEL]   exit a block prematurely
+lc [ EXPR ]    Returns lowercased EXPR.
+lcfirst [ EXPR ]       Returns EXPR with lower-cased first letter.
+length(EXPR)   return the number of characters in a string
+__LINE__       Current line in current source.
+link(OLDFILE,NEWFILE)  create a hard link in the filesystem
+listen(SOCKET,QUEUESIZE)       register your socket as a server
+local(LIST)    create a temporary value for a global variable
+localtime(EXPR)        convert UNIX time into record or string using local time
+lock(THING)    get a thread lock on a variable, subroutine, or method
+log(EXPR)      retrieve the natural logarithm for a number
+lstat(EXPR|FILEHANDLE|VAR)     stat a symbolic link
+m/PATTERN/iogsmx       match a string with a regular expression pattern
+map EXPR, LIST or map {BLOCK} LIST     Applies EXPR/BLOCK to elts of LIST.
 method  [NAME [(signature)]] { BODY }  method NAME;
-mkdir(FILENAME,MODE)
-msgctl(ID,CMD,ARG)
-msgget(KEY,FLAGS)
-msgrcv(ID,VAR,SIZE,TYPE.FLAGS)
-msgsnd(ID,MSG,FLAGS)
+mkdir(FILENAME,MODE)   create a directory
+msgctl(ID,CMD,ARG)     SysV IPC message control operations
+msgget(KEY,FLAGS)      get SysV IPC message queue
+msgrcv(ID,VAR,SIZE,TYPE.FLAGS) receive a SysV IPC message from a message queue
+msgsnd(ID,MSG,FLAGS)   send a SysV IPC message to a message queue
 my VAR or my (VAR1,...)        Introduces a lexical variable ($VAR, @ARR, or 
%HASH).
-our VAR or our (VAR1,...) Lexically enable a global variable ($V, @A, or %H).
-... ne ...     String inequality.
-next [LABEL]
-oct(EXPR)
-open(FILEHANDLE[,EXPR])
-opendir(DIRHANDLE,EXPR)
+next [LABEL]   iterate a block prematurely
+no MODULE [SYMBOL1, ...]  Partial reverse for `use'.  Runs `unimport' method.
+oct(EXPR)      convert a string to an octal number
+open(FILEHANDLE[,EXPR])        open a file, pipe, or descriptor
+opendir(DIRHANDLE,EXPR)        open a directory
 ord(EXPR)      ASCII value of the first char of the string.
-pack(TEMPLATE,LIST)
+our VAR or our (VAR1,...) Lexically enable a global variable ($V, @A, or %H).
+pack(TEMPLATE,LIST)    convert a list into a binary representation
 package NAME   Introduces package context.
+__PACKAGE__    Current package.
 pipe(READHANDLE,WRITEHANDLE)   Create a pair of filehandles on ends of a pipe.
-pop(ARRAY)
-print [FILEHANDLE] [(LIST)]
-printf [FILEHANDLE] (FORMAT,LIST)
-push(ARRAY,LIST)
+pop(ARRAY)     remove the last element from an array and return it
+pos STRING    Set/Get end-position of the last match over this string, see \\G.
+print [FILEHANDLE] [(LIST)]    output a list to a filehandle
+printf [FILEHANDLE] (FORMAT,LIST)      output a formatted list to a filehandle
+prototype \\&SUB       Returns prototype of the function given a reference.
+push(ARRAY,LIST)       append one or more elements to an array
 q/STRING/      Synonym for \\='STRING\\='
 qq/STRING/     Synonym for \"STRING\"
+qr/PATTERN/    compile pattern
+quotemeta      quote regular expression magic characters
+qw/STRING/     quote a list of words
 qx/STRING/     Synonym for \\=`STRING\\=`
-rand[(EXPR)]
-read(FILEHANDLE,SCALAR,LENGTH[,OFFSET])
-readdir(DIRHANDLE)
-readlink(EXPR)
-recv(SOCKET,SCALAR,LEN,FLAGS)
-redo [LABEL]
-rename(OLDNAME,NEWNAME)
-require [FILENAME | PERL_VERSION]
-reset[(EXPR)]
-return(LIST)
-reverse(LIST)
-rewinddir(DIRHANDLE)
-rindex(STR,SUBSTR[,OFFSET])
-rmdir(FILENAME)
-s/PATTERN/REPLACEMENT/gieoxsm
-say [FILEHANDLE] [(LIST)]
-scalar(EXPR)
-seek(FILEHANDLE,POSITION,WHENCE)
-seekdir(DIRHANDLE,POS)
-select(FILEHANDLE | RBITS,WBITS,EBITS,TIMEOUT)
-semctl(ID,SEMNUM,CMD,ARG)
-semget(KEY,NSEMS,SIZE,FLAGS)
-semop(KEY,...)
-send(SOCKET,MSG,FLAGS[,TO])
-setgrent
-sethostent(STAYOPEN)
-setnetent(STAYOPEN)
-setpgrp(PID,PGRP)
-setpriority(WHICH,WHO,PRIORITY)
-setprotoent(STAYOPEN)
-setpwent
-setservent(STAYOPEN)
-setsockopt(SOCKET,LEVEL,OPTNAME,OPTVAL)
-shift[(ARRAY)]
-shmctl(ID,CMD,ARG)
-shmget(KEY,SIZE,FLAGS)
-shmread(ID,VAR,POS,SIZE)
-shmwrite(ID,STRING,POS,SIZE)
-shutdown(SOCKET,HOW)
-sin(EXPR)
-sleep[(EXPR)]
-socket(SOCKET,DOMAIN,TYPE,PROTOCOL)
-socketpair(SOCKET1,SOCKET2,DOMAIN,TYPE,PROTOCOL)
-sort [SUBROUTINE] (LIST)
-splice(ARRAY,OFFSET[,LENGTH[,LIST]])
-split[(/PATTERN/[,EXPR[,LIMIT]])]
-sprintf(FORMAT,LIST)
-sqrt(EXPR)
-srand(EXPR)
-stat(EXPR|FILEHANDLE|VAR)
+rand[(EXPR)]   retrieve the next pseudorandom number
+read(FILEHANDLE,SCALAR,LENGTH[,OFFSET])        fixed-length buffered input
+readdir(DIRHANDLE)     get a directory from a directory handle
+readline FH    Synonym of <FH>.
+readlink(EXPR) determine where a symbolic link is pointing
+readpipe CMD   Synonym of \\=`CMD\\=`.
+recv(SOCKET,SCALAR,LEN,FLAGS)  receive a message over a Socket
+redo [LABEL]   start this loop iteration over again
+ref [ EXPR ]   Type of EXPR when dereferenced.
+rename(OLDNAME,NEWNAME)        change a filename
+require [FILENAME | PERL_VERSION]      load from a library at runtime
+reset[(EXPR)]  clear all variables of a given name
+return(LIST)   get out of a function early
+reverse(LIST)  flip a string or a list
+rewinddir(DIRHANDLE)   reset directory handle
+rindex(STR,SUBSTR[,OFFSET])    right-to-left substring search
+rmdir(FILENAME)        remove a directory
+s/PATTERN/REPLACEMENT/gieoxsm  replace a pattern with a string
+say [FILEHANDLE] [(LIST)]      output a list, appending a newline
+scalar(EXPR)   force a scalar context
+seek(FILEHANDLE,POSITION,WHENCE)       reposition file pointer
+seekdir(DIRHANDLE,POS) reposition directory pointer
+select(FILEHANDLE)     reset default output or do I/O multiplexing
+select(RBITS,WBITS,EBITS,TIMEOUT)      do I/O multiplexing
+semctl(ID,SEMNUM,CMD,ARG)      SysV semaphore control operations
+semget(KEY,NSEMS,SIZE,FLAGS)   get set of SysV semaphores
+semop(KEY,...) SysV semaphore operations
+send(SOCKET,MSG,FLAGS[,TO])    send a message over a socket
+setgrent       prepare group file for use
+sethostent(STAYOPEN)   prepare hosts file for use
+setnetent(STAYOPEN)    prepare networks file for use
+setpgrp(PID,PGRP)      set the process group of a process
+setpriority(WHICH,WHO,PRIORITY)        Process set a process\\='s nice value
+setprotoent(STAYOPEN)  etwork  prepare protocols file for use
+setpwent       prepare passwd file for use
+setservent(STAYOPEN)   prepare services file for use
+setsockopt(SOCKET,LEVEL,OPTNAME,OPTVAL)        set some socket options
+shift[(ARRAY)] remove the first element of an array, and return it
+shmctl(ID,CMD,ARG)     SysV shared memory operations
+shmget(KEY,SIZE,FLAGS) get SysV shared memory segment identifier
+shmread(ID,VAR,POS,SIZE)       read SysV shared memory
+shmwrite(ID,STRING,POS,SIZE)   write SysV shared memory
+shutdown(SOCKET,HOW)   close down just half of a socket connection
+sin(EXPR)      return the sine of a number
+sleep[(EXPR)]  block for some number of seconds
+socket(SOCKET,DOMAIN,TYPE,PROTOCOL)    create a socket
+socketpair(SOCKET1,SOCKET2,DOMAIN,TYPE,PROTOCOL)       create a pair of sockets
+sort [SUBROUTINE] (LIST)       sort a list of values
+splice(ARRAY,OFFSET[,LENGTH[,LIST]])   add or remove elements anywhere
+split[(/PATTERN/[,EXPR[,LIMIT]])]      split up a string using a regexp
+sprintf(FORMAT,LIST)   formatted print into a string
+sqrt(EXPR)     square root function
+srand(EXPR)    seed the random number generator
+stat(EXPR|FILEHANDLE|VAR)      get a file\\='s status information
 state VAR or state (VAR1,...)  Introduces a static lexical variable
-study[(SCALAR)]
+study[(SCALAR)]        no-op, formerly optimized input data for repeated 
searches
 sub [NAME [(format)]] { BODY } sub NAME [(format)];    sub [(format)] {...}
-substr(EXPR,OFFSET[,LEN])
-symlink(OLDFILE,NEWFILE)
-syscall(LIST)
-sysread(FILEHANDLE,SCALAR,LENGTH[,OFFSET])
+__SUB__        the current subroutine, or C<undef> if not in a subroutine
+substr(EXPR,OFFSET[,LEN])      get or alter a portion of a string
+symlink(OLDFILE,NEWFILE)       create a symbolic link to a file
+syscall(LIST)  execute an arbitrary system call
+sysopen FH, FILENAME, MODE [, PERM]    (MODE is numeric, see Fcntl.)
+sysread(FILEHANDLE,SCALAR,LENGTH[,OFFSET])     fixed-length unbuffered input
+sysseek(FILEHANDLE,POSITION,WHENCE) position I/O pointer on handle
 system([TRUENAME] ARGV0 [,ARGV])     or     system(SHELL_COMMAND_LINE)
-syswrite(FILEHANDLE,SCALAR,LENGTH[,OFFSET])
-tell[(FILEHANDLE)]
-telldir(DIRHANDLE)
-time
-times
-tr/SEARCHLIST/REPLACEMENTLIST/cds
-truncate(FILE|EXPR,LENGTH)
-umask[(EXPR)]
-undef[(EXPR)]
-unless (EXPR) { ... } [ else { ... } ] or EXPR unless EXPR
-unlink(LIST)
-unpack(TEMPLATE,EXPR)
-unshift(ARRAY,LIST)
-until (EXPR) { ... }                                   EXPR until EXPR
-utime(LIST)
-values(%HASH)
-vec(EXPR,OFFSET,BITS)
-wait
-waitpid(PID,FLAGS)
+syswrite(FILEHANDLE,SCALAR,LENGTH[,OFFSET])    fixed-length unbuffered output
+tell[(FILEHANDLE)]     get current seekpointer on a filehandle
+telldir(DIRHANDLE)     get current seekpointer on a directory handle
+tie VAR, CLASS, LIST   Hide an object behind a simple Perl variable.
+tied           Returns internal object for a tied data.
+time   return number of seconds since 1970
+times  return elapsed time for self and child processes
+tr/SEARCHLIST/REPLACEMENTLIST/cds      transliterate a string
+truncate(FILE|EXPR,LENGTH)     shorten a file
+uc [ EXPR ]    Returns upcased EXPR.
+ucfirst [ EXPR ]       Returns EXPR with upcased first letter.
+umask[(EXPR)]  set file creation mode mask
+undef[(EXPR)]  remove a variable or function definition
+unlink(LIST)   remove one link to a file
+unpack(TEMPLATE,EXPR)  convert binary structure into normal perl variables
+unshift(ARRAY,LIST)    prepend more elements to the beginning of a list
+untie VAR      Unlink an object from a simple Perl variable.
+use MODULE [SYMBOL1, ...]  Compile-time `require' with consequent `import'.
+utime(LIST)    set a file\\='s last access and modify times
+values(%HASH)  return a list of the values in a hash
+vec(EXPR,OFFSET,BITS)  test or set particular bits in a string
+wait   wait for any child process to die
+waitpid(PID,FLAGS)     wait for a particular child process to die
 wantarray      Returns true if the sub/eval is called in list context.
-warn(LIST)
-while  (EXPR) { ... }                                  EXPR while EXPR
-write[(EXPR|FILEHANDLE)]
-... x ...      Repeat string or array.
-x= ... Repetition assignment.
-y/SEARCHLIST/REPLACEMENTLIST/
-... | ...      Bitwise or.
-... || ...     Logical or.
-... // ...      Defined-or.
-~ ...          Unary bitwise complement.
+warn(LIST)     print debugging info
+write[(EXPR|FILEHANDLE)]       print a picture record
+y/SEARCHLIST/REPLACEMENTLIST/  transliterate a string
 #!     OS interpreter indicator.  If contains `perl', used for options, and -x.
 AUTOLOAD {...} Shorthand for `sub AUTOLOAD {...}'.
 CORE::         Prefix to access builtin function if imported sub obscures it.
 SUPER::                Prefix to lookup for a method in @ISA classes.
 DESTROY                Shorthand for `sub DESTROY {...}'.
-... EQ ...     Obsolete synonym of `eq'.
-... GE ...     Obsolete synonym of `ge'.
-... GT ...     Obsolete synonym of `gt'.
-... LE ...     Obsolete synonym of `le'.
-... LT ...     Obsolete synonym of `lt'.
-... NE ...     Obsolete synonym of `ne'.
-abs [ EXPR ]   absolute value
-... and ...            Low-precedence synonym for &&.
-bless REFERENCE [, PACKAGE]    Makes reference into an object of a package.
-chomp [LIST]   Strips $/ off LIST/$_.  Returns count.  Special if $/ eq 
\\='\\='!
-chr            Converts a number to char with the same ordinal.
 else           Part of if/unless {BLOCK} elsif {BLOCK} else {BLOCK}.
 elsif          Part of if/unless {BLOCK} elsif {BLOCK} else {BLOCK}.
-exists $HASH{KEY}      True if the key exists.
-fc EXPR    Returns the casefolded version of EXPR.
-format [NAME] =         Start of output format.  Ended by a single dot (.) on 
a line.
-formline PICTURE, LIST Backdoor into \"format\" processing.
-glob EXPR      Synonym of <EXPR>.
-lc [ EXPR ]    Returns lowercased EXPR.
-lcfirst [ EXPR ]       Returns EXPR with lower-cased first letter.
-grep EXPR,LIST  or grep {BLOCK} LIST   Filters LIST via EXPR/BLOCK.
-map EXPR, LIST or map {BLOCK} LIST     Applies EXPR/BLOCK to elts of LIST.
-no MODULE [SYMBOL1, ...]  Partial reverse for `use'.  Runs `unimport' method.
-not ...                Low-precedence synonym for ! - negation.
-... or ...             Low-precedence synonym for ||.
-pos STRING    Set/Get end-position of the last match over this string, see \\G.
-prototype FUNC   Returns the prototype of a function as a string, or undef.
-quotemeta [ EXPR ]     Quote regexp metacharacters.
-qw/WORD1 .../          Synonym of split(\\='\\=', \\='WORD1 ...\\=')
-readline FH    Synonym of <FH>.
-readpipe CMD   Synonym of \\=`CMD\\=`.
-ref [ EXPR ]   Type of EXPR when dereferenced.
-sysopen FH, FILENAME, MODE [, PERM]    (MODE is numeric, see Fcntl.)
-tie VAR, CLASS, LIST   Hide an object behind a simple Perl variable.
-tied           Returns internal object for a tied data.
-uc [ EXPR ]    Returns upcased EXPR.
-ucfirst [ EXPR ]       Returns EXPR with upcased first letter.
-untie VAR      Unlink an object from a simple Perl variable.
-use MODULE [SYMBOL1, ...]  Compile-time `require' with consequent `import'.
-... xor ...            Low-precedence synonym for exclusive or.
-prototype \\&SUB       Returns prototype of the function given a reference.
+default { ... } default case for given/when block
+defer { ... }  run this block after the containing block.
+for (EXPR;EXPR;EXPR) { ... }
+foreach [VAR] (@ARRAY) { ... }
+given (EXPR) { [ when (EXPR) { ... } ]+ [ default { ... } ]? }
+if (EXPR) { ... } [ elsif (EXPR) { ... } ... ] [ else { ... } ] or EXPR if EXPR
+unless (EXPR) { ... } [ else { ... } ] or EXPR unless EXPR
+until (EXPR) { ... }   EXPR until EXPR
+while  (EXPR) { ... }  EXPR while EXPR
 =head1         Top-level heading.
 =head2         Second-level heading.
 =head3         Third-level heading.
@@ -8820,8 +8834,6 @@ start with default arguments, then refine the slowdown 
regions."
       (message "to %s:%6s,%7s" l delta tot))
     tot))
 
-(defvar font-lock-cache-position)
-
 (defun cperl-emulate-lazy-lock (&optional window-size)
   "Emulate `lazy-lock' without `condition-case', so `debug-on-error' works.
 Start fontifying the buffer from the start (or end) using the given
diff --git a/lisp/progmodes/csharp-mode.el b/lisp/progmodes/csharp-mode.el
index db036aab685..37bb84ab5ba 100644
--- a/lisp/progmodes/csharp-mode.el
+++ b/lisp/progmodes/csharp-mode.el
@@ -45,6 +45,7 @@
 (declare-function treesit-node-start "treesit.c")
 (declare-function treesit-node-type "treesit.c")
 (declare-function treesit-node-child-by-field-name "treesit.c")
+(declare-function treesit-query-capture "treesit.c")
 
 (defgroup csharp nil
   "Major mode for editing C# code."
@@ -816,7 +817,7 @@ compilation and evaluation time conflicts."
    :language 'c-sharp
    :feature 'definition
    :override t
-   '((qualified_name (identifier) @font-lock-type-face)
+   `((qualified_name (identifier) @font-lock-type-face)
      (using_directive (identifier) @font-lock-type-face)
      (using_directive (name_equals
                        (identifier) @font-lock-type-face))
@@ -843,8 +844,13 @@ compilation and evaluation time conflicts."
      (class_declaration (identifier) @font-lock-type-face)
 
      (constructor_declaration name: (_) @font-lock-type-face)
-
-     (method_declaration type: [(identifier) (void_keyword)] 
@font-lock-type-face)
+     ;;; Handle different releases of tree-sitter-c-sharp.
+     ;;; Check if keyword void_keyword is available, then return the correct 
rule."
+     ,@(condition-case nil
+           (progn (treesit-query-capture 'csharp '((void_keyword) @capture))
+                  `((method_declaration type: [(identifier) (void_keyword)] 
@font-lock-type-face)))
+         (error
+          `((method_declaration type: [(identifier) (predefined_type)] 
@font-lock-type-face))))
      (method_declaration type: (generic_name (identifier) 
@font-lock-type-face))
      (method_declaration name: (_) @font-lock-function-name-face)
 
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 8ac21638a5b..65daa0941d5 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -219,9 +219,11 @@ chosen (interactively or automatically)."
                                  . ("dart" "language-server"
                                     "--client-id" "emacs.eglot-dart"))
                                 ((elixir-mode elixir-ts-mode heex-ts-mode)
-                                 . ,(if (and (fboundp 
'w32-shell-dos-semantics)                                             
(w32-shell-dos-semantics))
+                                 . ,(if (and (fboundp 'w32-shell-dos-semantics)
+                                             (w32-shell-dos-semantics))
                                         '("language_server.bat")
-                                      '("language_server.sh")))
+                                      (eglot-alternatives
+                                       '("language_server.sh" 
"start_lexical.sh"))))
                                 (ada-mode . ("ada_language_server"))
                                 (scala-mode . ,(eglot-alternatives
                                                 '("metals" "metals-emacs")))
@@ -421,6 +423,14 @@ This can be useful when using docker to run a language 
server.")
 
 ;;; Constants
 ;;;
+(defconst eglot--version
+  (eval-when-compile
+    (when byte-compile-current-file
+      (require 'lisp-mnt)
+      (lm-version byte-compile-current-file)))
+  "The version as a string of this version of Eglot.
+It is nil if Eglot is not byte-complied.")
+
 (defconst eglot--symbol-kind-names
   `((1 . "File") (2 . "Module")
     (3 . "Namespace") (4 . "Package") (5 . "Class")
@@ -1352,7 +1362,9 @@ This docstring appeases checkdoc, that's all."
                                         (eq (jsonrpc-process-type server)
                                             'network))
                               (emacs-pid))
-                            :clientInfo '(:name "Eglot")
+                            :clientInfo
+                            `(:name "Eglot" ,@(when eglot--version
+                                                `(:version ,eglot--version)))
                             ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py'
                             ;; into `/path/to/baz.py', so LSP groks it.
                             :rootPath (file-local-name
@@ -2447,16 +2459,16 @@ buffer."
 
 (defun eglot--post-self-insert-hook ()
   "Set `eglot--last-inserted-char', maybe call on-type-formatting."
-  (setq eglot--last-inserted-char last-input-event)
+  (setq eglot--last-inserted-char last-command-event)
   (let ((ot-provider (eglot--server-capable 
:documentOnTypeFormattingProvider)))
     (when (and ot-provider
                (ignore-errors ; github#906, some LS's send empty strings
-                 (or (eq last-input-event
+                 (or (eq eglot--last-inserted-char
                          (seq-first (plist-get ot-provider 
:firstTriggerCharacter)))
-                     (cl-find last-input-event
+                     (cl-find eglot--last-inserted-char
                               (plist-get ot-provider :moreTriggerCharacter)
                               :key #'seq-first))))
-      (eglot-format (point) nil last-input-event))))
+      (eglot-format (point) nil eglot--last-inserted-char))))
 
 (defvar eglot--workspace-symbols-cache (make-hash-table :test #'equal)
   "Cache of `workspace/Symbol' results  used by `xref-find-definitions'.")
@@ -2973,7 +2985,9 @@ for which LSP on-type-formatting should be requested."
                       :insertSpaces (if indent-tabs-mode :json-false t)
                       :insertFinalNewline (if require-final-newline t 
:json-false)
                       :trimFinalNewlines (if delete-trailing-lines t 
:json-false))
-       args)))))
+       args))
+     nil
+     on-type-format)))
 
 (defvar eglot-cache-session-completions t
   "If non-nil Eglot caches data during completion sessions.")
@@ -3367,7 +3381,7 @@ for which LSP on-type-formatting should be requested."
 (cl-defun eglot-imenu ()
   "Eglot's `imenu-create-index-function'.
 Returns a list as described in docstring of `imenu--index-alist'."
-  (unless (eglot--server-capable :textDocument/documentSymbol)
+  (unless (eglot--server-capable :documentSymbolProvider)
     (cl-return-from eglot-imenu))
   (let* ((res (eglot--request (eglot--current-server-or-lose)
                               :textDocument/documentSymbol
@@ -3380,8 +3394,9 @@ Returns a list as described in docstring of 
`imenu--index-alist'."
         (((SymbolInformation)) (eglot--imenu-SymbolInformation res))
         (((DocumentSymbol)) (eglot--imenu-DocumentSymbol res))))))
 
-(cl-defun eglot--apply-text-edits (edits &optional version)
-  "Apply EDITS for current buffer if at VERSION, or if it's nil."
+(cl-defun eglot--apply-text-edits (edits &optional version silent)
+  "Apply EDITS for current buffer if at VERSION, or if it's nil.
+If SILENT, don't echo progress in mode-line."
   (unless edits (cl-return-from eglot--apply-text-edits))
   (unless (or (not version) (equal version eglot--versioned-identifier))
     (jsonrpc-error "Edits on `%s' require version %d, you have %d"
@@ -3389,10 +3404,11 @@ Returns a list as described in docstring of 
`imenu--index-alist'."
   (atomic-change-group
     (let* ((change-group (prepare-change-group))
            (howmany (length edits))
-           (reporter (make-progress-reporter
-                      (format "[eglot] applying %s edits to `%s'..."
-                              howmany (current-buffer))
-                      0 howmany))
+           (reporter (unless silent
+                       (make-progress-reporter
+                        (format "[eglot] applying %s edits to `%s'..."
+                                howmany (current-buffer))
+                        0 howmany)))
            (done 0))
       (mapc (pcase-lambda (`(,newText ,beg . ,end))
               (let ((source (current-buffer)))
@@ -3404,12 +3420,14 @@ Returns a list as described in docstring of 
`imenu--index-alist'."
                         (save-restriction
                           (narrow-to-region beg end)
                           (replace-buffer-contents temp)))
-                      (eglot--reporter-update reporter (cl-incf done)))))))
+                      (when reporter
+                        (eglot--reporter-update reporter (cl-incf done))))))))
             (mapcar (eglot--lambda ((TextEdit) range newText)
                       (cons newText (eglot--range-region range 'markers)))
                     (reverse edits)))
       (undo-amalgamate-change-group change-group)
-      (progress-reporter-done reporter))))
+      (when reporter
+        (progress-reporter-done reporter)))))
 
 (defun eglot--apply-workspace-edit (wedit &optional confirm)
   "Apply the workspace edit WEDIT.  If CONFIRM, ask user first."
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index 4963785e56f..d0f15f049a9 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -84,6 +84,12 @@ All commands in `lisp-mode-shared-map' are inherited by this 
map."
      :help "Byte-compile the current file (if it has changed), then load 
compiled code"]
     ["Byte-recompile Directory..." byte-recompile-directory
      :help "Recompile every `.el' file in DIRECTORY that needs recompilation"]
+    ["Native-compile This File" emacs-lisp-native-compile
+     :help "Compile the current file containing the current buffer to native 
code"
+     :active (native-comp-available-p)]
+    ["Native-compile and Load" emacs-lisp-native-compile-and-load
+     :help "Compile the current file to native code, then load compiled native 
code"
+     :active (native-comp-available-p)]
     ["Disassemble Byte Compiled Object..." disassemble
      :help "Print disassembled code for OBJECT in a buffer"]
     "---"
@@ -217,6 +223,16 @@ All commands in `lisp-mode-shared-map' are inherited by 
this map."
 (declare-function native-compile "comp")
 (declare-function comp-write-bytecode-file "comp")
 
+(defun emacs-lisp-native-compile ()
+  "Native-compile synchronously the current file (if it has changed)."
+  (interactive nil emacs-lisp-mode)
+  (emacs-lisp--before-compile-buffer)
+  (let* ((byte+native-compile t)
+         (byte-to-native-output-buffer-file nil)
+         (eln (native-compile buffer-file-name)))
+    (when eln
+      (comp-write-bytecode-file eln))))
+
 (defun emacs-lisp-native-compile-and-load ()
   "Native-compile synchronously the current file (if it has changed).
 Load the compiled code when finished.
@@ -225,11 +241,8 @@ Use `emacs-lisp-byte-compile-and-load' in combination with
 `native-comp-jit-compilation' set to t to achieve asynchronous
 native compilation."
   (interactive nil emacs-lisp-mode)
-  (emacs-lisp--before-compile-buffer)
-  (let ((byte+native-compile t)
-        (byte-to-native-output-buffer-file nil))
-    (when-let ((eln (native-compile buffer-file-name)))
-      (load (file-name-sans-extension (comp-write-bytecode-file eln))))))
+  (when-let ((byte-file (emacs-lisp-native-compile)))
+    (load (file-name-sans-extension byte-file))))
 
 (defun emacs-lisp-macroexpand ()
   "Macroexpand the form after point.
@@ -421,6 +434,14 @@ be used instead.
 
 (defvar warning-minimum-log-level)
 
+(defvar elisp--local-macroenv
+  `((cl-eval-when . ,(lambda (&rest args) `(progn . ,(cdr args))))
+    (eval-when-compile . ,(lambda (&rest args) `(progn . ,args)))
+    (eval-and-compile . ,(lambda (&rest args) `(progn . ,args))))
+  "Environment to use while tentatively expanding macros.
+This is used to try and avoid the most egregious problems linked to the
+use of `macroexpand-all' as a way to find the \"underlying raw code\".")
+
 (defun elisp--local-variables ()
   "Return a list of locally let-bound variables at point."
   (save-excursion
@@ -436,15 +457,17 @@ be used instead.
                        (car (read-from-string
                              (concat txt "elisp--witness--lisp" closer)))
                      ((invalid-read-syntax end-of-file) nil)))
-             (macroexpand-advice (lambda (expander form &rest args)
-                                   (condition-case nil
-                                       (apply expander form args)
-                                     (error form))))
+             (macroexpand-advice
+              (lambda (expander form &rest args)
+                (condition-case err
+                    (apply expander form args)
+                  (error (message "Ignoring macroexpansion error: %S" err)
+                         form))))
              (sexp
               (unwind-protect
                   (let ((warning-minimum-log-level :emergency))
-                    (advice-add 'macroexpand :around macroexpand-advice)
-                    (macroexpand-all sexp))
+                    (advice-add 'macroexpand-1 :around macroexpand-advice)
+                    (macroexpand-all sexp elisp--local-macroenv))
                 (advice-remove 'macroexpand macroexpand-advice)))
              (vars (elisp--local-variables-1 nil sexp)))
         (delq nil
@@ -1372,9 +1395,9 @@ BEG and END are the start and end of the output in 
current buffer.
 VALUE is the Lisp value printed, ALT1 and ALT2 are strings for the
 alternative printed representations that can be displayed."
   (let ((map (make-sparse-keymap)))
-    (define-key map "\C-m" 'elisp-last-sexp-toggle-display)
-    (define-key map [down-mouse-2] 'mouse-set-point)
-    (define-key map [mouse-2] 'elisp-last-sexp-toggle-display)
+    (define-key map "\C-m" #'elisp-last-sexp-toggle-display)
+    (define-key map [down-mouse-2] #'mouse-set-point)
+    (define-key map [mouse-2] #'elisp-last-sexp-toggle-display)
     (add-text-properties
      beg end
      `(printed-value (,value ,alt1 ,alt2)
@@ -1431,7 +1454,7 @@ If CHAR is not a character, return nil."
                     (lambda (modif)
                       (cond ((eq modif 'super) "\\s-")
                             (t (string ?\\ (upcase (aref (symbol-name modif) 
0)) ?-))))
-                    mods "")
+                    mods)
                    (cond
                     ((memq c '(?\; ?\( ?\) ?\{ ?\} ?\[ ?\] ?\" ?\' ?\\)) 
(string ?\\ c))
                     ((eq c 127) "\\C-?")
@@ -1507,7 +1530,7 @@ If CHAR is not a character, return nil."
                   `(call-interactively
                     (lambda (&rest args) ,expr args))))
          expr)))))
-(define-obsolete-function-alias 'preceding-sexp 'elisp--preceding-sexp "25.1")
+(define-obsolete-function-alias 'preceding-sexp #'elisp--preceding-sexp "25.1")
 
 (defun elisp--eval-last-sexp (eval-last-sexp-arg-internal)
   "Evaluate sexp before point; print value in the echo area.
@@ -1636,9 +1659,8 @@ Reinitialize the face according to the `defface' 
specification."
                    ;; The second arg is an expression that evaluates to
                    ;; an expression.  The second evaluation is the one
                    ;; normally performed not by normal execution but by
-                   ;; custom-initialize-set (for example), which does not
-                   ;; use lexical-binding.
-                   (eval (eval (nth 2 form) lexical-binding))))
+                   ;; custom-initialize-set (for example).
+                   (eval (eval (nth 2 form) lexical-binding) t)))
         form)
        ;; `defface' is macroexpanded to `custom-declare-face'.
        ((eq (car form) 'custom-declare-face)
@@ -1778,7 +1800,7 @@ Elements are as follows:
     (or (progn (elisp-eldoc-var-docstring callback) str)
         (progn (elisp-eldoc-funcall callback) str))))
 
-(defalias 'elisp-eldoc-documentation-function 'elisp--documentation-one-liner
+(defalias 'elisp-eldoc-documentation-function #'elisp--documentation-one-liner
   "Return Elisp documentation for the thing at point as one-line string.
 This is meant as a backward compatibility aide to the \"old\"
 Elisp eldoc behavior.  Consider variable docstrings and function
diff --git a/lisp/progmodes/f90.el b/lisp/progmodes/f90.el
index deccc75b156..815c7352281 100644
--- a/lisp/progmodes/f90.el
+++ b/lisp/progmodes/f90.el
@@ -159,7 +159,6 @@
 ;; 3. Labels for "else" statements (F2003)?
 
 (defvar comment-auto-fill-only-comments)
-(defvar font-lock-keywords)
 
 ;; User options
 
diff --git a/lisp/progmodes/gud.el b/lisp/progmodes/gud.el
index 2b178e50684..09860a4cbde 100644
--- a/lisp/progmodes/gud.el
+++ b/lisp/progmodes/gud.el
@@ -3,7 +3,7 @@
 ;; Copyright (C) 1992-1996, 1998, 2000-2023 Free Software Foundation,
 ;; Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: unix, tools
 
diff --git a/lisp/progmodes/idlw-help.el b/lisp/progmodes/idlw-help.el
index 2d08714b7cf..a57a0c32ac7 100644
--- a/lisp/progmodes/idlw-help.el
+++ b/lisp/progmodes/idlw-help.el
@@ -1141,7 +1141,6 @@ When DING is non-nil, ring the bell as well."
          (goto-char pos)
          (recenter 0)))))
 
-(defvar font-lock-verbose)
 (defvar idlwave-mode-syntax-table)
 (defvar idlwave-font-lock-defaults)
 (defun idlwave-help-fontify ()
diff --git a/lisp/progmodes/idlwave.el b/lisp/progmodes/idlwave.el
index 488e6aa3d2d..3c00046a26a 100644
--- a/lisp/progmodes/idlwave.el
+++ b/lisp/progmodes/idlwave.el
@@ -4311,10 +4311,7 @@ automatically when called interactively.  When you need 
routine
 information updated immediately, leave NO-CONCATENATE nil."
   (interactive "P\np")
   ;; Stop any idle processing
-  (if (or (and (fboundp 'itimerp)
-              (itimerp idlwave-load-rinfo-idle-timer))
-         (and (fboundp 'timerp)
-              (timerp idlwave-load-rinfo-idle-timer)))
+  (if (timerp idlwave-load-rinfo-idle-timer)
       (cancel-timer idlwave-load-rinfo-idle-timer))
   (cond
    ((equal arg '(64))
@@ -4388,10 +4385,7 @@ information updated immediately, leave NO-CONCATENATE 
nil."
 (defvar idlwave-load-rinfo-steps-done (make-vector 6 nil))
 (defvar idlwave-load-rinfo-idle-timer nil)
 (defun idlwave-start-load-rinfo-timer ()
-  (if (or (and (fboundp 'itimerp)
-              (itimerp idlwave-load-rinfo-idle-timer))
-         (and (fboundp 'timerp)
-              (timerp idlwave-load-rinfo-idle-timer)))
+  (if (timerp idlwave-load-rinfo-idle-timer)
       (cancel-timer idlwave-load-rinfo-idle-timer))
   (setq idlwave-load-rinfo-steps-done (make-vector 6 nil))
   (setq idlwave-load-rinfo-idle-timer nil)
@@ -5341,7 +5335,6 @@ directories and save the routine info.
        (idlwave-path-alist-remove-flag dir-entry 'user)))
     (idlwave-scan-user-lib-files path-alist)))
 
-(defvar font-lock-mode)
 (defun idlwave-scan-user-lib-files (path-alist)
   ;; Scan the PRO files in PATH-ALIST and store the info in the user catalog
   (let* ((idlwave-scanning-lib t)
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index de1a820ba11..1d89b35aa2d 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -3427,6 +3427,18 @@ This function is intended for use in 
`after-change-functions'."
 
 ;;; Tree sitter integration
 
+(defun js-jsx--treesit-indent-compatibility-bb1f97b ()
+  "Indent rules helper, to handle different releases of tree-sitter-javascript.
+Check if a node type is available, then return the right indent rules."
+  ;; handle commit bb1f97b
+  (condition-case nil
+      (progn (treesit-query-capture 'javascript '((jsx_fragment) @capture))
+             `(((match "<" "jsx_fragment") parent 0)
+               ((parent-is "jsx_fragment") parent js-indent-level)))
+    (error
+     `(((match "<" "jsx_text") parent 0)
+       ((parent-is "jsx_text") parent js-indent-level)))))
+
 (defvar js--treesit-indent-rules
   (let ((switch-case (rx "switch_" (or "case" "default"))))
     `((javascript
@@ -3462,8 +3474,7 @@ This function is intended for use in 
`after-change-functions'."
        ((parent-is "statement_block") parent-bol js-indent-level)
 
        ;; JSX
-       ((match "<" "jsx_fragment") parent 0)
-       ((parent-is "jsx_fragment") parent js-indent-level)
+       ,@(js-jsx--treesit-indent-compatibility-bb1f97b)
        ((node-is "jsx_closing_element") parent 0)
        ((match "jsx_element" "statement") parent js-indent-level)
        ((parent-is "jsx_element") parent js-indent-level)
@@ -3599,21 +3610,10 @@ This function is intended for use in 
`after-change-functions'."
 
    :language 'javascript
    :feature 'jsx
-   '((jsx_opening_element
-      [(nested_identifier (identifier)) (identifier)]
-      @font-lock-function-call-face)
-
-     (jsx_closing_element
-      [(nested_identifier (identifier)) (identifier)]
-      @font-lock-function-call-face)
-
-     (jsx_self_closing_element
-      [(nested_identifier (identifier)) (identifier)]
-      @font-lock-function-call-face)
-
-     (jsx_attribute
-      (property_identifier)
-      @font-lock-constant-face))
+   '((jsx_opening_element name: (_) @font-lock-function-call-face)
+     (jsx_closing_element name: (_) @font-lock-function-call-face)
+     (jsx_self_closing_element name: (_) @font-lock-function-call-face)
+     (jsx_attribute (property_identifier) @font-lock-constant-face))
 
    :language 'javascript
    :feature 'number
diff --git a/lisp/progmodes/make-mode.el b/lisp/progmodes/make-mode.el
index 5ea03b9e852..37e4c439dca 100644
--- a/lisp/progmodes/make-mode.el
+++ b/lisp/progmodes/make-mode.el
@@ -3,7 +3,7 @@
 ;; Copyright (C) 1992-2023 Free Software Foundation, Inc.
 
 ;; Author: Thomas Neumann <tom@smart.bo.open.de>
-;;     Eric S. Raymond <esr@snark.thyrsus.com>
+;;     Eric S. Raymond <esr@thyrsus.com>
 ;; Maintainer: emacs-devel@gnu.org
 ;; Adapted-By: ESR
 ;; Keywords: unix, tools
@@ -44,10 +44,6 @@
 ;; prerequisites, which targets are out-of-date, and which have no
 ;; prerequisites.
 ;;
-;; The command C-c C-b pops up a browser window listing all target and
-;; macro names.  You can mark or unmark items with C-c SPC, and insert
-;; all marked items back in the Makefile with C-c TAB.
-;;
 ;; The command C-c TAB in the makefile buffer inserts a GNU make builtin.
 ;; You will be prompted for the builtin's arguments.
 ;;
@@ -66,17 +62,9 @@
 ;;   interact with font-lock.
 ;; * Would be nice to edit the commands in ksh-mode and have
 ;;   indentation and slashification done automatically.  Hard.
-;; * Consider removing browser mode.  It seems useless.
 ;; * ":" should notice when a new target is made and add it to the
 ;;   list (or at least set `makefile-need-target-pickup').
-;; * Make browser into a major mode.
 ;; * Clean up macro insertion stuff.  It is a mess.
-;; * Browser entry and exit is weird.  Normalize.
-;; * Browser needs to be rewritten.  Right now it is kind of a crock.
-;;   Should at least:
-;;    * Act more like dired/buffer menu/whatever.
-;;    * Highlight as mouse traverses.
-;;    * B2 inserts.
 ;; * Update documentation above.
 ;; * Update texinfo manual.
 ;; * Update files.el.
@@ -105,7 +93,7 @@
   :version "22.1")
 
 (defface makefile-shell
-  ()
+  '((t (:inherit default)))
   ;;'((((class color) (min-colors 88) (background light)) (:background  
"seashell1"))
   ;;  (((class color) (min-colors 88) (background dark)) (:background  
"seashell4")))
   "Face to use for additionally highlighting Shell commands in Font-Lock mode."
@@ -118,6 +106,7 @@
   "Face to use for additionally highlighting Perl code in Font-Lock mode."
   :version "22.1")
 
+(make-obsolete-variable 'makefile-browser-buffer-name nil "30.1")
 (defcustom makefile-browser-buffer-name "*Macros and Targets*"
   "Name of the macro- and target browser buffer."
   :type 'string)
@@ -152,10 +141,12 @@ Otherwise, a space is inserted.
 The default is t."
   :type 'boolean)
 
+(make-obsolete-variable 'makefile-browser-leftmost-column nil "30.1")
 (defcustom makefile-browser-leftmost-column 10
   "Number of blanks to the left of the browser selection mark."
   :type 'integer)
 
+(make-obsolete-variable 'makefile-browser-cursor-column nil "30.1")
 (defcustom makefile-browser-cursor-column 10
   "Column the cursor goes to when it moves up or down in the Makefile browser."
   :type 'integer)
@@ -168,14 +159,17 @@ The default is t."
   "If non-nil, `makefile-backslash-region' will align backslashes."
   :type 'boolean)
 
+(make-obsolete-variable 'makefile-browser-selected-mark nil "30.1")
 (defcustom makefile-browser-selected-mark "+  "
   "String used to mark selected entries in the Makefile browser."
   :type 'string)
 
+(make-obsolete-variable 'makefile-browser-unselected-mark nil "30.1")
 (defcustom makefile-browser-unselected-mark "   "
   "String used to mark unselected entries in the Makefile browser."
   :type 'string)
 
+(make-obsolete-variable 'makefile-browser-auto-advance-after-selection-p nil 
"30.1")
 (defcustom makefile-browser-auto-advance-after-selection-p t
   "If non-nil, cursor will move after item is selected in Makefile browser."
   :type 'boolean)
@@ -198,6 +192,7 @@ to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \"it seems 
necessary\"."
   "Normal hook run by `makefile-mode'."
   :type 'hook)
 
+(make-obsolete-variable 'makefile-browser-hook nil "30.1")
 (defvar makefile-browser-hook '())
 
 ;;
@@ -611,9 +606,6 @@ The function must satisfy this calling convention:
     ;; Other.
     ["Up To Date Overview" makefile-create-up-to-date-overview
      :help "Create a buffer containing an overview of the state of all known 
targets"]
-    ["Pop up Makefile Browser" makefile-switch-to-browser
-     ;; XXX: this needs a better string, the function is not documented...
-     :help "Pop up Makefile Browser"]
     ("Switch Makefile Type"
      ["GNU make" makefile-gmake-mode
       :help "An adapted `makefile-mode' that knows about GNU make"
@@ -641,6 +633,7 @@ The function must satisfy this calling convention:
       :selected (eq major-mode 'makefile-makepp-mode)])))
 
 
+(make-obsolete-variable 'makefile-browser-map nil "30.1")
 (defvar-keymap makefile-browser-map
   :doc "The keymap that is used in the macro- and target browser."
   "n"       #'makefile-browser-next-line
@@ -695,9 +688,11 @@ The function must satisfy this calling convention:
   "Table of all macro names known for this buffer.")
 (put 'makefile-macro-table 'risky-local-variable t)
 
-(defvar makefile-browser-client
+(make-obsolete-variable 'makefile-browser-client nil "30.1")
+(defvar makefile-browser-client nil
   "A buffer in Makefile mode that is currently using the browser.")
 
+(make-obsolete-variable 'makefile-browser-selection-vector nil "30.1")
 (defvar makefile-browser-selection-vector nil)
 (defvar makefile-has-prereqs nil)
 (defvar makefile-need-target-pickup t)
@@ -757,15 +752,8 @@ dependency, despite the colon.
 
 \\{makefile-mode-map}
 
-In the browser, use the following keys:
-
-\\{makefile-browser-map}
-
 Makefile mode can be configured by modifying the following variables:
 
-`makefile-browser-buffer-name':
-    Name of the macro- and target browser buffer.
-
 `makefile-target-colon':
     The string that gets appended to all target names
     inserted by `makefile-insert-target'.
@@ -783,24 +771,6 @@ Makefile mode can be configured by modifying the following 
variables:
    If you want a TAB (instead of a space) to be appended after the
    target colon, then set this to a non-nil value.
 
-`makefile-browser-leftmost-column':
-   Number of blanks to the left of the browser selection mark.
-
-`makefile-browser-cursor-column':
-   Column in which the cursor is positioned when it moves
-   up or down in the browser.
-
-`makefile-browser-selected-mark':
-   String used to mark selected entries in the browser.
-
-`makefile-browser-unselected-mark':
-   String used to mark unselected entries in the browser.
-
-`makefile-browser-auto-advance-after-selection-p':
-   If this variable is set to a non-nil value the cursor
-   will automagically advance to the next line after an item
-   has been selected in the browser.
-
 `makefile-pickup-everything-picks-up-filenames-p':
    If this variable is set to a non-nil value then
    `makefile-pickup-everything' also picks up filenames as targets
@@ -816,10 +786,6 @@ Makefile mode can be configured by modifying the following 
variables:
    IMPORTANT: Please note that enabling this option causes Makefile mode
    to MODIFY A FILE WITHOUT YOUR CONFIRMATION when \"it seems necessary\".
 
-`makefile-browser-hook':
-   A function or list of functions to be called just before the
-   browser is entered. This is executed in the makefile buffer.
-
 `makefile-special-targets-list':
    List of special targets. You will be offered to complete
    on one of those in the minibuffer whenever you enter a `.'.
@@ -1306,6 +1272,7 @@ Fill comments, backslashed lines, and variable 
definitions specially."
 ;;; ------------------------------------------------------------
 
 (defun makefile-browser-format-target-line (target selected)
+  (declare (obsolete nil "30.1"))
   (format
    (concat (make-string makefile-browser-leftmost-column ?\ )
           (if selected
@@ -1315,6 +1282,7 @@ Fill comments, backslashed lines, and variable 
definitions specially."
    target makefile-target-colon))
 
 (defun makefile-browser-format-macro-line (macro selected)
+  (declare (obsolete nil "30.1"))
   (format
    (concat (make-string makefile-browser-leftmost-column ?\ )
           (if selected
@@ -1323,14 +1291,21 @@ Fill comments, backslashed lines, and variable 
definitions specially."
           (makefile-format-macro-ref macro))))
 
 (defun makefile-browser-fill (targets macros)
+  (declare (obsolete nil "30.1"))
   (let ((inhibit-read-only t))
     (goto-char (point-min))
     (erase-buffer)
     (mapc
-     (lambda (item) (insert (makefile-browser-format-target-line (car item) 
nil) "\n"))
+     (lambda (item) (insert (with-suppressed-warnings
+                                ((obsolete 
makefile-browser-format-target-line))
+                              (makefile-browser-format-target-line (car item) 
nil))
+                            "\n"))
      targets)
     (mapc
-     (lambda (item) (insert (makefile-browser-format-macro-line (car item) 
nil) "\n"))
+     (lambda (item) (insert (with-suppressed-warnings
+                                ((obsolete makefile-browser-format-macro-line))
+                              (makefile-browser-format-macro-line (car item) 
nil))
+                            "\n"))
      macros)
     (sort-lines nil (point-min) (point-max))
     (goto-char (1- (point-max)))
@@ -1344,6 +1319,7 @@ Fill comments, backslashed lines, and variable 
definitions specially."
 
 (defun makefile-browser-next-line ()
   "Move the browser selection cursor to the next line."
+  (declare (obsolete nil "30.1"))
   (interactive)
   (unless (makefile-last-line-p)
     (forward-line 1)
@@ -1351,6 +1327,7 @@ Fill comments, backslashed lines, and variable 
definitions specially."
 
 (defun makefile-browser-previous-line ()
   "Move the browser selection cursor to the previous line."
+  (declare (obsolete nil "30.1"))
   (interactive)
   (unless (makefile-first-line-p)
     (forward-line -1)
@@ -1362,6 +1339,7 @@ Fill comments, backslashed lines, and variable 
definitions specially."
 
 (defun makefile-browser-quit ()
   "Leave the browser and return to the makefile buffer."
+  (declare (obsolete nil "30.1"))
   (interactive)
   (let ((my-client makefile-browser-client))
     (setq makefile-browser-client nil) ; we quit, so NO client!
@@ -1375,6 +1353,7 @@ Fill comments, backslashed lines, and variable 
definitions specially."
 
 (defun makefile-browser-toggle ()
   "Toggle the selection state of the browser item at the cursor position."
+  (declare (obsolete nil "30.1"))
   (interactive)
   (let ((this-line (count-lines (point-min) (point))))
     (setq this-line (max 1 this-line))
@@ -1387,19 +1366,24 @@ Fill comments, backslashed lines, and variable 
definitions specially."
          (let ((macro-name (makefile-browser-this-line-macro-name)))
            (delete-region (point) (progn (end-of-line) (point)))
            (insert
-            (makefile-browser-format-macro-line
-               macro-name
-               (makefile-browser-get-state-for-line this-line))))
+             (with-suppressed-warnings
+                 ((obsolete makefile-browser-format-macro-line))
+               (makefile-browser-format-macro-line
+                macro-name
+                (makefile-browser-get-state-for-line this-line)))))
        (let ((target-name (makefile-browser-this-line-target-name)))
          (delete-region (point) (progn (end-of-line) (point)))
          (insert
-          (makefile-browser-format-target-line
-             target-name
-             (makefile-browser-get-state-for-line this-line))))))
+           (with-suppressed-warnings
+               ((obsolete makefile-browser-format-target-line))
+             (makefile-browser-format-target-line
+              target-name
+              (makefile-browser-get-state-for-line this-line)))))))
     (beginning-of-line)
     (forward-char makefile-browser-cursor-column)
     (if makefile-browser-auto-advance-after-selection-p
-       (makefile-browser-next-line))))
+        (with-suppressed-warnings ((obsolete makefile-browser-next-line))
+          (makefile-browser-next-line)))))
 
 ;;;
 ;;; Making insertions into the client buffer
@@ -1412,6 +1396,7 @@ character, insert a new blank line, go to that line and 
indent by one TAB.
 This is most useful in the process of creating continued lines when copying
 large dependencies from the browser to the client buffer.
 \(point) advances accordingly in the client buffer."
+  (declare (obsolete nil "30.1"))
   (interactive)
   (with-current-buffer makefile-browser-client
     (end-of-line)
@@ -1420,6 +1405,7 @@ large dependencies from the browser to the client buffer.
 (defun makefile-browser-insert-selection ()
   "Insert all selected targets and/or macros in the makefile buffer.
 Insertion takes place at point."
+  (declare (obsolete nil "30.1"))
   (interactive)
   (save-excursion
     (goto-char (point-min))
@@ -1431,11 +1417,15 @@ Insertion takes place at point."
        (setq current-line (1+ current-line))))))
 
 (defun makefile-browser-insert-selection-and-quit ()
+  (declare (obsolete nil "30.1"))
   (interactive)
-  (makefile-browser-insert-selection)
-  (makefile-browser-quit))
+  (with-suppressed-warnings ((obsolete makefile-browser-insert-selection)
+                             (obsolete makefile-browser-quit))
+    (makefile-browser-insert-selection)
+    (makefile-browser-quit)))
 
 (defun makefile-browser-send-this-line-item ()
+  (declare (obsolete nil "30.1"))
   (if (makefile-browser-on-macro-line-p)
       (save-excursion
        (let ((macro-name (makefile-browser-this-line-macro-name)))
@@ -1447,10 +1437,12 @@ Insertion takes place at point."
        (insert target-name " ")))))
 
 (defun makefile-browser-start-interaction ()
+  (declare (obsolete nil "30.1"))
   (use-local-map makefile-browser-map)
   (setq buffer-read-only t))
 
 (defun makefile-browse (targets macros)
+  (declare (obsolete imenu "30.1"))
   (if (zerop (+ (length targets) (length macros)))
       (progn
        (beep)
@@ -1460,19 +1452,23 @@ Insertion takes place at point."
                   "Consider running \\[makefile-pickup-everything]"))))
     (let ((browser-buffer (get-buffer-create makefile-browser-buffer-name)))
       (pop-to-buffer browser-buffer)
-      (makefile-browser-fill targets macros)
+      (with-suppressed-warnings ((obsolete makefile-browser-fill))
+        (makefile-browser-fill targets macros))
       (shrink-window-if-larger-than-buffer)
       (setq-local makefile-browser-selection-vector
                   (make-vector (+ (length targets) (length macros)) nil))
-      (makefile-browser-start-interaction))))
+      (with-suppressed-warnings ((obsolete makefile-browser-start-interaction))
+        (makefile-browser-start-interaction)))))
 
 (defun makefile-switch-to-browser ()
+  (declare (obsolete imenu "30.1"))
   (interactive)
   (run-hooks 'makefile-browser-hook)
   (setq makefile-browser-client (current-buffer))
   (makefile-pickup-targets)
   (makefile-pickup-macros)
-  (makefile-browse makefile-target-table makefile-macro-table))
+  (with-suppressed-warnings ((obsolete makefile-browse))
+    (makefile-browse makefile-target-table makefile-macro-table)))
 
 
 
@@ -1724,12 +1720,14 @@ This acts according to the value of 
`makefile-tab-after-target-colon'."
 
 (defun makefile-browser-on-macro-line-p ()
   "Determine if point is on a macro line in the browser."
+  (declare (obsolete nil "30.1"))
   (save-excursion
     (beginning-of-line)
     (re-search-forward "\\$[{(]" (line-end-position) t)))
 
 (defun makefile-browser-this-line-target-name ()
   "Extract the target name from a line in the browser."
+  (declare (obsolete nil "30.1"))
   (save-excursion
     (end-of-line)
     (skip-chars-backward "^ \t")
@@ -1737,6 +1735,7 @@ This acts according to the value of 
`makefile-tab-after-target-colon'."
 
 (defun makefile-browser-this-line-macro-name ()
   "Extract the macro name from a line in the browser."
+  (declare (obsolete nil "30.1"))
   (save-excursion
     (beginning-of-line)
     (re-search-forward "\\$[{(]" (line-end-position) t)
@@ -1755,13 +1754,18 @@ Uses `makefile-use-curly-braces-for-macros-p'."
       (format "$(%s)" macro-name))))
 
 (defun makefile-browser-get-state-for-line (n)
+  (declare (obsolete nil "30.1"))
   (aref makefile-browser-selection-vector (1- n)))
 
 (defun makefile-browser-set-state-for-line (n to-state)
+  (declare (obsolete nil "30.1"))
   (aset makefile-browser-selection-vector (1- n) to-state))
 
 (defun makefile-browser-toggle-state-for-line (n)
-  (makefile-browser-set-state-for-line n (not 
(makefile-browser-get-state-for-line n))))
+  (declare (obsolete nil "30.1"))
+  (with-suppressed-warnings ((obsolete makefile-browser-set-state-for-line)
+                             (obsolete makefile-browser-get-state-for-line))
+    (makefile-browser-set-state-for-line n (not 
(makefile-browser-get-state-for-line n)))))
 
 (defun makefile-last-line-p ()
   (= (line-end-position) (point-max)))
diff --git a/lisp/progmodes/prog-mode.el b/lisp/progmodes/prog-mode.el
index a434c7e9058..16497097061 100644
--- a/lisp/progmodes/prog-mode.el
+++ b/lisp/progmodes/prog-mode.el
@@ -337,6 +337,8 @@ support it."
   (setq-local require-final-newline mode-require-final-newline)
   (setq-local parse-sexp-ignore-comments t)
   (add-hook 'context-menu-functions 'prog-context-menu 10 t)
+  ;; Enable text conversion in this buffer.
+  (setq-local text-conversion-style t)
   ;; Any programming language is always written left to right.
   (setq bidi-paragraph-direction 'left-to-right))
 
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index 03ed966cc45..7aaf7a9f9fb 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -24,11 +24,6 @@
 
 ;;; Commentary:
 
-;; NOTE: The project API is still experimental and can change in major,
-;; backward-incompatible ways.  Everyone is encouraged to try it, and
-;; report to us any problems or use cases we hadn't anticipated, by
-;; sending an email to emacs-devel, or `M-x report-emacs-bug'.
-;;
 ;; This file contains generic infrastructure for dealing with
 ;; projects, some utility functions, and commands using that
 ;; infrastructure.
@@ -994,12 +989,30 @@ pattern to search for."
     (read-regexp "Find regexp" (and sym (regexp-quote sym))
                  project-regexp-history-variable)))
 
+(defun project--find-default-from (filename project)
+  "Ensure FILENAME is in PROJECT.
+
+Usually, just return FILENAME.  But if
+`project-current-directory-override' is set, adjust it to be
+relative to PROJECT instead.
+
+This supports using a relative file name from the current buffer
+when switching projects with `project-switch-project' and then
+using a command like `project-find-file'."
+  (if-let (filename-proj (and project-current-directory-override
+                            (project-current nil default-directory)))
+      ;; file-name-concat requires Emacs 28+
+      (concat (file-name-as-directory (project-root project))
+              (file-relative-name filename (project-root filename-proj)))
+    filename))
+
 ;;;###autoload
 (defun project-find-file (&optional include-all)
   "Visit a file (with completion) in the current project.
 
 The filename at point (determined by `thing-at-point'), if any,
-is available as part of \"future history\".
+is available as part of \"future history\".  If none, the current
+buffer's file name is used.
 
 If INCLUDE-ALL is non-nil, or with prefix argument when called
 interactively, include all files under the project root, except
@@ -1010,7 +1023,7 @@ for VCS directories listed in 
`vc-directory-exclusion-list'."
          (dirs (list root)))
     (project-find-file-in
      (or (thing-at-point 'filename)
-         (and buffer-file-name (file-relative-name buffer-file-name root)))
+         (and buffer-file-name (project--find-default-from buffer-file-name 
pr)))
      dirs pr include-all)))
 
 ;;;###autoload
@@ -1018,17 +1031,23 @@ for VCS directories listed in 
`vc-directory-exclusion-list'."
   "Visit a file (with completion) in the current project or external roots.
 
 The filename at point (determined by `thing-at-point'), if any,
-is available as part of \"future history\".
+is available as part of \"future history\".  If none, the current
+buffer's file name is used.
 
 If INCLUDE-ALL is non-nil, or with prefix argument when called
 interactively, include all files under the project root, except
 for VCS directories listed in `vc-directory-exclusion-list'."
   (interactive "P")
+  (defvar project-file-history-behavior)
   (let* ((pr (project-current t))
          (dirs (cons
                 (project-root pr)
-                (project-external-roots pr))))
-    (project-find-file-in (thing-at-point 'filename) dirs pr include-all)))
+                (project-external-roots pr)))
+         (project-file-history-behavior t))
+    (project-find-file-in
+     (or (thing-at-point 'filename)
+         (and buffer-file-name (project--find-default-from buffer-file-name 
pr)))
+     dirs pr include-all)))
 
 (defcustom project-read-file-name-function #'project--read-file-cpd-relative
   "Function to call to read a file name from a list.
@@ -1041,6 +1060,28 @@ For the arguments list, see 
`project--read-file-cpd-relative'."
   :group 'project
   :version "27.1")
 
+(defcustom project-file-history-behavior t
+  "If `relativize', entries in `file-name-history' are adjusted.
+
+History entries shown in `project-find-file', `project-find-dir',
+(from `file-name-history') are adjusted to be relative to the
+current project root, instead of the project which added those
+paths.  This only affects history entries added by earlier calls
+to `project-find-file' or `project-find-dir'.
+
+This has the effect of sharing more history between projects."
+  :type '(choice (const :tag "Default behavior" t)
+                 (const :tag "Adjust to be relative to current" relativize))
+  :group 'project
+  :version "30.1")
+
+(defun project--transplant-file-name (filename project)
+  (when-let ((old-root (get-text-property 0 'project filename)))
+    (abbreviate-file-name
+     (expand-file-name
+      (file-relative-name filename old-root)
+      (project-root project)))))
+
 (defun project--read-file-cpd-relative (prompt
                                         all-files &optional predicate
                                         hist mb-default)
@@ -1063,14 +1104,18 @@ by the user at will."
                          (setq all-files
                                (delete common-parent-directory all-files))
                          t))
+         (mb-default (if (and common-parent-directory
+                              mb-default
+                              (file-name-absolute-p mb-default))
+                         (file-relative-name mb-default 
common-parent-directory)
+                       mb-default))
          (substrings (mapcar (lambda (s) (substring s cpd-length)) all-files))
          (_ (when included-cpd
               (setq substrings (cons "./" substrings))))
          (new-collection (project--file-completion-table substrings))
          (abbr-cpd (abbreviate-file-name common-parent-directory))
          (abbr-cpd-length (length abbr-cpd))
-         (relname (cl-letf ((history-add-new-input nil)
-                            ((symbol-value hist)
+         (relname (cl-letf (((symbol-value hist)
                              (mapcan
                               (lambda (s)
                                 (and (string-prefix-p abbr-cpd s)
@@ -1082,8 +1127,6 @@ by the user at will."
                                                      predicate
                                                      hist mb-default)))
          (absname (expand-file-name relname common-parent-directory)))
-    (when (and hist history-add-new-input)
-      (add-to-history hist (abbreviate-file-name absname)))
     absname))
 
 (defun project--read-file-absolute (prompt
@@ -1094,10 +1137,33 @@ by the user at will."
                                    predicate
                                    hist mb-default))
 
+(defun project--read-file-name ( project prompt
+                                 all-files &optional predicate
+                                 hist mb-default)
+  "Call `project-read-file-name-function' with appropriate history.
+
+Depending on `project-file-history-behavior', entries are made
+project-relative where possible."
+  (let ((file
+         (cl-letf ((history-add-new-input nil)
+                   ((symbol-value hist)
+                    (if (eq project-file-history-behavior 'relativize)
+                        (mapcar
+                         (lambda (f)
+                           (or (project--transplant-file-name f project) f))
+                         (symbol-value hist))
+                      (symbol-value hist))))
+           (funcall project-read-file-name-function
+                    prompt all-files predicate hist mb-default))))
+    (when (and hist history-add-new-input)
+      (add-to-history hist
+                      (propertize file 'project (project-root project))))
+    file))
+
 (defun project-find-file-in (suggested-filename dirs project &optional 
include-all)
   "Complete a file name in DIRS in PROJECT and visit the result.
 
-SUGGESTED-FILENAME is a relative file name, or part of it, which
+SUGGESTED-FILENAME is a file name, or part of it, which
 is used as part of \"future history\".
 
 If INCLUDE-ALL is non-nil, or with prefix argument when called
@@ -1114,9 +1180,10 @@ directories listed in `vc-directory-exclusion-list'."
                dirs)
             (project-files project dirs)))
          (completion-ignore-case read-file-name-completion-ignore-case)
-         (file (funcall project-read-file-name-function
-                        "Find file" all-files nil 'file-name-history
-                        suggested-filename)))
+         (file (project--read-file-name
+                project "Find file"
+                all-files nil 'file-name-history
+                suggested-filename)))
     (if (string= file "")
         (user-error "You didn't specify the file")
       (find-file file))))
@@ -1137,7 +1204,10 @@ directories listed in `vc-directory-exclusion-list'."
 
 ;;;###autoload
 (defun project-find-dir ()
-  "Start Dired in a directory inside the current project."
+  "Start Dired in a directory inside the current project.
+
+The current buffer's `default-directory' is available as part of
+\"future history\"."
   (interactive)
   (let* ((project (project-current t))
          (all-files (project-files project))
@@ -1148,11 +1218,13 @@ directories listed in `vc-directory-exclusion-list'."
          ;; https://stackoverflow.com/a/50685235/615245 for possible
          ;; implementation.
          (all-dirs (mapcar #'file-name-directory all-files))
-         (dir (funcall project-read-file-name-function
-                       "Dired"
-                       ;; Some completion UIs show duplicates.
-                       (delete-dups all-dirs)
-                       nil 'file-name-history)))
+         (dir (project--read-file-name
+               project "Dired"
+               ;; Some completion UIs show duplicates.
+               (delete-dups all-dirs)
+               nil 'file-name-history
+               (and default-directory
+                    (project--find-default-from default-directory project)))))
     (dired dir)))
 
 ;;;###autoload
@@ -1481,6 +1553,7 @@ Used by `project-kill-buffers'."
   :package-version '(project . "0.8.2"))
 ;;;###autoload(put 'project-kill-buffers-display-buffer-list 
'safe-local-variable #'booleanp)
 
+;; FIXME: Could this be replaced by `buffer-match-p' in Emacs 29+?
 (defun project--buffer-check (buf conditions)
   "Check if buffer BUF matches any element of the list CONDITIONS.
 See `project-kill-buffer-conditions' or
@@ -1586,7 +1659,12 @@ With some possible metadata (to be decided).")
           (when (file-exists-p filename)
             (with-temp-buffer
               (insert-file-contents filename)
-              (read (current-buffer)))))
+              (mapcar
+               (lambda (elem)
+                 (let ((name (car elem)))
+                   (list (if (file-remote-p name) name
+                           (abbreviate-file-name name)))))
+               (read (current-buffer))))))
     (unless (seq-every-p
              (lambda (elt) (stringp (car-safe elt)))
              project--list)
@@ -1606,7 +1684,12 @@ With some possible metadata (to be decided).")
       (insert ";;; -*- lisp-data -*-\n")
       (let ((print-length nil)
             (print-level nil))
-        (pp project--list (current-buffer)))
+        (pp (mapcar (lambda (elem)
+                      (let ((name (car elem)))
+                        (list (if (file-remote-p name) name
+                                (expand-file-name name)))))
+                    project--list)
+            (current-buffer)))
       (write-region nil nil filename nil 'silent))))
 
 ;;;###autoload
@@ -1615,7 +1698,7 @@ With some possible metadata (to be decided).")
 Save the result in `project-list-file' if the list of projects
 has changed, and NO-WRITE is nil."
   (project--ensure-read-project-list)
-  (let ((dir (project-root pr)))
+  (let ((dir (abbreviate-file-name (project-root pr))))
     (unless (equal (caar project--list) dir)
       (dolist (ent project--list)
         (when (equal dir (car ent))
@@ -1631,7 +1714,7 @@ result in `project-list-file'.  Announce the project's 
removal
 from the list using REPORT-MESSAGE, which is a format string
 passed to `message' as its first argument."
   (project--ensure-read-project-list)
-  (when-let ((ent (assoc project-root project--list)))
+  (when-let ((ent (assoc (abbreviate-file-name project-root) project--list)))
     (setq project--list (delq ent project--list))
     (message report-message project-root)
     (project--write-project-list)))
@@ -1821,6 +1904,18 @@ listed in the dispatch menu produced from 
`project-switch-commands'."
   :group 'project
   :version "28.1")
 
+(defcustom project-key-prompt-style (if (facep 'help-key-binding)
+                                        t
+                                      'brackets)
+  "Which presentation to use when asking to choose a command by key.
+
+When `brackets', use text brackets and `bold' for the character.
+Otherwise, use the face `help-key-binding' in the prompt."
+  :type '(choice (const :tag "Using help-key-binding face" t)
+                 (const :tag "Using bold face and brackets" brackets))
+  :group 'project
+  :version "30.1")
+
 (defun project--keymap-prompt ()
   "Return a prompt for the project switching dispatch menu."
   (mapconcat
@@ -1833,9 +1928,13 @@ listed in the dispatch menu produced from 
`project-switch-commands'."
      (let ((key (if key
                     (vector key)
                   (where-is-internal cmd (list project-prefix-map) t))))
-       (format "[%s] %s"
-               (propertize (key-description key) 'face 'bold)
-               label)))
+       (if (not (eq project-key-prompt-style 'brackets))
+           (format "%s %s"
+                   (propertize (key-description key) 'face 'help-key-binding)
+                   label)
+         (format "[%s] %s"
+                 (propertize (key-description key) 'face 'bold)
+                 label))))
    project-switch-commands
    "  "))
 
@@ -1887,5 +1986,22 @@ to directory DIR."
     (let ((project-current-directory-override dir))
       (call-interactively command))))
 
+;;;###autoload
+(defun project-uniquify-dirname-transform (dirname)
+  "Uniquify name of directory DIRNAME using `project-name', if in a project.
+
+If you set `uniquify-dirname-transform' to this function,
+slash-separated components from `project-name' will be appended to
+the buffer's directory name when buffers from two different projects
+would otherwise have the same name."
+  (if-let (proj (project-current nil dirname))
+      (let ((root (project-root proj)))
+        (expand-file-name
+         (file-name-concat
+          (file-name-directory root)
+          (project-name proj)
+          (file-relative-name dirname root))))
+    dirname))
+
 (provide 'project)
 ;;; project.el ends here
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index a23339a2180..4b940b3f13b 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -297,11 +297,18 @@
 
 (defcustom python-interpreter "python"
   "Python interpreter for noninteractive use.
-To customize the Python shell, modify `python-shell-interpreter'
-instead."
+Some Python interpreters also require changes to
+`python-interpreter-args'.
+
+To customize the Python interpreter for interactive use, modify
+`python-shell-interpreter' instead."
   :version "29.1"
   :type 'string)
 
+(defcustom python-interpreter-args ""
+  "Arguments for the Python interpreter for noninteractive use."
+  :version "30.1"
+  :type 'string)
 
 
 ;;; Bindings
@@ -2558,7 +2565,7 @@ position, else returns nil."
   (cond ((executable-find "python3") "python3")
         ((executable-find "python") "python")
         (t "python3"))
-  "Default Python interpreter for shell.
+  "Python interpreter for interactive use.
 
 Some Python interpreters also require changes to
 `python-shell-interpreter-args'.  In particular, setting
@@ -2573,11 +2580,12 @@ Some Python interpreters also require changes to
   :safe 'stringp)
 
 (defcustom python-shell-interpreter-args "-i"
-  "Default arguments for the Python interpreter."
+  "Arguments for the Python interpreter for interactive use."
   :type 'string)
 
 (defcustom python-shell-interpreter-interactive-arg "-i"
-  "Interpreter argument to force it to run interactively."
+  "Interpreter argument to force it to run interactively.
+This is used only for prompt detection."
   :type 'string
   :version "24.4")
 
@@ -5206,11 +5214,13 @@ def __FFAP_get_module_path(objstr):
 
 (defcustom python-check-command
   (cond ((executable-find "pyflakes") "pyflakes")
+        ((executable-find "ruff") "ruff")
+        ((executable-find "flake8") "flake8")
         ((executable-find "epylint") "epylint")
         (t "pyflakes"))
   "Command used to check a Python file."
   :type 'string
-  :version "29.1")
+  :version "30.1")
 
 (defcustom python-check-buffer-name
   "*Python check: %s*"
@@ -6505,18 +6515,25 @@ recursively."
       (let* ((temp (current-buffer))
              (status (if (bufferp source)
                          (with-current-buffer source
-                           (call-process-region (point-min) (point-max)
-                                                python-interpreter
-                                                nil (list temp nil) nil
-                                                "-c" python--list-imports
-                                                (or name "")))
+                           (apply #'call-process-region
+                                  (point-min) (point-max)
+                                  python-interpreter
+                                  nil (list temp nil) nil
+                                  (append
+                                   (split-string-shell-command
+                                    python-interpreter-args)
+                                   `("-c" ,python--list-imports)
+                                    (list (or name "")))))
                        (with-current-buffer buffer
                          (apply #'call-process
                                 python-interpreter
                                 nil (list temp nil) nil
-                                "-c" python--list-imports
-                                (or name "")
-                                (mapcar #'file-local-name source)))))
+                                (append
+                                 (split-string-shell-command
+                                  python-interpreter-args)
+                                 `("-c" ,python--list-imports)
+                                 (list (or name ""))
+                                 (mapcar #'file-local-name source))))))
              lines)
         (python--list-imports-check-status status)
         (goto-char (point-min))
@@ -6559,7 +6576,11 @@ Return non-nil if the buffer was actually modified."
                                (point-min) (point-max)
                                python-interpreter
                                nil (list temp nil) nil
-                               "-m" "isort" "-" args))
+                               (append
+                                 (split-string-shell-command
+                                  python-interpreter-args)
+                                 '("-m" "isort" "-")
+                                 args)))
                 (tick (buffer-chars-modified-tick)))
             (unless (eq 0 status)
               (error "%s exited with status %s (maybe isort is missing?)"
@@ -6629,10 +6650,14 @@ asking."
     (with-temp-buffer
       (let ((temp (current-buffer)))
         (with-current-buffer buffer
-          (call-process-region (point-min) (point-max)
-                               python-interpreter
-                               nil temp nil
-                               "-m" "pyflakes"))
+          (apply #'call-process-region
+                  (point-min) (point-max)
+                  python-interpreter
+                  nil temp nil
+                  (append
+                   (split-string-shell-command
+                    python-interpreter-args)
+                   '("-m" "pyflakes"))))
         (goto-char (point-min))
         (when (looking-at-p ".* No module named pyflakes$")
           (error "%s couldn't find pyflakes" python-interpreter))
@@ -6886,6 +6911,10 @@ implementations: `python-mode' and `python-ts-mode'."
                python-shell-completion-complete-or-indent))
   (function-put sym 'command-modes '(python-base-mode inferior-python-mode)))
 
+;;;###autoload
+(add-to-list 'auto-mode-alist
+             '("/\\(?:Pipfile\\|\\.?flake8\\)\\'" . conf-mode))
+
 (provide 'python)
 
 ;;; python.el ends here
diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el
index e441ffbbfe3..9d80bbd72dd 100644
--- a/lisp/progmodes/ruby-mode.el
+++ b/lisp/progmodes/ruby-mode.el
@@ -2697,18 +2697,18 @@ Currently there are `ruby-mode' and `ruby-ts-mode'."
 ;;;###autoload
 (add-to-list 'auto-mode-alist
              (cons (purecopy (concat "\\(?:\\.\\(?:"
-                                     "rbw?\\|ru\\|rake\\|thor"
+                                     "rbw?\\|ru\\|rake\\|thor\\|axlsx"
                                      "\\|jbuilder\\|rabl\\|gemspec\\|podspec"
                                      "\\)"
                                      "\\|/"
                                      "\\(?:Gem\\|Rake\\|Cap\\|Thor"
-                                     "\\|Puppet\\|Berks\\|Brew"
+                                     "\\|Puppet\\|Berks\\|Brew\\|Fast"
                                      "\\|Vagrant\\|Guard\\|Pod\\)file"
                                      "\\)\\'"))
                    'ruby-mode))
 
 ;;;###autoload
-(dolist (name (list "ruby" "rbx" "jruby" "ruby1.9" "ruby1.8"))
+(dolist (name (list "ruby" "rbx" "jruby" "j?ruby\\(?:[0-9.]+\\)"))
   (add-to-list 'interpreter-mode-alist (cons (purecopy name) 'ruby-mode)))
 
 (provide 'ruby-mode)
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index cfc10878922..ed4ea8e3618 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -1,7 +1,6 @@
 ;;; sh-script.el --- shell-script editing commands for Emacs  -*- 
lexical-binding:t -*-
 
-;; Copyright (C) 1993-1997, 1999, 2001-2023 Free Software Foundation,
-;; Inc.
+;; Copyright (C) 1993-2023 Free Software Foundation, Inc.
 
 ;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Old-Version: 2.0f
@@ -158,10 +157,6 @@
 (autoload 'shell-command-completion "shell")
 (autoload 'shell-environment-variable-completion "shell")
 
-(defvar font-lock-comment-face)
-(defvar font-lock-set-defaults)
-(defvar font-lock-string-face)
-
 
 (defgroup sh nil
   "Shell programming utilities."
diff --git a/lisp/progmodes/sql.el b/lisp/progmodes/sql.el
index 89d62ab3a61..004ae50ef50 100644
--- a/lisp/progmodes/sql.el
+++ b/lisp/progmodes/sql.el
@@ -235,10 +235,6 @@
 (require 'view)
 (eval-when-compile (require 'subr-x))   ; string-empty-p
 
-(defvar font-lock-keyword-face)
-(defvar font-lock-set-defaults)
-(defvar font-lock-string-face)
-
 ;;; Allow customization
 
 (defgroup SQL nil
@@ -1191,12 +1187,11 @@ Starts `sql-interactive-mode' after doing some setup."
 
 (defcustom sql-postgres-options '("-P" "pager=off")
   "List of additional options for `sql-postgres-program'.
-The default setting includes the -P option which breaks older versions
-of the psql client (such as version 6.5.3).  The -P option is equivalent
-to the --pset option.  If you want the psql to prompt you for a user
-name, add the string \"-u\" to the list of options.  If you want to
-provide a user name on the command line (newer versions such as 7.1),
-add your name with a \"-U\" prefix (such as \"-Umark\") to the list."
+The default -P option is equivalent to the --pset option.  If you
+want psql to prompt you for a user name, add the string \"-u\" to
+the list of options.  If you want to provide a user name on the
+command line, add your name with a \"-U\" prefix (such as
+\"-Umark\") to the list."
   :type '(repeat string)
   :version "20.8")
 
@@ -3096,9 +3091,7 @@ displayed."
 (defun sql-accumulate-and-indent ()
   "Continue SQL statement on the next line."
   (interactive)
-  (if (fboundp 'comint-accumulate)
-      (comint-accumulate)
-    (newline))
+  (comint-accumulate)
   (indent-according-to-mode))
 
 (defun sql-help-list-products (indent freep)
@@ -4033,7 +4026,7 @@ The list is maintained in SQL interactive buffers.")
 (defun sql--completion-table (string pred action)
   (when sql-completion-sqlbuf
     (with-current-buffer sql-completion-sqlbuf
-      (let ((schema (and (string-match "\\`\\(\\sw\\(:?\\sw\\|\\s_\\)*\\)[.]" 
string)
+      (let ((schema (and (string-match "\\`\\(\\sw\\(?:\\sw\\|\\s_\\)*\\)[.]" 
string)
                          (downcase (match-string 1 string)))))
 
         ;; If we haven't loaded any object name yet, load local schema
diff --git a/lisp/progmodes/typescript-ts-mode.el 
b/lisp/progmodes/typescript-ts-mode.el
index ccf0026d7ba..3f8e232b71f 100644
--- a/lisp/progmodes/typescript-ts-mode.el
+++ b/lisp/progmodes/typescript-ts-mode.el
@@ -33,6 +33,7 @@
 (require 'c-ts-common) ; For comment indent and filling.
 
 (declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-query-capture "treesit.c")
 
 (defcustom typescript-ts-mode-indent-offset 2
   "Number of spaces for each indentation step in `typescript-ts-mode'."
@@ -75,6 +76,18 @@
     table)
   "Syntax table for `typescript-ts-mode'.")
 
+(defun tsx-ts-mode--indent-compatibility-b893426 ()
+  "Indent rules helper, to handle different releases of tree-sitter-tsx.
+Check if a node type is available, then return the right indent rules."
+  ;; handle commit b893426
+  (condition-case nil
+      (progn (treesit-query-capture 'tsx '((jsx_fragment) @capture))
+             `(((match "<" "jsx_fragment") parent 0)
+               ((parent-is "jsx_fragment") parent 
typescript-ts-mode-indent-offset)))
+    (error
+     `(((match "<" "jsx_text") parent 0)
+       ((parent-is "jsx_text") parent typescript-ts-mode-indent-offset)))))
+
 (defun typescript-ts-mode--indent-rules (language)
   "Rules used for indentation.
 Argument LANGUAGE is either `typescript' or `tsx'."
@@ -110,16 +123,15 @@ Argument LANGUAGE is either `typescript' or `tsx'."
      ((parent-is "binary_expression") parent-bol 
typescript-ts-mode-indent-offset)
 
      ,@(when (eq language 'tsx)
-         `(((match "<" "jsx_fragment") parent 0)
-           ((parent-is "jsx_fragment") parent typescript-ts-mode-indent-offset)
-           ((node-is "jsx_closing_element") parent 0)
-           ((match "jsx_element" "statement") parent 
typescript-ts-mode-indent-offset)
-           ((parent-is "jsx_element") parent typescript-ts-mode-indent-offset)
-           ((parent-is "jsx_text") parent-bol typescript-ts-mode-indent-offset)
-           ((parent-is "jsx_opening_element") parent 
typescript-ts-mode-indent-offset)
-           ((parent-is "jsx_expression") parent-bol 
typescript-ts-mode-indent-offset)
-           ((match "/" "jsx_self_closing_element") parent 0)
-           ((parent-is "jsx_self_closing_element") parent 
typescript-ts-mode-indent-offset)))
+        (append (tsx-ts-mode--indent-compatibility-b893426)
+                `(((node-is "jsx_closing_element") parent 0)
+                  ((match "jsx_element" "statement") parent 
typescript-ts-mode-indent-offset)
+                  ((parent-is "jsx_element") parent 
typescript-ts-mode-indent-offset)
+                  ((parent-is "jsx_text") parent-bol 
typescript-ts-mode-indent-offset)
+                  ((parent-is "jsx_opening_element") parent 
typescript-ts-mode-indent-offset)
+                  ((parent-is "jsx_expression") parent-bol 
typescript-ts-mode-indent-offset)
+                  ((match "/" "jsx_self_closing_element") parent 0)
+                  ((parent-is "jsx_self_closing_element") parent 
typescript-ts-mode-indent-offset))))
      ;; FIXME(Theo): This no-node catch-all should be removed.  When is it 
needed?
      (no-node parent-bol 0))))
 
@@ -130,7 +142,7 @@ Argument LANGUAGE is either `typescript' or `tsx'."
     "export" "extends" "finally" "for" "from" "function"
     "get" "if" "implements" "import" "in" "instanceof" "interface" "is" "infer"
     "keyof" "let" "namespace" "new" "of" "private" "protected"
-    "public" "readonly" "return" "set" "static" "switch"
+    "public" "readonly" "return" "satisfies" "set" "static" "switch"
     "target" "throw" "try" "type" "typeof" "var" "void"
     "while" "with" "yield")
   "TypeScript keywords for tree-sitter font-locking.")
@@ -142,6 +154,39 @@ Argument LANGUAGE is either `typescript' or `tsx'."
     "&&" "||" "!" "?.")
   "TypeScript operators for tree-sitter font-locking.")
 
+(defun tsx-ts-mode--font-lock-compatibility-bb1f97b (language)
+  "Font lock rules helper, to handle different releases of tree-sitter-tsx.
+Check if a node type is available, then return the right font lock rules.
+Argument LANGUAGE is either `typescript' or `tsx'."
+  ;; handle commit bb1f97b
+  ;; Warning: treesitter-query-capture says both node types are valid,
+  ;; but then raises an error if the wrong node type is used. So it is
+  ;; important to check with the new node type (member_expression)
+  (condition-case nil
+      (progn (treesit-query-capture language '((member_expression) @capture))
+            '((jsx_opening_element
+               [(member_expression (identifier)) (identifier)]
+               @typescript-ts-jsx-tag-face)
+
+              (jsx_closing_element
+               [(member_expression (identifier)) (identifier)]
+               @typescript-ts-jsx-tag-face)
+
+              (jsx_self_closing_element
+               [(member_expression (identifier)) (identifier)]
+               @typescript-ts-jsx-tag-face)))
+    (error '((jsx_opening_element
+             [(nested_identifier (identifier)) (identifier)]
+             @typescript-ts-jsx-tag-face)
+
+            (jsx_closing_element
+             [(nested_identifier (identifier)) (identifier)]
+             @typescript-ts-jsx-tag-face)
+
+            (jsx_self_closing_element
+             [(nested_identifier (identifier)) (identifier)]
+             @typescript-ts-jsx-tag-face)))))
+
 (defun typescript-ts-mode--font-lock-settings (language)
   "Tree-sitter font-lock settings.
 Argument LANGUAGE is either `typescript' or `tsx'."
@@ -293,19 +338,8 @@ Argument LANGUAGE is either `typescript' or `tsx'."
 
    :language language
    :feature 'jsx
-   `((jsx_opening_element
-      [(nested_identifier (identifier)) (identifier)]
-      @typescript-ts-jsx-tag-face)
-
-     (jsx_closing_element
-      [(nested_identifier (identifier)) (identifier)]
-      @typescript-ts-jsx-tag-face)
-
-     (jsx_self_closing_element
-      [(nested_identifier (identifier)) (identifier)]
-      @typescript-ts-jsx-tag-face)
-
-     (jsx_attribute (property_identifier) @typescript-ts-jsx-attribute-face))
+   (append (tsx-ts-mode--font-lock-compatibility-bb1f97b language)
+          `((jsx_attribute (property_identifier) 
@typescript-ts-jsx-attribute-face)))
 
    :language language
    :feature 'number
diff --git a/lisp/progmodes/vera-mode.el b/lisp/progmodes/vera-mode.el
index 07d3ef07d7b..d4f8f71b789 100644
--- a/lisp/progmodes/vera-mode.el
+++ b/lisp/progmodes/vera-mode.el
@@ -5,7 +5,7 @@
 ;; Author:      Reto Zimmermann <reto@gnu.org>
 ;; Version:     2.28
 ;; Keywords:    languages vera
-;; WWW:         https://guest.iis.ee.ethz.ch/~zimmi/emacs/vera-mode.html
+;; URL:         https://iis-people.ee.ethz.ch/~zimmi/emacs/vera-mode.html
 
 ;; Yoni Rabkin <yoni@rabkins.net> contacted the maintainer of this
 ;; file on 18/3/2008, and the maintainer agreed that when a bug is
diff --git a/lisp/progmodes/vhdl-mode.el b/lisp/progmodes/vhdl-mode.el
index 77d862f1a17..8d0a10c0918 100644
--- a/lisp/progmodes/vhdl-mode.el
+++ b/lisp/progmodes/vhdl-mode.el
@@ -6,7 +6,7 @@
 ;;              Rodney J. Whitby <software.vhdl-mode@rwhitby.net>
 ;; Maintainer:  Reto Zimmermann <reto@gnu.org>
 ;; Keywords:    languages vhdl
-;; WWW:         https://guest.iis.ee.ethz.ch/~zimmi/emacs/vhdl-mode.html
+;; URL:         https://iis-people.ee.ethz.ch/~zimmi/emacs/vhdl-mode.html
 
 ;; Yoni Rabkin <yoni@rabkins.net> contacted the maintainer of this
 ;; file on 18/3/2008, and the maintainer agreed that when a bug is
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index 643eea1b0a3..3f75f8d7132 100644
--- a/lisp/progmodes/xref.el
+++ b/lisp/progmodes/xref.el
@@ -1613,7 +1613,8 @@ is nil, prompt only if there's no usable symbol at point."
 (defun xref-find-references-and-replace (from to)
   "Replace all references to identifier FROM with TO."
   (interactive
-   (let* ((query-replace-read-from-default 'find-tag-default)
+   (let* ((query-replace-read-from-default
+           (lambda () (xref-backend-identifier-at-point (xref-find-backend))))
           (common
            (query-replace-read-args "Query replace identifier" nil)))
      (list (nth 0 common) (nth 1 common))))
diff --git a/lisp/replace.el b/lisp/replace.el
index 1555731f6e3..eeac734f3bd 100644
--- a/lisp/replace.el
+++ b/lisp/replace.el
@@ -417,8 +417,9 @@ Replacement transfers the case pattern of the old text to 
the
 new text, if both `case-fold-search' and `case-replace' are
 non-nil and FROM-STRING has no uppercase letters.
 \(Transferring the case pattern means that if the old text
-matched is all caps, or capitalized, then its replacement is
-respectively upcased or capitalized.)
+matched is all caps, or all of its words are capitalized, then its
+replacement is respectively upcased or capitalized.  For more
+details about this, see `replace-match'.)
 
 Ignore read-only matches if `query-replace-skip-read-only' is non-nil,
 ignore hidden matches if `search-invisible' is nil, and ignore more
@@ -492,8 +493,9 @@ there are uppercase letters in REGEXP.
 Replacement transfers the case pattern of the old text to the new
 text, if both `case-fold-search' and `case-replace' are non-nil
 and REGEXP has no uppercase letters.  (Transferring the case pattern
-means that if the old text matched is all caps, or capitalized,
-then its replacement is respectively upcased or capitalized.)
+means that if the old text matched is all caps, or all of its words
+are capitalized, then its replacement is respectively upcased or
+capitalized.  For more details about this, see `replace-match'.)
 
 Ignore read-only matches if `query-replace-skip-read-only' is non-nil,
 ignore hidden matches if `search-invisible' is nil, and ignore more
diff --git a/lisp/saveplace.el b/lisp/saveplace.el
index 18d296ba2d9..a28de6f63a8 100644
--- a/lisp/saveplace.el
+++ b/lisp/saveplace.el
@@ -49,6 +49,17 @@
 Each element looks like (FILENAME . POSITION);
 visiting file FILENAME goes automatically to position POSITION
 rather than the beginning of the buffer.
+A list element can also have the form
+
+   (DIRECTORY (dired-filename . FILENAME))
+
+where DIRECTORY is the name of a directory ending in a slash,
+and FILENAME is the name of a file in that directory.  This
+format is used for saving places in Dired buffers, see the
+function `save-place-dired-hook'; the FILENAME is the file
+where point was located in the Dired listing of DIRECTORY
+when the place in that buffer was recorded.
+
 This alist is saved between Emacs sessions.")
 
 (defcustom save-place-file (locate-user-emacs-file "places" ".emacs-places")
@@ -399,7 +410,8 @@ It runs the hook `save-place-after-find-file-hook'."
 (declare-function dired-goto-file "dired" (file))
 
 (defun save-place-dired-hook ()
-  "Position the point in a Dired buffer."
+  "Position point in a Dired buffer according to its saved place.
+This is run via `dired-initial-position-hook', which see."
   (or save-place-loaded (save-place-load-alist-from-file))
   (when-let ((directory (and (derived-mode-p 'dired-mode)
                              (boundp 'dired-subdir-alist)
diff --git a/lisp/server.el b/lisp/server.el
index c3325e5a24c..10f15598221 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -182,8 +182,10 @@ space (this means characters from ! to ~; or from code 33 
to
   :type 'hook)
 
 (defcustom server-after-make-frame-hook nil
-  "Hook run when the Emacs server creates a client frame.
-The created frame is selected when the hook is called."
+  "Hook run when the Emacs server starts using a client frame.
+The client frame is selected when the hook is called.
+The client frame could be a newly-created frame, or an
+existing frame reused for this purpose."
   :type 'hook
   :version "27.1")
 
@@ -328,6 +330,9 @@ ENV should be in the same format as `process-environment'."
 (defun server-delete-client (proc &optional noframe)
   "Delete PROC, including its buffers, terminals and frames.
 If NOFRAME is non-nil, let the frames live.
+If NOFRAME is the symbol \\='dont-kill-client, also don't
+delete PROC or its terminals, just kill its buffers: this is
+for when `find-alternate-file' calls this via `kill-buffer-hook'.
 Updates `server-clients'."
   (server-log (concat "server-delete-client" (if noframe " noframe")) proc)
   ;; Force a new lookup of client (prevents infinite recursion).
@@ -364,23 +369,28 @@ Updates `server-clients'."
            (set-frame-parameter frame 'client nil)
            (delete-frame frame))))
 
-      (setq server-clients (delq proc server-clients))
+      (or (eq noframe 'dont-kill-client)
+          (setq server-clients (delq proc server-clients)))
 
       ;; Delete the client's tty, except on Windows (both GUI and
       ;; console), where there's only one terminal and does not make
       ;; sense to delete it, or if we are explicitly told not.
       (unless (or (eq system-type 'windows-nt)
+                  ;; 'find-alternate-file' caused the last client
+                  ;; buffer to be killed, but we will reuse the client
+                  ;; for another buffer.
+                  (eq noframe 'dont-kill-client)
                   (process-get proc 'no-delete-terminal))
        (let ((terminal (process-get proc 'terminal)))
          ;; Only delete the terminal if it is non-nil.
          (when (and terminal (eq (terminal-live-p terminal) t))
            (delete-terminal terminal))))
 
-      ;; Delete the client's process.
-      (if (eq (process-status proc) 'open)
-         (delete-process proc))
-
-      (server-log "Deleted" proc))))
+      ;; Delete the client's process (or don't).
+      (unless (eq noframe 'dont-kill-client)
+        (if (eq (process-status proc) 'open)
+           (delete-process proc))
+        (server-log "Deleted" proc)))))
 
 (defvar server-log-time-function #'current-time-string
   "Function to generate timestamps for `server-buffer'.")
@@ -1588,7 +1598,8 @@ FOR-KILLING if non-nil indicates that we are called from 
`kill-buffer'."
                ;; frames, which might change the current buffer.  We
                ;; don't want that (bug#640).
                (save-current-buffer
-                 (server-delete-client proc))
+                 (server-delete-client proc
+                                        find-alternate-file-dont-kill-client))
              (server-delete-client proc))))))
     (when (and (bufferp buffer) (buffer-name buffer))
       ;; We may or may not kill this buffer;
diff --git a/lisp/shell.el b/lisp/shell.el
index 0a24b4ea4c2..b554ee5add9 100644
--- a/lisp/shell.el
+++ b/lisp/shell.el
@@ -595,6 +595,8 @@ Shell buffers.  It implements `shell-completion-execonly' 
for
   ;; Don't use pcomplete's defaulting mechanism, rely on
   ;; shell-dynamic-complete-functions instead.
   (setq-local pcomplete-default-completion-function #'ignore)
+  ;; Do not expand remote file names.
+  (setq-local pcomplete-remote-file-ignore t)
   (setq-local comint-input-autoexpand shell-input-autoexpand)
   ;; Not needed in shell-mode because it's inherited from comint-mode, but
   ;; placed here for read-shell-command.
@@ -1374,7 +1376,12 @@ Returns t if successful."
     (while path-dirs
       (setq dir (file-name-as-directory (comint-directory (or (car path-dirs) 
".")))
            comps-in-dir (and (file-accessible-directory-p dir)
-                             (file-name-all-completions filenondir dir)))
+                             (condition-case nil
+                                  (file-name-all-completions filenondir dir)
+                                ;; Systems such as Android sometimes
+                                ;; put inaccessible directories in
+                                ;; PATH.
+                                (permission-denied nil))))
       ;; Go thru each completion found, to see whether it should be used.
       (while comps-in-dir
        (setq file (car comps-in-dir)
diff --git a/lisp/simple.el b/lisp/simple.el
index d0fbc68d47e..50d6abffb93 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -4099,10 +4099,11 @@ default values.")
   "Amalgamate undo if necessary.
 This function can be called before an amalgamating command.  It
 removes the previous `undo-boundary' if a series of such calls
-have been made.  By default `self-insert-command' and
-`delete-char' are the only amalgamating commands, although this
-function could be called by any command wishing to have this
-behavior."
+have been made.  `self-insert-command' and `delete-char' are the
+most common amalgamating commands, although this function can be
+called by any command which desires this behavior.
+`analyze-text-conversion' (which see) is also an amalgamating
+command in most circumstances."
   (let ((last-amalgamating-count
          (undo-auto--last-boundary-amalgamating-number)))
     (setq undo-auto--this-command-amalgamating t)
@@ -5661,7 +5662,7 @@ argument should still be a \"useful\" string for such 
uses."
                   ;; interrupt this. If they interrupt it, we want to continue
                   ;; so we become selection owner, so this doesn't stay slow.
                   (if (eq (window-system) 'x)
-                      (ignore-error 'quit (funcall 
interprogram-paste-function))
+                      (ignore-error quit (funcall interprogram-paste-function))
                     (funcall interprogram-paste-function)))))
         (when interprogram-paste
           (setq interprogram-paste
@@ -10285,18 +10286,34 @@ SYMBOL is the name of this modifier, as a symbol.
 LSHIFTBY is the numeric value of this modifier, in keyboard events.
 PREFIX is the string that represents this modifier in an event type symbol."
   (if (numberp event)
-      (cond ((eq symbol 'control)
-            (if (<= 64 (upcase event) 95)
-                (- (upcase event) 64)
-              (logior (ash 1 lshiftby) event)))
-           ((eq symbol 'shift)
-             ;; FIXME: Should we also apply this "upcase" behavior of shift
-             ;; to non-ascii letters?
-            (if (<= ?a (downcase event) ?z)
-                (upcase event)
-              (logior (ash 1 lshiftby) event)))
-           (t
-            (logior (ash 1 lshiftby) event)))
+      ;; Use the base event to determine how the control and shift
+      ;; modifiers should be applied.
+      (let* ((base-event (event-basic-type event)))
+        (cond ((eq symbol 'control)
+              (if (<= 64 (upcase base-event) 95)
+                   ;; Apply the control modifier...
+                  (logior (- (upcase base-event) 64)
+                           ;; ... and any additional modifiers
+                           ;; specified in the original event...
+                           (logand event (logior ?\M-\0 ?\C-\0 ?\S-\0
+                                                ?\H-\0 ?\s-\0 ?\A-\0))
+                           ;; ... including any shift modifier that
+                           ;; `event-basic-type' may have removed.
+                           (if (<= ?A event ?Z) ?\S-\0 0))
+                (logior (ash 1 lshiftby) event)))
+             ((eq symbol 'shift)
+               ;; FIXME: Should we also apply this "upcase" behavior of shift
+               ;; to non-ascii letters?
+              (if (<= ?a base-event ?z)
+                   ;; Apply the Shift modifier.
+                  (logior (upcase base-event)
+                           ;; ... and any additional modifiers
+                           ;; specified in the original event.
+                           (logand event (logior ?\M-\0 ?\C-\0 ?\S-\0
+                                                ?\H-\0 ?\s-\0 ?\A-\0)))
+                (logior (ash 1 lshiftby) event)))
+             (t
+              (logior (ash 1 lshiftby) event))))
     (if (memq symbol (event-modifiers event))
        event
       (let ((event-type (if (symbolp event) event (car event))))
@@ -10569,7 +10586,7 @@ call `normal-erase-is-backspace-mode' (which see) 
instead."
        (if (if (eq normal-erase-is-backspace 'maybe)
                (and (not noninteractive)
                     (or (memq system-type '(ms-dos windows-nt))
-                       (memq window-system '(w32 ns pgtk haiku))
+                       (memq window-system '(w32 ns pgtk haiku android))
                         (and (eq window-system 'x)
                              (fboundp 'x-backspace-delete-keys-p)
                              (x-backspace-delete-keys-p))
@@ -10779,10 +10796,13 @@ warning using STRING as the message.")
 
 ;;; Generic dispatcher commands
 
-;; Macro `define-alternatives' is used to create generic commands.
-;; Generic commands are these (like web, mail, news, encrypt, irc, etc.)
-;; that can have different alternative implementations where choosing
-;; among them is exclusively a matter of user preference.
+;; Macro `define-alternatives' can be used to create generic commands.
+;; Generic commands are commands that can have different alternative
+;; implementations, and choosing among them is the matter of user
+;; preference in each case.  For example, you could have a generic
+;; command `open' capable of "opening" a text file, a URL, a
+;; directory, or a binary file, and each of these alternatives would
+;; invoke a different Emacs function.
 
 ;; (define-alternatives COMMAND) creates a new interactive command
 ;; M-x COMMAND and a customizable variable COMMAND-alternatives.
@@ -10792,26 +10812,38 @@ warning using STRING as the message.")
 ;; ;;;###autoload (push '("My impl name" . my-impl-symbol) COMMAND-alternatives
 
 (defmacro define-alternatives (command &rest customizations)
-  "Define the new command `COMMAND'.
+  "Define a new generic COMMAND which can have several implementations.
 
-The argument `COMMAND' should be a symbol.
+The argument `COMMAND' should be an unquoted symbol.
 
 Running `\\[execute-extended-command] COMMAND RET' for \
-the first time prompts for which
-alternative to use and records the selected command as a custom
-variable.
+the first time prompts for the
+alternative implementation to use and records the selected alternative.
+Thereafter, `\\[execute-extended-command] COMMAND RET' will \
+automatically invoke the recorded selection.
 
 Running `\\[universal-argument] \\[execute-extended-command] COMMAND RET' \
-prompts again for an alternative
-and overwrites the previous choice.
-
-The variable `COMMAND-alternatives' contains an alist with
-alternative implementations of COMMAND.  `define-alternatives'
-does not have any effect until this variable is set.
-
-CUSTOMIZATIONS, if non-nil, should be composed of alternating
-`defcustom' keywords and values to add to the declaration of
-`COMMAND-alternatives' (typically :group and :version)."
+again prompts for an alternative
+and overwrites the previous selection.
+
+The macro creates a `defcustom' named `COMMAND-alternatives'.
+CUSTOMIZATIONS, if non-nil, should be pairs of `defcustom'
+keywords and values to add to the definition of that `defcustom';
+typically, these keywords will be :group and :version with the
+appropriate values.
+
+To be useful, the value of `COMMAND-alternatives' should be an
+alist describing the alternative implementations of COMMAND.
+The elements of this alist should be of the form
+  (ALTERNATIVE-NAME . FUNCTION)
+where ALTERNATIVE-NAME is the name of the alternative to be shown
+to the user as a selectable alternative, and FUNCTION is the
+interactive function to call which implements that alternative.
+The variable could be populated with associations describing the
+alternatives either before or after invoking `define-alternatives';
+if the variable is not defined when `define-alternatives' is invoked,
+the macro will create it with a nil value, and your Lisp program
+should then populate it."
   (declare (indent defun))
   (let* ((command-name (symbol-name command))
          (varalt-name (concat command-name "-alternatives"))
@@ -10994,8 +11026,139 @@ If the buffer doesn't exist, create it first."
   "Change value in PLIST of PROP to VAL, comparing with `equal'."
   (declare (obsolete plist-put "29.1"))
   (plist-put plist prop val #'equal))
+
 
 
+;; Text conversion support.  See textconv.c for more details about
+;; what this is.
+
+;; Actually in textconv.c.
+(defvar text-conversion-edits)
+
+;; Actually in elec-pair.el.
+(defvar electric-pair-preserve-balance)
+(declare-function electric-pair-analyze-conversion "elec-pair.el")
+
+;; Actually in emacs-lisp/timer.el.
+(declare-function timer-set-time "emacs-lisp/timer.el")
+
+(defvar-local post-text-conversion-hook nil
+  "Hook run after text is inserted by an input method.
+Each function in this list is run until one returns non-nil.
+When run, `last-command-event' is bound to the last character
+that was inserted by the input method.")
+
+(defun analyze-text-conversion ()
+  "Analyze the results of the previous text conversion event.
+
+For each insertion:
+
+  - Look for the insertion of a string starting or ending with a
+    character inside `auto-fill-chars', and fill the text around
+    it if `auto-fill-mode' is enabled.
+
+  - Look for the insertion of a new line, and cause automatic
+    line breaking of the previous line when `auto-fill-mode' is
+    enabled.
+
+  - Look for the deletion of a single electric pair character,
+    and delete the adjascent pair if
+    `electric-pair-delete-adjacent-pairs'.
+
+  - Run `post-self-insert-functions' for the last character of
+    any inserted text so that modes such as `electric-pair-mode'
+    can work.
+
+  - Run `post-text-conversion-hook' with `last-command-event' set
+    to the last character of any inserted text to finish up.
+
+Finally, amalgamate recent changes to the undo list with previous
+ones, unless a new line has been inserted or auto-fill has taken
+place.  If undo information is being recorded, make sure
+`undo-auto-current-boundary-timer' will run within the next 5
+seconds."
+  (interactive)
+  (let ((any-nonephemeral nil))
+    ;; The list must be processed in reverse.
+    (dolist (edit (reverse text-conversion-edits))
+      ;; Filter out ephemeral edits and deletions after point.  Here, we
+      ;; are only interested in insertions or deletions whose contents
+      ;; can be identified.
+      (when (stringp (nth 3 edit))
+        (with-current-buffer (car edit)
+          (if (not (eq (nth 1 edit) (nth 2 edit)))
+              ;; Process this insertion.  (nth 3 edit) is the text which
+              ;; was inserted.
+              (let* ((inserted (nth 3 edit))
+                     ;; Get the first and last characters.
+                     (start (aref inserted 0))
+                     (end (aref inserted (1- (length inserted))))
+                     ;; Figure out whether or not to auto-fill.
+                     (auto-fill-p (or (aref auto-fill-chars start)
+                                      (aref auto-fill-chars end)))
+                     ;; Figure out whether or not a newline was inserted.
+                     (newline-p (string-search "\n" inserted))
+                     ;; Save the current undo list to figure out
+                     ;; whether or not auto-fill has actually taken
+                     ;; place.
+                     (old-undo-list buffer-undo-list))
+                (save-excursion
+                  (if (and auto-fill-function newline-p)
+                      (progn (goto-char (nth 2 edit))
+                             (previous-logical-line)
+                             (funcall auto-fill-function))
+                    (when (and auto-fill-function auto-fill-p)
+                      (progn (goto-char (nth 2 edit))
+                             (funcall auto-fill-function))))
+                  ;; Record whether or not this edit should result in
+                  ;; an undo boundary being added.
+                  (setq any-nonephemeral
+                        (or any-nonephemeral newline-p
+                            ;; See if auto-fill has taken place by
+                            ;; comparing the current undo list with
+                            ;; the saved head.
+                            (not (eq old-undo-list
+                                     buffer-undo-list)))))
+                (goto-char (nth 2 edit))
+                (let ((last-command-event end))
+                  (unless (run-hook-with-args-until-success
+                           'post-text-conversion-hook)
+                    (run-hooks 'post-self-insert-hook))))
+            ;; Process this deletion before point.  (nth 2 edit) is the
+            ;; text which was deleted.  Input methods typically prefer
+            ;; to edit words instead of deleting characters off their
+            ;; ends, but they seem to always send proper requests for
+            ;; deletion for punctuation.
+            (when (and (boundp 'electric-pair-delete-adjacent-pairs)
+                       (symbol-value 'electric-pair-delete-adjacent-pairs)
+                       ;; Make sure elec-pair is loaded.
+                       (fboundp 'electric-pair-analyze-conversion)
+                       ;; Only do this if only a single edit happened.
+                       text-conversion-edits)
+              (save-excursion
+                (goto-char (nth 2 edit))
+                (electric-pair-analyze-conversion (nth 3 edit))))))))
+    ;; If all edits were ephemeral, make this an amalgamating command.
+    ;; Then, make sure that an undo boundary is placed within the next
+    ;; five seconds.
+    (unless any-nonephemeral
+      (undo-auto-amalgamate)
+      (let ((timer undo-auto-current-boundary-timer))
+        (if timer
+            ;; The timer is already running.  See if it's due to expire
+            ;; within the next five seconds.
+            (let ((time (list (aref timer 1) (aref timer 2)
+                              (aref timer 3))))
+              (unless (<= (time-convert (time-subtract time nil)
+                                        'integer)
+                          5)
+                ;; It's not, so make it run in 5 seconds.
+                (timer-set-time undo-auto-current-boundary-timer
+                                (time-add nil 5))))
+          ;; Otherwise, start it for five seconds from now.
+          (setq undo-auto-current-boundary-timer
+                (run-at-time 5 nil #'undo-auto--boundary-timer)))))))
+
 (provide 'simple)
 
 ;;; simple.el ends here
diff --git a/lisp/speedbar.el b/lisp/speedbar.el
index 0115a6f4ae4..67d4e8c4df1 100644
--- a/lisp/speedbar.el
+++ b/lisp/speedbar.el
@@ -662,7 +662,7 @@ the dot should NOT be quoted in with \\.  Other regular 
expression
 matchers are allowed however.  EXTENSION may be a single string or a
 list of strings."
   (interactive "sExtension: ")
-  (if (not (listp extension)) (setq extension (list extension)))
+  (setq extension (ensure-list extension))
   (while extension
     (if (member (car extension) speedbar-supported-extension-expressions)
        nil
@@ -677,8 +677,7 @@ list of strings."
 This function will modify `speedbar-ignored-directory-regexp' and add
 DIRECTORY-EXPRESSION to `speedbar-ignored-directory-expressions'."
   (interactive "sDirectory regex: ")
-  (if (not (listp directory-expression))
-      (setq directory-expression (list directory-expression)))
+  (setq directory-expression (ensure-list directory-expression))
   (while directory-expression
     (if (member (car directory-expression) 
speedbar-ignored-directory-expressions)
        nil
@@ -3531,7 +3530,7 @@ to be at the beginning of a line in the etags buffer.
 
 This variable is ignored if `speedbar-use-imenu-flag' is non-nil.")
 
-(defcustom speedbar-fetch-etags-command "etags"
+(defcustom speedbar-fetch-etags-command etags-program-name
   "Command used to create an etags file.
 This variable is ignored if `speedbar-use-imenu-flag' is t."
   :group 'speedbar
diff --git a/lisp/sqlite-mode.el b/lisp/sqlite-mode.el
index c3047c786f7..8cb94485369 100644
--- a/lisp/sqlite-mode.el
+++ b/lisp/sqlite-mode.el
@@ -126,7 +126,7 @@
         (forward-line 1)
         (if (looking-at " ")
             ;; Delete the info.
-            (delete-region (point) (if (re-search-forward "^[^ ]" nil t)
+            (delete-region (point) (if (re-search-forward "^[^ \t]" nil t)
                                        (match-beginning 0)
                                      (point-max)))
           ;; Insert the info.
diff --git a/lisp/startup.el b/lisp/startup.el
index 7f601668369..4d0e59ba4f3 100644
--- a/lisp/startup.el
+++ b/lisp/startup.el
@@ -553,11 +553,24 @@ the updated value."
     (setq startup--original-eln-load-path
           (copy-sequence native-comp-eln-load-path))))
 
+(defvar android-fonts-enumerated nil
+  "Whether or not fonts have been enumerated already.
+On Android, Emacs uses this variable internally at startup.")
+
 (defun normal-top-level ()
   "Emacs calls this function when it first starts up.
 It sets `command-line-processed', processes the command-line,
 reads the initialization files, etc.
 It is the default value of the variable `top-level'."
+  ;; Initialize the Android font driver late.
+  ;; This is done here because it needs the `mac-roman' coding system
+  ;; to be loaded.
+  (when (and (featurep 'android)
+             (fboundp 'android-enumerate-fonts)
+             (not android-fonts-enumerated))
+    (funcall 'android-enumerate-fonts)
+    (setq android-fonts-enumerated t))
+
   (if command-line-processed
       (message internal--top-level-message)
     (setq command-line-processed t)
@@ -1010,6 +1023,7 @@ init-file, or to a default value if loading is not 
possible."
          ;; Use (startup--witness) instead of nil, so we can detect when the
          ;; init files set `debug-ignored-errors' to nil.
          (if init-file-debug '(startup--witness) debug-ignored-errors))
+        (d-i-e-standard debug-ignored-errors)
         ;; The init file might contain byte-code with embedded NULs,
         ;; which can cause problems when read back, so disable nul
         ;; byte detection.  (Bug#52554)
@@ -1098,8 +1112,16 @@ the `--debug-init' option to view a complete error 
backtrace."
 
       ;; If we can tell that the init file altered debug-on-error,
       ;; arrange to preserve the value that it set up.
-      (or (eq debug-ignored-errors d-i-e-initial)
-          (setq d-i-e-from-init-file (list debug-ignored-errors)))
+      (unless (eq debug-ignored-errors d-i-e-initial)
+        (if (memq 'startup--witness debug-ignored-errors)
+            ;; The init file wants to add errors to the standard
+            ;; value, so we need to emulate that.
+            (setq d-i-e-from-init-file
+                  (list (append d-i-e-standard
+                                (remq 'startup--witness
+                                      debug-ignored-errors))))
+          ;; The init file _replaces_ the standard value.
+          (setq d-i-e-from-init-file (list debug-ignored-errors))))
       (or (eq debug-on-error debug-on-error-initial)
           (setq debug-on-error-should-be-set t
                 debug-on-error-from-init-file debug-on-error)))
diff --git a/lisp/strokes.el b/lisp/strokes.el
index 293bdf0f369..694677ada0b 100644
--- a/lisp/strokes.el
+++ b/lisp/strokes.el
@@ -1211,12 +1211,7 @@ the stroke as a character in some language."
 ;;\\{edit-strokes-mode-map}"
 ;;  (setq truncate-lines nil
 ;;     auto-show-mode nil              ; don't want problems here either
-;;     mode-popup-menu edit-strokes-menu) ; what about extent-specific stuff?
-;;  (and (featurep 'menubar)
-;;       current-menubar
-;;       (setq-local current-menubar
-;;                   (copy-sequence current-menubar))
-;;       (add-submenu nil edit-strokes-menu)))
+;;     mode-popup-menu edit-strokes-menu)) ; what about extent-specific stuff?
 
 ;;(let ((map edit-strokes-mode-map))
 ;;  (define-key map "<" 'beginning-of-buffer)
diff --git a/lisp/subr.el b/lisp/subr.el
index db709cb600a..0bdfe6a8e0d 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -427,8 +427,8 @@ PREFIX is a string, and defaults to \"g\"."
     (make-symbol (format "%s%d" (or prefix "g") num))))
 
 (defun ignore (&rest _arguments)
-  "Do nothing and return nil.
-This function accepts any number of ARGUMENTS, but ignores them.
+  "Ignore ARGUMENTS, do nothing, and return nil.
+This function accepts any number of arguments in ARGUMENTS.
 Also see `always'."
   ;; Not declared `side-effect-free' because we don't want calls to it
   ;; elided; see `byte-compile-ignore'.
@@ -437,8 +437,8 @@ Also see `always'."
   nil)
 
 (defun always (&rest _arguments)
-  "Do nothing and return t.
-This function accepts any number of ARGUMENTS, but ignores them.
+  "Ignore ARGUMENTS, do nothing, and return t.
+This function accepts any number of arguments in ARGUMENTS.
 Also see `ignore'."
   (declare (pure t) (side-effect-free error-free))
   t)
@@ -1655,8 +1655,9 @@ in the current Emacs session, then this function may 
return nil."
 
 (defun event-start (event)
   "Return the starting position of EVENT.
-EVENT should be a mouse click, drag, or key press event.  If
-EVENT is nil, the value of `posn-at-point' is used instead.
+EVENT should be a mouse click, drag, touch screen, or key press
+event.  If EVENT is nil, the value of `posn-at-point' is used
+instead.
 
 The following accessor functions are used to access the elements
 of the position:
@@ -1679,17 +1680,34 @@ nil or (STRING . POSITION)'.
 
 For more information, see Info node `(elisp)Click Events'."
   (declare (side-effect-free t))
-  (or (and (consp event) (nth 1 event))
-      (event--posn-at-point)))
+  (if (and (consp event)
+           (or (eq (car event) 'touchscreen-begin)
+               (eq (car event) 'touchscreen-end)))
+      ;; Touch screen begin and end events save their information in a
+      ;; different format, where the mouse position list is the cdr of
+      ;; (nth 1 event).
+      (cdadr event)
+    (or (and (consp event)
+             ;; Ignore touchscreen update events.  They store the posn
+             ;; in a different format, and can have multiple posns.
+             (not (eq (car event) 'touchscreen-update))
+             (nth 1 event))
+        (event--posn-at-point))))
 
 (defun event-end (event)
   "Return the ending position of EVENT.
-EVENT should be a click, drag, or key press event.
+EVENT should be a click, drag, touch screen, or key press event.
 
 See `event-start' for a description of the value returned."
   (declare (side-effect-free t))
-  (or (and (consp event) (nth (if (consp (nth 2 event)) 2 1) event))
-      (event--posn-at-point)))
+  (if (and (consp event)
+           (or (eq (car event) 'touchscreen-begin)
+               (eq (car event) 'touchscreen-end)))
+      (cdadr event)
+    (or (and (consp event)
+             (not (eq (car event) 'touchscreen-update))
+             (nth (if (consp (nth 2 event)) 2 1) event))
+        (event--posn-at-point))))
 
 (defsubst event-click-count (event)
   "Return the multi-click count of EVENT, a click or drag event.
@@ -1979,10 +1997,14 @@ instead; it will indirectly limit the specpdl stack 
size as well.")
                         'native-comp-enable-subr-trampolines
                         "29.1")
 
+(defvaralias 'comp-enable-subr-trampolines 
'native-comp-enable-subr-trampolines)
+
 (make-obsolete-variable 'native-comp-deferred-compilation
                         'native-comp-jit-compilation
                         "29.1")
 
+(defvaralias 'native-comp-deferred-compilation 'native-comp-jit-compilation)
+
 
 ;;;; Alternate names for functions - these are not being phased out.
 
@@ -3091,6 +3113,11 @@ So escape sequences and keyboard encoding are taken into 
account.
 When there's an ambiguity because the key looks like the prefix of
 some sort of escape sequence, the ambiguity is resolved via `read-key-delay'.
 
+Also in contrast to `read-event', input method text conversion
+will be disabled while the key sequence is read, so that
+character input events will always be generated for keyboard
+input.
+
 If the optional argument PROMPT is non-nil, display that as a
 prompt.
 
@@ -3149,7 +3176,8 @@ only unbound fallback disabled is downcasing of the last 
event."
                   (lookup-key global-map [tool-bar])))
              map))
           (let* ((keys
-                  (catch 'read-key (read-key-sequence-vector prompt nil t)))
+                  (catch 'read-key (read-key-sequence-vector prompt nil t
+                                                             nil nil t)))
                  (key (aref keys 0)))
             (if (and (> (length keys) 1)
                      (memq key '(mode-line header-line
@@ -3335,6 +3363,8 @@ causes it to evaluate `help-form' and display the result."
        (while (not done)
          (unless (get-text-property 0 'face prompt)
            (setq prompt (propertize prompt 'face 'minibuffer-prompt)))
+          ;; Display the on screen keyboard if it exists.
+          (frame-toggle-on-screen-keyboard (selected-frame) nil)
          (setq char (let ((inhibit-quit inhibit-keyboard-quit))
                       (read-key prompt)))
          (and show-help (buffer-live-p (get-buffer helpbuf))
@@ -3495,6 +3525,9 @@ an error message."
     (minibuffer-message "Wrong answer")
     (sit-for 2)))
 
+;; Defined in textconv.c.
+(defvar overriding-text-conversion-style)
+
 (defun read-char-from-minibuffer (prompt &optional chars history)
   "Read a character from the minibuffer, prompting for it with PROMPT.
 Like `read-char', but uses the minibuffer to read and return a character.
@@ -3509,7 +3542,15 @@ while calling this function, then pressing `help-char'
 causes it to evaluate `help-form' and display the result.
 There is no need to explicitly add `help-char' to CHARS;
 `help-char' is bound automatically to `help-form-show'."
-  (let* ((map (if (consp chars)
+
+  ;; If text conversion is enabled in this buffer, then it will only
+  ;; be disabled the next time `force-mode-line-update' happens.
+  (when (and (bound-and-true-p overriding-text-conversion-style)
+             (bound-and-true-p text-conversion-style))
+    (force-mode-line-update))
+
+  (let* ((overriding-text-conversion-style nil)
+         (map (if (consp chars)
                   (or (gethash (list help-form (cons help-char chars))
                                read-char-from-minibuffer-map-hash)
                       (let ((map (make-sparse-keymap))
@@ -3521,22 +3562,27 @@ There is no need to explicitly add `help-char' to CHARS;
                         ;; being a command char.
                         (when help-form
                           (define-key map (vector help-char)
-                            (lambda ()
-                              (interactive)
-                              (let ((help-form msg)) ; lexically bound msg
-                                (help-form-show)))))
+                                      (lambda ()
+                                        (interactive)
+                                        (let ((help-form msg)) ; lexically 
bound msg
+                                          (help-form-show)))))
                         (dolist (char chars)
                           (define-key map (vector char)
-                            #'read-char-from-minibuffer-insert-char))
+                                      #'read-char-from-minibuffer-insert-char))
                         (define-key map [remap self-insert-command]
-                          #'read-char-from-minibuffer-insert-other)
+                                    #'read-char-from-minibuffer-insert-other)
                         (puthash (list help-form (cons help-char chars))
                                  map read-char-from-minibuffer-map-hash)
                         map))
                 read-char-from-minibuffer-map))
          ;; Protect this-command when called from pre-command-hook (bug#45029)
          (this-command this-command)
-         (result (read-from-minibuffer prompt nil map nil (or history t)))
+         (result (progn
+                   ;; Disable text conversion if it is enabled.
+                   ;; (bug#65370)
+                   (when (fboundp 'set-text-conversion-style)
+                     (set-text-conversion-style text-conversion-style))
+                   (read-from-minibuffer prompt nil map nil (or history t))))
          (char
           (if (> (length result) 0)
               ;; We have a string (with one character), so return the first 
one.
@@ -3634,9 +3680,14 @@ confusing to some users.")
        (or (consp last-nonmenu-event)   ; invoked by a mouse event
            (and (null last-nonmenu-event)
                 (consp last-input-event))
+           (featurep 'android)         ; Prefer dialog boxes on Android.
            from--tty-menu-p)            ; invoked via TTY menu
        use-dialog-box))
 
+;; Actually in textconv.c.
+(defvar overriding-text-conversion-style)
+(declare-function set-text-conversion-style "textconv.c")
+
 (defun y-or-n-p (prompt)
   "Ask user a \"y or n\" question.
 Return t if answer is \"y\" and nil if it is \"n\".
@@ -3746,6 +3797,9 @@ like) while `y-or-n-p' is running)."
       (setq prompt (funcall padded prompt))
       (let* ((enable-recursive-minibuffers t)
              (msg help-form)
+             ;; Disable text conversion so that real Y or N events are
+             ;; sent.
+             (overriding-text-conversion-style nil)
              (keymap (let ((map (make-composed-keymap
                                  y-or-n-p-map query-replace-map)))
                        (when help-form
@@ -3759,9 +3813,15 @@ like) while `y-or-n-p' is running)."
                        map))
              ;; Protect this-command when called from pre-command-hook 
(bug#45029)
              (this-command this-command)
-             (str (read-from-minibuffer
-                   prompt nil keymap nil
-                   (or y-or-n-p-history-variable t))))
+             (str (progn
+                    ;; If the minibuffer is already active, the
+                    ;; selected window might not change.  Disable
+                    ;; text conversion by hand.
+                    (when (fboundp 'set-text-conversion-style)
+                      (set-text-conversion-style text-conversion-style))
+                    (read-from-minibuffer
+                     prompt nil keymap nil
+                     (or y-or-n-p-history-variable t)))))
         (setq answer (if (member str '("y" "Y")) 'act 'skip)))))
     (let ((ret (eq answer 'act)))
       (unless noninteractive
@@ -4074,17 +4134,10 @@ buffer, use `without-restriction' with the same LABEL 
argument.
 \(fn START END [:label LABEL] BODY)"
   (declare (indent 2) (debug t))
   (if (eq (car rest) :label)
-      `(internal--with-restriction ,start ,end (lambda () ,@(cddr rest))
-                                 ,(cadr rest))
-    `(internal--with-restriction ,start ,end (lambda () ,@rest))))
-
-(defun internal--with-restriction (start end body &optional label)
-  "Helper function for `with-restriction', which see."
-  (save-restriction
-    (if label
-        (internal--labeled-narrow-to-region start end label)
-      (narrow-to-region start end))
-    (funcall body)))
+      `(save-restriction
+         (internal--labeled-narrow-to-region ,start ,end ,(cadr rest))
+         ,@(cddr rest))
+    `(save-restriction (narrow-to-region ,start ,end) ,@rest)))
 
 (defmacro without-restriction (&rest rest)
   "Execute BODY without restrictions.
@@ -4097,16 +4150,8 @@ by `with-restriction' with the same LABEL argument are 
lifted.
 \(fn [:label LABEL] BODY)"
   (declare (indent 0) (debug t))
   (if (eq (car rest) :label)
-      `(internal--without-restriction (lambda () ,@(cddr rest))
-                                    ,(cadr rest))
-    `(internal--without-restriction (lambda () ,@rest))))
-
-(defun internal--without-restriction (body &optional label)
-  "Helper function for `without-restriction', which see."
-  (save-restriction
-    (if label (internal--unlabel-restriction label))
-    (widen)
-    (funcall body)))
+      `(save-restriction (internal--labeled-widen ,(cadr rest)) ,@(cddr rest))
+    `(save-restriction (widen) ,@rest)))
 
 (defun find-tag-default-bounds ()
   "Determine the boundaries of the default tag, based on text at point.
@@ -5098,30 +5143,41 @@ the function `undo--wrap-and-run-primitive-undo'."
              (kill-local-variable 'before-change-functions))
            (if local-acf (setq after-change-functions acf)
              (kill-local-variable 'after-change-functions))))
-        (when (not (eq buffer-undo-list t))
-          (let ((ap-elt
-                (list 'apply
-                      (- end end-marker)
-                      beg
-                      (marker-position end-marker)
-                      #'undo--wrap-and-run-primitive-undo
-                      beg (marker-position end-marker)
-                      ;; We will truncate this list by side-effect below.
-                      buffer-undo-list))
-               (ptr buffer-undo-list))
-           (if (not (eq buffer-undo-list old-bul))
-               (progn
-                 (while (and (not (eq (cdr ptr) old-bul))
-                             ;; In case garbage collection has removed OLD-BUL.
-                             (or (cdr ptr)
-                                 (progn
-                                   (message "combine-change-calls: 
buffer-undo-list broken")
-                                   nil)))
-                   (setq ptr (cdr ptr)))
-                 ;; Truncate the list that's in the `apply' entry.
-                 (setcdr ptr nil)
-                 (push ap-elt buffer-undo-list)
-                 (setcdr buffer-undo-list old-bul)))))
+       ;; If buffer-undo-list is neither t (in which case undo
+       ;; information is not recorded) nor equal to buffer-undo-list
+       ;; before body was funcalled (in which case (funcall body) did
+       ;; not add items to buffer-undo-list) ...
+       (unless (or (eq buffer-undo-list t)
+                   (eq buffer-undo-list old-bul))
+         (let ((ptr buffer-undo-list) body-undo-list)
+           ;; ... then loop over buffer-undo-list, until the head of
+           ;; buffer-undo-list before body was funcalled is found, or
+           ;; ptr is nil (which may happen if garbage-collect has
+           ;; been called after (funcall body) and has removed
+           ;; entries of buffer-undo-list that were added by (funcall
+           ;; body)), and add these entries to body-undo-list.
+           (while (and ptr (not (eq ptr old-bul)))
+             (push (car ptr) body-undo-list)
+             (setq ptr (cdr ptr)))
+           (setq body-undo-list (nreverse body-undo-list))
+           ;; Warn if garbage-collect has truncated buffer-undo-list
+           ;; behind our back.
+           (when (and old-bul (not ptr))
+             (message
+               "combine-change-calls: buffer-undo-list has been truncated"))
+           ;; Add an (apply ...) entry to buffer-undo-list, using
+           ;; body-undo-list ...
+           (push (list 'apply
+                       (- end end-marker)
+                       beg
+                       (marker-position end-marker)
+                       #'undo--wrap-and-run-primitive-undo
+                       beg (marker-position end-marker)
+                       body-undo-list)
+                 buffer-undo-list)
+           ;; ... and set the cdr of buffer-undo-list to
+           ;; buffer-undo-list before body was funcalled.
+           (setcdr buffer-undo-list old-bul)))
        (if (not inhibit-modification-hooks)
            (run-hook-with-args 'after-change-functions
                                beg (marker-position end-marker)
diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el
index 14b340f6183..6b286598a14 100644
--- a/lisp/tab-bar.el
+++ b/lisp/tab-bar.el
@@ -36,6 +36,8 @@
   (require 'seq)
   (require 'icons))
 
+(autoload 'cl--set-substring "cl-lib")
+
 
 (defgroup tab-bar nil
   "Frame-local tabs."
@@ -162,7 +164,8 @@ For easier selection of tabs by their numbers, consider 
customizing
   (declare-function icons--register "icons")
   (unless (iconp 'tab-bar-new)
     (define-icon tab-bar-new nil
-      `((image "tabs/new.xpm"
+      `((image "symbols/plus_16.svg" "tabs/new.xpm"
+               :face shadow
                :margin ,tab-bar-button-margin
                :ascent center)
         ;; (emoji "➕")
@@ -175,7 +178,9 @@ For easier selection of tabs by their numbers, consider 
customizing
 
   (unless (iconp 'tab-bar-close)
     (define-icon tab-bar-close nil
-      `((image "tabs/close.xpm"
+      `((image "symbols/cross_16.svg" "tabs/close.xpm"
+               :face shadow
+               :height (1.0 . em)
                :margin ,tab-bar-button-margin
                :ascent center)
         ;; (emoji " ❌")
@@ -189,7 +194,10 @@ For easier selection of tabs by their numbers, consider 
customizing
 
   (unless (iconp 'tab-bar-menu-bar)
     (define-icon tab-bar-menu-bar nil
-      '(;; (emoji "🍔")
+      `((image "symbols/menu_16.svg"
+               :margin ,tab-bar-button-margin
+               :ascent center)
+        ;; (emoji "🍔")
         (symbol "☰")
         (text "Menu" :face tab-bar-tab-inactive))
       "Icon for the menu bar."
@@ -341,10 +349,12 @@ only when you click on its \"x\" close button."
     (unless (eq tab-number t)
       (tab-bar-close-tab tab-number))))
 
-(defun tab-bar-mouse-context-menu (event)
-  "Pop up the context menu for the tab on which you click."
+(defun tab-bar-mouse-context-menu (event &optional posn)
+  "Pop up the context menu for the tab on which you click.
+EVENT is a mouse or touch screen event.  POSN is nil or the
+position of EVENT."
   (interactive "e")
-  (let* ((item (tab-bar--event-to-item (event-start event)))
+  (let* ((item (tab-bar--event-to-item (or posn (event-start event))))
          (tab-number (tab-bar--key-to-number (nth 0 item)))
          (menu (make-sparse-keymap (propertize "Context Menu" 'hide t))))
 
@@ -397,6 +407,78 @@ at the mouse-down event to the position at mouse-up event."
       (tab-bar-move-tab-to
        (if (null to) (1+ (tab-bar--current-tab-index)) to) from))))
 
+
+
+;;; Tab bar touchscreen support.
+
+(declare-function touch-screen-track-tap "touch-screen.el")
+
+(defun tab-bar-handle-timeout ()
+  "Handle a touch-screen timeout on the tab bar.
+Beep, then throw to `context-menu' and return."
+  (beep)
+  (throw 'context-menu 'context-menu))
+
+(defun tab-bar-touchscreen-begin (event)
+  "Handle a touchscreen begin EVENT on the tab bar.
+
+Determine where the touch was made.  If it was made on a tab
+itself, start a timer set to go off after a certain amount of
+time, and wait for the touch point to be released, and either
+display a context menu or select a tab as appropriate.
+
+Otherwise, if it was made on a button, close or create a tab as
+appropriate."
+  (interactive "e")
+  (let* ((posn (cdadr event))
+         (item (tab-bar--event-to-item posn))
+         (number (tab-bar--key-to-number (car item)))
+         timer)
+    (when (eq (catch 'context-menu
+                (cond ((integerp number)
+                       ;; The touch began on a tab.  Start a context
+                       ;; menu timer and start tracking the tap.
+                       (unwind-protect
+                           (progn
+                             (setq timer (run-at-time touch-screen-delay nil
+                                                      
#'tab-bar-handle-timeout))
+                             ;; Now wait for the tap to complete.
+                             (when (touch-screen-track-tap event)
+                               ;; And select the tab, or close it,
+                               ;; depending on whether or not the
+                               ;; close button was pressed.
+                               (if (caddr item)
+                                   (tab-bar-close-tab number)
+                                 (tab-bar-select-tab number))))
+                         ;; Cancel the timer.
+                         (cancel-timer timer)))
+                      ((and (memq (car item) '(add-tab history-back
+                                                       history-forward))
+                            (functionp (cadr item)))
+                       ;; This is some kind of button.  Wait for the
+                       ;; tap to complete and press it.
+                       (when (touch-screen-track-tap event)
+                         (call-interactively (cadr item))))
+                      (t
+                       ;; The touch began on the tab bar itself.
+                       ;; Start a context menu timer and start
+                       ;; tracking the tap, but don't do anything
+                       ;; afterwards.
+                       (unwind-protect
+                           (progn
+                             (setq timer (run-at-time touch-screen-delay nil
+                                                      
#'tab-bar-handle-timeout))
+                             ;; Now wait for the tap to complete.
+                             (touch-screen-track-tap event))
+                         ;; Cancel the timer.
+                         (cancel-timer timer)))))
+              'context-menu)
+      ;; Display the context menu in response to a time out waiting
+      ;; for the tap to complete.
+      (tab-bar-mouse-context-menu event posn))))
+
+
+
 (defvar-keymap tab-bar-map
   :doc "Keymap for the commands used on the tab bar."
   "<down-mouse-1>"  #'tab-bar-mouse-down-1
@@ -418,7 +500,8 @@ at the mouse-down event to the position at mouse-up event."
   "S-<wheel-up>"    #'tab-bar-move-tab-backward
   "S-<wheel-down>"  #'tab-bar-move-tab
   "S-<wheel-left>"  #'tab-bar-move-tab-backward
-  "S-<wheel-right>" #'tab-bar-move-tab)
+  "S-<wheel-right>" #'tab-bar-move-tab
+  "<touchscreen-begin>" #'tab-bar-touchscreen-begin)
 
 (global-set-key [tab-bar]
                 `(menu-item ,(purecopy "tab bar") ,(make-sparse-keymap)
@@ -699,6 +782,31 @@ Return its existing value or a new value."
   (set-frame-parameter frame 'tabs tabs))
 
 
+(defun tab-bar-tab-name-format-truncated (name _tab _i)
+  "Truncate the tab name.
+The maximal length is specified by `tab-bar-tab-name-truncated-max'.
+Append ellipsis `tab-bar-tab-name-ellipsis' at the end."
+  (if (< (length name) tab-bar-tab-name-truncated-max)
+      name
+    (truncate-string-to-width
+     name tab-bar-tab-name-truncated-max nil nil
+     tab-bar-tab-name-ellipsis)))
+
+(defun tab-bar-tab-name-format-hints (name _tab i)
+  "Show absolute numbers on tabs in the tab bar before the tab name.
+It has effect when `tab-bar-tab-hints' is non-nil."
+  (if tab-bar-tab-hints (concat (format "%d " i) name) name))
+
+(defun tab-bar-tab-name-format-close-button (name tab _i)
+  "Show the tab close button.
+The variable `tab-bar-close-button-show' defines when to show it."
+  (if (and tab-bar-close-button-show
+           (not (eq tab-bar-close-button-show
+                    (if (eq (car tab) 'current-tab) 'non-selected 'selected)))
+           tab-bar-close-button)
+      (concat name tab-bar-close-button)
+    name))
+
 (defcustom tab-bar-tab-face-function #'tab-bar-tab-face-default
   "Function to define a tab face.
 Function gets one argument: a tab."
@@ -709,6 +817,38 @@ Function gets one argument: a tab."
 (defun tab-bar-tab-face-default (tab)
   (if (eq (car tab) 'current-tab) 'tab-bar-tab 'tab-bar-tab-inactive))
 
+(defun tab-bar-tab-name-format-face (name tab _i)
+  "Apply the face to the tab name.
+It uses the function `tab-bar-tab-face-function'."
+  (add-face-text-property
+   0 (length name) (funcall tab-bar-tab-face-function tab) t name)
+  name)
+
+(defcustom tab-bar-tab-name-format-functions
+  '(tab-bar-tab-name-format-hints
+    tab-bar-tab-name-format-close-button
+    tab-bar-tab-name-format-face)
+  "Functions called to modify the tab name.
+Each function is called with three arguments: the name returned
+by the previously called modifier, the tab and its number.
+It should return the formatted tab name to display in the tab bar."
+  :type '(repeat
+          (choice (function-item tab-bar-tab-name-format-truncated)
+                  (function-item tab-bar-tab-name-format-hints)
+                  (function-item tab-bar-tab-name-format-close-button)
+                  (function-item tab-bar-tab-name-format-face)
+                  (function :tag "Custom function")))
+  :group 'tab-bar
+  :version "30.1")
+
+(defun tab-bar-tab-name-format-default (tab i)
+  (let ((name (copy-sequence (alist-get 'name tab))))
+    (run-hook-wrapped 'tab-bar-tab-name-format-functions
+                      (lambda (fun)
+                        (setq name (funcall fun name tab i))
+                        nil))
+    name))
+
 (defcustom tab-bar-tab-name-format-function #'tab-bar-tab-name-format-default
   "Function to format a tab name.
 Function gets two arguments, the tab and its number, and should return
@@ -721,18 +861,6 @@ the formatted tab name to display in the tab bar."
   :group 'tab-bar
   :version "28.1")
 
-(defun tab-bar-tab-name-format-default (tab i)
-  (let ((current-p (eq (car tab) 'current-tab)))
-    (propertize
-     (concat (if tab-bar-tab-hints (format "%d " i) "")
-             (alist-get 'name tab)
-             (or (and tab-bar-close-button-show
-                      (not (eq tab-bar-close-button-show
-                               (if current-p 'non-selected 'selected)))
-                      tab-bar-close-button)
-                 ""))
-     'face (funcall tab-bar-tab-face-function tab))))
-
 (defcustom tab-bar-format '(tab-bar-format-history
                             tab-bar-format-tabs
                             tab-bar-separator
@@ -790,7 +918,7 @@ Used by `tab-bar-format-menu-bar'."
 (defun tab-bar-format-menu-bar ()
   "Produce the Menu button for the tab bar that shows the menu bar."
   `((menu-bar menu-item ,tab-bar-menu-bar-button
-     tab-bar-menu-bar :help "Menu Bar")))
+     tab-bar-menu-bar :help "Menu bar")))
 
 (defun tab-bar-format-history ()
   "Produce back and forward buttons for the tab bar.
@@ -817,13 +945,13 @@ You can hide these buttons by customizing 
`tab-bar-format' and removing
         menu-item
         ,(funcall tab-bar-tab-name-format-function tab i)
         ignore
-        :help "Current tab")))
+        :help ,(alist-get 'name tab))))
     (t
      `((,(intern (format "tab-%i" i))
         menu-item
         ,(funcall tab-bar-tab-name-format-function tab i)
         ,(alist-get 'binding tab)
-        :help "Click to visit tab"))))
+        :help ,(alist-get 'name tab)))))
    (when (alist-get 'close-binding tab)
      `((,(if (eq (car tab) 'current-tab) 'C-current-tab
            (intern (format "C-tab-%i" i)))
@@ -2156,7 +2284,7 @@ and can restore them."
 
         (unless (iconp 'tab-bar-back)
           (define-icon tab-bar-back nil
-            `((image "tabs/left-arrow.xpm"
+            `((image "symbols/chevron_left_16.svg" "tabs/left-arrow.xpm"
                      :margin ,tab-bar-button-margin
                      :ascent center)
               (text " < "))
@@ -2166,7 +2294,7 @@ and can restore them."
 
         (unless (iconp 'tab-bar-forward)
           (define-icon tab-bar-forward nil
-            `((image "tabs/right-arrow.xpm"
+            `((image "symbols/chevron_right_16.svg" "tabs/right-arrow.xpm"
                      :margin ,tab-bar-button-margin
                      :ascent center)
               (text " > "))
diff --git a/lisp/tab-line.el b/lisp/tab-line.el
index 1958f12975f..e277d1fb9ed 100644
--- a/lisp/tab-line.el
+++ b/lisp/tab-line.el
@@ -29,6 +29,7 @@
 
 (require 'cl-lib)
 (require 'seq) ; tab-line.el is not pre-loaded so it's safe to use it here
+(require 'icons)
 
 
 (defgroup tab-line nil
@@ -137,33 +138,38 @@ function `tab-line-tab-face-group'."
 
 (defvar-keymap tab-line-tab-map
   :doc "Local keymap for `tab-line-mode' window tabs."
-  "<tab-line> <down-mouse-1>" #'tab-line-select-tab
-  "<tab-line> <mouse-2>"      #'tab-line-close-tab
-  "<tab-line> <down-mouse-3>" #'tab-line-tab-context-menu
+  "<tab-line> <down-mouse-1>"      #'tab-line-select-tab
+  "<tab-line> <mouse-2>"           #'tab-line-close-tab
+  "<tab-line> <down-mouse-3>"      #'tab-line-tab-context-menu
+  "<tab-line> <touchscreen-begin>" #'tab-line-select-tab
   "RET" #'tab-line-select-tab)
 
 (defvar-keymap tab-line-add-map
   :doc "Local keymap to add `tab-line-mode' window tabs."
-  "<tab-line> <down-mouse-1>" #'tab-line-new-tab
-  "<tab-line> <down-mouse-2>" #'tab-line-new-tab
+  "<tab-line> <down-mouse-1>"      #'tab-line-new-tab
+  "<tab-line> <down-mouse-2>"      #'tab-line-new-tab
+  "<tab-line> <touchscreen-begin>" #'tab-line-new-tab
   "RET" #'tab-line-new-tab)
 
 (defvar-keymap tab-line-tab-close-map
   :doc "Local keymap to close `tab-line-mode' window tabs."
-  "<tab-line> <mouse-1>" #'tab-line-close-tab
-  "<tab-line> <mouse-2>" #'tab-line-close-tab)
+  "<tab-line> <mouse-1>"           #'tab-line-close-tab
+  "<tab-line> <mouse-2>"           #'tab-line-close-tab
+  "<tab-line> <touchscreen-begin>" #'tab-line-close-tab)
 
 (defvar-keymap tab-line-left-map
   :doc "Local keymap to scroll `tab-line-mode' window tabs to the left."
-  "<tab-line> <down-mouse-1>" #'tab-line-hscroll-left
-  "<tab-line> <down-mouse-2>" #'tab-line-hscroll-left
-  "RET" #'tab-line-new-tab)
+  "<tab-line> <down-mouse-1>"      #'tab-line-hscroll-left
+  "<tab-line> <down-mouse-2>"      #'tab-line-hscroll-left
+  "<tab-line> <touchscreen-begin>" #'tab-line-hscroll-left
+  "RET"                            #'tab-line-new-tab)
 
 (defvar-keymap tab-line-right-map
   :doc "Local keymap to scroll `tab-line-mode' window tabs to the right."
-  "<tab-line> <down-mouse-1>" #'tab-line-hscroll-right
-  "<tab-line> <down-mouse-2>" #'tab-line-hscroll-right
-  "RET" #'tab-line-new-tab)
+  "<tab-line> <down-mouse-1>"      #'tab-line-hscroll-right
+  "<tab-line> <down-mouse-2>"      #'tab-line-hscroll-right
+  "<tab-line> <touchscreen-begin>" #'tab-line-hscroll-right
+  "RET"                            #'tab-line-new-tab)
 
 
 (defcustom tab-line-new-tab-choice t
@@ -185,12 +191,19 @@ If the value is a function, call it with no arguments."
   :group 'tab-line
   :version "27.1")
 
+(define-icon tab-line-new nil
+  `((image "symbols/plus_16.svg" "tabs/new.xpm"
+           :face shadow
+           :margin (2 . 0)
+           :ascent center)
+    (text " + "))
+  "Icon for creating a new tab."
+  :version "30.1"
+  :help-echo "New tab")
+
 (defvar tab-line-new-button
-  (propertize " + "
-              'display '(image :type xpm
-                               :file "tabs/new.xpm"
-                               :margin (2 . 0)
-                               :ascent center)
+  (propertize (icon-string 'tab-line-new)
+              'rear-nonsticky nil
               'keymap tab-line-add-map
               'mouse-face 'tab-line-highlight
               'help-echo "Click to add tab")
@@ -213,34 +226,54 @@ If nil, don't show it at all."
   :group 'tab-line
   :version "27.1")
 
+(define-icon tab-line-close nil
+  `((image "symbols/cross_16.svg" "tabs/close.xpm"
+           :face shadow
+           :height (1.0 . em)
+           :margin (2 . 0)
+           :ascent center)
+    (text " x"))
+  "Icon for closing the clicked tab."
+  :version "30.1"
+  :help-echo "Click to close tab")
+
 (defvar tab-line-close-button
-  (propertize " x"
-              'display '(image :type xpm
-                               :file "tabs/close.xpm"
-                               :margin (2 . 0)
-                               :ascent center)
+  (propertize (icon-string 'tab-line-close)
+              'rear-nonsticky nil ;; important to not break auto-scroll
               'keymap tab-line-tab-close-map
               'mouse-face 'tab-line-close-highlight
               'help-echo "Click to close tab")
   "Button for closing the clicked tab.")
 
+(define-icon tab-line-left nil
+  `((image "symbols/chevron_left_16.svg" "tabs/left-arrow.xpm"
+           :face shadow
+           :margin (2 . 0)
+           :ascent center)
+    (text " <"))
+  "Icon for scrolling horizontally to the left."
+  :version "30.1")
+
 (defvar tab-line-left-button
-  (propertize " <"
-              'display '(image :type xpm
-                               :file "tabs/left-arrow.xpm"
-                               :margin (2 . 0)
-                               :ascent center)
+  (propertize (icon-string 'tab-line-left)
+              'rear-nonsticky nil
               'keymap tab-line-left-map
               'mouse-face 'tab-line-highlight
               'help-echo "Click to scroll left")
   "Button for scrolling horizontally to the left.")
 
+(define-icon tab-line-right nil
+  `((image "symbols/chevron_right_16.svg" "tabs/right-arrow.xpm"
+           :face shadow
+           :margin (2 . 0)
+           :ascent center)
+    (text "> "))
+  "Icon for scrolling horizontally to the right."
+  :version "30.1")
+
 (defvar tab-line-right-button
-  (propertize "> "
-              'display '(image :type xpm
-                               :file "tabs/right-arrow.xpm"
-                               :margin (2 . 0)
-                               :ascent center)
+  (propertize (icon-string 'tab-line-right)
+              'rear-nonsticky nil
               'keymap tab-line-right-map
               'mouse-face 'tab-line-highlight
               'help-echo "Click to scroll right")
@@ -326,6 +359,48 @@ or by `tab-line-tabs-buffer-groups'."
   "Function to return a global list of buffers.
 Used only for `tab-line-tabs-mode-buffers' and `tab-line-tabs-buffer-groups'.")
 
+
+
+;;; Touch screen support.
+
+(defun tab-line-track-tap (event &optional function)
+  "Track a tap starting from EVENT.
+If EVENT is not a `touchscreen-begin' event, return t.
+Otherwise, return t if the tap completes successfully, and nil if
+the tap should be ignored.
+
+If FUNCTION is specified and the tap does not complete within
+`touch-screen-delay' seconds, display the appropriate context
+menu by calling FUNCTION with EVENT, and return nil."
+  (if (not (eq (car-safe event) 'touchscreen-begin))
+      t
+    (let ((result (catch 'context-menu
+                    (let (timer)
+                      (unwind-protect
+                          (progn
+                            (when function
+                              (setq timer
+                                    (run-at-time touch-screen-delay t
+                                                 #'throw 'context-menu
+                                                 'context-menu)))
+                            (touch-screen-track-tap event))
+                        (when timer
+                          (cancel-timer timer)))))))
+      (cond ((eq result 'context-menu)
+             (prog1 nil
+               (funcall function event)))
+            (result t)))))
+
+(defun tab-line-event-start (event)
+  "Like `event-start'.
+However, return the correct mouse position list if EVENT is a
+`touchscreen-begin' event."
+  (or (and (eq (car-safe event) 'touchscreen-begin)
+           (cdadr event))
+      (event-start event)))
+
+
+
 (defun tab-line-tabs-buffer-list ()
   (seq-filter (lambda (b) (and (buffer-live-p b)
                                (/= (aref (buffer-name b) 0) ?\s)))
@@ -484,21 +559,27 @@ which the tab will represent."
       (setf face (funcall fn tab tabs face buffer-p selected-p)))
     (apply 'propertize
            (concat (propertize (string-replace "%" "%%" name) ;; (bug#57848)
+                               'face face
                                'keymap tab-line-tab-map
                                'help-echo (if selected-p "Current tab"
                                             "Click to select tab")
                                ;; Don't turn mouse-1 into mouse-2 (bug#49247)
                                'follow-link 'ignore)
-                   (or (and (or buffer-p (assq 'buffer tab) (assq 'close tab))
-                            tab-line-close-button-show
-                            (not (eq tab-line-close-button-show
-                                     (if selected-p 'non-selected 'selected)))
-                            tab-line-close-button)
-                       ""))
+                   (let ((close (or (and (or buffer-p (assq 'buffer tab)
+                                             (assq 'close tab))
+                                         tab-line-close-button-show
+                                         (not (eq tab-line-close-button-show
+                                                  (if selected-p 'non-selected
+                                                    'selected)))
+                                         tab-line-close-button)
+                                    "")))
+                     (setq close (copy-sequence close))
+                     ;; Don't overwrite the icon face
+                     (add-face-text-property 0 (length close) face t close)
+                     close))
            `(
              tab ,tab
              ,@(if selected-p '(selected t))
-             face ,face
              mouse-face tab-line-highlight))))
 
 (defun tab-line-format-template (tabs)
@@ -637,7 +718,7 @@ the selected tab visible."
       (erase-buffer)
       (apply 'insert strings)
       (goto-char (point-min))
-      (add-face-text-property (point-min) (point-max) 'tab-line)
+      (add-face-text-property (point-min) (point-max) 'tab-line t)
       ;; Continuation means tab-line doesn't fit completely,
       ;; thus scroll arrows are needed for scrolling.
       (setq show-arrows (> (vertical-motion 1) 0))
@@ -719,17 +800,21 @@ the selected tab visible."
   "Scroll the tab line ARG positions to the right.
 Interactively, ARG is the prefix numeric argument and defaults to 1."
   (interactive (list current-prefix-arg last-nonmenu-event))
-  (let ((window (and (listp event) (posn-window (event-start event)))))
-    (tab-line-hscroll arg window)
-    (force-mode-line-update window)))
+  (when (tab-line-track-tap event)
+    (let ((window (and (listp event)
+                       (posn-window (tab-line-event-start event)))))
+      (tab-line-hscroll arg window)
+      (force-mode-line-update window))))
 
 (defun tab-line-hscroll-left (&optional arg event)
   "Scroll the tab line ARG positions to the left.
 Interactively, ARG is the prefix numeric argument and defaults to 1."
   (interactive (list current-prefix-arg last-nonmenu-event))
-  (let ((window (and (listp event) (posn-window (event-start event)))))
-    (tab-line-hscroll (- (or arg 1)) window)
-    (force-mode-line-update window)))
+  (when (tab-line-track-tap event)
+    (let ((window (and (listp event)
+                       (posn-window (tab-line-event-start event)))))
+      (tab-line-hscroll (- (or arg 1)) window)
+      (force-mode-line-update window))))
 
 
 (defun tab-line-new-tab (&optional event)
@@ -738,15 +823,16 @@ This command is usually invoked by clicking on the 
plus-shaped button
 on the tab line.  Switching to another buffer also adds a new tab
 corresponding to the new buffer shown in the window."
   (interactive (list last-nonmenu-event))
-  (if (functionp tab-line-new-tab-choice)
-      (funcall tab-line-new-tab-choice)
-    (let ((tab-line-tabs-buffer-groups mouse-buffer-menu-mode-groups))
-      (if (and (listp event)
-               (display-popup-menus-p)
-               (not tty-menu-open-use-tmm))
-          (mouse-buffer-menu event) ; like (buffer-menu-open)
-        ;; tty menu doesn't support mouse clicks, so use tmm
-        (tmm-prompt (mouse-buffer-menu-keymap))))))
+  (when (tab-line-track-tap event)
+    (if (functionp tab-line-new-tab-choice)
+        (funcall tab-line-new-tab-choice)
+      (let ((tab-line-tabs-buffer-groups mouse-buffer-menu-mode-groups))
+        (if (and (listp event)
+                 (display-popup-menus-p)
+                 (not tty-menu-open-use-tmm))
+            (mouse-buffer-menu event) ; like (buffer-menu-open)
+          ;; tty menu doesn't support mouse clicks, so use tmm
+          (tmm-prompt (mouse-buffer-menu-keymap)))))))
 
 (defun tab-line-select-tab (&optional event)
   "Switch to the buffer specified by the tab on which you click.
@@ -754,16 +840,17 @@ This command maintains the original order of prev/next 
buffers.
 So, for example, switching to a previous tab is equivalent to
 using the `previous-buffer' command."
   (interactive "e")
-  (let* ((posnp (event-start event))
-         (tab (tab-line--get-tab-property 'tab (car (posn-string posnp))))
-         (buffer (if (bufferp tab) tab (cdr (assq 'buffer tab)))))
-    (if buffer
-        (tab-line-select-tab-buffer buffer (posn-window posnp))
-      (let ((select (cdr (assq 'select tab))))
-        (when (functionp select)
-          (with-selected-window (posn-window posnp)
-            (funcall select)
-            (force-mode-line-update)))))))
+  (when (tab-line-track-tap event #'tab-line-tab-context-menu)
+    (let* ((posnp (tab-line-event-start event))
+           (tab (tab-line--get-tab-property 'tab (car (posn-string posnp))))
+           (buffer (if (bufferp tab) tab (cdr (assq 'buffer tab)))))
+      (if buffer
+          (tab-line-select-tab-buffer buffer (posn-window posnp))
+        (let ((select (cdr (assq 'select tab))))
+          (when (functionp select)
+            (with-selected-window (posn-window posnp)
+              (funcall select)
+              (force-mode-line-update))))))))
 
 (defun tab-line-select-tab-buffer (buffer &optional window)
   (let* ((window-buffer (window-buffer window))
@@ -869,25 +956,27 @@ This command is usually invoked by clicking on the close 
button on the
 right side of the tab.  This command buries the buffer, so it goes out of
 sight of the tab line."
   (interactive (list last-nonmenu-event))
-  (let* ((posnp (and (listp event) (event-start event)))
-         (window (and posnp (posn-window posnp)))
-         (tab (tab-line--get-tab-property 'tab (car (posn-string posnp))))
-         (buffer (if (bufferp tab) tab (cdr (assq 'buffer tab))))
-         (close-function (unless (bufferp tab) (cdr (assq 'close tab)))))
-    (with-selected-window (or window (selected-window))
-      (cond
-       ((functionp close-function)
-        (funcall close-function))
-       ((eq tab-line-close-tab-function 'kill-buffer)
-        (kill-buffer buffer))
-       ((eq tab-line-close-tab-function 'bury-buffer)
-        (if (eq buffer (current-buffer))
-            (bury-buffer)
-          (set-window-prev-buffers nil (assq-delete-all buffer 
(window-prev-buffers)))
-          (set-window-next-buffers nil (delq buffer (window-next-buffers)))))
-       ((functionp tab-line-close-tab-function)
-        (funcall tab-line-close-tab-function tab)))
-      (force-mode-line-update))))
+  (when (tab-line-track-tap event)
+    (let* ((posnp (and (listp event)
+                       (tab-line-event-start event)))
+           (window (and posnp (posn-window posnp)))
+           (tab (tab-line--get-tab-property 'tab (car (posn-string posnp))))
+           (buffer (if (bufferp tab) tab (cdr (assq 'buffer tab))))
+           (close-function (unless (bufferp tab) (cdr (assq 'close tab)))))
+      (with-selected-window (or window (selected-window))
+        (cond
+         ((functionp close-function)
+          (funcall close-function))
+         ((eq tab-line-close-tab-function 'kill-buffer)
+          (kill-buffer buffer))
+         ((eq tab-line-close-tab-function 'bury-buffer)
+          (if (eq buffer (current-buffer))
+              (bury-buffer)
+            (set-window-prev-buffers nil (assq-delete-all buffer 
(window-prev-buffers)))
+            (set-window-next-buffers nil (delq buffer (window-next-buffers)))))
+         ((functionp tab-line-close-tab-function)
+          (funcall tab-line-close-tab-function tab)))
+        (force-mode-line-update)))))
 
 (defun tab-line-tab-context-menu (&optional event)
   "Pop up the context menu for a tab-line tab."
diff --git a/lisp/tar-mode.el b/lisp/tar-mode.el
index c9206028e94..2b6329fecc3 100644
--- a/lisp/tar-mode.el
+++ b/lisp/tar-mode.el
@@ -215,6 +215,98 @@ Preserve the modified states of the buffers and set 
`tar-data-swapped'."
   "Round S up to the next multiple of 512."
   (ash (ash (+ s 511) -9) 9))
 
+;; Reference:
+;; 
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_02
+(defconst pax-extended-attribute-record-regexp
+  ;; We omit attributes that are "reserved" by Posix, since no
+  ;; processing has been defined for them.
+  "\\([0-9]+\\) 
\\(gid\\|gname\\|hdrcharset\\|linkpath\\|mtime\\|path\\|size\\|uid\\|uname\\)="
+  "Regular expression for looking up extended attributes in a
+Posix-standard pax extended header of a tar file.
+Only attributes that `tar-mode' can grok are mentioned.")
+
+(defconst pax-gid-index 0)
+(defconst pax-gname-index 1)
+(defconst pax-linkpath-index 2)
+(defconst pax-mtime-index 3)
+(defconst pax-path-index 4)
+(defconst pax-size-index 5)
+(defconst pax-uid-index 6)
+(defconst pax-uname-index 7)
+(defsubst pax-header-gid (attr-vec)
+  (aref attr-vec pax-gid-index))
+(defsubst pax-header-gname (attr-vec)
+  (aref attr-vec pax-gname-index))
+(defsubst pax-header-linkpath (attr-vec)
+  (aref attr-vec pax-linkpath-index))
+(defsubst pax-header-mtime (attr-vec)
+  (aref attr-vec pax-mtime-index))
+(defsubst pax-header-path (attr-vec)
+  (aref attr-vec pax-path-index))
+(defsubst pax-header-size (attr-vec)
+  (aref attr-vec pax-size-index))
+(defsubst pax-header-uid (attr-vec)
+  (aref attr-vec pax-uid-index))
+(defsubst pax-header-uname (attr-vec)
+  (aref attr-vec pax-uid-index))
+
+(defsubst pax-decode-string (str coding)
+  (if str
+      (decode-coding-string str coding)
+    str))
+
+(defvar tar-attr-vector (make-vector 8 nil))
+(defun tar-parse-pax-extended-header (pos)
+  "Parse a pax external header of a Posix-format tar file."
+  (let ((end (+ pos 512))
+        (result tar-attr-vector)
+        (coding 'utf-8-unix)
+        attr value record-len value-len)
+    (fillarray result nil)
+    (goto-char pos)
+    (while (and (< pos end)
+                (re-search-forward pax-extended-attribute-record-regexp
+                                   end 'move))
+      (setq record-len (string-to-number (match-string 1))
+            attr (match-string 2)
+            value-len (- record-len
+                         (length (match-string 1))
+                         1
+                         (length (match-string 2))
+                         2)
+            value (buffer-substring (point) (+ (point) value-len)))
+      (setq pos (goto-char (+ (point) value-len 1)))
+      (cond
+       ((equal attr "gid")
+        (aset result pax-gid-index value))
+       ((equal attr "gname")
+        (aset result pax-gname-index value))
+       ((equal attr "linkpath")
+        (aset result pax-linkpath-index value))
+       ((equal attr "mtime")
+        (aset result pax-mtime-index (string-to-number value)))
+       ((equal attr "path")
+        (aset result pax-path-index value))
+       ((equal attr "size")
+        (aset result pax-size-index value))
+       ((equal attr "uid")
+        (aset result pax-uid-index value))
+       ((equal attr "uname")
+        (aset result pax-uname-index value))
+       ((equal attr "hdrcharset")
+        (setq coding (if (equal value "BINARY") 'no-conversion 'utf-8-unix))))
+      (setq pos (+ pos (skip-chars-forward "\000"))))
+    ;; Decode string-valued attributes.
+    (aset result pax-gname-index
+          (pax-decode-string (aref result pax-gname-index) coding))
+    (aset result pax-linkpath-index
+          (pax-decode-string (aref result pax-linkpath-index) coding))
+    (aset result pax-path-index
+          (pax-decode-string (aref result pax-path-index) coding))
+    (aset result pax-uname-index
+          (pax-decode-string (aref result pax-uname-index) coding))
+    result))
+
 (defun tar-header-block-tokenize (pos coding &optional disable-slash)
   "Return a `tar-header' structure.
 This is a list of name, mode, uid, gid, size,
@@ -276,12 +368,11 @@ of the file header.  This is used for \"old GNU\" Tar 
format."
                  ;; format (i.e. "ustar ") but some POSIX Tar files
                  ;; (with "ustar\0") have been seen using it as well.
                  (member magic-str '("ustar " "ustar\0")))
-            ;; This is a GNU Tar long-file-name header.
             (let* ((size (tar-parse-octal-integer
                           string tar-size-offset tar-time-offset))
                    ;; The long name is in the next 512-byte block.
-                   ;; We've already moved POS there, when we computed
-                   ;; STRING above.
+                   ;; We've already moved POS there, when we
+                   ;; computed STRING above.
                   (name (decode-coding-string
                           ;; -1 so as to strip the terminating 0 byte.
                          (buffer-substring pos (+ pos size -1)) coding))
@@ -310,28 +401,74 @@ of the file header.  This is used for \"old GNU\" Tar 
format."
               (setf (tar-header-header-start descriptor)
                     (copy-marker (- pos 512) t))
               descriptor)
-
-          (make-tar-header
-           (copy-marker pos nil)
-           name
-           (tar-parse-octal-integer string tar-mode-offset tar-uid-offset)
-           (tar-parse-octal-integer string tar-uid-offset tar-gid-offset)
-           (tar-parse-octal-integer string tar-gid-offset tar-size-offset)
-           (tar-parse-octal-integer string tar-size-offset tar-time-offset)
-           (tar-parse-octal-integer string tar-time-offset tar-chk-offset)
-           (tar-parse-octal-integer string tar-chk-offset tar-linkp-offset)
-           link-p
-           linkname
-           uname-valid-p
-           (when uname-valid-p
-             (decode-coding-string
-              (substring string tar-uname-offset uname-end) coding))
-           (when uname-valid-p
-             (decode-coding-string
-              (substring string tar-gname-offset gname-end) coding))
-           (tar-parse-octal-integer string tar-dmaj-offset tar-dmin-offset)
-           (tar-parse-octal-integer string tar-dmin-offset tar-prefix-offset)
-           ))))))
+          ;; Posix pax extended header.  FIXME: support ?g as well.
+          (if (and (eq link-p (- ?x ?0))
+                   (member magic-str '("ustar " "ustar\0")))
+              ;;      Get whatever attributes are in the extended header,
+              (let* ((pax-attrs (tar-parse-pax-extended-header pos))
+                     (gid (pax-header-gid pax-attrs))
+                     (gname (pax-header-gname pax-attrs))
+                     (linkpath (pax-header-linkpath pax-attrs))
+                     (mtime (pax-header-mtime pax-attrs))
+                     (path (pax-header-path pax-attrs))
+                     (size (pax-header-size pax-attrs))
+                     (uid (pax-header-uid pax-attrs))
+                     (uname (pax-header-uname pax-attrs))
+                     ;; Tokenize the header of the _real_ file entry,
+                     ;; which is further 512 bytes into the archive.
+                     (descriptor
+                      (tar-header-block-tokenize (+ pos 512) coding
+                                                 'ignore-trailing-slash)))
+                ;; Fix the descriptor of the real file entry by
+                ;; overriding some of the fields with the information
+                ;; from the extended header.
+                (if gid
+                    (setf (tar-header-gid descriptor) gid))
+                (if gname
+                    (setf (tar-header-gname descriptor) gname))
+                (if linkpath
+                    (setf (tar-header-link-name descriptor) linkpath))
+                (if mtime
+                    (setf (tar-header-date descriptor) mtime))
+                (if path
+                    (setf (tar-header-name descriptor) path))
+                (if size
+                    (setf (tar-header-size descriptor) size))
+                (if uid
+                    (setf (tar-header-uid descriptor) uid))
+                (if uname
+                    (setf (tar-header-uname descriptor) uname))
+                descriptor)
+
+            (make-tar-header
+             (copy-marker pos nil)
+             name
+             (tar-parse-octal-integer string tar-mode-offset
+                                      tar-uid-offset)
+             (tar-parse-octal-integer string tar-uid-offset
+                                      tar-gid-offset)
+             (tar-parse-octal-integer string tar-gid-offset
+                                      tar-size-offset)
+             (tar-parse-octal-integer string tar-size-offset
+                                      tar-time-offset)
+             (tar-parse-octal-integer string tar-time-offset
+                                      tar-chk-offset)
+             (tar-parse-octal-integer string tar-chk-offset
+                                      tar-linkp-offset)
+             link-p
+             linkname
+             uname-valid-p
+             (when uname-valid-p
+               (decode-coding-string
+                (substring string tar-uname-offset uname-end) coding))
+             (when uname-valid-p
+               (decode-coding-string
+                (substring string tar-gname-offset gname-end) coding))
+             (tar-parse-octal-integer string tar-dmaj-offset
+                                      tar-dmin-offset)
+             (tar-parse-octal-integer string tar-dmin-offset
+                                      tar-prefix-offset)
+             )))))))
 
 ;; Pseudo-field.
 (defun tar-header-data-end (descriptor)
diff --git a/lisp/term.el b/lisp/term.el
index 73f583ff718..b8466b21332 100644
--- a/lisp/term.el
+++ b/lisp/term.el
@@ -972,12 +972,7 @@ underlying shell."
 (defun term--update-term-menu (&optional force)
   (when (and (lookup-key term-mode-map [menu-bar terminal])
              (or force (frame-or-buffer-changed-p)))
-    (let ((buffer-list
-           (seq-filter
-            (lambda (buffer)
-              (provided-mode-derived-p (buffer-local-value 'major-mode buffer)
-                                       'term-mode))
-            (buffer-list))))
+    (let ((buffer-list (match-buffers '(derived-mode . term-mode))))
       (easy-menu-change
        nil
        "Terminal Buffers"
@@ -1129,6 +1124,7 @@ Commands in line mode:
 \\{term-mode-map}
 
 Entry to this mode runs the hooks on `term-mode-hook'."
+  :interactive nil
   ;; we do not want indent to sneak in any tabs
   (setq indent-tabs-mode nil)
   (setq buffer-display-table term-display-table)
@@ -1139,6 +1135,9 @@ Entry to this mode runs the hooks on `term-mode-hook'."
   (setq-local term-last-input-end (make-marker))
   (setq-local term-last-input-match "")
 
+  ;; Always display the onscreen keyboard.
+  (setq-local touch-screen-display-keyboard t)
+
   ;; These local variables are set to their local values:
   (make-local-variable 'term-saved-home-marker)
   (make-local-variable 'term-saved-cursor)
@@ -1729,7 +1728,12 @@ Nil if unknown.")
       (push (format "EMACS=%s (term:%s)" emacs-version term-protocol-version)
             process-environment))
     (apply #'start-process name buffer
-          "/bin/sh" "-c"
+           ;; On Android, /bin doesn't exist, and the default shell is
+           ;; found as /system/bin/sh.
+          (if (eq system-type 'android)
+               "/system/bin/sh"
+             "/bin/sh")
+           "-c"
           (format "stty -nl echo rows %d columns %d sane 2>%s;\
 if [ $1 = .. ]; then shift; fi; exec \"$@\""
                   term-height term-width null-device)
@@ -2059,7 +2063,7 @@ See `term-replace-by-expanded-history'.  Returns t if 
successful."
               ;; We cannot know the interpreter's idea of input line numbers.
               (goto-char (match-end 0))
               (message "Absolute reference cannot be expanded"))
-             ((looking-at "!-\\([0-9]+\\)\\(:?[0-9^$*-]+\\)?")
+             ((looking-at "!-\\([0-9]+\\):?\\([0-9^$*-]+\\)?")
               ;; Just a number of args from `number' lines backward.
               (let ((number (1- (string-to-number
                                  (buffer-substring (match-beginning 1)
@@ -2082,7 +2086,7 @@ See `term-replace-by-expanded-history'.  Returns t if 
successful."
                t t)
               (message "History item: previous"))
              ((looking-at
-               "!\\??\\({\\(.+\\)}\\|\\(\\sw+\\)\\)\\(:?[0-9^$*-]+\\)?")
+               "!\\??\\({\\(.+\\)}\\|\\(\\sw+\\)\\):?\\([0-9^$*-]+\\)?")
               ;; Most recent input starting with or containing (possibly
               ;; protected) string, maybe just a number of args.  Phew.
               (let* ((mb1 (match-beginning 1)) (me1 (match-end 1))
@@ -2961,7 +2965,7 @@ See `term-prompt-regexp'."
 ;; It emulates (most of the features of) a VT100/ANSI-style terminal.
 
 ;; References:
-;; [ctlseqs]: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+;; [ctlseqs]: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 ;; [ECMA-48]: 
https://www.ecma-international.org/publications/standards/Ecma-048.htm
 ;; [vt100]: https://vt100.net/docs/vt100-ug/chapter3.html
 
diff --git a/lisp/term/AT386.el b/lisp/term/AT386.el
index 27065b73591..541b7c5aad0 100644
--- a/lisp/term/AT386.el
+++ b/lisp/term/AT386.el
@@ -2,7 +2,7 @@
 
 ;; Copyright (C) 1992, 2001-2023 Free Software Foundation, Inc.
 
-;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
+;; Author: Eric S. Raymond <esr@thyrsus.com>
 ;; Keywords: terminals
 
 ;; This file is part of GNU Emacs.
diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el
new file mode 100644
index 00000000000..d425ea401a9
--- /dev/null
+++ b/lisp/term/android-win.el
@@ -0,0 +1,237 @@
+;;; x-win.el --- parse relevant switches and set up for Android  -*- 
lexical-binding:t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Author: FSF
+;; Keywords: terminals, i18n, android
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file contains the support for initializing the Lisp side of
+;; Android windowing.
+
+;;; Code:
+
+
+(unless (featurep 'android)
+  (error "%s: Loading android-win without having Android"
+         invocation-name))
+
+;; Documentation-purposes only: actually loaded in loadup.el.
+(require 'frame)
+(require 'mouse)
+(require 'fontset)
+(require 'dnd)
+(require 'touch-screen)
+
+(add-to-list 'display-format-alist '(".*" . android))
+
+(declare-function android-get-connection "androidfns.c")
+
+;; Window system initialization.  This is extremely simple because all
+;; initialization is done in android_term_init.
+
+(cl-defmethod window-system-initialization (&context (window-system android)
+                                                     &optional _ignored)
+  "Set up the window system.  WINDOW-SYSTEM must be ANDROID.
+DISPLAY is ignored on Android."
+  ;; Create the default fontset.
+  (create-default-fontset)
+  ;; Just make sure the window system was initialized at startup.
+  (android-get-connection))
+
+(cl-defmethod frame-creation-function (params &context (window-system android))
+  (x-create-frame-with-faces params))
+
+(cl-defmethod handle-args-function (args &context (window-system android))
+  ;; Android has no command line to provide arguments on.
+  ;; However, call x-handle-args to handle file name args.
+  (x-handle-args args))
+
+
+;;; Selection support.
+
+(declare-function android-clipboard-exists-p "androidselect.c")
+(declare-function android-get-clipboard "androidselect.c")
+(declare-function android-set-clipboard "androidselect.c")
+(declare-function android-clipboard-owner-p "androidselect.c")
+(declare-function android-get-clipboard-targets "androidselect.c")
+(declare-function android-get-clipboard-data "androidselect.c")
+
+(defvar android-primary-selection nil
+  "The last string placed in the primary selection.
+Nil if there was no such string.
+
+Android does not have a primary selection of its own, so Emacs
+emulates one inside Lisp.")
+
+(defun android-get-clipboard-1 (data-type)
+  "Return the clipboard data.
+DATA-TYPE is a selection conversion target.  `STRING' means to
+return the contents of the clipboard as a string.  `TARGETS'
+means to return supported data types as a vector.
+
+Interpret any other symbol as a MIME type, and return its
+corresponding data."
+  (or (and (eq data-type 'STRING)
+           (android-get-clipboard))
+      (and (eq data-type 'TARGETS)
+           (android-clipboard-exists-p)
+           (vconcat [TARGETS STRING]
+                    (let ((i nil))
+                      (dolist (type (android-get-clipboard-targets))
+                        ;; Don't report plain text as a valid target.
+                        (unless (equal type "text/plain")
+                          (push (intern type) i)))
+                      (nreverse i))))
+      (and (symbolp data-type)
+           (android-get-clipboard-data (symbol-name data-type)))))
+
+(defun android-get-primary (data-type)
+  "Return the last string placed in the primary selection, or nil.
+Return nil if DATA-TYPE is anything other than STRING or TARGETS."
+  (when android-primary-selection
+    (or (and (eq data-type 'STRING)
+             android-primary-selection)
+        (and (eq data-type 'TARGETS)
+             [TARGETS]))))
+
+(defun android-selection-bounds (value)
+  "Return bounds of selection value VALUE.
+The return value is a list (BEG END BUF) if VALUE is a cons of
+two markers or an overlay.  Otherwise, it is nil."
+  (cond ((bufferp value)
+        (with-current-buffer value
+          (when (mark t)
+            (list (mark t) (point) value))))
+       ((and (consp value)
+             (markerp (car value))
+             (markerp (cdr value)))
+        (when (and (marker-buffer (car value))
+                   (buffer-name (marker-buffer (car value)))
+                   (eq (marker-buffer (car value))
+                       (marker-buffer (cdr value))))
+          (list (marker-position (car value))
+                (marker-position (cdr value))
+                (marker-buffer (car value)))))
+       ((overlayp value)
+        (when (overlay-buffer value)
+          (list (overlay-start value)
+                (overlay-end value)
+                (overlay-buffer value))))))
+
+(defun android-encode-select-string (value)
+  "Turn VALUE into a string suitable for placing in the clipboard.
+VALUE should be something suitable for passing to
+`gui-set-selection'."
+  (unless (stringp value)
+    (when-let ((bounds (android-selection-bounds value)))
+      (setq value (ignore-errors
+                    (with-current-buffer (nth 2 bounds)
+                      (buffer-substring (nth 0 bounds)
+                                        (nth 1 bounds)))))))
+  value)
+
+(cl-defmethod gui-backend-get-selection (type data-type
+                                              &context (window-system android))
+  (cond ((eq type 'CLIPBOARD)
+         (android-get-clipboard-1 data-type))
+        ((eq type 'PRIMARY)
+         (android-get-primary data-type))))
+
+(cl-defmethod gui-backend-selection-exists-p (selection
+                                              &context (window-system android))
+  (cond ((eq selection 'CLIPBOARD)
+         (android-clipboard-exists-p))
+        ((eq selection 'PRIMARY)
+         (not (null android-primary-selection)))))
+
+(cl-defmethod gui-backend-selection-owner-p (selection
+                                             &context (window-system android))
+  (cond ((eq selection 'CLIPBOARD)
+         (let ((ownership (android-clipboard-owner-p)))
+           ;; If ownership is `lambda', then Emacs couldn't determine
+           ;; whether or not it owns the clipboard.
+           (and (not (eq ownership 'lambda)) ownership)))
+        ((eq selection 'PRIMARY)
+         ;; Emacs always owns its own primary selection as long as it
+         ;; exists.
+         (not (null android-primary-selection)))))
+
+(cl-defmethod gui-backend-set-selection (type value
+                                              &context (window-system android))
+  ;; First, try to turn value into a string.
+  ;; Don't set anything if that did not work.
+  (when-let ((string (android-encode-select-string value)))
+    (cond ((eq type 'CLIPBOARD)
+           (android-set-clipboard string))
+          ((eq type 'PRIMARY)
+           (setq android-primary-selection string)))))
+
+;;; Character composition display.
+
+(defvar android-preedit-overlay nil
+  "The overlay currently used to display preedit text from a compose 
sequence.")
+
+;; With some input methods, text gets inserted before Emacs is told to
+;; remove any preedit text that was displayed, which causes both the
+;; preedit overlay and the text to be visible for a brief period of
+;; time.  This pre-command-hook clears the overlay before any command
+;; and should be set whenever a preedit overlay is visible.
+(defun android-clear-preedit-text ()
+  "Clear the pre-edit overlay and remove itself from pre-command-hook.
+This function should be installed in `pre-command-hook' whenever
+preedit text is displayed."
+  (when android-preedit-overlay
+    (delete-overlay android-preedit-overlay)
+    (setq android-preedit-overlay nil))
+  (remove-hook 'pre-command-hook #'android-clear-preedit-text))
+
+(defun android-preedit-text (event)
+  "Display preedit text from a compose sequence in EVENT.
+EVENT is a preedit-text event."
+  (interactive "e")
+  (when android-preedit-overlay
+    (delete-overlay android-preedit-overlay)
+    (setq android-preedit-overlay nil)
+    (remove-hook 'pre-command-hook #'android-clear-preedit-text))
+  (when (nth 1 event)
+    (let ((string (propertize (nth 1 event) 'face '(:underline t))))
+      (setq android-preedit-overlay (make-overlay (point) (point)))
+      (add-hook 'pre-command-hook #'android-clear-preedit-text)
+      (overlay-put android-preedit-overlay 'window (selected-window))
+      (overlay-put android-preedit-overlay 'before-string string))))
+
+(define-key special-event-map [preedit-text] 'android-preedit-text)
+
+
+;; Android cursor shapes, named according to the X scheme.
+;; Many X cursors are missing.
+
+(defconst x-pointer-arrow 1000)
+(defconst x-pointer-left-ptr 1000)
+(defconst x-pointer-left-side 1020)
+(defconst x-pointer-sb-h-double-arrow 1014)
+(defconst x-pointer-sb-v-double-arrow 1015)
+(defconst x-pointer-watch 1004)
+(defconst x-pointer-xterm 1008)
+(defconst x-pointer-invisible 0)
+
+
+(provide 'android-win)
+;; android-win.el ends here.
diff --git a/lisp/textmodes/artist.el b/lisp/textmodes/artist.el
index e0f311552d0..08613db600c 100644
--- a/lisp/textmodes/artist.el
+++ b/lisp/textmodes/artist.el
@@ -2800,7 +2800,9 @@ EXTRA-ARGS for figlet, for the command line, may be 
specified."
 (defun artist-figlet-get-font-list ()
   "Read fonts in with the shell command.
 Returns a list of strings."
-  (let* ((cmd-interpreter "/bin/sh")
+  (let* ((cmd-interpreter (if (eq system-type 'android)
+                              "/system/bin/sh"
+                            "/bin/sh"))
         (ls-cmd          artist-figlet-list-fonts-command)
         (result          (artist-system cmd-interpreter ls-cmd nil))
         (exit-code       (elt result 0))
diff --git a/lisp/textmodes/bibtex.el b/lisp/textmodes/bibtex.el
index 394f80f47ee..32d2786b86c 100644
--- a/lisp/textmodes/bibtex.el
+++ b/lisp/textmodes/bibtex.el
@@ -1845,7 +1845,7 @@ Initialized by `bibtex-set-dialect'.")
   ;; Assume that field names begin at the beginning of a line.
   (concat "^[ \t]*"
           (regexp-opt (delete-dups (mapcar #'caar bibtex-generate-url-list)) t)
-          "[ \t]*=[ \t]*")
+          "[ \t\n]*=[ \t\n]*")
   "Regexp for `bibtex-font-lock-url' derived from `bibtex-generate-url-list'.")
 
 (defvar bibtex-string-empty-key nil
diff --git a/lisp/textmodes/conf-mode.el b/lisp/textmodes/conf-mode.el
index d15fba9c43a..4d0dde7752b 100644
--- a/lisp/textmodes/conf-mode.el
+++ b/lisp/textmodes/conf-mode.el
@@ -245,6 +245,7 @@ This variable is best set in the file local variables, or 
through
     ("^\\s-*\\(.+?\\)\\(?:\\[\\(.*?\\)\\]\\)?\\s-*="
      (1 'font-lock-variable-name-face)
      (2 'font-lock-constant-face nil t))
+    ;; Must be lower-case according to the TOML spec.
     ("\\_<false\\|true\\_>" 0 'font-lock-keyword-face))
   "Keywords to highlight in Conf TOML mode.")
 
@@ -435,6 +436,7 @@ The optional arg FONT-LOCK is the value for 
FONT-LOCK-KEYWORDS."
   (setq-local comment-start comment)
   (setq-local comment-start-skip
               (concat (regexp-quote comment-start) "+\\s *"))
+  (setq-local text-conversion-style t)
   (if font-lock
       (setq-local font-lock-defaults `(,font-lock nil t nil nil))))
 
@@ -643,7 +645,10 @@ For details see `conf-mode'.  Example:
 
 \[entry]
 value = \"some string\""
-  (conf-mode-initialize "#" 'conf-toml-font-lock-keywords)
+  (conf-mode-initialize "#")
+  ;; Booleans are "always lowercase", so we must *not* use case
+  ;; folding.  Therefore, we can't set it using `conf-mode-initialize´.
+  (setq-local font-lock-defaults `(,conf-toml-font-lock-keywords nil nil nil 
nil))
   (setq-local conf-assignment-column 0)
   (setq-local conf-assignment-sign ?=))
 
diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el
index 97c4ce9f32d..c73f92aa0b3 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -1747,6 +1747,7 @@ Ispell is then restarted because the local words could 
conflict.")
 
 (defvar-local ispell-buffer-session-localwords nil
   "List of words accepted for session in this buffer.")
+(put 'ispell-buffer-session-localwords 'safe-local-variable 
#'list-of-strings-p)
 
 (defvar ispell-parser 'use-mode-name
   "Indicates whether ispell should parse the current buffer as TeX Code.
diff --git a/lisp/textmodes/less-css-mode.el b/lisp/textmodes/less-css-mode.el
index c3905afb4e7..2e4d71fb631 100644
--- a/lisp/textmodes/less-css-mode.el
+++ b/lisp/textmodes/less-css-mode.el
@@ -215,7 +215,7 @@ directory by default."
 ;;;###autoload (add-to-list 'auto-mode-alist '("\\.less\\'" . less-css-mode))
 ;;;###autoload
 (define-derived-mode less-css-mode css-mode "Less"
-  "Major mode for editing Less files (http://lesscss.org/).
+  "Major mode for editing Less files (https://lesscss.org/).
 Special commands:
 \\{less-css-mode-map}"
   (font-lock-add-keywords nil less-css-font-lock-keywords)
diff --git a/lisp/textmodes/paragraphs.el b/lisp/textmodes/paragraphs.el
index 6c33380b6bd..fd28bfe8016 100644
--- a/lisp/textmodes/paragraphs.el
+++ b/lisp/textmodes/paragraphs.el
@@ -201,7 +201,6 @@ This is desirable in modes where blank lines are the 
paragraph delimiters."
   :type 'boolean
   :safe #'booleanp)
 
-;; Silence the compiler.
 (defun forward-paragraph (&optional arg)
   "Move forward to end of paragraph.
 With argument ARG, do it ARG times;
diff --git a/lisp/textmodes/reftex-cite.el b/lisp/textmodes/reftex-cite.el
index 6beae816257..eaa03c96fe9 100644
--- a/lisp/textmodes/reftex-cite.el
+++ b/lisp/textmodes/reftex-cite.el
@@ -566,7 +566,7 @@ If FORMAT is non-nil `format' entry accordingly."
           (reftex-get-bib-field "booktitle" entry "in: %s"))
          (t ""))))
     (setq authors (reftex-truncate authors 30 t t))
-    (when (reftex-use-fonts)
+    (when reftex-use-fonts
       (put-text-property 0 (length key)     'face reftex-label-face
                          key)
       (put-text-property 0 (length authors) 'face reftex-bib-author-face
@@ -609,7 +609,7 @@ If FORMAT is non-nil `format' entry accordingly."
     (push text lines)
     (setq text (mapconcat #'identity (nreverse lines) "\n     "))
 
-    (when (reftex-use-fonts)
+    (when reftex-use-fonts
       (put-text-property 0 (length text) 'face reftex-bib-author-face text))
     (concat key "\n     " text "\n\n")))
 
diff --git a/lisp/textmodes/reftex-global.el b/lisp/textmodes/reftex-global.el
index acf0891432f..b8b0ae6a061 100644
--- a/lisp/textmodes/reftex-global.el
+++ b/lisp/textmodes/reftex-global.el
@@ -39,8 +39,10 @@ The TAGS file is also immediately visited with 
`visit-tags-table'."
   (reftex-access-scan-info current-prefix-arg)
   (let* ((master (reftex-TeX-master-file))
          (files  (reftex-all-document-files))
-         (cmd    (format "etags %s" (mapconcat #'shell-quote-argument
-                                              files " "))))
+         (cmd    (format "%s %s"
+                         etags-program-name
+                         (mapconcat #'shell-quote-argument
+                                   files " "))))
     (with-current-buffer (reftex-get-file-buffer-force master)
       (message "Running etags to create TAGS file...")
       (shell-command cmd)
diff --git a/lisp/textmodes/reftex-index.el b/lisp/textmodes/reftex-index.el
index c7a297d5dac..7cb15c7e99a 100644
--- a/lisp/textmodes/reftex-index.el
+++ b/lisp/textmodes/reftex-index.el
@@ -536,14 +536,10 @@ SPC=view TAB=goto RET=goto+hide [e]dit [q]uit [r]escan 
[f]ollow [?]Help
                    (nth 2 (car reftex-index-restriction-data))
                  reftex-index-restriction-indicator)))
 
-      (if (reftex-use-fonts)
+      (if reftex-use-fonts
           (put-text-property (point-min) (point)
                              'face reftex-index-header-face))
-      (if (fboundp 'cursor-intangible-mode)
-          (cursor-intangible-mode 1)
-        ;; If `cursor-intangible' is not available, fallback on the old
-        ;; intrusive `intangible' property.
-        (put-text-property (point-min) (point) 'intangible t))
+      (cursor-intangible-mode 1)
       (add-text-properties (point-min) (point)
                            '(cursor-intangible t
                              front-sticky (cursor-intangible)
@@ -571,7 +567,7 @@ SPC=view TAB=goto RET=goto+hide [e]dit [q]uit [r]escan 
[f]ollow [?]Help
          (context-indent (concat indent "  "))
          (section-chars (mapcar #'identity reftex-index-section-letters))
          (this-section-char 0)
-         (font (reftex-use-fonts))
+         (font reftex-use-fonts)
          (bor (car reftex-index-restriction-data))
          (eor (nth 1 reftex-index-restriction-data))
          (mouse-face
diff --git a/lisp/textmodes/reftex-ref.el b/lisp/textmodes/reftex-ref.el
index da0779c8e8d..64cf3fa4bbb 100644
--- a/lisp/textmodes/reftex-ref.el
+++ b/lisp/textmodes/reftex-ref.el
@@ -781,10 +781,9 @@ When called with 2 \\[universal-argument] prefix args, 
disable magic word recogn
         (funcall errorf "Label %s not found" label))
       found)))
 
-(defvar font-lock-mode)
 (defun reftex-show-entry (beg-hlt end-hlt)
   ;; Show entry if point is hidden
-  (let* ((n (/ (reftex-window-height) 2))
+  (let* ((n (/ (window-height) 2))
          (beg (save-excursion
                (re-search-backward "[\n\r]" nil 1 n) (point)))
          (end (save-excursion
diff --git a/lisp/textmodes/reftex-sel.el b/lisp/textmodes/reftex-sel.el
index 3c9f9ca94c8..61baf9408c2 100644
--- a/lisp/textmodes/reftex-sel.el
+++ b/lisp/textmodes/reftex-sel.el
@@ -204,7 +204,7 @@ During a selection process, these are the local bindings.
   ;;           a used member near to this one, as a possible starting point.
   ;; XR-PREFIX is the prefix to put in front of labels.
   ;; TOC-BUFFER means this is to fill the toc buffer.
-  (let* ((font (reftex-use-fonts))
+  (let* ((font reftex-use-fonts)
          (cnt 0)
          (index -1)
          (toc-indent " ")
diff --git a/lisp/textmodes/reftex-toc.el b/lisp/textmodes/reftex-toc.el
index 3b3f892a688..312ccb0f2be 100644
--- a/lisp/textmodes/reftex-toc.el
+++ b/lisp/textmodes/reftex-toc.el
@@ -1,6 +1,6 @@
 ;;; reftex-toc.el --- RefTeX's table of contents mode  -*- lexical-binding: t; 
-*-
 
-;; Copyright (C) 1997-2000, 2003-2023 Free Software Foundation, Inc.
+;; Copyright (C) 1997-2023 Free Software Foundation, Inc.
 
 ;; Author: Carsten Dominik <dominik@science.uva.nl>
 ;; Maintainer: auctex-devel@gnu.org
@@ -215,9 +215,7 @@ When called with a raw \\[universal-argument] prefix, 
rescan the document first.
          (here-I-am (if reftex--rebuilding-toc
                         (get 'reftex-toc :reftex-data)
                       (car (reftex-where-am-I))))
-         (unsplittable (if (fboundp 'frame-property)
-                           (frame-property (selected-frame) 'unsplittable)
-                         (frame-parameter nil 'unsplittable)))
+         (unsplittable (frame-parameter nil 'unsplittable))
          offset toc-window)
 
     (if (setq toc-window (get-buffer-window
@@ -265,13 +263,9 @@ SPC=view TAB=goto RET=goto+hide [q]uit [r]escan [l]abels 
[f]ollow [x]r [?]Help
 ------------------------------------------------------------------------------
 " (abbreviate-file-name reftex-last-toc-master)))
 
-      (if (reftex-use-fonts)
+      (if reftex-use-fonts
           (put-text-property (point-min) (point) 'font-lock-face 
reftex-toc-header-face))
-      (if (fboundp 'cursor-intangible-mode)
-          (cursor-intangible-mode 1)
-        ;; If `cursor-intangible' is not available, fallback on the old
-        ;; intrusive `intangible' property.
-        (put-text-property (point-min) (point) 'intangible t))
+      (cursor-intangible-mode 1)
       (add-text-properties (point-min) (point)
                            '(cursor-intangible t
                              front-sticky (cursor-intangible)
@@ -385,11 +379,8 @@ SPC=view TAB=goto RET=goto+hide [q]uit [r]escan [l]abels 
[f]ollow [x]r [?]Help
   ;; Check if FRAME is the dedicated TOC frame.
   ;; If yes, and ERROR is non-nil, throw an error.
   (setq frame (or frame (selected-frame)))
-  (let ((res (equal
-              (if (fboundp 'frame-property)
-                  (frame-property frame 'name)
-                (frame-parameter  frame 'name))
-              "RefTeX TOC Frame")))
+  (let ((res (equal (frame-parameter frame 'name)
+                    "RefTeX TOC Frame")))
     (if (and res error)
         (error (substitute-command-keys
                 "This frame is view-only.  Use \\[reftex-toc] \
@@ -586,10 +577,7 @@ With prefix arg 1, restrict index to the section at point."
 (defun reftex-toc-revert (&rest _)
   "Regenerate the TOC from the internal lists."
   (interactive)
-  (let ((unsplittable
-         (if (fboundp 'frame-property)
-             (frame-property (selected-frame) 'unsplittable)
-           (frame-parameter nil 'unsplittable)))
+  (let ((unsplittable (frame-parameter nil 'unsplittable))
         (reftex--rebuilding-toc t))
     (if unsplittable
         (switch-to-buffer
@@ -1036,12 +1024,9 @@ always show the current section in connection with the 
option
 `reftex-auto-recenter-toc'."
   (interactive)
   (catch 'exit
-    (let* ((frames (frame-list)) frame
-           (get-frame-prop-func (if (fboundp 'frame-property)
-                                    'frame-property
-                                  'frame-parameter)))
+    (let* ((frames (frame-list)) frame)
       (while (setq frame (pop frames))
-        (if (equal (funcall get-frame-prop-func frame 'name)
+        (if (equal (frame-parameter frame 'name)
                    "RefTeX TOC Frame")
             (progn
               (delete-frame frame)
diff --git a/lisp/textmodes/reftex-vars.el b/lisp/textmodes/reftex-vars.el
index 60ee40180d1..ebe49ae9fef 100644
--- a/lisp/textmodes/reftex-vars.el
+++ b/lisp/textmodes/reftex-vars.el
@@ -1933,7 +1933,6 @@ The value of this variable will only have any effect when
 
 (defcustom reftex-use-fonts t
   "Non-nil means, use fonts in *toc* and selection buffers.
-Font-lock must be loaded as well to actually get fontified display.
 When changing this option, a rescan may be necessary to activate the change."
   :group 'reftex-fontification-configurations
   :type 'boolean)
diff --git a/lisp/textmodes/reftex.el b/lisp/textmodes/reftex.el
index 916e0d89a1d..ae3ae1a198d 100644
--- a/lisp/textmodes/reftex.el
+++ b/lisp/textmodes/reftex.el
@@ -1,6 +1,6 @@
 ;;; reftex.el --- minor mode for doing \label, \ref, \cite, \index in LaTeX  
-*- lexical-binding: t; -*-
 
-;; Copyright (C) 1997-2000, 2003-2023 Free Software Foundation, Inc.
+;; Copyright (C) 1997-2023 Free Software Foundation, Inc.
 
 ;; Author: Carsten Dominik <dominik@science.uva.nl>
 ;; Maintainer: auctex-devel@gnu.org
@@ -250,9 +250,6 @@ on the menu bar.
 (defvar LaTeX-label-function)
 (defvar tex-main-file)
 (defvar outline-minor-mode)
-(defvar font-lock-mode)
-(defvar font-lock-keywords)
-(defvar font-lock-fontify-region-function)
 
 ;;; =========================================================================
 ;;;
@@ -1664,11 +1661,6 @@ When DIE is non-nil, throw an error if file not found."
       (pop alist))
     (nreverse out)))
 
-(defun reftex-window-height ()
-  (if (fboundp 'window-displayed-height)
-      (window-displayed-height)
-    (window-height)))
-
 (defun reftex-enlarge-to-fit (buf2 &optional keep-current)
   ;; Enlarge other window displaying buffer to show whole buffer if possible.
   ;; If KEEP-CURRENT in non-nil, current buffer must remain visible.
@@ -1680,7 +1672,7 @@ When DIE is non-nil, throw an error if file not found."
       (unless (and (pos-visible-in-window-p (point-min))
                    (pos-visible-in-window-p (point-max)))
         (enlarge-window (1+ (- (count-lines (point-min) (point-max))
-                               (reftex-window-height))))))
+                               (window-height))))))
     (cond
      ((window-live-p win1) (select-window win1))
      (keep-current
@@ -1705,7 +1697,7 @@ When DIE is non-nil, throw an error if file not found."
           (unless (and (pos-visible-in-window-p (point-min))
                        (pos-visible-in-window-p (point-max)))
             (enlarge-window (1+ (- (count-lines (point-min) (point-max))
-                                   (reftex-window-height)))))
+                                   (window-height)))))
           (setq truncate-lines t))
         (if (and (pos-visible-in-window-p (point-min))
                  (pos-visible-in-window-p (point-max)))
@@ -2032,21 +2024,14 @@ IGNORE-WORDS List of words which should be removed from 
the string."
 ;;;
 ;;; Fontification and Highlighting
 
-(defun reftex-use-fonts ()
-  ;; Return t if we can and want to use fonts.
-  (and ; window-system
-       reftex-use-fonts
-       (featurep 'font-lock)))
-
 (defun reftex-refontify ()
   ;; Return t if we need to refontify context
-  (and (reftex-use-fonts)
+  (and reftex-use-fonts
        (or (eq t reftex-refontify-context)
            (and (eq 1 reftex-refontify-context)
                 ;; Test of we use the font-lock version of x-symbol
                 (and (featurep 'x-symbol-tex) (not (boundp 
'x-symbol-mode)))))))
 
-(defvar font-lock-defaults-computed)
 (defun reftex-fontify-select-label-buffer (parent-buffer)
   ;; Fontify the `*RefTeX Select*' buffer.  Buffer is temporarily renamed to
   ;; start with none-SPC char, because Font-Lock otherwise refuses operation.
@@ -2274,20 +2259,17 @@ IGNORE-WORDS List of words which should be removed from 
the string."
 (defun reftex-create-customize-menu ()
   "Create a full customization menu for RefTeX, insert it into the menu."
   (interactive)
-  (if (fboundp 'customize-menu-create)
-      (progn
-        (easy-menu-change
-         '("Ref") "Customize"
-         `(["Browse RefTeX group" reftex-customize t]
-           "--"
-           ,(customize-menu-create 'reftex)
-           ["Set" Custom-set t]
-           ["Save" Custom-save t]
-           ["Reset to Current" Custom-reset-current t]
-           ["Reset to Saved" Custom-reset-saved t]
-           ["Reset to Standard Settings" Custom-reset-standard t]))
-        (message "\"Ref\"-menu now contains full customization menu"))
-    (error "Cannot expand menu (outdated version of cus-edit.el)")))
+  (easy-menu-change
+   '("Ref") "Customize"
+   `(["Browse RefTeX group" reftex-customize t]
+     "--"
+     ,(customize-menu-create 'reftex)
+     ["Set" Custom-set t]
+     ["Save" Custom-save t]
+     ["Reset to Current" Custom-reset-current t]
+     ["Reset to Saved" Custom-reset-saved t]
+     ["Reset to Standard Settings" Custom-reset-standard t]))
+  (message "\"Ref\"-menu now contains full customization menu"))
 
 
 ;;; Misc
@@ -2348,6 +2330,12 @@ Your bug report will be posted to the AUCTeX bug 
reporting list.
 
 (setq reftex-tables-dirty t)  ; in case this file is evaluated by hand
 
+(define-obsolete-function-alias 'reftex-window-height #'window-height "30.1")
+
+(defun reftex-use-fonts ()
+  (declare (obsolete "use variable `reftex-use-fonts' instead." "30.1"))
+  reftex-use-fonts)
+
 (provide 'reftex)
 
 ;;; reftex.el ends here
diff --git a/lisp/textmodes/remember.el b/lisp/textmodes/remember.el
index 95e18b7d1d5..84a0185f7b5 100644
--- a/lisp/textmodes/remember.el
+++ b/lisp/textmodes/remember.el
@@ -7,7 +7,7 @@
 ;; Created: 29 Mar 1999
 ;; Old-Version: 2.0
 ;; Keywords: data memory todo pim
-;; URL: http://gna.org/projects/remember-el/
+;; URL: http://gna.org/projects/remember-el/ [dead link]
 
 ;; This file is part of GNU Emacs.
 
diff --git a/lisp/textmodes/rst.el b/lisp/textmodes/rst.el
index 9d68feed0e0..d2661a44734 100644
--- a/lisp/textmodes/rst.el
+++ b/lisp/textmodes/rst.el
@@ -170,8 +170,7 @@ When FUN is called match data is just set by `looking-at' 
and
 point is at the beginning of the line.  Return nil if moving
 forward failed or otherwise the return value of FUN.  Preserve
 global match data, point, mark and current buffer."
-  (unless (listp rst-re-args)
-    (setq rst-re-args (list rst-re-args)))
+  (setq rst-re-args (ensure-list rst-re-args))
   (unless fun
     (setq fun #'identity))
   (save-match-data
@@ -1473,7 +1472,7 @@ for modes derived from Text mode, like Mail mode."
   :version "21.1")
 
 ;; FIXME: Default must match suggestion in
-;;        http://sphinx-doc.org/rest.html#sections for Python documentation.
+;;        https://sphinx-doc.org/rest.html#sections for Python documentation.
 (defcustom rst-preferred-adornments '((?= over-and-under 1)
                                      (?= simple 0)
                                      (?- simple 0)
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index d80cdc34775..27f3b2acd1c 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -2680,7 +2680,6 @@ HTML Autoview mode is a buffer-local minor mode for use 
with
   "<html lang=\"en\">" \n
   "<head>" \n
   "<meta charset=\"utf-8\">" \n
-  "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">" \n
   "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" \n
   "<title>" (skeleton-read "Page Title: ") "</title>" \n
   "</head>" \n
diff --git a/lisp/textmodes/tex-mode.el b/lisp/textmodes/tex-mode.el
index 294c9792f39..41c4a6a1373 100644
--- a/lisp/textmodes/tex-mode.el
+++ b/lisp/textmodes/tex-mode.el
@@ -1,7 +1,6 @@
 ;;; tex-mode.el --- TeX, LaTeX, and SliTeX mode commands  -*- 
lexical-binding:t -*-
 
-;; Copyright (C) 1985-1986, 1989, 1992, 1994-1999, 2001-2023 Free
-;; Software Foundation, Inc.
+;; Copyright (C) 1985-2023 Free Software Foundation, Inc.
 
 ;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: tex
@@ -33,9 +32,6 @@
   (require 'cl-lib)
   (require 'skeleton))
 
-(defvar font-lock-comment-face)
-(defvar font-lock-doc-face)
-
 (require 'shell)
 (require 'compile)
 
diff --git a/lisp/textmodes/text-mode.el b/lisp/textmodes/text-mode.el
index 48cefc74d06..ccba1b063ab 100644
--- a/lisp/textmodes/text-mode.el
+++ b/lisp/textmodes/text-mode.el
@@ -41,6 +41,9 @@
   "Non-nil if this buffer's major mode is a variant of Text mode.")
 (make-obsolete-variable 'text-mode-variant 'derived-mode-p "27.1")
 
+;; Actually defined in textconv.c.
+(defvar text-conversion-style)
+
 (defvar text-mode-syntax-table
   (let ((st (make-syntax-table)))
     (modify-syntax-entry ?\" ".   " st)
@@ -125,6 +128,9 @@ You can thus get the full benefit of adaptive filling
 Turning on Text mode runs the normal hook `text-mode-hook'."
   (setq-local text-mode-variant t)
   (setq-local require-final-newline mode-require-final-newline)
+
+  ;; Enable text conversion in this buffer.
+  (setq-local text-conversion-style t)
   (add-hook 'context-menu-functions 'text-mode-context-menu 10 t))
 
 (define-derived-mode paragraph-indent-text-mode text-mode "Parindent"
diff --git a/lisp/textmodes/toml-ts-mode.el b/lisp/textmodes/toml-ts-mode.el
index 2c491034372..193c83f9aec 100644
--- a/lisp/textmodes/toml-ts-mode.el
+++ b/lisp/textmodes/toml-ts-mode.el
@@ -39,8 +39,8 @@
 (defcustom toml-ts-mode-indent-offset 2
   "Number of spaces for each indentation step in `toml-ts-mode'."
   :version "29.1"
-  :type 'integer
-  :safe 'integerp
+  :type 'natnum
+  :safe 'natnump
   :group 'toml)
 
 (defvar toml-ts-mode--syntax-table
diff --git a/lisp/thingatpt.el b/lisp/thingatpt.el
index f3367290dee..72acb0b749f 100644
--- a/lisp/thingatpt.el
+++ b/lisp/thingatpt.el
@@ -250,7 +250,8 @@ Prefer the enclosing string with fallback on sexp at point.
             (goto-char (nth 8 ppss))
             (cons (point) (progn (forward-sexp) (point))))
         ;; At the beginning of the string
-        (if (eq (char-syntax (char-after)) ?\")
+        (if (let ((ca (char-after)))
+              (and ca (eq (char-syntax ca) ?\")))
             (let ((bound (bounds-of-thing-at-point 'sexp)))
              (and bound
                   (<= (car bound) (point)) (< (point) (cdr bound))
@@ -359,6 +360,10 @@ E.g.:
     (and (file-exists-p filename)
          filename)))
 
+(put 'existing-filename 'bounds-of-thing-at-point
+     (lambda ()
+       (and (thing-at-point 'existing-filename)
+            (bounds-of-thing-at-point 'filename))))
 (put 'existing-filename 'thing-at-point 'thing-at-point-file-at-point)
 
 ;; Faces
diff --git a/lisp/time.el b/lisp/time.el
index 522bec46ac6..3f10deff5fd 100644
--- a/lisp/time.el
+++ b/lisp/time.el
@@ -139,7 +139,8 @@ make the mail indicator stand out on a color display."
   :version "22.1"
   :type '(choice (const :tag "None" nil) face))
 
-(defface display-time-date-and-time nil
+(defface display-time-date-and-time
+  '((t nil))
   "Face for `display-time-format'."
   :group 'mode-line-faces
   :version "30.1")
diff --git a/lisp/tool-bar.el b/lisp/tool-bar.el
index 1a0faf3a584..ab81a21211d 100644
--- a/lisp/tool-bar.el
+++ b/lisp/tool-bar.el
@@ -83,6 +83,14 @@ buffer-locally and add the items you want to it with
 `tool-bar-add-item', `tool-bar-add-item-from-menu' and related
 functions.")
 
+(defvar secondary-tool-bar-map nil
+  "Optional secondary keymap for the tool bar.
+
+If non-nil, tool bar items defined within this map are displayed
+in a line below the tool bar if the `tool-bar-position' frame
+parameter is set to `top', and above the tool bar it is set to
+`bottom'.")
+
 (global-set-key [tool-bar]
                `(menu-item ,(purecopy "tool bar") ignore
                            :filter tool-bar-make-keymap))
@@ -91,15 +99,21 @@ functions.")
 
 (defconst tool-bar-keymap-cache (make-hash-table :test #'equal))
 
-(defun tool-bar--cache-key ()
+(defsubst tool-bar--cache-key ()
   (cons (frame-terminal) (sxhash-eq tool-bar-map)))
 
+(defsubst tool-bar--secondary-cache-key ()
+  (cons (frame-terminal) (sxhash-eq secondary-tool-bar-map)))
+
 (defun tool-bar--flush-cache ()
   "Remove all cached entries that refer to the current `tool-bar-map'."
   (let ((id (sxhash-eq tool-bar-map))
+        (secondary-id (and secondary-tool-bar-map
+                           (sxhash-eq secondary-tool-bar-map)))
         (entries nil))
     (maphash (lambda (k _)
-               (when (equal (cdr k) id)
+               (when (or (equal (cdr k) id)
+                         (equal (cdr k) secondary-id))
                  (push k entries)))
              tool-bar-keymap-cache)
     (dolist (k entries)
@@ -107,14 +121,54 @@ functions.")
 
 (defun tool-bar-make-keymap (&optional _ignore)
   "Generate an actual keymap from `tool-bar-map'.
+If `secondary-tool-bar-map' is non-nil, take it into account as well.
 Its main job is to figure out which images to use based on the display's
 color capability and based on the available image libraries."
-  (or (gethash (tool-bar--cache-key) tool-bar-keymap-cache)
-      (setf (gethash (tool-bar--cache-key) tool-bar-keymap-cache)
-            (tool-bar-make-keymap-1))))
-
-(defun tool-bar-make-keymap-1 ()
-  "Generate an actual keymap from `tool-bar-map', without caching."
+  (let* ((key (tool-bar--cache-key))
+         (base-keymap
+          (or (gethash key tool-bar-keymap-cache)
+              (setf (gethash key tool-bar-keymap-cache)
+                    (tool-bar-make-keymap-1))))
+        (secondary-keymap
+         (and secondary-tool-bar-map
+              (or (gethash (tool-bar--secondary-cache-key)
+                           tool-bar-keymap-cache)
+                  (setf (gethash (tool-bar--secondary-cache-key)
+                                 tool-bar-keymap-cache)
+                        (tool-bar-make-keymap-1
+                         secondary-tool-bar-map))))))
+    (if secondary-keymap
+        (or (ignore-errors
+              (progn
+                ;; Determine the value of the `tool-bar-position' frame
+                ;; parameter.
+                (let ((position (frame-parameter nil 'tool-bar-position)))
+                  (cond ((eq position 'top)
+                         ;; Place `base-keymap' above `secondary-keymap'.
+                         (append base-keymap (list (list (gensym)
+                                                         'menu-item
+                                                         "" 'ignore
+                                                         :wrap t))
+                                 (cdr secondary-keymap)))
+                        ((eq position 'bottom)
+                         ;; Place `secondary-keymap' above `base-keymap'.
+                         (append secondary-keymap (list (list (gensym)
+                                                              'menu-item
+                                                              "" 'ignore
+                                                              :wrap t))
+                                 (cdr base-keymap)))
+                        ;; If the tool bar position isn't known, don't
+                        ;; display the secondary keymap at all.
+                        (t base-keymap)))))
+            ;; If combining both keymaps fails, return the base
+            ;; keymap.
+            base-keymap)
+      base-keymap)))
+
+(defun tool-bar-make-keymap-1 (&optional map)
+  "Generate an actual keymap from `tool-bar-map', without caching.
+MAP is either a keymap to use as a source for menu items, or nil,
+in which case the value of `tool-bar-map' is used instead."
   (mapcar (lambda (bind)
             (let (image-exp plist)
               (when (and (eq (car-safe (cdr-safe bind)) 'menu-item)
@@ -136,7 +190,7 @@ color capability and based on the available image 
libraries."
                                        bind))
                    (plist-put plist :image image))))
              bind))
-         tool-bar-map))
+         (or map tool-bar-map)))
 
 ;;;###autoload
 (defun tool-bar-add-item (icon def key &rest props)
@@ -322,6 +376,207 @@ Customize `tool-bar-mode' if you want to show or hide the 
tool bar."
             (modify-all-frames-parameters
              (list (cons 'tool-bar-position val))))))
 
+
+
+;; Modifier bar mode.
+;; This displays a small tool bar containing modifier keys
+;; above or below the main tool bar itself.
+
+(defvar modifier-bar-modifier-list nil
+  "List of modifiers that are currently applied.
+Each symbol in this list represents a modifier button that has
+been pressed as part of decoding this key sequence.")
+
+(declare-function set-text-conversion-style "textconv.c")
+
+;; These functions are very similar to their counterparts in
+;; simple.el, but allow combining multiple modifier buttons together.
+
+(defun tool-bar-apply-modifiers (event modifiers)
+  "Apply the specified list of MODIFIERS to EVENT.
+MODIFIERS must be a list containing only the symbols `alt',
+`super', `hyper', `shift', `control' and `meta'.
+Return EVENT with the specified modifiers applied."
+  (dolist (modifier modifiers)
+    (cond
+     ((eq modifier 'alt)
+      (setq event (event-apply-modifier event 'alt 22 "A-")))
+     ((eq modifier 'super)
+      (setq event (event-apply-modifier event 'super 23 "s-")))
+     ((eq modifier 'hyper)
+      (setq event (event-apply-modifier event 'hyper 24 "H-")))
+     ((eq modifier 'shift)
+      (setq event (event-apply-modifier event 'shift 25 "S-")))
+     ((eq modifier 'control)
+      (setq event (event-apply-modifier event 'control 26 "C-")))
+     ((eq modifier 'meta)
+      (setq event (event-apply-modifier event 'meta 27 "M-")))))
+  event)
+
+(defvar overriding-text-conversion-style)
+
+(defun modifier-bar-button (init-modifier-list)
+  "Decode the key sequence associated with a modifier bar button.
+INIT-MODIFIER-LIST is a list of one symbol describing the button
+being pressed.
+
+Bind `modifier-bar-modifier-list' to INIT-MODIFIER-LIST.  Read
+events, adding each subsequent modifier bar event's associated
+modifier to that list while updating the tool bar to disable
+buttons that were pressed.  Return any other event read with all
+modifier keys read applied.
+
+Temporarily disable text conversion and display the on screen
+keyboard while doing so."
+  ;; Save the previously used text conversion style.
+  (let ((old-text-conversion-style text-conversion-style)
+        ;; Clear the list of modifiers currently pressed.
+        (modifier-bar-modifier-list init-modifier-list))
+    ;; Disable text conversion.
+    (when (fboundp 'set-text-conversion-style)
+      (set-text-conversion-style nil))
+    (unwind-protect
+        (progn
+          ;; Display the on screen keyboard.
+          (frame-toggle-on-screen-keyboard nil nil)
+          ;; Update the tool bar to disable this modifier key.
+          (force-mode-line-update)
+          (let* ((modifiers init-modifier-list) event1
+                 (overriding-text-conversion-style nil)
+                 (event (read-event)))
+            ;; Combine any more modifier key presses.
+            (while (eq event 'tool-bar)
+              (setq event1 (event-basic-type (read-event)))
+              ;; Reject unknown tool bar events.
+              (unless (memq event1 '(alt super hyper shift control meta))
+                (user-error "Unknown tool-bar event %s" event1))
+              ;; If `event' is the name of a modifier key, apply that
+              ;; modifier key as well.
+              (unless (memq event1 modifiers)
+                (push event1 modifiers)
+                ;; This list is used to check which tool bar buttons
+                ;; need to be enabled.
+                (push event1 modifier-bar-modifier-list))
+              ;; Update the tool bar to disable the modifier button
+              ;; that was read.
+              (force-mode-line-update)
+              (redisplay)
+              ;; Read another event.
+              (setq event (read-event)))
+            ;; EVENT is a keyboard event to which the specified list of
+            ;; modifier keys should be applied.
+            (vector (tool-bar-apply-modifiers event modifiers))))
+      ;; Re-enable text conversion if necessary.
+      (unless (or (not (fboundp 'set-text-conversion-style))
+                  (eq old-text-conversion-style text-conversion-style))
+        (set-text-conversion-style old-text-conversion-style t))
+      ;; Re-enable all modifier bar buttons which may have been
+      ;; disabled.
+      (force-mode-line-update))))
+
+(defun tool-bar-event-apply-alt-modifier (_ignore-prompt)
+  "Like `event-apply-alt-modifier'.
+However, take additional modifier tool bar items into account;
+apply any extra modifiers bound to subsequent `tool-bar' events."
+  (modifier-bar-button '(alt)))
+
+(defun tool-bar-event-apply-super-modifier (_ignore-prompt)
+  "Like `event-apply-super-modifier'.
+However, take additional modifier tool bar items into account;
+apply any extra modifiers bound to subsequent `tool-bar' events."
+  (modifier-bar-button '(super)))
+
+(defun tool-bar-event-apply-hyper-modifier (_ignore-prompt)
+  "Like `event-apply-hyper-modifier'.
+However, take additional modifier tool bar items into account;
+apply any extra modifiers bound to subsequent `tool-bar' events."
+  (modifier-bar-button '(hyper)))
+
+(defun tool-bar-event-apply-shift-modifier (_ignore-prompt)
+  "Like `event-apply-shift-modifier'.
+However, take additional modifier tool bar items into account;
+apply any extra modifiers bound to subsequent `tool-bar' events."
+  (modifier-bar-button '(shift)))
+
+(defun tool-bar-event-apply-control-modifier (_ignore-prompt)
+  "Like `event-apply-control-modifier'.
+However, take additional modifier tool bar items into account;
+apply any extra modifiers bound to subsequent `tool-bar' events."
+  (modifier-bar-button '(control)))
+
+(defun tool-bar-event-apply-meta-modifier (_ignore-prompt)
+  "Like `event-apply-meta-modifier'.
+However, take additional modifier tool bar items into account;
+apply any extra modifiers bound to subsequent `tool-bar' events."
+  (modifier-bar-button '(meta)))
+
+(defun modifier-bar-available-p (modifier)
+  "Return whether the modifier button for MODIFIER should be enabled.
+Return t if MODIFIER has not yet been selected as part of
+decoding the current key sequence, nil otherwise."
+  (not (memq modifier modifier-bar-modifier-list)))
+
+(define-minor-mode modifier-bar-mode
+  "Toggle display of the modifier bar.
+
+When enabled, a small tool bar will be displayed next to the tool
+bar containing items bound to
+`tool-bar-event-apply-control-modifier' and its related commands,
+which see."
+  :init-value nil
+  :global t
+  :group 'tool-bar
+  (if modifier-bar-mode
+      (progn
+        (setq secondary-tool-bar-map
+              ;; The commands specified in the menu items here are not
+              ;; used.  Instead, Emacs relies on each of the tool bar
+              ;; events being specified in `input-decode-map'.
+              `(keymap (control menu-item "Control Key"
+                                event-apply-control-modifier
+                                :help "Add Control modifier to the following 
event"
+                                :image ,(tool-bar--image-expression "ctrl")
+                                :enable (modifier-bar-available-p 'control))
+                       (shift menu-item "Shift Key"
+                              event-apply-shift-modifier
+                              :help "Add Shift modifier to the following event"
+                              :image ,(tool-bar--image-expression "shift")
+                              :enable (modifier-bar-available-p 'shift))
+                       (meta menu-item "Meta Key"
+                             event-apply-meta-modifier
+                             :help "Add Meta modifier to the following event"
+                             :image ,(tool-bar--image-expression "meta")
+                             :enable (modifier-bar-available-p 'meta))
+                       (alt menu-item "Alt Key"
+                            event-apply-alt-modifier
+                            :help "Add Alt modifier to the following event"
+                            :image ,(tool-bar--image-expression "alt")
+                            :enable (modifier-bar-available-p 'alt))
+                       (super menu-item "Super Key"
+                              event-apply-super-modifier
+                              :help "Add Super modifier to the following event"
+                              :image ,(tool-bar--image-expression "super")
+                              :enable (modifier-bar-available-p 'super))
+                       (hyper menu-item "Hyper Key"
+                              event-apply-hyper-modifier
+                              :help "Add Hyper modifier to the following event"
+                              :image ,(tool-bar--image-expression "hyper")
+                              :enable (modifier-bar-available-p 'hyper))))
+        (define-key input-decode-map [tool-bar control]
+                    #'tool-bar-event-apply-control-modifier)
+        (define-key input-decode-map [tool-bar shift]
+                    #'tool-bar-event-apply-shift-modifier)
+        (define-key input-decode-map [tool-bar meta]
+                    #'tool-bar-event-apply-meta-modifier)
+        (define-key input-decode-map [tool-bar alt]
+                    #'tool-bar-event-apply-alt-modifier)
+        (define-key input-decode-map [tool-bar super]
+                    #'tool-bar-event-apply-super-modifier)
+        (define-key input-decode-map [tool-bar hyper]
+                    #'tool-bar-event-apply-hyper-modifier))
+    (setq secondary-tool-bar-map nil))
+  ;; Update the mode line now.
+  (force-mode-line-update t))
 
 (provide 'tool-bar)
 
diff --git a/lisp/tooltip.el b/lisp/tooltip.el
index 8d92caed08e..6f8a489e60c 100644
--- a/lisp/tooltip.el
+++ b/lisp/tooltip.el
@@ -194,13 +194,13 @@ This might return nil if the event did not occur over a 
buffer."
 (defun tooltip-cancel-delayed-tip ()
   "Disable the tooltip timeout."
   (when tooltip-timeout-id
-    (disable-timeout tooltip-timeout-id)
+    (cancel-timer tooltip-timeout-id)
     (setq tooltip-timeout-id nil)))
 
 (defun tooltip-start-delayed-tip ()
   "Add a one-shot timeout to call function `tooltip-timeout'."
   (setq tooltip-timeout-id
-       (add-timeout (tooltip-delay) 'tooltip-timeout nil)))
+        (run-with-timer (tooltip-delay) nil 'tooltip-timeout nil)))
 
 (defun tooltip-timeout (_object)
   "Function called when timer with id `tooltip-timeout-id' fires."
diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el
new file mode 100644
index 00000000000..577c993efcf
--- /dev/null
+++ b/lisp/touch-screen.el
@@ -0,0 +1,1596 @@
+;;; touch-screen.el --- touch screen support for X and Android  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Maintainer: emacs-devel@gnu.org
+;; Package: emacs
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides code to recognize simple touch screen gestures.
+;; It is used on X and Android, currently the only systems where Emacs
+;; supports touch input.
+;;
+;; See (elisp)Touchscreen Events for a description of the details of
+;; touch events.
+
+;;; Code:
+
+(defvar touch-screen-current-tool nil
+  "The touch point currently being tracked, or nil.
+If non-nil, this is a list of nine elements: the ID of the touch
+point being tracked, the window where the touch began, a cons
+containing the last known position of the touch point, relative
+to that window, a field used to store data while tracking the
+touch point, the initial position of the touchpoint, and another
+four fields to used store data while tracking the touch point.
+See `touch-screen-handle-point-update' and
+`touch-screen-handle-point-up' for the meanings of the fourth
+element.")
+
+(defvar touch-screen-set-point-commands '(mouse-set-point)
+  "List of commands known to set the point.
+This is used to determine whether or not to display the on-screen
+keyboard after a mouse command is executed in response to a
+`touchscreen-end' event.")
+
+(defvar touch-screen-current-timer nil
+  "Timer used to track long-presses.
+This is always cleared upon any significant state change.")
+
+(defvar touch-screen-translate-prompt nil
+  "Prompt given to the touch screen translation function.
+If non-nil, the touch screen key event translation machinery
+is being called from `read-sequence' or some similar function.")
+
+(defcustom touch-screen-display-keyboard nil
+  "If non-nil, always display the on screen keyboard.
+A buffer local value means to always display the on screen
+keyboard when the buffer is selected."
+  :type 'boolean
+  :group 'mouse
+  :version "30.1")
+
+(defcustom touch-screen-delay 0.7
+  "Delay in seconds before Emacs considers a touch to be a long-press."
+  :type 'number
+  :group 'mouse
+  :version "30.1")
+
+(defcustom touch-screen-precision-scroll nil
+  "Whether or not to use precision scrolling for touch screens.
+See `pixel-scroll-precision-mode' for more details."
+  :type 'boolean
+  :group 'mouse
+  :version "30.1")
+
+(defcustom touch-screen-word-select nil
+  "Whether or not to select whole words while dragging to select.
+If non-nil, long-press events (see `touch-screen-delay') followed
+by dragging will try to select entire words."
+  :type 'boolean
+  :group 'mouse
+  :version "30.1")
+
+(defcustom touch-screen-extend-selection nil
+  "If non-nil, restart drag-to-select upon a tap on point or mark.
+When enabled, tapping on the character containing the point or
+mark will resume dragging where it left off while the region is
+active."
+  :type 'boolean
+  :group 'mouse
+  :version "30.1")
+
+(defcustom touch-screen-preview-select nil
+  "If non-nil, display a preview while selecting text.
+When enabled, a preview of the visible line within the window
+will be displayed in the echo area while dragging combined with
+an indication of the position of point within that line."
+  :type 'boolean
+  :group 'mouse
+  :version "30.1")
+
+(defvar-local touch-screen-word-select-bounds nil
+  "The start and end positions of the word last selected.
+Normally a cons of those two positions or nil if no word was
+selected.")
+
+(defvar-local touch-screen-word-select-initial-word nil
+  "The start and end positions of the first word to be selected.
+Used in an attempt to keep this word selected during later
+dragging.")
+
+
+
+;;; Scroll gesture.
+
+(defun touch-screen-relative-xy (posn window)
+  "Return the coordinates of POSN, a mouse position list.
+However, return the coordinates relative to WINDOW.
+
+If (posn-window posn) is the same as window, simply return the
+coordinates in POSN.  Otherwise, convert them to the frame, and
+then back again.
+
+If WINDOW is the symbol `frame', simply convert the coordinates
+to the frame that they belong in."
+  (if (or (eq (posn-window posn) window)
+          (and (eq window 'frame)
+               (framep (posn-window posn))))
+      (posn-x-y posn)
+    (let ((xy (posn-x-y posn))
+          (edges (and (windowp window)
+                      (window-inside-pixel-edges window))))
+      ;; Make the X and Y positions frame relative.
+      (when (windowp (posn-window posn))
+        (let ((edges (window-inside-pixel-edges
+                      (posn-window posn))))
+          (setq xy (cons (+ (car xy) (car edges))
+                         (+ (cdr xy) (cadr edges))))))
+      (if (eq window 'frame)
+          xy
+        ;; Make the X and Y positions window relative again.
+        (cons (- (car xy) (car edges))
+              (- (cdr xy) (cadr edges)))))))
+
+(defun touch-screen-handle-scroll (dx dy)
+  "Scroll the display assuming that a touch point has moved by DX and DY.
+Perform vertical scrolling by DY, using `pixel-scroll-precision'
+if `touch-screen-precision-scroll' is enabled.  Next, perform
+horizontal scrolling according to the movement in DX."
+  ;; Perform vertical scrolling first.  Do not ding at buffer limits.
+  ;; Show a message instead.
+  (condition-case nil
+      (if touch-screen-precision-scroll
+          (progn
+            (if (> dy 0)
+                (pixel-scroll-precision-scroll-down-page dy)
+              (pixel-scroll-precision-scroll-up-page (- dy)))
+            ;; Now set `lines-vscrolled' to an value that will result
+            ;; in hscroll being disabled if dy looks as if a
+            ;; significant amount of scrolling is about to take
+            ;; Otherwise, horizontal scrolling may then interfere with
+            ;; precision scrolling.
+            (when (> (abs dy) 10)
+              (setcar (nthcdr 7 touch-screen-current-tool) 10)))
+        ;; Start conventional scrolling.  First, determine the
+        ;; direction in which the scrolling is taking place.  Load the
+        ;; accumulator value.
+        (let ((accumulator (or (nth 5 touch-screen-current-tool) 0))
+              (window (cadr touch-screen-current-tool))
+              (lines-vscrolled (or (nth 7 touch-screen-current-tool) 0)))
+          (setq accumulator (+ accumulator dy)) ; Add dy.
+          ;; Figure out how much it has scrolled and how much remains
+          ;; on the top or bottom of the window.
+          (while (catch 'again
+                   (let* ((line-height (window-default-line-height window)))
+                     (if (and (< accumulator 0)
+                              (>= (- accumulator) line-height))
+                         (progn
+                           (setq accumulator (+ accumulator line-height))
+                           (scroll-down 1)
+                           (setq lines-vscrolled (1+ lines-vscrolled))
+                           (when (not (zerop accumulator))
+                             ;; If there is still an outstanding
+                             ;; amount to scroll, do this again.
+                             (throw 'again t)))
+                       (when (and (> accumulator 0)
+                                  (>= accumulator line-height))
+                         (setq accumulator (- accumulator line-height))
+                         (scroll-up 1)
+                         (setq lines-vscrolled (1+ lines-vscrolled))
+                         (when (not (zerop accumulator))
+                           ;; If there is still an outstanding amount
+                           ;; to scroll, do this again.
+                           (throw 'again t)))))
+                   ;; Scrolling is done.  Move the accumulator back to
+                   ;; touch-screen-current-tool and break out of the
+                   ;; loop.
+                   (setcar (nthcdr 5 touch-screen-current-tool) accumulator)
+                   (setcar (nthcdr 7 touch-screen-current-tool)
+                           lines-vscrolled)
+                   nil))))
+    (beginning-of-buffer
+     (message (error-message-string '(beginning-of-buffer))))
+    (end-of-buffer
+     (message (error-message-string '(end-of-buffer)))))
+
+  ;; Perform horizontal scrolling by DX, as this does not signal at
+  ;; the beginning of the buffer.
+  (let ((accumulator (or (nth 6 touch-screen-current-tool) 0))
+        (window (cadr touch-screen-current-tool))
+        (lines-vscrolled (or (nth 7 touch-screen-current-tool) 0))
+        (lines-hscrolled (or (nth 8 touch-screen-current-tool) 0)))
+    (setq accumulator (+ accumulator dx)) ; Add dx;
+    ;; Figure out how much it has scrolled and how much remains on the
+    ;; left or right of the window.  If a line has already been
+    ;; vscrolled but no hscrolling has happened, don't hscroll, as
+    ;; otherwise it is too easy to hscroll by accident.
+    (if (or (> lines-hscrolled 0)
+            (< lines-vscrolled 1))
+        (while (catch 'again
+                 (let* ((column-width (frame-char-width (window-frame 
window))))
+                   (if (and (< accumulator 0)
+                            (>= (- accumulator) column-width))
+                       (progn
+                         (setq accumulator (+ accumulator column-width))
+                         (scroll-right 1)
+                         (setq lines-hscrolled (1+ lines-hscrolled))
+                         (when (not (zerop accumulator))
+                           ;; If there is still an outstanding amount
+                           ;; to scroll, do this again.
+                           (throw 'again t)))
+                     (when (and (> accumulator 0)
+                                (>= accumulator column-width))
+                       (setq accumulator (- accumulator column-width))
+                       (scroll-left 1)
+                       (setq lines-hscrolled (1+ lines-hscrolled))
+                       (when (not (zerop accumulator))
+                         ;; If there is still an outstanding amount to
+                         ;; scroll, do this again.
+                         (throw 'again t)))))
+                 ;; Scrolling is done.  Move the accumulator back to
+                 ;; touch-screen-current-tool and break out of the
+                 ;; loop.
+                 (setcar (nthcdr 6 touch-screen-current-tool) accumulator)
+                 (setcar (nthcdr 8 touch-screen-current-tool) lines-hscrolled)
+                 nil)))))
+
+(defun touch-screen-scroll (event)
+  "Scroll the window within EVENT, a `touchscreen-scroll' event.
+If `touch-screen-precision-scroll', scroll the window vertically
+by the number of pixels specified within that event.  Else,
+scroll the window by one line for every
+`window-default-line-height' pixels worth of movement.
+
+If EVENT also specifies horizontal motion and no significant
+amount of vertical scrolling has taken place, also scroll the
+window horizontally in conjunction with the number of pixels in
+the event."
+  (interactive "e")
+  (let ((window (nth 1 event))
+        (dx (nth 2 event))
+        (dy (nth 3 event)))
+    (with-selected-window window
+      (touch-screen-handle-scroll dx dy))))
+
+(global-set-key [touchscreen-scroll] #'touch-screen-scroll)
+
+
+
+;;; Drag-to-select gesture.
+
+(defun touch-screen-hold (event)
+  "Handle a long press EVENT.
+Ding and select the window at EVENT, then activate the mark.  If
+`touch-screen-word-select' is enabled, try to select the whole
+word around EVENT; otherwise, set point to the location of EVENT."
+  (interactive "e")
+  (let* ((posn (cadr event))
+         (point (posn-point posn))
+         (window (posn-window posn)))
+    (when (and point
+               ;; Make sure WINDOW is not an inactive minibuffer
+               ;; window.
+               (or (not (eq window
+                            (minibuffer-window
+                             (window-frame window))))
+                   (minibuffer-window-active-p window)))
+      (beep)
+      (select-window window)
+      (if (or (not touch-screen-word-select)
+              (when-let* ((char (char-after point))
+                          (class (char-syntax char)))
+                ;; Don't select words if point isn't inside a word
+                ;; constituent or similar.
+                (not (or (eq class ?w) (eq class ?_)))))
+          (progn
+            ;; Set the mark and activate it.
+            (setq touch-screen-word-select-initial-word nil
+                  touch-screen-word-select-bounds nil)
+            (push-mark point)
+            (goto-char point)
+            (activate-mark))
+        ;; Start word selection by trying to obtain the position
+        ;; around point.
+        (let ((word-start nil)
+              (word-end nil))
+          (unless (posn-object posn)
+            ;; If there's an object under POSN avoid trying to
+            ;; ascertain the bounds of the word surrounding it.
+            (save-excursion
+              (goto-char point)
+              (forward-word-strictly)
+              ;; Set word-end to ZV if there is no word after this
+              ;; one.
+              (setq word-end (point))
+              ;; Now try to move backwards.  Set word-start to BEGV if
+              ;; this word is there.
+              (backward-word-strictly)
+              (setq word-start (point))))
+          ;; Check if word-start and word-end are identical, if there
+          ;; is an object under POSN, or if point is looking at or
+          ;; outside a word.
+          (if (or (eq word-start word-end)
+                  (>= word-start point))
+              (progn
+                ;; If so, clear the bounds and set and activate the
+                ;; mark.
+                (setq touch-screen-word-select-bounds nil
+                      touch-screen-word-select-initial-word nil)
+                (push-mark point)
+                (goto-char point)
+                (activate-mark))
+            ;; Otherwise, select the word.  Move point to either the
+            ;; end or the start of the word, depending on which is
+            ;; closer to EVENT.
+            (let ((diff-beg (- point word-start))
+                  (diff-end (- word-end point))
+                  use-end)
+              (if (> diff-beg diff-end)
+                  ;; Set the point to the end of the word.
+                  (setq use-end t)
+                (if (< diff-end diff-beg)
+                    (setq use-end nil)
+                  ;; POINT is in the middle of the word.  Use its
+                  ;; window coordinates to establish whether or not it
+                  ;; is closer to the start of the word or to the end
+                  ;; of the word.
+                  (let ((posn-beg (posn-at-point word-start))
+                        (posn-end (posn-at-point word-end)))
+                    ;; Give up if there's an object at either of those
+                    ;; positions, or they're not on the same row.
+                    ;; If one of the positions isn't visible, use the
+                    ;; window end.
+                    (if (and posn-beg posn-end
+                             (not (posn-object posn-beg))
+                             (not (posn-object posn-end))
+                             (eq (cdr (posn-col-row posn-beg))
+                                 (cdr (posn-col-row posn-end))))
+                        (setq use-end nil)
+                      ;; Compare the pixel positions.
+                      (setq point (car (posn-x-y posn))
+                            diff-beg (- point (car (posn-x-y posn-beg)))
+                            diff-end (- (car (posn-x-y posn-end)) point))
+                      ;; Now determine whether or not point should be
+                      ;; moved to the end.
+                      (setq use-end (>= diff-beg diff-end))))))
+              (if use-end
+                  (progn
+                    (push-mark word-start)
+                    (activate-mark)
+                    (goto-char word-end))
+                (progn
+                    (push-mark word-end)
+                    (activate-mark)
+                    (goto-char word-start)))
+              ;; Record the bounds of the selected word.
+              (setq touch-screen-word-select-bounds
+                    (cons word-start word-end)
+                    ;; Save this for the benefit of touch-screen-drag.
+                    touch-screen-word-select-initial-word
+                    (cons word-start word-end)))))))))
+
+(defun touch-screen-preview-select ()
+  "Display a preview of the line around point in the echo area.
+Unless the minibuffer is an active or the current line is
+excessively tall, display an indication of the position of point
+and the contents of the visible line around it within the echo
+area.
+
+If the selected window is hscrolled or lines may be truncated,
+attempt to find the extents of the text between column 0 and the
+right most column of the window using `posn-at-x-y'."
+  (interactive)
+  ;; First, establish that the minibuffer isn't active and the line
+  ;; isn't taller than two times the frame character height.
+  (unless (or (> (minibuffer-depth) 0)
+              ;; The code below doesn't adapt well to buffers
+              ;; containing long lines.
+              (long-line-optimizations-p)
+              (let ((window-line-height (window-line-height))
+                    (maximum-height (* 2 (frame-char-height))))
+                (unless window-line-height
+                  ;; `window-line-height' isn't available.
+                  ;; Redisplay first and try to ascertain the height
+                  ;; of the line again.
+                  (redisplay t)
+                  (setq window-line-height (window-line-height)))
+                ;; `window-line-height' might still be unavailable.
+                (and window-line-height
+                     (> (car window-line-height)
+                        maximum-height))))
+    (catch 'hscrolled-away
+      (let ((beg nil) end string y)
+        ;; Detect whether or not the window is hscrolled.  If it
+        ;; is, set beg to the location of the first column
+        ;; instead.
+        (when (> (window-hscroll) 0)
+          (setq y (+ (or (cdr (posn-x-y (posn-at-point)))
+                         (throw 'hscrolled-away t))
+                     (window-header-line-height)
+                     (window-tab-line-height)))
+          (let* ((posn (posn-at-x-y 0 y))
+                 (point (posn-point posn)))
+            (setq beg point)))
+        ;; Check if lines are being truncated; if so, use the
+        ;; character at the end of the window as the end of the
+        ;; text to be displayed, as the visual line may extend
+        ;; past the window.
+        (when (or truncate-lines beg) ; truncate-lines or hscroll.
+          (setq y (or y (+ (or (cdr (posn-x-y (posn-at-point)))
+                               (throw 'hscrolled-away t))
+                           (window-header-line-height)
+                           (window-tab-line-height))))
+          (let* ((posn (posn-at-x-y (1- (window-width nil t)) y))
+                 (point (posn-point posn)))
+            (setq end point)))
+        ;; Now find the rest of the visual line.
+        (save-excursion
+          (unless beg
+            (beginning-of-visual-line)
+            (setq beg (point)))
+          (unless end
+            (end-of-visual-line)
+            (setq end (point))))
+        ;; Obtain a substring containing the beginning of the
+        ;; visual line and the end.
+        (setq string (buffer-substring beg end))
+        ;; Hack `invisible' properties within the new string.
+        ;; Look for each change of the property that is a variable
+        ;; name and replace it with its actual value according to
+        ;; `buffer-invisibility-spec'.
+        (when (listp buffer-invisibility-spec)
+          (let ((index 0)
+                (property (get-text-property 0
+                                             'invisible
+                                             string))
+                index1 invisible)
+            (while index
+              ;; Find the end of this text property.
+              (setq index1 (next-single-property-change index
+                                                        'invisible
+                                                        string))
+              ;; Replace the property with whether or not it is
+              ;; non-nil.
+              (when property
+                (setq invisible nil)
+                (catch 'invisible
+                  (dolist (spec buffer-invisibility-spec)
+                    ;; Process one element of the buffer
+                    ;; invisibility specification.
+                    (if (consp spec)
+                        (when (eq (cdr spec) 't)
+                          ;; (ATOM . t) makes N invisible if N is
+                          ;; equal to ATOM or a list containing
+                          ;; ATOM.
+                          (when (or (eq (car spec) property)
+                                    (and (listp spec)
+                                         (memq (car spec) invisible)))
+                            (throw 'invisible (setq invisible t))))
+                      ;; Otherwise, N is invisible if SPEC is
+                      ;; equal to N.
+                      (when (eq spec property)
+                        (throw 'invisible (setq invisible t))))))
+                (put-text-property index (or index1
+                                             (- end beg))
+                                   'invisible invisible string))
+              ;; Set index to that of the next text property and
+              ;; continue.
+              (setq index index1
+                    property (and index1
+                                  (get-text-property index1
+                                                     'invisible
+                                                     string))))))
+        (let ((resize-mini-windows t) difference width
+              (message-log-max nil))
+          ;; Find the offset of point from beg and display a cursor
+          ;; below.
+          (setq difference (- (point) beg)
+                width (string-pixel-width
+                       (substring string 0 difference)))
+          (message "%s\n%s^" string
+                   (propertize " "
+                               'display (list 'space
+                                              :width (list width)))))
+        nil))))
+
+(defun touch-screen-drag (event)
+  "Handle a drag EVENT by setting the region to its new point.
+If `touch-screen-word-select' and EVENT lies outside the last
+word that was selected, select the word that now contains POINT.
+Scroll the window if EVENT's coordinates are outside its text
+area."
+  (interactive "e")
+  (let* ((posn (cadr event)) ; Position of the tool.
+         (point (posn-point posn)) ; Point of the event.
+         ;; Window where the tap originated.
+         (window (nth 1 touch-screen-current-tool))
+         ;; The currently selected window.  Used to redisplay within
+         ;; the correct window while scrolling.
+         (old-window (selected-window))
+         ;; Whether or not text should be selected word-by-word.
+         (word-select touch-screen-word-select)
+         ;; Cons containing the confines of the word initially
+         ;; selected when the touchpoint was first held down.
+         (initial touch-screen-word-select-initial-word)
+         initial-point)
+    ;; Keep dragging.
+    (with-selected-window window
+      ;; Figure out what character to go to.  If this posn is in the
+      ;; window, go to (posn-point posn).  If not, then go to the line
+      ;; before either window start or window end.
+      (setq initial-point (point))
+      (when (or (not point)
+                (not (eq point initial-point)))
+        (if (and (eq (posn-window posn) window)
+                 point
+                 ;; point must be visible in the window.  If it isn't,
+                 ;; the window must be scrolled.
+                 (pos-visible-in-window-p point))
+            (let* ((bounds touch-screen-word-select-bounds)
+                   (maybe-select-word (or (not touch-screen-word-select)
+                                          (or (not bounds)
+                                              (> point (cdr bounds))
+                                              (< point (car bounds))))))
+              (if (and word-select
+                       ;; point is now outside the last word selected.
+                       maybe-select-word
+                       (not (posn-object posn))
+                       (when-let* ((char (char-after point))
+                                   (class (char-syntax char)))
+                         ;; Don't select words if point isn't inside a
+                         ;; word constituent or similar.
+                         (or (eq class ?w) (eq class ?_))))
+                  ;; Determine the confines of the word containing
+                  ;; POINT.
+                  (let (word-start word-end)
+                    (save-excursion
+                      (goto-char point)
+                      (forward-word-strictly)
+                      ;; Set word-end to ZV if there is no word after
+                      ;; this one.
+                      (setq word-end (point))
+                      ;; Now try to move backwards.  Set word-start to
+                      ;; BEGV if this word is there.
+                      (backward-word-strictly)
+                      (setq word-start (point)))
+                    (let ((mark (mark)))
+                      ;; Extend the region to cover either word-end or
+                      ;; word-start; whether to goto word-end or
+                      ;; word-start is subject to the position of the
+                      ;; mark relative to point.
+                      (if (< word-start mark)
+                          ;; The start of the word is behind mark.
+                          ;; Extend the region towards the start.
+                          (goto-char word-start)
+                        ;; Else, go to the end of the word.
+                        (goto-char word-end))
+                      ;; If point is less than mark, which is is less
+                      ;; than the end of the word that was originally
+                      ;; selected, try to keep it selected by moving
+                      ;; mark there.
+                      (when (and initial (<= (point) mark)
+                                 (< mark (cdr initial)))
+                        (set-mark (cdr initial)))
+                      ;; Do the opposite when the converse is true.
+                      (when (and initial (>= (point) mark)
+                                 (> mark (car initial)))
+                        (set-mark (car initial))))
+                    (if bounds
+                        (progn (setcar bounds word-start)
+                               (setcdr bounds word-end))
+                      (setq touch-screen-word-select-bounds
+                            (cons word-start word-end))))
+                (when maybe-select-word
+                  (goto-char (posn-point posn))
+                  (when initial
+                    ;; If point is less than mark, which is is less
+                    ;; than the end of the word that was originally
+                    ;; selected, try to keep it selected by moving
+                    ;; mark there.
+                    (when (and (<= (point) (mark))
+                               (< (mark) (cdr initial)))
+                      (set-mark (cdr initial)))
+                    ;; Do the opposite when the converse is true.
+                    (when (and (>= (point) (mark))
+                               (> (mark) (car initial)))
+                      (set-mark (car initial))))
+                  (setq touch-screen-word-select-bounds nil)))
+              ;; Finally, display a preview of the line around point
+              ;; if requested by the user.
+              (when (and touch-screen-preview-select
+                         (not (eq (point) initial-point)))
+                (touch-screen-preview-select)))
+          ;; POSN is outside the window.  Scroll accordingly.
+          (let* ((relative-xy
+                  (touch-screen-relative-xy posn window))
+                 (xy (posn-x-y posn))
+                 ;; The height of the window's text area.
+                 (body-height (window-body-height nil t))
+                 ;; This is used to find the character closest to
+                 ;; POSN's column at the bottom of the window.
+                 (height (- body-height
+                            ;; Use the last row of the window, not its
+                            ;; last pixel.
+                            (frame-char-height)))
+                 (midpoint (/ body-height 2))
+                 (scroll-conservatively 101))
+            (cond
+             ((< (cdr relative-xy) midpoint)
+              ;; POSN is before half the window, yet POINT does not
+              ;; exist or is not completely visible within.  Scroll
+              ;; downwards.
+              (ignore-errors
+                ;; Scroll down by a single line.
+                (scroll-down 1)
+                ;; After scrolling, look up the new posn at EVENT's
+                ;; column and go there.
+                (setq posn (posn-at-x-y (car xy) 0)
+                      point (posn-point posn))
+                (if point
+                    (goto-char point)
+                  ;; If there's no buffer position at that column, go
+                  ;; to the window start.
+                  (goto-char (window-start)))
+                ;; If word selection is enabled, now try to keep the
+                ;; initially selected word within the active region.
+                (when word-select
+                  (when initial
+                    ;; If point is less than mark, which is is less
+                    ;; than the end of the word that was originally
+                    ;; selected, try to keep it selected by moving
+                    ;; mark there.
+                    (when (and (<= (point) (mark))
+                               (< (mark) (cdr initial)))
+                      (set-mark (cdr initial)))
+                    ;; Do the opposite when the converse is true.
+                    (when (and (>= (point) (mark))
+                               (> (mark) (car initial)))
+                      (set-mark (car initial))))
+                  (setq touch-screen-word-select-bounds nil))
+                ;; Display a preview of the line now around point if
+                ;; requested by the user.
+                (when touch-screen-preview-select
+                  (touch-screen-preview-select))
+                ;; Select old-window, so that redisplay doesn't
+                ;; display WINDOW as selected if it isn't already.
+                (with-selected-window old-window
+                  ;; Now repeat this every `mouse-scroll-delay' until
+                  ;; input becomes available, but scroll down a few
+                  ;; more lines.
+                  (while (sit-for mouse-scroll-delay)
+                    ;; Select WINDOW again.
+                    (with-selected-window window
+                      ;; Keep scrolling down until input becomes
+                      ;; available.
+                      (scroll-down 4)
+                      ;; After scrolling, look up the new posn at
+                      ;; EVENT's column and go there.
+                      (setq posn (posn-at-x-y (car xy) 0)
+                            point (posn-point posn))
+                      (if point
+                          (goto-char point)
+                        ;; If there's no buffer position at that
+                        ;; column, go to the window start.
+                        (goto-char (window-start)))
+                      ;; If word selection is enabled, now try to keep
+                      ;; the initially selected word within the active
+                      ;; region.
+                      (when word-select
+                        (when initial
+                          ;; If point is less than mark, which is is
+                          ;; less than the end of the word that was
+                          ;; originally selected, try to keep it
+                          ;; selected by moving mark there.
+                          (when (and (<= (point) (mark))
+                                     (< (mark) (cdr initial)))
+                            (set-mark (cdr initial)))
+                          ;; Do the opposite when the converse is true.
+                          (when (and (>= (point) (mark))
+                                     (> (mark) (car initial)))
+                            (set-mark (car initial))))
+                        (setq touch-screen-word-select-bounds nil))
+                      ;; Display a preview of the line now around
+                      ;; point if requested by the user.
+                      (when touch-screen-preview-select
+                        (touch-screen-preview-select))))))
+              (setq touch-screen-word-select-bounds nil))
+             ((>= (cdr relative-xy) midpoint)
+              ;; Default to scrolling upwards even if POSN is still
+              ;; within the confines of the window.  If POINT is
+              ;; partially visible, and the branch above hasn't been
+              ;; taken it must be somewhere at the bottom of the
+              ;; window, so scroll downwards.
+              (ignore-errors
+                ;; Scroll up by a single line.
+                (scroll-up 1)
+                ;; After scrolling, look up the new posn at EVENT's
+                ;; column and go there.
+                (setq posn (posn-at-x-y (car xy) height)
+                      point (posn-point posn))
+                (if point
+                    (goto-char point)
+                  ;; If there's no buffer position at that column, go
+                  ;; to the window start.
+                  (goto-char (window-end nil t)))
+                ;; If word selection is enabled, now try to keep
+                ;; the initially selected word within the active
+                ;; region.
+                (when word-select
+                  (when initial
+                    ;; If point is less than mark, which is is less
+                    ;; than the end of the word that was originally
+                    ;; selected, try to keep it selected by moving
+                    ;; mark there.
+                    (when (and (<= (point) (mark))
+                               (< (mark) (cdr initial)))
+                      (set-mark (cdr initial)))
+                    ;; Do the opposite when the converse is true.
+                    (when (and (>= (point) (mark))
+                               (> (mark) (car initial)))
+                      (set-mark (car initial))))
+                  (setq touch-screen-word-select-bounds nil))
+                ;; Display a preview of the line now around point if
+                ;; requested by the user.
+                (when touch-screen-preview-select
+                  (touch-screen-preview-select))
+                ;; Select old-window, so that redisplay doesn't
+                ;; display WINDOW as selected if it isn't already.
+                (with-selected-window old-window
+                  ;; Now repeat this every `mouse-scroll-delay' until
+                  ;; input becomes available, but scroll down a few
+                  ;; more lines.
+                  (while (sit-for mouse-scroll-delay)
+                    ;; Select WINDOW again.
+                    (with-selected-window window
+                      ;; Keep scrolling down until input becomes
+                      ;; available.
+                      (scroll-up 4)
+                      ;; After scrolling, look up the new posn at
+                      ;; EVENT's column and go there.
+                      (setq posn (posn-at-x-y (car xy) height)
+                            point (posn-point posn))
+                      (if point
+                          (goto-char point)
+                        ;; If there's no buffer position at that
+                        ;; column, go to the window start.
+                        (goto-char (window-end nil t)))
+                      ;; If word selection is enabled, now try to keep
+                      ;; the initially selected word within the active
+                      ;; region.
+                      (when word-select
+                        (when initial
+                          ;; If point is less than mark, which is is less
+                          ;; than the end of the word that was originally
+                          ;; selected, try to keep it selected by moving
+                          ;; mark there.
+                          (when (and (<= (point) (mark))
+                                     (< (mark) (cdr initial)))
+                            (set-mark (cdr initial)))
+                          ;; Do the opposite when the converse is true.
+                          (when (and (>= (point) (mark))
+                                     (> (mark) (car initial)))
+                            (set-mark (car initial))))
+                        (setq touch-screen-word-select-bounds nil))
+                      ;; Display a preview of the line now around
+                      ;; point if requested by the user.
+                      (when touch-screen-preview-select
+                        (touch-screen-preview-select))))))))))))))
+
+(defun touch-screen-restart-drag (event)
+  "Restart dragging to select text.
+Set point to the location of EVENT within its window while
+keeping the bounds of the region intact, and set up state for
+`touch-screen-drag'."
+  (interactive "e")
+  (let* ((posn (event-start event))
+         (window (posn-window posn))
+         (point (posn-point posn)))
+    (with-selected-window window
+      (let ((current-point (point))
+            (current-mark (mark)))
+        ;; Ensure that mark and point haven't changed since EVENT was
+        ;; generated, and the region is still active.
+        (when (or (eq point current-point)
+                  (eq point current-mark)
+                  (region-active-p))
+          (when (eq point current-mark)
+            ;; Exchange point and mark.
+            (exchange-point-and-mark))
+          ;; Clear the state necessary to set up dragging.  Don't try
+          ;; to select entire words immediately after dragging starts,
+          ;; to allow for fine grained selection inside a word.
+          (setq touch-screen-word-select-bounds nil
+                touch-screen-word-select-initial-word nil))))))
+
+(global-set-key [touchscreen-hold] #'touch-screen-hold)
+(global-set-key [touchscreen-drag] #'touch-screen-drag)
+(global-set-key [touchscreen-restart-drag] #'touch-screen-restart-drag)
+
+
+
+;; Touch screen event translation.  The code here translates raw touch
+;; screen events into `touchscreen-scroll' events and mouse events in
+;; a ``DWIM'' fashion, consulting the keymaps at the position of the
+;; mouse event to determine the best course of action, while also
+;; recognizing drag-to-select and other gestures.
+
+(defun touch-screen-handle-timeout (arg)
+  "Start the touch screen timeout or handle it depending on ARG.
+When ARG is nil, start the `touch-screen-current-timer' to go off
+in `touch-screen-delay' seconds, and call this function with ARG
+t.
+
+When ARG is t, set the fourth element of
+`touch-screen-current-tool' to `held', and generate a
+`touchscreen-hold' event at the original position of that tool."
+  (if (not arg)
+      ;; Cancel the touch screen long-press timer, if it is still
+      ;; there by any chance.
+      (progn
+        (when touch-screen-current-timer
+          (cancel-timer touch-screen-current-timer))
+        (setq touch-screen-current-timer
+              (run-at-time touch-screen-delay nil
+                           #'touch-screen-handle-timeout
+                           t)))
+    ;; Set touch-screen-current-timer to nil.
+    (setq touch-screen-current-timer nil)
+    (when touch-screen-current-tool
+      ;; Set the state to `held'.
+      (setcar (nthcdr 3 touch-screen-current-tool) 'held)
+      ;; Generate an input event at the original position of the mark.
+      ;; This assumes that the timer is running within
+      ;; `touch-screen-translate-touch'.
+      (let ((posn (nth 4 touch-screen-current-tool)))
+        (throw 'input-event (list 'touchscreen-hold posn))))))
+
+(defun touch-screen-handle-point-update (point)
+  "Notice that the touch point POINT has changed position.
+Perform the editing operations or throw to the input translation
+function with an input event tied to any gesture that is
+recognized.
+
+POINT must be the touch point currently being tracked as
+`touch-screen-current-tool'.
+
+If the fourth element of `touch-screen-current-tool' is nil, then
+the touch has just begun.  Determine how much POINT has moved.
+If POINT has moved upwards or downwards by a significant amount,
+then set the fourth element to `scroll'.  Then, generate a
+`touchscreen-scroll' event with the window that POINT was
+initially placed upon, and pixel deltas describing how much point
+has moved relative to its previous position in the X and Y axes.
+
+If the fourth element of `touchscreen-current-tool' is `scroll',
+then generate a `touchscreen-scroll' event with the window that
+qPOINT was initially placed upon, and pixel deltas describing how
+much point has moved relative to its previous position in the X
+and Y axes.
+
+If the fourth element of `touch-screen-current-tool' is
+`mouse-drag' and `track-mouse' is non-nil, then generate a
+`mouse-movement' event with the position of POINT.
+
+If the fourth element of `touch-screen-current-tool' is `held',
+then the touch has been held down for some time.  If motion
+happens, set the field to `drag'.  Then, generate a
+`touchscreen-drag' event.
+
+If the fourth element of `touch-screen-current-tool' is
+`restart-drag', set the field to `drag' and generate a
+`touchscreen-drag'.
+
+If the fourth element of `touch-screen-current-tool' is `drag',
+then move point to the position of POINT."
+  (let ((window (nth 1 touch-screen-current-tool))
+        (what (nth 3 touch-screen-current-tool)))
+    (cond ((null what)
+           (let* ((posn (cdr point))
+                  (last-posn (nth 2 touch-screen-current-tool))
+                  (original-posn (nth 4 touch-screen-current-tool))
+                  ;; Now get the position of X and Y relative to
+                  ;; WINDOW.
+                  (relative-xy
+                   (touch-screen-relative-xy posn window))
+                  (col (and (not (posn-area original-posn))
+                            (car (posn-col-row original-posn
+                                               (posn-window posn)))))
+                  ;; Don't start horizontal scrolling if the touch
+                  ;; point originated within two columns of the window
+                  ;; edges, as systems like Android use those two
+                  ;; columns to implement gesture navigation.
+                  (diff-x-eligible
+                   (and col (> col 2)
+                        (< col (- (window-width window) 2))))
+                  (diff-x (- (car last-posn) (car relative-xy)))
+                  (diff-y (- (cdr last-posn) (cdr relative-xy))))
+             (when (or (> diff-y 10)
+                       (and diff-x-eligible
+                            (> diff-x (frame-char-width)))
+                       (< diff-y -10)
+                       (and diff-x-eligible
+                            (< diff-x (- (frame-char-width)))))
+               (setcar (nthcdr 3 touch-screen-current-tool)
+                       'scroll)
+               (setcar (nthcdr 2 touch-screen-current-tool)
+                       relative-xy)
+               ;; Cancel the touch screen long-press timer, if it is
+               ;; still there by any chance.
+               (when touch-screen-current-timer
+                 (cancel-timer touch-screen-current-timer)
+                 (setq touch-screen-current-timer nil))
+               ;; Generate a `touchscreen-scroll' event with `diff-x'
+               ;; and `diff-y'.
+               (throw 'input-event
+                      (list 'touchscreen-scroll
+                            window diff-x diff-y)))))
+          ((eq what 'scroll)
+           ;; Cancel the touch screen long-press timer, if it is still
+           ;; there by any chance.
+           (when touch-screen-current-timer
+             (cancel-timer touch-screen-current-timer)
+             (setq touch-screen-current-timer nil))
+           (let* ((posn (cdr point))
+                  (last-posn (nth 2 touch-screen-current-tool))
+                  ;; Now get the position of X and Y relative to
+                  ;; WINDOW.
+                  (relative-xy
+                   (touch-screen-relative-xy posn window))
+                  (diff-x (- (car last-posn) (car relative-xy)))
+                  (diff-y (- (cdr last-posn) (cdr relative-xy))))
+             (setcar (nthcdr 3 touch-screen-current-tool)
+                     'scroll)
+             (setcar (nthcdr 2 touch-screen-current-tool)
+                     relative-xy)
+             (unless (and (zerop diff-x) (zerop diff-y))
+               (throw 'input-event
+                      ;; Generate a `touchscreen-scroll' event with
+                      ;; `diff-x' and `diff-y'.
+                      (list 'touchscreen-scroll
+                            window diff-x diff-y)))))
+          ((eq what 'mouse-drag)
+           ;; There was a `down-mouse-1' event bound at the starting
+           ;; point of the event.  Generate a mouse-motion event if
+           ;; mouse movement is being tracked.
+           (when track-mouse
+             (throw 'input-event (list 'mouse-movement
+                                       (cdr point)))))
+          ((eq what 'held)
+           (let* ((posn (cdr point)))
+             ;; Now start dragging.
+             (setcar (nthcdr 3 touch-screen-current-tool)
+                     'drag)
+             ;; Generate a (touchscreen-drag POSN) event.
+             ;; `touchscreen-hold' was generated when the timeout
+             ;; fired.
+             (throw 'input-event (list 'touchscreen-drag posn))))
+          ((eq what 'restart-drag)
+           (let* ((posn (cdr point)))
+             ;; Now start dragging.
+             (setcar (nthcdr 3 touch-screen-current-tool)
+                     'drag)
+             ;; Generate a (touchscreen-drag POSN) event.
+             ;; `touchscreen-restart-drag' was generated when the
+             ;; timeout fired.
+             (throw 'input-event (list 'touchscreen-drag posn))))
+          ((eq what 'drag)
+           (let* ((posn (cdr point)))
+             ;; Generate a (touchscreen-drag POSN) event.
+             (throw 'input-event (list 'touchscreen-drag posn)))))))
+
+(defun touch-screen-window-selection-changed (frame)
+  "Notice that FRAME's selected window has changed.
+Cancel any timer that is supposed to hide the keyboard in
+response to the minibuffer being closed."
+  (with-selected-frame frame
+    (unless (and (or buffer-read-only
+                     (get-text-property (point) 'read-only))
+                 ;; Don't hide the on-screen keyboard if it's always
+                 ;; supposed to be displayed.
+                 (not touch-screen-display-keyboard))
+      ;; Prevent hiding the minibuffer from hiding the on screen
+      ;; keyboard.
+      (when minibuffer-on-screen-keyboard-timer
+        (cancel-timer minibuffer-on-screen-keyboard-timer)
+        (setq minibuffer-on-screen-keyboard-timer nil)))))
+
+(defun touch-screen-handle-point-up (point prefix)
+  "Notice that POINT has been removed from the screen.
+POINT should be the point currently tracked as
+`touch-screen-current-tool'.
+PREFIX should be a virtual function key used to look up key
+bindings.
+
+If the fourth element of `touch-screen-current-tool' is nil or
+`restart-drag', move point to the position of POINT, selecting
+the window under POINT as well, and deactivate the mark; if there
+is a button or link at POINT, call the command bound to `mouse-2'
+there.  Otherwise, call the command bound to `mouse-1'.
+
+If the fourth element of `touch-screen-current-tool' is
+`mouse-drag', then generate either a `mouse-1' or a
+`drag-mouse-1' event depending on how far the position of POINT
+is from the starting point of the touch.
+
+If the fourth element of `touch-screen-current-tool' is
+`mouse-1-menu', then generate a `down-mouse-1' event at the
+original position of the tool to display its bound keymap as a
+menu.
+
+If the command being executed is listed in
+`touch-screen-set-point-commands' also display the on-screen
+keyboard if the current buffer and the character at the new point
+is not read-only."
+  (let ((what (nth 3 touch-screen-current-tool))
+        (posn (cdr point)) window point)
+    (cond ((or (null what)
+               ;; If dragging has been restarted but the touch point
+               ;; hasn't been moved, translate the sequence into a
+               ;; regular mouse click.
+               (eq what 'restart-drag))
+           (when (windowp (posn-window posn))
+             (setq point (posn-point posn)
+                   window (posn-window posn))
+             ;; Select the window that was tapped given that it isn't
+             ;; an inactive minibuffer window.
+             (when (or (not (eq window
+                                (minibuffer-window
+                                 (window-frame window))))
+                       (minibuffer-window-active-p window))
+               (select-window window))
+             ;; Now simulate a mouse click there.  If there is a link
+             ;; or a button, use mouse-2 to push it.
+             (let* ((event (list (if (or (mouse-on-link-p posn)
+                                         (and point (button-at point)))
+                                     'mouse-2
+                                   'mouse-1)
+                                 posn))
+                    ;; Look for the command bound to this event.
+                    (command (key-binding (if prefix
+                                                 (vector prefix
+                                                         (car event))
+                                            (vector (car event)))
+                                          t nil posn)))
+               (deactivate-mark)
+               (when point
+                 ;; This is necessary for following links.
+                 (goto-char point))
+               ;; Figure out if the on screen keyboard needs to be
+               ;; displayed.
+               (when command
+                 (if (memq command touch-screen-set-point-commands)
+                     (if touch-screen-translate-prompt
+                         ;; When a `mouse-set-point' command is
+                         ;; encountered and
+                         ;; `touch-screen-handle-touch' is being
+                         ;; called from the keyboard command loop,
+                         ;; call it immediately so that point is set
+                         ;; prior to the on screen keyboard being
+                         ;; displayed.
+                         (call-interactively command nil
+                                             (vector event))
+                       (if (and (or (not buffer-read-only)
+                                    touch-screen-display-keyboard)
+                                ;; Detect the splash screen and avoid
+                                ;; displaying the on screen keyboard
+                                ;; there.
+                                (not (equal (buffer-name) "*GNU Emacs*")))
+                           ;; Once the on-screen keyboard has been
+                           ;; opened, add
+                           ;; `touch-screen-window-selection-changed'
+                           ;; as a window selection change function
+                           ;; This then prevents it from being hidden
+                           ;; after exiting the minibuffer.
+                           (progn
+                             (add-hook 'window-selection-change-functions
+                                       #'touch-screen-window-selection-changed)
+                             (frame-toggle-on-screen-keyboard (selected-frame)
+                                                              nil))
+                         ;; Otherwise, hide the on screen keyboard
+                         ;; now.
+                         (frame-toggle-on-screen-keyboard (selected-frame) t))
+                       ;; But if it's being called from `describe-key'
+                       ;; or some such, return it as a key sequence.
+                       (throw 'input-event event)))
+                 ;; If not, return the event.
+                 (throw 'input-event event)))))
+          ((eq what 'mouse-drag)
+           ;; Generate a corresponding `mouse-1' event.
+           (let* ((new-window (posn-window posn))
+                  (new-point (posn-point posn))
+                  (old-posn (nth 4 touch-screen-current-tool))
+                  (old-window (posn-window posn))
+                  (old-point (posn-point posn)))
+             (throw 'input-event
+                    ;; If the position of the touch point hasn't
+                    ;; changed, or it doesn't start or end on a
+                    ;; window...
+                    (if (and (not old-point) (not new-point))
+                        ;; Should old-point and new-point both equal
+                        ;; nil, compare the posn areas and nominal
+                        ;; column position.  If either are different,
+                        ;; generate a drag event.
+                        (let ((new-col-row (posn-col-row posn))
+                              (new-area (posn-area posn))
+                              (old-col-row (posn-col-row old-posn))
+                              (old-area (posn-area old-posn)))
+                          (if (and (equal new-col-row old-col-row)
+                                   (eq new-area old-area))
+                              ;; ... generate a mouse-1 event...
+                              (list 'mouse-1 posn)
+                            ;; ... otherwise, generate a drag-mouse-1 event.
+                            (list 'drag-mouse-1 (cons old-window
+                                                      old-posn)
+                                  (cons new-window posn))))
+                      (if (and (eq new-window old-window)
+                               (eq new-point old-point)
+                               (windowp new-window)
+                               (windowp old-window))
+                          ;; ... generate a mouse-1 event...
+                          (list 'mouse-1 posn)
+                        ;; ... otherwise, generate a drag-mouse-1 event.
+                        (list 'drag-mouse-1 (cons old-window
+                                                  old-posn)
+                              (cons new-window posn)))))))
+          ((eq what 'mouse-1-menu)
+           ;; Generate a `down-mouse-1' event at the position the tap
+           ;; took place.
+           (throw 'input-event
+                  (list 'down-mouse-1
+                        (nth 4 touch-screen-current-tool)))))))
+
+(defun touch-screen-handle-touch (event prefix &optional interactive)
+  "Handle a single touch EVENT, and perform associated actions.
+EVENT can either be a `touchscreen-begin', `touchscreen-update' or
+`touchscreen-end' event.
+PREFIX is either nil, or a symbol specifying a virtual function
+key to apply to EVENT.
+
+If INTERACTIVE, execute the command associated with any event
+generated instead of throwing `input-event'.  Otherwise, throw
+`input-event' with a single input event if that event should take
+the place of EVENT within the key sequence being translated, or
+`nil' if all tools have been released."
+  (interactive "e\ni\np")
+  (if interactive
+      ;; Called interactively (probably from wid-edit.el.)
+      ;; Add any event generated to `unread-command-events'.
+      (let ((event1
+             (let ((current-key-remap-sequence (vector event)))
+               (touch-screen-translate-touch nil))))
+        (when (vectorp event1)
+          (setq unread-command-events
+                (nconc unread-command-events
+                       (nreverse (append event1 nil))))))
+    (cond
+     ((eq (car event) 'touchscreen-begin)
+      ;; A tool was just pressed against the screen.  Figure out the
+      ;; window where it is and make it the tool being tracked on the
+      ;; window.
+      (let* ((touchpoint (caadr event))
+             (position (cdadr event))
+             (window (posn-window position))
+             (point (posn-point position))
+             binding tool-list)
+        ;; Cancel the touch screen timer, if it is still there by any
+        ;; chance.
+        (when touch-screen-current-timer
+          (cancel-timer touch-screen-current-timer)
+          (setq touch-screen-current-timer nil))
+        ;; Replace any previously ongoing gesture.  If POSITION has no
+        ;; window or position, make it nil instead.
+        (setq tool-list (and (windowp window)
+                             (list touchpoint window
+                                   (posn-x-y position)
+                                   nil position
+                                   nil nil nil nil))
+              touch-screen-current-tool tool-list)
+
+        ;; Select the window underneath the event as the checks below
+        ;; will look up keymaps and markers inside its buffer.
+        (save-selected-window
+          ;; Check if `touch-screen-extend-selection' is enabled, the
+          ;; tap lies on the point or the mark, and the region is
+          ;; active.  If that's the case, set the fourth element of
+          ;; `touch-screen-current-tool' to `restart-drag', then
+          ;; generate a `touchscreen-restart-drag' event.
+          (when tool-list
+            ;; tool-list is always non-nil where the selected window
+            ;; matters.
+            (select-window window)
+            (when (and touch-screen-extend-selection
+                       (or (eq point (point))
+                           (eq point (mark)))
+                       (region-active-p)
+                       ;; Only restart drag-to-select if the tap falls
+                       ;; on the same row as the selection.  This
+                       ;; prevents dragging from starting if the tap
+                       ;; is below the last window line with text and
+                       ;; `point' is at ZV, as the user most likely
+                       ;; meant to scroll the window instead.
+                       (when-let* ((posn-point (posn-at-point point))
+                                   (posn-row (cdr (posn-col-row posn-point))))
+                         (eq (cdr (posn-col-row position)) posn-row)))
+              ;; Indicate that a drag is about to restart.
+              (setcar (nthcdr 3 tool-list) 'restart-drag)
+              ;; Generate the `restart-drag' event.
+              (throw 'input-event (list 'touchscreen-restart-drag
+                                        position))))
+          ;; Determine if there is a command bound to `down-mouse-1'
+          ;; at the position of the tap and that command is not a
+          ;; command whose functionality is replaced by the long-press
+          ;; mechanism.  If so, set the fourth element of
+          ;; `touch-screen-current-tool' to `mouse-drag' and generate
+          ;; an emulated `mouse-1' event.
+          ;;
+          ;; If the command in question is a keymap, set that element
+          ;; to `mouse-1-menu' instead of `mouse-drag', and don't
+          ;; generate a `down-mouse-1' event immediately.  Instead,
+          ;; wait for the touch point to be released.
+          (if (and tool-list
+                   (and (setq binding
+                              (key-binding (if prefix
+                                               (vector prefix
+                                                       'down-mouse-1)
+                                             [down-mouse-1])
+                                           t nil position))
+                        (not (and (symbolp binding)
+                                  (get binding 'ignored-mouse-command)))))
+              (if (or (keymapp binding)
+                      (and (symbolp binding)
+                           (get binding 'mouse-1-menu-command)))
+                  ;; binding is a keymap, or a command that does
+                  ;; almost the same thing.  If a `mouse-1' event is
+                  ;; generated after the keyboard command loop
+                  ;; displays it as a menu, that event could cause
+                  ;; unwanted commands to be run.  Set what to
+                  ;; `mouse-1-menu' instead and wait for the up event
+                  ;; to display the menu.
+                  (setcar (nthcdr 3 tool-list) 'mouse-1-menu)
+                (progn (setcar (nthcdr 3 tool-list) 'mouse-drag)
+                       (throw 'input-event (list 'down-mouse-1 position))))
+            (and point
+                 ;; Start the long-press timer.
+                 (touch-screen-handle-timeout nil))))))
+     ((eq (car event) 'touchscreen-update)
+      (unless touch-screen-current-tool
+        ;; If a stray touchscreen-update event arrives (most likely
+        ;; from the menu bar), stop translating this sequence.
+        (throw 'input-event nil))
+      ;; The positions of tools currently pressed against the screen
+      ;; have changed.  If there is a tool being tracked as part of a
+      ;; gesture, look it up in the list of tools.
+      (let ((new-point (assq (car touch-screen-current-tool)
+                             (cadr event))))
+        (when new-point
+          (touch-screen-handle-point-update new-point))))
+     ((eq (car event) 'touchscreen-end)
+      ;; A tool has been removed from the screen.  If it is the tool
+      ;; currently being tracked, clear `touch-screen-current-tool'.
+      (when (eq (caadr event) (car touch-screen-current-tool))
+        ;; Cancel the touch screen long-press timer, if it is still
+        ;; there by any chance.
+        (when touch-screen-current-timer
+          (cancel-timer touch-screen-current-timer)
+          (setq touch-screen-current-timer nil))
+        (unwind-protect
+            ;; Don't perform any actions associated with releasing the
+            ;; tool if the touch sequence was intercepted by another
+            ;; program.
+            (unless (caddr event)
+              (touch-screen-handle-point-up (cadr event) prefix))
+          ;; Make sure the tool list is cleared even if
+          ;; `touch-screen-handle-point-up' throws.
+          (setq touch-screen-current-tool nil)))
+      ;; Throw to the key translation function.
+      (throw 'input-event nil)))))
+
+;; Mark `mouse-drag-region' as ignored for the purposes of mouse click
+;; emulation.
+
+(put 'mouse-drag-region 'ignored-mouse-command t)
+
+(defun touch-screen-translate-touch (prompt)
+  "Translate touch screen events into a sequence of mouse events.
+PROMPT is the prompt string given to `read-key-sequence', or nil
+if this function is being called from the keyboard command loop.
+Value is a new key sequence.
+
+Read the touch screen event within `current-key-remap-sequence'
+and give it to `touch-screen-handle-touch'.  Return any key
+sequence signaled.
+
+If `touch-screen-handle-touch' does not signal for an event to be
+returned after the last element of the key sequence is read,
+continue reading touch screen events until
+`touch-screen-handle-touch' signals.  Return a sequence
+consisting of the first event encountered that is not a touch
+screen event.
+
+In addition to non-touchscreen events read, key sequences
+returned may contain any one of the following events:
+
+  (touchscreen-scroll WINDOW DX DY)
+
+where WINDOW specifies a window to scroll, and DX and DY are
+integers describing how many pixels to be scrolled horizontally
+and vertically,
+
+  (touchscreen-hold POSN)
+  (touchscreen-drag POSN)
+
+where POSN is the position of the long-press or touchpoint
+motion,
+
+  (touchscreen-restart-drag POSN)
+
+where POSN is the position of the tap,
+
+  (down-mouse-1 POSN)
+  (drag-mouse-1 POSN)
+
+where POSN is the position of the mouse button press or click,
+
+  (mouse-1 POSN)
+  (mouse-2 POSN)
+
+where POSN is the position of the mouse click, either `mouse-2'
+if POSN is on a link or a button, or `mouse-1' otherwise."
+  (unwind-protect
+      ;; Save the virtual function key if this is a mode line event.
+      (let* ((prefix-specified
+              ;; Virtual prefix keys can be nil for events that fall
+              ;; outside a frame or within its internal border.
+              (> (length current-key-remap-sequence) 1))
+             (prefix (and prefix-specified
+                          (aref current-key-remap-sequence 0)))
+             (touch-screen-translate-prompt prompt)
+             (event (catch 'input-event
+                      ;; First, process the one event already within
+                      ;; `current-key-remap-sequence'.
+                      (touch-screen-handle-touch
+                       (aref current-key-remap-sequence
+                             (if prefix-specified 1 0))
+                       prefix)
+                      ;; Next, continue reading input events.
+                      (while t
+                        (let ((event1 (read-event)))
+                          ;; If event1 is a virtual function key, make
+                          ;; it the new prefix.
+                          (if (memq event1 '(mode-line tab-line nil
+                                             vertical-line
+                                             header-line tool-bar tab-bar
+                                             left-fringe right-fringe
+                                             left-margin right-margin
+                                             right-divider bottom-divider))
+                              (setq prefix event1)
+                            ;; If event1 is not a touch screen event,
+                            ;; return it.
+                            (if (not (memq (car-safe event1)
+                                           '(touchscreen-begin
+                                             touchscreen-end
+                                             touchscreen-update)))
+                                (throw 'input-event event1)
+                              ;; Process this event as well.
+                              (touch-screen-handle-touch event1 prefix))))))))
+        ;; Return a key sequence consisting of event
+        ;; or an empty vector if it is nil, meaning that
+        ;; no key events have been translated.
+        (if event (or (and prefix (consp event)
+                           ;; Only generate virtual function keys for
+                           ;; mouse events.
+                           (memq (car event)
+                                 '(down-mouse-1 mouse-1
+                                   mouse-2 mouse-movement))
+                           ;; If this is a mode line event, then
+                           ;; generate the appropriate function key.
+                           (vector prefix event))
+                      (vector event))
+          ""))
+    ;; Cancel the touch screen long-press timer, if it is still there
+    ;; by any chance.  If the timer is to operate correctly, it must
+    ;; fire within the catch block above.
+    (when touch-screen-current-timer
+      (cancel-timer touch-screen-current-timer)
+      (setq touch-screen-current-timer nil))))
+
+(define-key function-key-map [touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [mode-line touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [mode-line touchscreen-end]
+            #'touch-screen-translate-touch)
+
+;; These are used to translate events sent from the internal border or
+;; from outside the frame.
+
+(define-key function-key-map [nil touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [nil touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [header-line touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [header-line touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [bottom-divider touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [bottom-divider touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-divider touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-divider touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-divider touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-divider touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [left-fringe touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [left-fringe touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-fringe touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-fringe touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [left-margin touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [left-margin touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-margin touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-margin touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [tool-bar touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [tool-bar touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [tab-bar touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [tab-bar touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [tab-line touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [tab-line touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [vertical-line touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [vertical-line touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [nil touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [nil touchscreen-end]
+            #'touch-screen-translate-touch)
+
+
+;; Exports.  These functions are intended for use externally.
+
+(defun touch-screen-track-tap (event &optional update data)
+  "Track a single tap starting from EVENT.
+EVENT should be a `touchscreen-begin' event.
+
+Read touch screen events until a `touchscreen-end' event is
+received with the same ID as in EVENT.  If UPDATE is non-nil and
+a `touchscreen-update' event is received in the mean time and
+contains a touch point with the same ID as in EVENT, call UPDATE
+with that event and DATA.
+
+Return nil immediately if any other kind of event is received;
+otherwise, return t once the `touchscreen-end' event arrives."
+  (let ((disable-inhibit-text-conversion t))
+    (catch 'finish
+      (while t
+        (let ((new-event (read-event nil)))
+          (cond
+           ((eq (car-safe new-event) 'touchscreen-update)
+            (when (and update (assq (caadr event) (cadr new-event)))
+              (funcall update new-event data)))
+           ((eq (car-safe new-event) 'touchscreen-end)
+            (throw 'finish
+                   ;; Now determine whether or not the `touchscreen-end'
+                   ;; event has the same ID as EVENT.  If it doesn't,
+                   ;; then this is another touch, so return nil.
+                   (eq (caadr event) (caadr new-event))))
+           (t (throw 'finish nil))))))))
+
+(defun touch-screen-track-drag (event update &optional data)
+  "Track a single drag starting from EVENT.
+EVENT should be a `touchscreen-begin' event.
+
+Read touch screen events until a `touchscreen-end' event is
+received with the same ID as in EVENT.  For each
+`touchscreen-update' event received in the mean time containing a
+touch point with the same ID as in EVENT, call UPDATE with the
+touch point in event and DATA, once the touch point has moved
+significantly by at least 5 pixels from where it was in EVENT.
+
+Return nil immediately if any other kind of event is received;
+otherwise, return either t or `no-drag' once the
+`touchscreen-end' event arrives; return `no-drag' returned if the
+touch point in EVENT did not move significantly, and t otherwise."
+  (let ((return-value 'no-drag)
+        (start-xy (touch-screen-relative-xy (cdadr event)
+                                            'frame))
+        (disable-inhibit-text-conversion t))
+    (catch 'finish
+      (while t
+        (let ((new-event (read-event nil)))
+          (cond
+           ((eq (car-safe new-event) 'touchscreen-update)
+            (when-let* ((tool (assq (caadr event) (nth 1 new-event)))
+                        (xy (touch-screen-relative-xy (cdr tool) 'frame)))
+              (when (or (> (- (car xy) (car start-xy)) 5)
+                        (< (- (car xy) (car start-xy)) -5)
+                        (> (- (cdr xy) (cdr start-xy)) 5)
+                        (< (- (cdr xy) (cdr start-xy)) -5))
+                (setq return-value t))
+              (when (and update tool (eq return-value t))
+                (funcall update new-event data))))
+           ((eq (car-safe new-event) 'touchscreen-end)
+            (throw 'finish
+                   ;; Now determine whether or not the `touchscreen-end'
+                   ;; event has the same ID as EVENT.  If it doesn't,
+                   ;; then this is another touch, so return nil.
+                   (and (eq (caadr event) (caadr new-event))
+                        return-value)))
+           (t (throw 'finish nil))))))))
+
+
+
+;;; Event handling exports.  These functions are intended for use by
+;;; Lisp commands bound to touch screen gesture events.
+
+(defun touch-screen-inhibit-drag ()
+  "Inhibit subsequent `touchscreen-drag' events from being sent.
+Prevent `touchscreen-drag' and translated mouse events from being
+sent until the touch sequence currently being translated ends.
+Must be called from a command bound to a `touchscreen-hold' or
+`touchscreen-drag' event."
+  (let* ((tool touch-screen-current-tool)
+         (current-what (nth 4 tool)))
+    ;; Signal an error if no hold or drag is in progress.
+    (when (and (not (eq current-what 'hold)
+                    (eq current-what 'drag)))
+      (error "Calling `touch-screen-inhibit-drag' outside hold or drag"))
+    ;; Now set the fourth element of tool to `command-inhibit'.
+    (setcar (nthcdr 3 tool) 'command-inhibit)))
+
+
+
+(provide 'touch-screen)
+
+;;; touch-screen ends here
diff --git a/lisp/transient.el b/lisp/transient.el
index 78496843284..52c21871548 100644
--- a/lisp/transient.el
+++ b/lisp/transient.el
@@ -6,7 +6,7 @@
 ;; URL: https://github.com/magit/transient
 ;; Keywords: extensions
 
-;; Package-Version: 0.4.1
+;; Package-Version: 0.4.3
 ;; Package-Requires: ((emacs "26.1"))
 
 ;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -67,6 +67,7 @@
 
 (defvar display-line-numbers) ; since Emacs 26.1
 (defvar Man-notify-method)
+(defvar pp-default-function) ; since Emacs 29.1
 
 (defmacro transient--with-emergency-exit (&rest body)
   (declare (indent defun))
@@ -565,7 +566,9 @@ the previous prefix."
   (setq list (cl-sort (copy-sequence list) #'string< :key #'car))
   (with-temp-file file
     (let ((print-level nil)
-          (print-length nil))
+          (print-length nil)
+          (pp-default-function 'pp-28)
+          (fill-column 999))
       (pp list (current-buffer)))))
 
 (defvar transient-values
@@ -953,7 +956,7 @@ keyword.
   (pcase-let ((`(,class ,slots ,_ ,docstr ,_)
                (transient--expand-define-args args arglist)))
     `(progn
-       (defalias ',name ,(transient--default-infix-command))
+       (defalias ',name #'transient--default-infix-command)
        (put ',name 'interactive-only t)
        (put ',name 'command-modes (list 'not-a-mode))
        (put ',name 'function-documentation ,docstr)
@@ -969,6 +972,15 @@ example, sets a variable, use `transient-define-infix' 
instead.
 
 \(fn NAME ARGLIST [DOCSTRING] [KEYWORD VALUE]...)")
 
+(defun transient--default-infix-command ()
+  "Most transient infix commands are but an alias for this command."
+  (interactive)
+  (let ((obj (transient-suffix-object)))
+    (transient-infix-set obj (transient-infix-read obj)))
+  (transient--show))
+(put 'transient--default-infix-command 'interactive-only t)
+(put 'transient--default-infix-command 'command-modes (list 'not-a-mode))
+
 (defun transient--expand-define-args (args &optional arglist)
   (unless (listp arglist)
     (error "Mandatory ARGLIST is missing"))
@@ -1071,11 +1083,16 @@ example, sets a variable, use `transient-define-infix' 
instead.
                               (if (and desc (or (stringp desc) (symbolp desc)))
                                   desc
                                 (plist-get args :key)))))))
-          (setq args (plist-put args :command
-                                `(defalias ',sym ,(macroexp-quote cmd))))))
+          (setq args (plist-put
+                      args :command
+                      `(prog1 ',sym
+                         (put ',sym 'interactive-only t)
+                         (put ',sym 'command-modes (list 'not-a-mode))
+                         (defalias ',sym ,(macroexp-quote cmd)))))))
        ((or (stringp car)
             (and car (listp car)))
-        (let ((arg pop))
+        (let ((arg pop)
+              (sym nil))
           (cl-typecase arg
             (list
              (setq args (plist-put args :shortarg (car  arg)))
@@ -1085,9 +1102,13 @@ example, sets a variable, use `transient-define-infix' 
instead.
              (when-let ((shortarg (transient--derive-shortarg arg)))
                (setq args (plist-put args :shortarg shortarg)))
              (setq args (plist-put args :argument arg))))
-          (setq args (plist-put args :command
-                                (list 'quote (intern (format "transient:%s:%s"
-                                                             prefix arg)))))
+          (setq sym (intern (format "transient:%s:%s" prefix arg)))
+          (setq args (plist-put
+                      args :command
+                      `(prog1 ',sym
+                         (put ',sym 'interactive-only t)
+                         (put ',sym 'command-modes (list 'not-a-mode))
+                         (defalias ',sym #'transient--default-infix-command))))
           (cond ((and car (not (keywordp car)))
                  (setq class 'transient-option)
                  (setq args (plist-put args :reader (macroexp-quote pop))))
@@ -1116,26 +1137,6 @@ example, sets a variable, use `transient-define-infix' 
instead.
           (macroexp-quote (or class 'transient-suffix))
           (cons 'list args))))
 
-(defun transient--default-infix-command ()
-  (cons 'lambda
-        '(()
-          (interactive)
-          (let ((obj (transient-suffix-object)))
-            (transient-infix-set obj (transient-infix-read obj)))
-          (transient--show))))
-
-(defun transient--ensure-infix-command (obj)
-  (let ((cmd (oref obj command)))
-    (unless (or (commandp cmd)
-                (get cmd 'transient--infix-command))
-      (if (or (cl-typep obj 'transient-switch)
-              (cl-typep obj 'transient-option))
-          (put cmd 'transient--infix-command
-               (transient--default-infix-command))
-        ;; This is not an anonymous infix argument.
-        (when (transient--use-suffix-p obj)
-          (error "Suffix %s is not defined or autoloaded as a command" 
cmd))))))
-
 (defun transient--derive-shortarg (arg)
   (save-match-data
     (and (string-match "\\`\\(-[a-zA-Z]\\)\\(\\'\\|=\\)" arg)
@@ -1420,11 +1421,11 @@ Each suffix commands is associated with an object, 
which holds
 additional information about the suffix, such as its value (in
 the case of an infix command, which is a kind of suffix command).
 
-This function is intended to be called by infix commands, whose
-command definition usually (at least when defined using
-`transient-define-infix') is this:
+This function is intended to be called by infix commands, which
+are usually aliases of `transient--default-infix-command', which
+is defined like this:
 
-   (lambda ()
+   (defun transient--default-infix-command ()
      (interactive)
      (let ((obj (transient-suffix-object)))
        (transient-infix-set obj (transient-infix-read obj)))
@@ -1441,7 +1442,7 @@ commands) may also need the object to guide their 
behavior.
 This function attempts to return the object associated with the
 current suffix command even if the suffix command was not invoked
 from a transient.  (For some suffix command that is a valid thing
-to do, for others it is not.)  In that case nil may be returned
+to do, for others it is not.)  In that case nil may be returned,
 if the command was not defined using one of the macros intended
 to define such commands.
 
@@ -1457,7 +1458,7 @@ probably use this instead:
       (let ((suffixes
              (cl-remove-if-not
               (lambda (obj)
-                (eq (transient--suffix-command obj)
+                (eq (oref obj command)
                     (or command
                         (if (eq this-command 'transient-set-level)
                             ;; This is how it can look up for which
@@ -1480,38 +1481,6 @@ probably use this instead:
       (transient-init-value obj)
       obj)))
 
-(defun transient--suffix-command (object)
-  "Return the command represented by OBJECT.
-
-If the value of OBJECT's `command' slot is a command, then return
-that.  Otherwise it is a symbol whose `transient--infix-command'
-property holds an anonymous command, which is returned instead."
-  (cl-check-type object transient-suffix)
-  (let ((sym (oref object command)))
-    (if (commandp sym)
-        sym
-      (get sym 'transient--infix-command))))
-
-(defun transient--suffix-symbol (arg)
-  "Return a symbol representing ARG.
-
-ARG must be a command and/or a symbol.  If it is a symbol,
-then just return it.  Otherwise return the symbol whose
-`transient--infix-command' property's value is ARG."
-  (or (cl-typep arg 'command)
-      (cl-typep arg 'symbol)
-      (signal 'wrong-type-argument `((command symbol) ,arg)))
-  (if (symbolp arg)
-      arg
-    (let* ((obj (transient-suffix-object))
-           (sym (oref obj command)))
-      (if (eq (get sym 'transient--infix-command) arg)
-          sym
-        (catch 'found
-          (mapatoms (lambda (sym)
-                      (when (eq (get sym 'transient--infix-command) arg)
-                        (throw 'found sym)))))))))
-
 ;;; Keymaps
 
 (defvar-keymap transient-base-map
@@ -1709,7 +1678,7 @@ of the corresponding object."
                       (funcall transient-substitute-key-function obj)))
           (oset obj key key))
         (let ((kbd (kbd key))
-              (cmd (transient--suffix-command obj)))
+              (cmd (oref obj command)))
           (when-let ((conflict (and transient-detect-key-conflicts
                                     (transient--lookup-key map kbd))))
             (unless (eq cmd conflict)
@@ -1737,13 +1706,12 @@ of the corresponding object."
       (keymap-set map "<handle-switch-frame>" #'transient--do-suspend))
     (dolist (obj transient--suffixes)
       (let* ((cmd (oref obj command))
-             (sub-prefix (and (symbolp cmd) (get cmd 'transient--prefix) t))
-             (sym (transient--suffix-symbol cmd)))
+             (sub-prefix (and (symbolp cmd) (get cmd 'transient--prefix) t)))
         (cond
          ((oref obj inapt)
-          (define-key map (vector sym) #'transient--do-warn-inapt))
+          (define-key map (vector cmd) #'transient--do-warn-inapt))
          ((slot-boundp obj 'transient)
-          (define-key map (vector sym)
+          (define-key map (vector cmd)
             (let ((do (oref obj transient)))
               (pcase (list do sub-prefix)
                 ('(t     t) #'transient--do-recurse)
@@ -1753,8 +1721,8 @@ of the corresponding object."
                 ('(nil   t) #'transient--do-replace)
                 ('(nil nil) #'transient--do-exit)
                 (_          do)))))
-         ((not (lookup-key transient-predicate-map (vector sym)))
-          (define-key map (vector sym)
+         ((not (lookup-key transient-predicate-map (vector cmd)))
+          (define-key map (vector cmd)
             (if sub-prefix
                 #'transient--do-replace
               (or (oref transient--prefix transient-suffix)
@@ -1904,21 +1872,28 @@ value.  Otherwise return CHILDREN as is."
 (defun transient--init-suffix (levels spec)
   (pcase-let* ((`(,level ,class ,args) spec)
                (cmd (plist-get args :command))
-               (level (or (alist-get (transient--suffix-symbol cmd) levels)
-                          level)))
+               (level (or (alist-get cmd levels) level)))
     (let ((fn (and (symbolp cmd)
                    (symbol-function cmd))))
       (when (autoloadp fn)
         (transient--debug "   autoload %s" cmd)
         (autoload-do-load fn)))
     (when (transient--use-level-p level)
-      (let ((obj (if-let ((proto (and cmd
-                                      (symbolp cmd)
-                                      (get cmd 'transient--suffix))))
+      (unless (and cmd (symbolp cmd))
+        (error "BUG: Non-symbolic suffix command: %s" cmd))
+      (let ((obj (if-let ((proto (get cmd 'transient--suffix)))
                      (apply #'clone proto :level level args)
-                   (apply class :level level args))))
+                   (apply class :command cmd :level level args))))
+        (cond ((commandp cmd))
+              ((or (cl-typep obj 'transient-switch)
+                   (cl-typep obj 'transient-option))
+               ;; As a temporary special case, if the package was compiled
+               ;; with an older version of Transient, then we must define
+               ;; "anonymous" switch and option commands here.
+               (defalias cmd #'transient--default-infix-command))
+              ((transient--use-suffix-p obj)
+               (error "Suffix command %s is not defined or autoloaded" cmd)))
         (transient--init-suffix-key obj)
-        (transient--ensure-infix-command obj)
         (when (transient--use-suffix-p obj)
           (if (transient--inapt-suffix-p obj)
               (oset obj inapt t)
@@ -2074,8 +2049,7 @@ value.  Otherwise return CHILDREN as is."
 
 (defun transient--get-predicate-for (cmd &optional suffix-only)
   (or (ignore-errors
-        (lookup-key transient--predicate-map
-                    (vector (transient--suffix-symbol cmd))))
+        (lookup-key transient--predicate-map (vector cmd)))
       (and (not suffix-only)
            (let ((pred (oref transient--prefix transient-non-suffix)))
              (pcase pred
@@ -2189,36 +2163,65 @@ value.  Otherwise return CHILDREN as is."
        ,@body)))
 
 (defun transient--wrap-command ()
+  (if (>= emacs-major-version 30)
+      (transient--wrap-command-30)
+    (transient--wrap-command-29)))
+
+(defun transient--wrap-command-30 ()
+  (letrec
+      ((prefix transient--prefix)
+       (suffix this-command)
+       (advice (lambda (fn &rest args)
+                 (interactive
+                  (lambda (spec)
+                    (let ((abort t))
+                      (unwind-protect
+                         (prog1 (advice-eval-interactive-spec spec)
+                           (setq abort nil))
+                       (when abort
+                          (when-let ((unwind (oref prefix unwind-suffix)))
+                            (transient--debug 'unwind-interactive)
+                            (funcall unwind suffix))
+                          (advice-remove suffix advice)
+                          (oset prefix unwind-suffix nil))))))
+                 (unwind-protect
+                     (apply fn args)
+                   (when-let ((unwind (oref prefix unwind-suffix)))
+                     (transient--debug 'unwind-command)
+                     (funcall unwind suffix))
+                   (advice-remove suffix advice)
+                   (oset prefix unwind-suffix nil)))))
+    (advice-add suffix :around advice '((depth . -99)))))
+
+(defun transient--wrap-command-29 ()
   (let* ((prefix transient--prefix)
-         (suffix this-command))
-    (letrec ((advice
-              (lambda (fn &rest args)
-                (interactive
-                 (lambda (spec)
-                   (let ((abort t))
-                     (unwind-protect
-                        (prog1 (advice-eval-interactive-spec spec)
-                          (setq abort nil))
-                      (when abort
-                        (when-let ((unwind (oref prefix unwind-suffix)))
-                          (transient--debug 'unwind-interactive)
-                          (funcall unwind suffix))
-                        (if (symbolp suffix)
-                            (advice-remove suffix advice)
-                          (remove-function suffix advice))
-                        (oset prefix unwind-suffix nil))))))
-                (unwind-protect
-                    (apply fn args)
+         (suffix this-command)
+         (advice nil)
+         (advice-interactive
+          (lambda (spec)
+            (let ((abort t))
+              (unwind-protect
+                 (prog1 (advice-eval-interactive-spec spec)
+                   (setq abort nil))
+               (when abort
                   (when-let ((unwind (oref prefix unwind-suffix)))
-                    (transient--debug 'unwind-command)
+                    (transient--debug 'unwind-interactive)
                     (funcall unwind suffix))
-                  (if (symbolp suffix)
-                      (advice-remove suffix advice)
-                    (remove-function suffix advice))
-                  (oset prefix unwind-suffix nil)))))
-      (if (symbolp suffix)
-          (advice-add suffix :around advice '((depth . -99)))
-        (add-function :around (var suffix) advice '((depth . -99)))))))
+                  (advice-remove suffix advice)
+                  (oset prefix unwind-suffix nil))))))
+         (advice-body
+          (lambda (fn &rest args)
+            (unwind-protect
+                (apply fn args)
+              (when-let ((unwind (oref prefix unwind-suffix)))
+                (transient--debug 'unwind-command)
+                (funcall unwind suffix))
+              (advice-remove suffix advice)
+              (oset prefix unwind-suffix nil)))))
+    (setq advice `(lambda (fn &rest args)
+                    (interactive ,advice-interactive)
+                    (apply ',advice-body fn args)))
+    (advice-add suffix :around advice '((depth . -99)))))
 
 (defun transient--premature-post-command ()
   (and (equal (this-command-keys-vector) [])
@@ -2339,7 +2342,7 @@ value.  Otherwise return CHILDREN as is."
       (if (symbolp arg)
           (message "-- %-22s (cmd: %s, event: %S, exit: %s%s)"
                    arg
-                   (or (ignore-errors (transient--suffix-symbol this-command))
+                   (or (and (symbolp this-command) this-command)
                        (if (byte-code-function-p this-command)
                            "#[...]"
                          this-command))
@@ -2522,12 +2525,9 @@ prefix argument and pivot to `transient-update'."
            (propertize "?"   'face 'transient-key)
            ;; `this-command' is `transient-undefined' or `transient-inapt'.
            ;; Show the command (`this-original-command') the user actually
-           ;; tried to invoke.  For an anonymous inapt command that is a
-           ;; lambda expression, which cannot be mapped to a symbol, so
-           ;; forgo displaying the command.
-           (if-let ((cmd (ignore-errors
-                           (symbol-name (transient--suffix-symbol
-                                         this-original-command)))))
+           ;; tried to invoke.
+           (if-let ((cmd (or (ignore-errors (symbol-name 
this-original-command))
+                             (ignore-errors (symbol-name this-command)))))
                (format " [%s]" (propertize cmd 'face 'font-lock-warning-face))
              ""))
   (unless (and transient--transient-map
@@ -2617,8 +2617,7 @@ transient is active."
                                (transient-suffix-object command)))
                           (transient--show)
                           (transient--read-number-N
-                           (format "Set level for `%s': "
-                                   (transient--suffix-symbol command))
+                           (format "Set level for `%s': " command)
                            nil nil (not (eq command prefix)))))))))))
   (cond
    ((not command)
@@ -2627,7 +2626,7 @@ transient is active."
    (level
     (let* ((prefix (oref transient--prefix command))
            (alist (alist-get prefix transient-levels))
-           (sym (transient--suffix-symbol command)))
+           (sym command))
       (if (eq command prefix)
           (progn (oset transient--prefix level level)
                  (setq sym t))
@@ -3459,7 +3458,7 @@ Optional support for popup buttons is also implemented 
here."
     (if transient-enable-popup-navigation
         (make-text-button str nil
                           'type 'transient
-                          'command (transient--suffix-command obj))
+                          'command (oref obj command))
       str)))
 
 (cl-defmethod transient-format ((obj transient-infix))
@@ -3659,7 +3658,7 @@ that, else its name.
 
 Intended to be temporarily used as the `:suffix-description' of
 a prefix command, while porting a regular keymap to a transient."
-  (let ((command (transient--suffix-symbol (oref obj command))))
+  (let ((command (oref obj command)))
     (if-let ((doc (documentation command)))
         (propertize (car (split-string doc "\n")) 'face 'font-lock-doc-face)
       (propertize (symbol-name command) 'face 'font-lock-function-name-face))))
@@ -3687,7 +3686,7 @@ prefix method."
   (cond
    ((eq this-command 'transient-help)
     (transient-show-help transient--prefix))
-   ((let ((prefix (get (transient--suffix-command obj)
+   ((let ((prefix (get (oref obj command)
                        'transient--prefix)))
       (and prefix (not (eq (oref transient--prefix command) this-command))
            (prog1 t (transient-show-help prefix)))))
diff --git a/lisp/type-break.el b/lisp/type-break.el
index 1aa2b9d2997..494ed80c496 100644
--- a/lisp/type-break.el
+++ b/lisp/type-break.el
@@ -584,13 +584,13 @@ INTERVAL is the full length of an interval (defaults to 
TIME)."
   (type-break-check-post-command-hook)
   (type-break-cancel-schedule)
   (type-break-time-warning-schedule time 'reset)
-  (type-break-run-at-time (max 1 time) nil 'type-break-alarm)
+  (run-at-time (max 1 time) nil 'type-break-alarm)
   (setq type-break-time-next-break
         (type-break-time-sum start (or interval time))))
 
 (defun type-break-cancel-schedule ()
   (type-break-cancel-time-warning-schedule)
-  (type-break-cancel-function-timers 'type-break-alarm)
+  (cancel-function-timers 'type-break-alarm)
   (setq type-break-alarm-p nil)
   (setq type-break-time-next-break nil))
 
@@ -621,7 +621,7 @@ INTERVAL is the full length of an interval (defaults to 
TIME)."
 
       ;(let (type-break-current-time-warning-interval)
       ;  (type-break-cancel-time-warning-schedule))
-      (type-break-run-at-time (max 1 time) nil 'type-break-time-warning-alarm)
+      (run-at-time (max 1 time) nil 'type-break-time-warning-alarm)
 
       (cond
        (resetp
@@ -631,7 +631,7 @@ INTERVAL is the full length of an interval (defaults to 
TIME)."
         (setq type-break-warning-countdown-string-type "seconds"))))))))
 
 (defun type-break-cancel-time-warning-schedule ()
-  (type-break-cancel-function-timers 'type-break-time-warning-alarm)
+  (cancel-function-timers 'type-break-time-warning-alarm)
   (remove-hook 'type-break-post-command-hook 'type-break-time-warning)
   (setq type-break-current-time-warning-interval
         type-break-time-warning-intervals)
@@ -984,21 +984,6 @@ With optional non-nil ALL, force redisplay of all 
mode-lines."
   (add-hook 'post-command-hook 'type-break-run-tb-post-command-hook 'append))
 
 
-;;; Timer wrapper functions
-;;
-;; These shield type-break from variations in the interval timer packages
-;; for different versions of Emacs.
-
-(defun type-break-run-at-time (time repeat function)
-  (condition-case nil (or (require 'timer) (require 'itimer)) (error nil))
-  (run-at-time time repeat function))
-
-(defvar timer-dont-exit)
-(defun type-break-cancel-function-timers (function)
-  (let ((timer-dont-exit t))
-    (cancel-function-timers function)))
-
-
 ;;; Demo wrappers
 
 (defun type-break-catch-up-event ()
@@ -1144,6 +1129,8 @@ With optional non-nil ALL, force redisplay of all 
mode-lines."
             (kill-buffer buffer-name))))))
 
 (define-obsolete-function-alias 'timep 'type-break-timep "29.1")
+(define-obsolete-function-alias 'type-break-run-at-time #'run-at-time "30.1")
+(define-obsolete-function-alias 'type-break-cancel-function-timers 
#'cancel-function-timers "30.1")
 
 
 (provide 'type-break)
diff --git a/lisp/uniquify.el b/lisp/uniquify.el
index d1ca455b673..2ad2fb0eeac 100644
--- a/lisp/uniquify.el
+++ b/lisp/uniquify.el
@@ -168,6 +168,32 @@ This can be handy when you have deep parallel hierarchies."
 That means that when `buffer-file-name' is set to nil, `list-buffers-directory'
 contains the name of the directory which the buffer is visiting.")
 
+(defcustom uniquify-dirname-transform #'identity
+  "Function to transform buffer's directory name when uniquifying buffer's 
name.
+
+When `uniquify-buffer-name-style' is non-nil, Emacs makes buffer
+names unique by adding components of the buffer's directory name
+until the resulting name is unique.  This function is used to
+transform the buffer's directory name during this uniquifying
+process, allowing the unique buffer name to include strings
+from sources other than the buffer's directory.  The default is
+`identity', so the unmodified buffer's directory name is used for
+uniquifying.
+
+This function is called with the buffer's directory name and
+should return a string which names a file (that does not need to
+actually exist in the filesystem); the components of this file
+name will then be used to uniquify the buffer's name.
+
+To include components from the `project-name' of the buffer, set
+this variable to `project-uniquify-dirname-transform'."
+  :type '(choice (function-item :tag "Use directory name as-is" identity)
+                 (function-item :tag "Include project name in directory name"
+                                #'project-uniquify-dirname-transform)
+                 function)
+  :version "30.1"
+  :group 'uniquify)
+
 ;;; Utilities
 
 ;; uniquify-fix-list data structure
@@ -209,7 +235,8 @@ this rationalization."
   ;; this buffer.
   (with-current-buffer newbuf (setq uniquify-managed nil))
   (when dirname
-    (setq dirname (expand-file-name (directory-file-name dirname)))
+    (setq dirname (funcall uniquify-dirname-transform
+                           (expand-file-name (directory-file-name dirname))))
     (let ((fix-list (list (uniquify-make-item base dirname newbuf
                                               nil)))
          items)
@@ -268,10 +295,11 @@ in `uniquify-list-buffers-directory-modes', otherwise 
returns nil."
               (if (memq major-mode uniquify-list-buffers-directory-modes)
                   list-buffers-directory))))
       (when filename
-       (directory-file-name
-        (file-name-directory
-         (expand-file-name
-          (directory-file-name filename))))))))
+        (funcall uniquify-dirname-transform
+                 (directory-file-name
+                 (file-name-directory
+                  (expand-file-name
+                   (directory-file-name filename)))))))))
 
 (defun uniquify-rerationalize-w/o-cb (fix-list)
   "Re-rationalize the buffers in FIX-LIST, but ignoring `current-buffer'."
diff --git a/lisp/use-package/bind-key.el b/lisp/use-package/bind-key.el
index b216c668d83..c45b9e546f7 100644
--- a/lisp/use-package/bind-key.el
+++ b/lisp/use-package/bind-key.el
@@ -196,6 +196,7 @@ can safely be called at any time."
                                (key-description ,namevar))
                              (if (symbolp ,keymap) ,keymap (quote ,keymap))))
             (,bindingvar (lookup-key ,kmapvar ,keyvar)))
+       (require 'bind-key)      ; ensure `personal-keybindings' is in scope
        (let ((entry (assoc ,kdescvar personal-keybindings))
              (details (list ,command
                             (unless (numberp ,bindingvar)
diff --git a/lisp/use-package/use-package-core.el 
b/lisp/use-package/use-package-core.el
index e0e16134ed3..34c45b7aec3 100644
--- a/lisp/use-package/use-package-core.el
+++ b/lisp/use-package/use-package-core.el
@@ -327,12 +327,15 @@ Must be set before loading `use-package'."
       (set-default sym value))
   :group 'use-package)
 
+;; Redundant in Emacs 26 or later, which already highlights macro names.
 (defconst use-package-font-lock-keywords
   '(("(\\(use-package\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?"
      (1 font-lock-keyword-face)
      (2 font-lock-constant-face nil t))))
-
-(font-lock-add-keywords 'emacs-lisp-mode use-package-font-lock-keywords)
+(make-obsolete-variable 'use-package-font-lock-keywords
+                        'lisp-el-font-lock-keywords "30.1")
+(when (< emacs-major-version 26)
+  (font-lock-add-keywords 'emacs-lisp-mode use-package-font-lock-keywords))
 
 (defcustom use-package-compute-statistics nil
   "If non-nil, compute statistics concerned `use-package' declarations.
@@ -1036,15 +1039,23 @@ meaning:
   Configured        :config has been processed (the package is loaded!)
   Initialized       :init has been processed (load status unknown)
   Prefaced          :preface has been processed
-  Declared          the use-package declaration was seen"
+  Declared          the use-package declaration was seen
+
+Customize the user option `use-package-compute-statistics' to
+enable gathering statistics."
   (interactive)
-  (with-current-buffer (get-buffer-create "*use-package statistics*")
-    (setq tabulated-list-entries
-          (mapcar #'use-package-statistics-convert
-                  (hash-table-keys use-package-statistics)))
-    (use-package-statistics-mode)
-    (tabulated-list-print)
-    (display-buffer (current-buffer))))
+  (let ((statistics (hash-table-keys use-package-statistics)))
+    (unless statistics
+      (if use-package-compute-statistics
+          (user-error "No use-package statistics available")
+        (user-error (concat "Customize `use-package-compute-statistics'"
+                            " to enable reporting"))))
+    (with-current-buffer (get-buffer-create "*use-package statistics*")
+      (setq tabulated-list-entries
+            (mapcar #'use-package-statistics-convert statistics))
+      (use-package-statistics-mode)
+      (tabulated-list-print)
+      (display-buffer (current-buffer)))))
 
 (defvar use-package-statistics-status-order
   '(("Declared"    . 0)
@@ -1055,6 +1066,7 @@ meaning:
 (define-derived-mode use-package-statistics-mode tabulated-list-mode
   "use-package statistics"
   "Show current statistics gathered about `use-package' declarations."
+  :interactive nil
   (setq tabulated-list-format
         ;; The sum of column width is 80 characters:
         [("Package" 25 t)
diff --git a/lisp/userlock.el b/lisp/userlock.el
index 562bc0a0a9f..4623608f1db 100644
--- a/lisp/userlock.el
+++ b/lisp/userlock.el
@@ -110,10 +110,11 @@ You can <\\`q'>uit; don't modify this file."))
 
 (defun userlock--check-content-unchanged (filename)
   (with-demoted-errors "Unchanged content check: %S"
-    ;; Even tho we receive `filename', we know that `filename' refers to the 
current
-    ;; buffer's file.
-    (cl-assert (equal (expand-file-name filename)
-                      (expand-file-name buffer-file-truename)))
+    ;; Even tho we receive `filename', we know that `filename' refers
+    ;; to the current buffer's file.
+    (cl-assert (or (null buffer-file-truename) ; temporary buffer
+                   (equal (expand-file-name filename)
+                          (expand-file-name buffer-file-truename))))
     ;; Note: rather than read the file and compare to the buffer, we could save
     ;; the buffer and compare to the file, but for encrypted data this
     ;; wouldn't work well (and would risk exposing the data).
@@ -135,7 +136,15 @@ You can <\\`q'>uit; don't modify this file."))
                          (compare-buffer-substrings
                           buf start end
                           (current-buffer) (point-min) (point-max))))))
-          (set-visited-file-modtime)
+          ;; We know that some buffer visits FILENAME, because our
+          ;; caller (see lock_file) verified that.  Thus, we set the
+          ;; modtime in that buffer, to cater to use case where the
+          ;; file is about to be written to from some buffer that
+          ;; doesn't visit any file, like a temporary buffer.
+          (let ((buf (get-file-buffer (file-truename filename))))
+            (when buf  ; If we cannot find the visiting buffer, punt.
+              (with-current-buffer buf
+                (set-visited-file-modtime))))
           'unchanged)))))
 
 ;;;###autoload
diff --git a/lisp/vc/cvs-status.el b/lisp/vc/cvs-status.el
index 7982cd89efc..7532a059d3b 100644
--- a/lisp/vc/cvs-status.el
+++ b/lisp/vc/cvs-status.el
@@ -352,10 +352,8 @@ the list is a three-string list TAG, KIND, REV."
        (delete-region pt (point)))
       tags)))
 
-(defvar font-lock-mode)
 ;; (defun cvs-refontify (beg end)
-;;   (when (and font-lock-mode
-;;             (fboundp 'font-lock-fontify-region))
+;;   (when font-lock-mode
 ;;     (font-lock-fontify-region (1- beg) (1+ end))))
 
 (defun cvs-status-trees ()
diff --git a/lisp/vc/diff.el b/lisp/vc/diff.el
index 90c43d111f5..a411d98da31 100644
--- a/lisp/vc/diff.el
+++ b/lisp/vc/diff.el
@@ -165,7 +165,7 @@ returns the buffer used."
   (unless (bufferp new) (setq new (expand-file-name new)))
   (unless (bufferp old) (setq old (expand-file-name old)))
   (or switches (setq switches diff-switches)) ; If not specified, use default.
-  (unless (listp switches) (setq switches (list switches)))
+  (setq switches (ensure-list switches))
   (or buf (setq buf (get-buffer-create "*Diff*")))
   (diff-check-labels)
   (let* ((old-alt (diff-file-local-copy old))
diff --git a/lisp/vc/ediff-util.el b/lisp/vc/ediff-util.el
index 8011d7ce2e0..c4ebe20d7e4 100644
--- a/lisp/vc/ediff-util.el
+++ b/lisp/vc/ediff-util.el
@@ -3741,7 +3741,7 @@ Ediff Control Panel to restore highlighting."
 ;; these buffers).
 ;; EXCL-BUFF-LIST is an exclusion list.
 (defun ediff-other-buffer (excl-buff-lst)
-  (or (listp excl-buff-lst) (setq excl-buff-lst (list excl-buff-lst)))
+  (setq excl-buff-lst (ensure-list excl-buff-lst))
   (let* ((all-buffers (nconc (ediff-get-selected-buffers) (buffer-list)))
         ;; we compute this the second time because we need to do memq on it
         ;; later, and nconc above will break it. Either this or use slow
diff --git a/lisp/vc/smerge-mode.el b/lisp/vc/smerge-mode.el
index c39a9cc2f22..7847a6c7670 100644
--- a/lisp/vc/smerge-mode.el
+++ b/lisp/vc/smerge-mode.el
@@ -255,10 +255,6 @@ Can be nil if the style is undecided, or else:
 - `diff3-E'
 - `diff3-A'")
 
-;; Compiler pacifiers
-(defvar font-lock-mode)
-(defvar font-lock-keywords)
-
 ;;;;
 ;;;; Actual code
 ;;;;
diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el
index 53d58870b32..d5226910211 100644
--- a/lisp/vc/vc-dir.el
+++ b/lisp/vc/vc-dir.el
@@ -785,8 +785,7 @@ MARK-FILES should be a list of absolute filenames."
 
 (defun vc-dir-mark-state-files (states)
   "Mark files that are in the state specified by the list in STATES."
-  (unless (listp states)
-    (setq states (list states)))
+  (setq states (ensure-list states))
   (ewoc-map
    (lambda (filearg)
      (when (memq (vc-dir-fileinfo->state filearg) states)
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index dfca944dc74..c689eec444b 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -1342,8 +1342,10 @@ This prompts for a branch to merge from."
 (defun vc-git-repository-url (file-or-dir &optional remote-name)
   (let ((default-directory (vc-git-root file-or-dir)))
     (with-temp-buffer
-      (vc-git-command (current-buffer) 0 nil "remote" "get-url"
-                      (or remote-name "origin"))
+      ;; The "get-url" subcommand of "git remote" was new in git 2.7.0;
+      ;; "git config" also works in older versions.  -- rgr, 15-Aug-23.
+      (let ((opt-name (concat "remote." (or remote-name "origin") ".url")))
+       (vc-git-command (current-buffer) 0 (list "config" "--get" opt-name)))
       (buffer-substring-no-properties (point-min) (1- (point-max))))))
 
 ;; Everywhere but here, follows vc-git-command, which uses vc-do-command
@@ -1629,7 +1631,6 @@ This requires git 1.8.4 or later, for the \"-L\" option 
of \"git log\"."
     map))
 
 (defvar vc-git--log-view-long-font-lock-keywords nil)
-(defvar font-lock-keywords)
 (defvar vc-git-region-history-font-lock-keywords
   '((vc-git-region-history-font-lock)))
 
diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el
index 182d76882bb..b62420393aa 100644
--- a/lisp/vc/vc-hg.el
+++ b/lisp/vc/vc-hg.el
@@ -497,7 +497,6 @@ This requires hg 4.4 or later, for the \"-L\" option of 
\"hg log\"."
     map))
 
 (defvar vc-hg--log-view-long-font-lock-keywords nil)
-(defvar font-lock-keywords)
 (defvar vc-hg-region-history-font-lock-keywords
   '((vc-hg-region-history-font-lock)))
 
diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el
index 00a7659209e..e75165ea2e9 100644
--- a/lisp/vc/vc-hooks.el
+++ b/lisp/vc/vc-hooks.el
@@ -321,8 +321,7 @@ backend is tried first."
      ((and (file-name-directory file)
            (string-match vc-ignore-dir-regexp (file-name-directory file)))
       nil)
-     ((and (boundp 'file-name-handler-alist)
-          (setq handler (find-file-name-handler file 'vc-registered)))
+     ((setq handler (find-file-name-handler file 'vc-registered))
       ;; handler should set vc-backend and return t if registered
       (funcall handler 'vc-registered file))
      (t
diff --git a/lisp/vc/vc-rcs.el b/lisp/vc/vc-rcs.el
index c2112b76ad3..3d6907cbec1 100644
--- a/lisp/vc/vc-rcs.el
+++ b/lisp/vc/vc-rcs.el
@@ -864,14 +864,15 @@ and CVS."
 (defvar vc-rcs-rcs2log-program
   (let (exe)
     (cond ((file-executable-p
-            (setq exe (expand-file-name "rcs2log" exec-directory)))
+            (setq exe (expand-file-name rcs2log-program-name
+                                        exec-directory)))
            exe)
           ;; In the unlikely event that someone is running an
           ;; uninstalled Emacs and wants to do something RCS-related.
           ((file-executable-p
             (setq exe (expand-file-name "lib-src/rcs2log" source-directory)))
            exe)
-          (t "rcs2log")))
+          (t rcs2log-program-name)))
   "Path to the `rcs2log' program (normally in `exec-directory').")
 
 (autoload 'vc-buffer-sync "vc-dispatcher")
diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el
index 6c3094719ed..be7fa46c28e 100644
--- a/lisp/vc/vc.el
+++ b/lisp/vc/vc.el
@@ -1121,19 +1121,8 @@ possible values of STATE are explained in `vc-state', 
and MODEL in
 the returned list.
 
 BEWARE: this function may change the current buffer."
-  (let (new-buf res)
-    (with-current-buffer (or (buffer-base-buffer) (current-buffer))
-      (setq res
-            (vc-deduce-fileset-1 not-state-changing
-                                 allow-unregistered
-                                 state-model-only-files))
-      (setq new-buf (current-buffer)))
-    (set-buffer new-buf)
-    res))
-
-(defun vc-deduce-fileset-1 (not-state-changing
-                            allow-unregistered
-                            state-model-only-files)
+  (when (buffer-base-buffer)
+    (set-buffer (buffer-base-buffer)))
   (let (backend)
     (cond
      ((derived-mode-p 'vc-dir-mode)
@@ -1158,7 +1147,7 @@ BEWARE: this function may change the current buffer."
                                      (derived-mode-p 'diff-mode)))))
       (progn                  ;FIXME: Why not `with-current-buffer'? --Stef.
        (set-buffer vc-parent-buffer)
-       (vc-deduce-fileset-1 not-state-changing allow-unregistered 
state-model-only-files)))
+        (vc-deduce-fileset not-state-changing allow-unregistered 
state-model-only-files)))
      ((and (not buffer-file-name)
           (setq backend (vc-responsible-backend default-directory)))
       (list backend nil))
diff --git a/lisp/version.el b/lisp/version.el
index 9cadc59237f..0eb4ea76f4f 100644
--- a/lisp/version.el
+++ b/lisp/version.el
@@ -26,6 +26,31 @@
 
 ;;; Code:
 
+
+
+(defun android-read-build-system ()
+  "Obtain the host name of the system on which Emacs was built.
+Use the data stored in the special file `/assets/build_info'.
+Value is the string ``Unknown'' upon failure, else the hostname
+of the build system."
+  (with-temp-buffer
+    (insert-file-contents "/assets/build_info")
+    (let ((string (buffer-substring 1 (line-end-position))))
+      (and (not (equal string "Unknown")) string))))
+
+(defun android-read-build-time ()
+  "Obtain the time at which Emacs was built.
+Use the data stored in the special file `/assets/build_info'.
+Value is nil upon failure, else the time in the same format as
+returned by `current-time'."
+  (with-temp-buffer
+    (insert-file-contents "/assets/build_info")
+    (end-of-line)
+    (let ((number (read (current-buffer))))
+      (time-convert number 'list))))
+
+
+
 (defconst emacs-major-version
   (progn (string-match "^[0-9]+" emacs-version)
          (string-to-number (match-string 0 emacs-version)))
@@ -36,10 +61,21 @@
          (string-to-number (match-string 1 emacs-version)))
   "Minor version number of this version of Emacs.")
 
-(defconst emacs-build-system (system-name)
+;; N.B. (featurep 'android) is tested for in addition to
+;; `system-type', because that can also be Android on a TTY-only
+;; Android build that doesn't employ the window system packaging
+;; support.  (bug#65319)
+(defconst emacs-build-system (or (and (featurep 'android)
+                                      (eq system-type 'android)
+                                      (android-read-build-system))
+                                 (system-name))
   "Name of the system on which Emacs was built, or nil if not available.")
 
-(defconst emacs-build-time (if emacs-build-system (current-time))
+(defconst emacs-build-time (if emacs-build-system
+                               (or (and (featurep 'android)
+                                        (eq system-type 'android)
+                                        (android-read-build-time))
+                                   (current-time)))
   "Time at which Emacs was dumped out, or nil if not available.")
 
 (defconst emacs-build-number 1          ; loadup.el may increment this
@@ -130,9 +166,22 @@ or if we could not determine the revision.")
                  (looking-at "[[:xdigit:]]\\{40\\}"))
           (match-string 0)))))
 
+(defun emacs-repository-version-android ()
+  "Return the Emacs repository revision Emacs was built from.
+Value is nil if Emacs was not built from a repository checkout.
+Use information from the `/assets/version' special file."
+  (with-temp-buffer
+    (insert-file-contents "/assets/version")
+    (let ((string (buffer-substring 1 (line-end-position))))
+      (and (not (equal string "Unknown")) string))))
+
 (defun emacs-repository-get-version (&optional dir _external)
   "Try to return as a string the repository revision of the Emacs sources.
 The format of the returned string is dependent on the VCS in use.
+
+If Emacs is built for Android, use the version information
+embedded in the Emacs installation package.
+
 Value is nil if the sources do not seem to be under version
 control, or if we could not determine the revision.  Note that
 this reports on the current state of the sources, which may not
@@ -140,13 +189,28 @@ correspond to the running Emacs.
 
 Optional argument DIR is a directory to use instead of `source-directory'.
 Optional argument EXTERNAL is ignored."
-  (emacs-repository-version-git (or dir source-directory)))
+  (cond ((and (featurep 'android)
+              (eq system-type 'android))
+         (emacs-repository-version-android))
+        (t (emacs-repository-version-git
+            (or dir source-directory)))))
 
 (defvar emacs-repository-branch nil
   "String giving the repository branch from which this Emacs was built.
 Value is nil if Emacs was not built from a repository checkout,
 or if we could not determine the branch.")
 
+(defun emacs-repository-branch-android ()
+  "Return the Emacs repository branch Emacs was built from.
+Value is nil if Emacs was not built from a repository checkout.
+Use information from the `/assets/version' special file."
+  (with-temp-buffer
+    (insert-file-contents "/assets/version")
+    (end-of-line)
+    (forward-char)
+    (let ((string (buffer-substring (point) (line-end-position))))
+      (and (not (equal string "Unknown")) string))))
+
 (defun emacs-repository-branch-git (dir)
   "Ask git itself for the branch information for directory DIR."
   (message "Waiting for git...")
@@ -162,12 +226,20 @@ or if we could not determine the branch.")
 (defun emacs-repository-get-branch (&optional dir)
   "Try to return as a string the repository branch of the Emacs sources.
 The format of the returned string is dependent on the VCS in use.
+
+If Emacs is built for Android, use the version information
+embedded in the Emacs installation package.
+
 Value is nil if the sources do not seem to be under version
 control, or if we could not determine the branch.  Note that
 this reports on the current state of the sources, which may not
 correspond to the running Emacs.
 
 Optional argument DIR is a directory to use instead of `source-directory'."
-  (emacs-repository-branch-git (or dir source-directory)))
+  (cond ((and (featurep 'android)
+              (eq system-type 'android))
+         (emacs-repository-branch-android))
+        (t (emacs-repository-branch-git
+            (or dir source-directory)))))
 
 ;;; version.el ends here
diff --git a/lisp/wdired.el b/lisp/wdired.el
index 5c745cc9aab..7b9c75d36b1 100644
--- a/lisp/wdired.el
+++ b/lisp/wdired.el
@@ -556,8 +556,24 @@ non-nil means return old filename."
                         ;; been modified with their new name keeping
                         ;; the ones that are unmodified at the same place.
                         (cl-loop for f in (cdr dired-directory)
-                                 collect (or (assoc-default f files-renamed)
-                                             f))))))
+                                 collect
+                                 (or (assoc-default f files-renamed)
+                                     ;; F could be relative or
+                                     ;; abbreviated, whereas
+                                     ;; files-renamed always consists
+                                     ;; of absolute file names.
+                                     (let ((relative
+                                            (not (file-name-absolute-p f)))
+                                           (match
+                                            (assoc-default (expand-file-name f)
+                                                           files-renamed)))
+                                       (cond
+                                        ;; If it was relative, convert
+                                        ;; the new name back to relative.
+                                        ((and match relative)
+                                         (file-relative-name match))
+                                        (t match)))
+                                     f))))))
          ;; Re-sort the buffer.
          (revert-buffer)
          (let ((inhibit-read-only t))
diff --git a/lisp/wid-edit.el b/lisp/wid-edit.el
index 234f3d9b74d..ae060c92d0e 100644
--- a/lisp/wid-edit.el
+++ b/lisp/wid-edit.el
@@ -64,8 +64,9 @@
 
 ;;; Compatibility.
 
-(defun widget-event-point (event)
-  "Character position of the end of event if that exists, or nil."
+(defsubst widget-event-point (event)
+  "Character position of the end of event if that exists, or nil.
+EVENT can either be a mouse event or a touch screen event."
   (posn-point (event-end event)))
 
 (defun widget-button-release-event-p (event)
@@ -286,7 +287,7 @@ in the key vector, as in the argument of `define-key'."
   (let ((items (mapc (lambda (x)
                        (when (consp x)
                          (dotimes (i (1- (length x)))
-                           (when (char-or-string-p (nth i x))
+                           (when (stringp (nth i x))
                              (setcar (nthcdr i x)
                                      (substitute-command-keys
                                       (car (nthcdr i x))))))))
@@ -643,8 +644,7 @@ Return a list whose car contains all members of VALS that 
matched WIDGET."
 (defun widget-prompt-value (widget prompt &optional value unbound)
   "Prompt for a value matching WIDGET, using PROMPT.
 The current value is assumed to be VALUE, unless UNBOUND is non-nil."
-  (unless (listp widget)
-    (setq widget (list widget)))
+  (setq widget (ensure-list widget))
   (setq prompt (format "[%s] %s" (widget-type widget) prompt))
   (setq widget (widget-convert widget))
   (let ((answer (widget-apply widget :prompt-value prompt value unbound)))
@@ -1025,6 +1025,7 @@ button end points."
     (define-key map [backtab] 'widget-backward)
     (define-key map [down-mouse-2] 'widget-button-click)
     (define-key map [down-mouse-1] 'widget-button-click)
+    (define-key map [touchscreen-begin] 'widget-button-click)
     ;; The following definition needs to avoid using escape sequences that
     ;; might get converted to ^M when building loaddefs.el
     (define-key map [(control ?m)] 'widget-button-press)
@@ -1082,6 +1083,7 @@ If nil, point returns to its original position after 
invoking a button.")
 
 (defun widget-button--check-and-call-button (event button)
   "Call BUTTON if BUTTON is a widget and EVENT is correct for it.
+EVENT can either be a mouse event or a touchscreen-begin event.
 If nothing was called, return non-nil."
   (let* ((oevent event)
          (mouse-1 (memq (event-basic-type event) '(mouse-1 down-mouse-1)))
@@ -1101,40 +1103,49 @@ If nothing was called, return non-nil."
                 (face (overlay-get overlay 'face))
                 (mouse-face (overlay-get overlay 'mouse-face)))
            (unwind-protect
-               ;; Read events, including mouse-movement
-               ;; events, waiting for a release event.  If we
-               ;; began with a mouse-1 event and receive a
-               ;; movement event, that means the user wants
-               ;; to perform drag-selection, so cancel the
-               ;; button press and do the default mouse-1
-               ;; action.  For mouse-2, just highlight/
-               ;; unhighlight the button the mouse was
-               ;; initially on when we move over it.
+               ;; Read events, including mouse-movement events,
+               ;; waiting for a release event.  If we began with a
+               ;; mouse-1 event and receive a movement event, that
+               ;; means the user wants to perform drag-selection, so
+               ;; cancel the button press and do the default mouse-1
+               ;; action.  For mouse-2, just highlight/ unhighlight
+               ;; the button the mouse was initially on when we move
+               ;; over it.
+                ;;
+                ;; If this function was called in response to a
+                ;; touchscreen event, then wait for a corresponding
+                ;; touchscreen-end event instead.
                (save-excursion
                  (when face            ; avoid changing around image
                    (overlay-put overlay 'face pressed-face)
                    (overlay-put overlay 'mouse-face pressed-face))
-                 (unless (widget-apply button :mouse-down-action event)
-                   (let ((track-mouse t))
-                     (while (not (widget-button-release-event-p event))
-                        (setq event (read--potential-mouse-event))
-                       (when (and mouse-1 (mouse-movement-p event))
-                         (push event unread-command-events)
-                         (setq event oevent)
-                         (throw 'button-press-cancelled t))
-                       (unless (or (integerp event)
-                                   (memq (car event)
-                                          '(switch-frame select-window))
-                                   (eq (car event) 'scroll-bar-movement))
-                         (setq pos (widget-event-point event))
-                         (if (and pos
-                                  (eq (get-char-property pos 'button)
-                                      button))
-                             (when face
-                               (overlay-put overlay 'face pressed-face)
-                               (overlay-put overlay 'mouse-face pressed-face))
-                           (overlay-put overlay 'face face)
-                           (overlay-put overlay 'mouse-face mouse-face))))))
+                  (if (eq (car event) 'touchscreen-begin)
+                      ;; This a touchscreen event and must be handled
+                      ;; specially through `touch-screen-track-tap'.
+                      (progn
+                        (unless (touch-screen-track-tap event)
+                          (throw 'button-press-cancelled t)))
+                    (unless (widget-apply button :mouse-down-action event)
+                      (let ((track-mouse t))
+                        (while (not (widget-button-release-event-p event))
+                          (setq event (read--potential-mouse-event))
+                          (when (and mouse-1 (mouse-movement-p event))
+                            (push event unread-command-events)
+                            (setq event oevent)
+                            (throw 'button-press-cancelled t))
+                          (unless (or (integerp event)
+                                      (memq (car event)
+                                            '(switch-frame select-window))
+                                      (eq (car event) 'scroll-bar-movement))
+                            (setq pos (widget-event-point event))
+                            (if (and pos
+                                     (eq (get-char-property pos 'button)
+                                         button))
+                                (when face
+                                  (overlay-put overlay 'face pressed-face)
+                                  (overlay-put overlay 'mouse-face 
pressed-face))
+                              (overlay-put overlay 'face face)
+                              (overlay-put overlay 'mouse-face 
mouse-face)))))))
 
                  ;; When mouse is released over the button, run
                  ;; its action function.
@@ -1163,30 +1174,37 @@ If nothing was called, return non-nil."
 
        (when (or (null button)
                   (widget-button--check-and-call-button event button))
-         (let ((up t)
+         (let ((up (not (eq (car event) 'touchscreen-begin)))
                 command)
            ;; Mouse click not on a widget button.  Find the global
            ;; command to run, and check whether it is bound to an
            ;; up event.
-           (if mouse-1
-               (cond ((setq command    ;down event
-                            (lookup-key widget-global-map [down-mouse-1]))
-                      (setq up nil))
-                     ((setq command    ;up event
-                            (lookup-key widget-global-map [mouse-1]))))
-             (cond ((setq command      ;down event
-                          (lookup-key widget-global-map [down-mouse-2]))
-                    (setq up nil))
-                   ((setq command      ;up event
-                          (lookup-key widget-global-map [mouse-2])))))
+            (cond
+             ((eq (car event) 'touchscreen-begin)
+              (setq command 'touch-screen-handle-touch))
+             (mouse-1 (cond ((setq command     ;down event
+                                   (lookup-key widget-global-map 
[down-mouse-1]))
+                             (setq up nil))
+                            ((setq command     ;up event
+                                   (lookup-key widget-global-map [mouse-1])))))
+             (t (cond ((setq command   ;down event
+                             (lookup-key widget-global-map [down-mouse-2]))
+                       (setq up nil))
+                      ((setq command   ;up event
+                             (lookup-key widget-global-map [mouse-2]))))))
            (when up
              ;; Don't execute up events twice.
-             (while (not (widget-button-release-event-p event))
+             (while (not (and (widget-button-release-event-p event)))
                (setq event (read--potential-mouse-event))))
            (when command
              (call-interactively command)))))
     (message "You clicked somewhere weird.")))
 
+;; Make sure `touch-screen-handle-touch' abstains from emulating
+;; down-mouse-1 events for `widget-button-click'.
+
+(put 'widget-button-click 'ignored-mouse-command t)
+
 (defun widget-button-press (pos &optional event)
   "Invoke button at POS."
   (interactive "@d")
@@ -2127,7 +2145,8 @@ the earlier input."
        ;; `widget-setup' is called.
        (overlay (cons (make-marker) (make-marker))))
     (widget-put widget :field-overlay overlay)
-    (insert value)
+    (when value
+      (insert value))
     (and size
         (< (length value) size)
         (insert-char ?\s (- size (length value))))
@@ -3655,7 +3674,9 @@ match-alternatives: %S"
                            value
                            (widget-get widget :match)
                            (widget-get widget :match-alternatives))
-                          :warning))
+                          :warning)
+                         ;; Make sure we will `read' a string.
+                         (setq value (prin1-to-string value)))
                        (read value)))
 
 (defun widget-restricted-sexp-match (widget value)
@@ -3779,8 +3800,19 @@ like the newline character or the tab character."
 (define-widget 'list 'group
   "A Lisp list."
   :tag "List"
+  :default-get #'widget-list-default-get
   :format "%{%t%}:\n%v")
 
+(defun widget-list-default-get (widget)
+  "Return the default external value for a list WIDGET.
+
+The default value is the one stored in the :value property, even if it is nil,
+or a list with the default value of each component of the list WIDGET."
+  (widget-apply widget :value-to-external
+                (if (widget-member widget :value)
+                    (widget-get widget :value)
+                  (widget-group-default-get widget))))
+
 (define-widget 'vector 'group
   "A Lisp vector."
   :tag "Vector"
@@ -3909,7 +3941,6 @@ example:
            value-type widget-plist-value-type))
     `(group :format "Key: %v" :inline t ,key-type ,value-type)))
 
-
 ;;; The `alist' Widget.
 ;;
 ;; Association lists.
@@ -3919,6 +3950,7 @@ example:
   :key-type '(sexp :tag "Key")
   :value-type '(sexp :tag "Value")
   :convert-widget 'widget-alist-convert-widget
+  :default-get #'widget-alist-default-get
   :tag "Alist")
 
 (defvar widget-alist-value-type)       ;Dynamic variable
@@ -3953,6 +3985,25 @@ example:
       (setq key-type `(const ,option)
            value-type widget-alist-value-type))
     `(cons :format "Key: %v" ,key-type ,value-type)))
+
+(defun widget-alist-default-get (widget)
+  "Return the default value for WIDGET, an alist widget.
+
+The default value may be one of:
+- The one stored in the :value property, even if it is nil.
+- If WIDGET has options available, an alist consisting of the
+default values for each option.
+- nil, otherwise."
+  (widget-apply widget :value-to-external
+                (cond ((widget-member widget :value)
+                       (widget-get widget :value))
+                      ((widget-get widget :options)
+                       (mapcar #'widget-default-get
+                               ;; Last one is the editable-list part, and
+                               ;; we don't want those showing up as
+                               ;; part of the default value.  (Bug#63290)
+                               (butlast (widget-get widget :args))))
+                      (t nil))))
 
 (define-widget 'choice 'menu-choice
   "A union of several sexp types.
@@ -3985,7 +4036,8 @@ current choice is inline."
                 nil)
                ((= (length args) 1)
                 (nth 0 args))
-               ((and (= (length args) 2)
+                ((and widget-choice-toggle
+                      (= (length args) 2)
                      (memq old args))
                 (if (eq old (nth 0 args))
                     (nth 1 args)
diff --git a/lisp/windmove.el b/lisp/windmove.el
index 746a440bacb..96b8597ae09 100644
--- a/lisp/windmove.el
+++ b/lisp/windmove.el
@@ -485,7 +485,7 @@ Default value of MODIFIERS is `shift'."
   (interactive)
   (unless modifiers (setq modifiers 'shift))
   (when (eq modifiers 'none) (setq modifiers nil))
-  (unless (listp modifiers) (setq modifiers (list modifiers)))
+  (setq modifiers (ensure-list modifiers))
   (windmove-install-defaults nil modifiers
                              '((windmove-left left)
                                (windmove-right right)
@@ -626,7 +626,7 @@ Default value of MODIFIERS is `shift-meta'."
   (interactive)
   (unless modifiers (setq modifiers '(shift meta)))
   (when (eq modifiers 'none) (setq modifiers nil))
-  (unless (listp modifiers) (setq modifiers (list modifiers)))
+  (setq modifiers (ensure-list modifiers))
   (windmove-install-defaults nil modifiers
                              '((windmove-display-left left)
                                (windmove-display-right right)
@@ -703,10 +703,10 @@ Default value of PREFIX is \\`C-x' and MODIFIERS is 
`shift'."
   (interactive)
   (unless prefix (setq prefix '(?\C-x)))
   (when (eq prefix 'none) (setq prefix nil))
-  (unless (listp prefix) (setq prefix (list prefix)))
+  (setq prefix (ensure-list prefix))
   (unless modifiers (setq modifiers '(shift)))
   (when (eq modifiers 'none) (setq modifiers nil))
-  (unless (listp modifiers) (setq modifiers (list modifiers)))
+  (setq modifiers (ensure-list modifiers))
   (windmove-install-defaults prefix modifiers
                              '((windmove-delete-left left)
                                (windmove-delete-right right)
@@ -766,7 +766,7 @@ Default value of MODIFIERS is `shift-super'."
   (interactive)
   (unless modifiers (setq modifiers '(shift super)))
   (when (eq modifiers 'none) (setq modifiers nil))
-  (unless (listp modifiers) (setq modifiers (list modifiers)))
+  (setq modifiers (ensure-list modifiers))
   (windmove-install-defaults nil modifiers
                              '((windmove-swap-states-left left)
                                (windmove-swap-states-right right)
diff --git a/lisp/window.el b/lisp/window.el
index d91bbabc010..b9b032c33e9 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -2121,12 +2121,16 @@ remapped (see `face-remapping-alist'), the function 
returns the
 information for the remapped face."
    (with-selected-window (window-normalize-window window t)
      (if (display-multi-font-p)
-        (let* ((face (if face face 'default))
-               (info (font-info (face-font face)))
-               (width (aref info 11)))
-          (if (> width 0)
-             width
-            (aref info 10)))
+         ;; Opening the XLFD returned by `font-info' may be
+         ;; unsuccessful.  Use `frame-char-width' as a recourse if
+         ;; such a situation transpires.
+         (or (when-let* ((face (if face face 'default))
+                        (info (font-info (face-font face)))
+                        (width (aref info 11)))
+              (if (> width 0)
+                  width
+                (aref info 10)))
+             (frame-char-width))
        (frame-char-width))))
 
 (defun window-font-height (&optional window face)
@@ -2138,9 +2142,10 @@ remapped (see `face-remapping-alist'), the function 
returns the
 information for the remapped face."
    (with-selected-window (window-normalize-window window t)
      (if (display-multi-font-p)
-        (let* ((face (if face face 'default))
-               (info (font-info (face-font face))))
-          (aref info 3))
+        (or (when-let* ((face (if face face 'default))
+                        (info (font-info (face-font face))))
+              (aref info 3))
+             (frame-char-height))
        (frame-char-height))))
 
 (defvar overflow-newline-into-fringe)
@@ -7985,8 +7990,7 @@ indirectly called by the latter."
                           buffer-mode))
          (curwin (selected-window))
          (curframe (selected-frame)))
-    (unless (listp allowed-modes)
-      (setq allowed-modes (list allowed-modes)))
+    (setq allowed-modes (ensure-list allowed-modes))
     (let (same-mode-same-frame
           same-mode-other-frame
           derived-mode-same-frame
diff --git a/lisp/woman.el b/lisp/woman.el
index e4e3d176d08..e20a2399c00 100644
--- a/lisp/woman.el
+++ b/lisp/woman.el
@@ -1338,8 +1338,8 @@ PATH-DIRS should be a list of general manual directories 
(like
 manual directory regexps (like `woman-path').
 Ignore any paths that are unreadable or not directories."
   ;; Allow each path to be a single string or a list of strings:
-  (if (not (listp path-dirs)) (setq path-dirs (list path-dirs)))
-  (if (not (listp path-regexps)) (setq path-regexps (list path-regexps)))
+  (setq path-dirs (ensure-list path-dirs))
+  (setq path-regexps (ensure-list path-regexps))
   (let (head dirs path)
     (dolist (dir path-dirs)
       (when (consp dir)
@@ -2081,8 +2081,6 @@ European characters."
 
 ;;; The main decoding driver:
 
-(defvar font-lock-mode)                        ; for the compiler
-
 (defun woman-decode-buffer ()
   "Decode a buffer in UN*X man-page source format.
 No external programs are used."
diff --git a/m4/clock_time.m4 b/m4/clock_time.m4
index d624a73d35d..28534db1c76 100644
--- a/m4/clock_time.m4
+++ b/m4/clock_time.m4
@@ -1,4 +1,4 @@
-# clock_time.m4 serial 12
+# clock_time.m4 serial 13
 dnl Copyright (C) 2002-2006, 2009-2023 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -12,9 +12,17 @@ dnl with or without modifications, as long as this notice is 
preserved.
 
 AC_DEFUN([gl_CLOCK_TIME],
 [
+  AC_REQUIRE([AC_CANONICAL_HOST])
+
   dnl Persuade glibc and Solaris <time.h> to declare these functions.
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
 
+  # On mingw, these functions are defined in the libwinpthread library,
+  # which is better avoided.  In fact, the clock_gettime function is buggy
+  # in 32-bit mingw, when -D__MINGW_USE_VC2005_COMPAT is used (which Gnulib's
+  # year2038 module does): It leaves the upper 32 bits of the tv_sec field
+  # of the result uninitialized.
+
   # Solaris 2.5.1 needs -lposix4 to get the clock_gettime function.
   # Solaris 7 prefers the library name -lrt to the obsolescent name -lposix4.
 
@@ -23,12 +31,22 @@ AC_DEFUN([gl_CLOCK_TIME],
   # library, inducing unnecessary run-time overhead.
   CLOCK_TIME_LIB=
   AC_SUBST([CLOCK_TIME_LIB])
-  gl_saved_libs=$LIBS
-    AC_SEARCH_LIBS([clock_gettime], [rt posix4],
-                   [test "$ac_cv_search_clock_gettime" = "none required" ||
-                    CLOCK_TIME_LIB=$ac_cv_search_clock_gettime])
-    AC_CHECK_FUNCS([clock_getres clock_gettime clock_settime])
-  LIBS=$gl_saved_libs
+  case "$host_os" in
+    mingw*)
+      ac_cv_func_clock_getres=no
+      ac_cv_func_clock_gettime=no
+      ac_cv_func_clock_settime=no
+      ;;
+    *)
+      gl_saved_libs=$LIBS
+        AC_SEARCH_LIBS([clock_gettime], [rt posix4],
+                       [test "$ac_cv_search_clock_gettime" = "none required" ||
+                        CLOCK_TIME_LIB=$ac_cv_search_clock_gettime])
+        AC_CHECK_FUNCS([clock_getres clock_gettime clock_settime])
+      LIBS=$gl_saved_libs
+      ;;
+  esac
+
   # For backward compatibility.
   LIB_CLOCK_GETTIME="$CLOCK_TIME_LIB"
   AC_SUBST([LIB_CLOCK_GETTIME])
diff --git a/m4/getdelim.m4 b/m4/getdelim.m4
new file mode 100644
index 00000000000..60555b9718b
--- /dev/null
+++ b/m4/getdelim.m4
@@ -0,0 +1,114 @@
+# getdelim.m4 serial 19
+
+dnl Copyright (C) 2005-2007, 2009-2023 Free Software Foundation, Inc.
+dnl
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_PREREQ([2.59])
+
+AC_DEFUN([gl_FUNC_GETDELIM],
+[
+  AC_REQUIRE([gl_STDIO_H_DEFAULTS])
+  AC_REQUIRE([AC_CANONICAL_HOST])
+
+  dnl Persuade glibc <stdio.h> to declare getdelim().
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_DECLS_ONCE([getdelim])
+
+  gl_CHECK_FUNCS_ANDROID([getdelim], [[#include <stdio.h>]])
+  if test $ac_cv_func_getdelim = yes; then
+    HAVE_GETDELIM=1
+    dnl Found it in some library.  Verify that it works.
+    AC_CACHE_CHECK([for working getdelim function],
+      [gl_cv_func_working_getdelim],
+      [case "$host_os" in
+         darwin*)
+           dnl On macOS 10.13, valgrind detected an out-of-bounds read during
+           dnl the GNU sed test suite:
+           dnl   Invalid read of size 16
+           dnl      at 0x100EE6A05: _platform_memchr$VARIANT$Base (in 
/usr/lib/system/libsystem_platform.dylib)
+           dnl      by 0x100B7B0BD: getdelim (in 
/usr/lib/system/libsystem_c.dylib)
+           dnl      by 0x10000B0BE: ck_getdelim (utils.c:254)
+           gl_cv_func_working_getdelim=no ;;
+         *)
+           echo fooNbarN | tr -d '\012' | tr N '\012' > conftest.data
+           AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#    include <stdio.h>
+#    include <stdlib.h>
+#    include <string.h>
+    int main ()
+    {
+      FILE *in = fopen ("./conftest.data", "r");
+      if (!in)
+        return 1;
+      {
+        /* Test result for a NULL buffer and a zero size.
+           Based on a test program from Karl Heuer.  */
+        char *line = NULL;
+        size_t siz = 0;
+        int len = getdelim (&line, &siz, '\n', in);
+        if (!(len == 4 && line && strcmp (line, "foo\n") == 0))
+          { free (line); fclose (in); return 2; }
+        free (line);
+      }
+      {
+        /* Test result for a NULL buffer and a non-zero size.
+           This crashes on FreeBSD 8.0.  */
+        char *line = NULL;
+        size_t siz = (size_t)(~0) / 4;
+        if (getdelim (&line, &siz, '\n', in) == -1)
+          { fclose (in); return 3; }
+        free (line);
+      }
+      fclose (in);
+      return 0;
+    }
+    ]])],
+             [gl_cv_func_working_getdelim=yes],
+             [gl_cv_func_working_getdelim=no],
+             [dnl We're cross compiling.
+              dnl Guess it works on glibc2 systems and musl systems.
+              AC_EGREP_CPP([Lucky GNU user],
+                [
+#include <features.h>
+#ifdef __GNU_LIBRARY__
+ #if (__GLIBC__ >= 2) && !defined __UCLIBC__
+  Lucky GNU user
+ #endif
+#endif
+                ],
+                [gl_cv_func_working_getdelim="guessing yes"],
+                [case "$host_os" in
+                   *-musl* | midipix*) gl_cv_func_working_getdelim="guessing 
yes" ;;
+                   *)                  
gl_cv_func_working_getdelim="$gl_cross_guess_normal" ;;
+                 esac
+                ])
+             ])
+           ;;
+       esac
+      ])
+    case "$gl_cv_func_working_getdelim" in
+      *yes) ;;
+      *) REPLACE_GETDELIM=1 ;;
+    esac
+  else
+    HAVE_GETDELIM=0
+    case "$gl_cv_onwards_func_getdelim" in
+      future*) REPLACE_GETDELIM=1 ;;
+    esac
+  fi
+
+  if test $ac_cv_have_decl_getdelim = no; then
+    HAVE_DECL_GETDELIM=0
+  fi
+])
+
+# Prerequisites of lib/getdelim.c.
+AC_DEFUN([gl_PREREQ_GETDELIM],
+[
+  AC_CHECK_FUNCS([flockfile funlockfile])
+  AC_CHECK_DECLS([getc_unlocked])
+])
diff --git a/m4/getline.m4 b/m4/getline.m4
new file mode 100644
index 00000000000..83e7e9315f6
--- /dev/null
+++ b/m4/getline.m4
@@ -0,0 +1,111 @@
+# getline.m4 serial 33
+
+dnl Copyright (C) 1998-2003, 2005-2007, 2009-2023 Free Software Foundation,
+dnl Inc.
+dnl
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_PREREQ([2.59])
+
+dnl See if there's a working, system-supplied version of the getline function.
+dnl We can't just do AC_REPLACE_FUNCS([getline]) because some systems
+dnl have a function by that name in -linet that doesn't have anything
+dnl to do with the function we need.
+AC_DEFUN([gl_FUNC_GETLINE],
+[
+  AC_REQUIRE([gl_STDIO_H_DEFAULTS])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+
+  dnl Persuade glibc <stdio.h> to declare getline().
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_DECLS_ONCE([getline])
+
+  gl_CHECK_FUNCS_ANDROID([getline], [[#include <stdio.h>]])
+  if test $ac_cv_func_getline = yes; then
+    dnl Found it in some library.  Verify that it works.
+    AC_CACHE_CHECK([for working getline function],
+      [am_cv_func_working_getline],
+      [echo fooNbarN | tr -d '\012' | tr N '\012' > conftest.data
+       AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#    include <stdio.h>
+#    include <stdlib.h>
+#    include <string.h>
+    int main ()
+    {
+      FILE *in = fopen ("./conftest.data", "r");
+      if (!in)
+        return 1;
+      {
+        /* Test result for a NULL buffer and a zero size.
+           Based on a test program from Karl Heuer.  */
+        char *line = NULL;
+        size_t siz = 0;
+        int len = getline (&line, &siz, in);
+        if (!(len == 4 && line && strcmp (line, "foo\n") == 0))
+          { free (line); fclose (in); return 2; }
+        free (line);
+      }
+      {
+        /* Test result for a NULL buffer and a non-zero size.
+           This crashes on FreeBSD 8.0.  */
+        char *line = NULL;
+        size_t siz = (size_t)(~0) / 4;
+        if (getline (&line, &siz, in) == -1)
+          { fclose (in); return 3; }
+        free (line);
+      }
+      fclose (in);
+      return 0;
+    }
+    ]])],
+         [am_cv_func_working_getline=yes],
+         [am_cv_func_working_getline=no],
+         [dnl We're cross compiling.
+          dnl Guess it works on glibc2 systems and musl systems.
+          AC_EGREP_CPP([Lucky GNU user],
+            [
+#include <features.h>
+#ifdef __GNU_LIBRARY__
+ #if (__GLIBC__ >= 2) && !defined __UCLIBC__
+  Lucky GNU user
+ #endif
+#endif
+            ],
+            [am_cv_func_working_getline="guessing yes"],
+            [case "$host_os" in
+               *-musl* | midipix*) am_cv_func_working_getline="guessing yes" ;;
+               *)                  
am_cv_func_working_getline="$gl_cross_guess_normal" ;;
+             esac
+            ])
+         ])
+      ])
+  else
+    am_cv_func_working_getline=no
+    case "$gl_cv_onwards_func_getline" in
+      future*) REPLACE_GETLINE=1 ;;
+    esac
+  fi
+
+  if test $ac_cv_have_decl_getline = no; then
+    HAVE_DECL_GETLINE=0
+  fi
+
+  case "$am_cv_func_working_getline" in
+    *yes) ;;
+    *)
+      dnl Set REPLACE_GETLINE always: Even if we have not found the broken
+      dnl getline function among $LIBS, it may exist in libinet and the
+      dnl executable may be linked with -linet.
+      REPLACE_GETLINE=1
+      ;;
+  esac
+])
+
+# Prerequisites of lib/getline.c.
+AC_DEFUN([gl_PREREQ_GETLINE],
+[
+  :
+])
diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4
index 882b5869755..14ff92040a4 100644
--- a/m4/gnulib-comp.m4
+++ b/m4/gnulib-comp.m4
@@ -51,6 +51,7 @@ AC_DEFUN([gl_EARLY],
   # Code from module at-internal:
   # Code from module attribute:
   # Code from module binary-io:
+  # Code from module boot-time:
   # Code from module builtin-expect:
   # Code from module byteswap:
   # Code from module c-ctype:
@@ -104,8 +105,10 @@ AC_DEFUN([gl_EARLY],
   # Code from module fsync:
   # Code from module futimens:
   # Code from module gen-header:
+  # Code from module getdelim:
   # Code from module getdtablesize:
   # Code from module getgroups:
+  # Code from module getline:
   # Code from module getloadavg:
   # Code from module getopt-gnu:
   # Code from module getopt-posix:
@@ -241,6 +244,7 @@ AC_DEFUN([gl_INIT],
   gl_ASSERT_H
   gl_CONDITIONAL_HEADER([assert.h])
   AC_PROG_MKDIR_P
+  gl_PREREQ_READUTMP_H
   gl___BUILTIN_EXPECT
   gl_BYTESWAP
   gl_CONDITIONAL_HEADER([byteswap.h])
@@ -346,6 +350,12 @@ AC_DEFUN([gl_INIT],
   gl_CONDITIONAL([GL_COND_OBJ_FUTIMENS],
                  [test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1])
   gl_SYS_STAT_MODULE_INDICATOR([futimens])
+  gl_FUNC_GETLINE
+  gl_CONDITIONAL([GL_COND_OBJ_GETLINE], [test $REPLACE_GETLINE = 1])
+  AM_COND_IF([GL_COND_OBJ_GETLINE], [
+    gl_PREREQ_GETLINE
+  ])
+  gl_STDIO_MODULE_INDICATOR([getline])
   AC_REQUIRE([AC_CANONICAL_HOST])
   gl_GETLOADAVG
   gl_CONDITIONAL([GL_COND_OBJ_GETLOADAVG],
@@ -642,6 +652,7 @@ AC_DEFUN([gl_INIT],
   gl_gnulib_enabled_dirfd=false
   gl_gnulib_enabled_925677f0343de64b89a9f0c790b4104c=false
   gl_gnulib_enabled_euidaccess=false
+  gl_gnulib_enabled_getdelim=false
   gl_gnulib_enabled_getdtablesize=false
   gl_gnulib_enabled_getgroups=false
   gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=false
@@ -713,6 +724,19 @@ AC_DEFUN([gl_INIT],
       func_gl_gnulib_m4code_6099e9737f757db36c47fa9d9f02e88c
     fi
   }
+  func_gl_gnulib_m4code_getdelim ()
+  {
+    if $gl_gnulib_enabled_getdelim; then :; else
+      gl_FUNC_GETDELIM
+      gl_CONDITIONAL([GL_COND_OBJ_GETDELIM],
+                     [test $HAVE_GETDELIM = 0 || test $REPLACE_GETDELIM = 1])
+      AM_COND_IF([GL_COND_OBJ_GETDELIM], [
+        gl_PREREQ_GETDELIM
+      ])
+      gl_STDIO_MODULE_INDICATOR([getdelim])
+      gl_gnulib_enabled_getdelim=true
+    fi
+  }
   func_gl_gnulib_m4code_getdtablesize ()
   {
     if $gl_gnulib_enabled_getdtablesize; then :; else
@@ -978,6 +1002,9 @@ AC_DEFUN([gl_INIT],
   if test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1; then
     func_gl_gnulib_m4code_utimens
   fi
+  if test $REPLACE_GETLINE = 1; then
+    func_gl_gnulib_m4code_getdelim
+  fi
   if case $host_os in mingw*) false;; *) test $HAVE_GETLOADAVG = 0 || test 
$REPLACE_GETLOADAVG = 1;; esac; then
     func_gl_gnulib_m4code_open
   fi
@@ -1017,6 +1044,7 @@ AC_DEFUN([gl_INIT],
   AM_CONDITIONAL([gl_GNULIB_ENABLED_dirfd], [$gl_gnulib_enabled_dirfd])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_925677f0343de64b89a9f0c790b4104c], 
[$gl_gnulib_enabled_925677f0343de64b89a9f0c790b4104c])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_euidaccess], 
[$gl_gnulib_enabled_euidaccess])
+  AM_CONDITIONAL([gl_GNULIB_ENABLED_getdelim], [$gl_gnulib_enabled_getdelim])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_getdtablesize], 
[$gl_gnulib_enabled_getdtablesize])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getgroups])
   AM_CONDITIONAL([gl_GNULIB_ENABLED_be453cec5eecf5731a274f2de7f2db36], 
[$gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36])
@@ -1226,6 +1254,9 @@ AC_DEFUN([gl_FILE_LIST], [
   lib/attribute.h
   lib/binary-io.c
   lib/binary-io.h
+  lib/boot-time-aux.h
+  lib/boot-time.c
+  lib/boot-time.h
   lib/byteswap.in.h
   lib/c++defs.h
   lib/c-ctype.c
@@ -1284,8 +1315,10 @@ AC_DEFUN([gl_FILE_LIST], [
   lib/ftoastr.h
   lib/futimens.c
   lib/get-permissions.c
+  lib/getdelim.c
   lib/getdtablesize.c
   lib/getgroups.c
+  lib/getline.c
   lib/getloadavg.c
   lib/getopt-cdefs.in.h
   lib/getopt-core.h
@@ -1355,6 +1388,7 @@ AC_DEFUN([gl_FILE_LIST], [
   lib/rawmemchr.valgrind
   lib/readlink.c
   lib/readlinkat.c
+  lib/readutmp.h
   lib/realloc.c
   lib/regcomp.c
   lib/regex.c
@@ -1462,8 +1496,10 @@ AC_DEFUN([gl_FILE_LIST], [
   m4/fsusage.m4
   m4/fsync.m4
   m4/futimens.m4
+  m4/getdelim.m4
   m4/getdtablesize.m4
   m4/getgroups.m4
+  m4/getline.m4
   m4/getloadavg.m4
   m4/getopt.m4
   m4/getrandom.m4
@@ -1512,6 +1548,7 @@ AC_DEFUN([gl_FILE_LIST], [
   m4/rawmemchr.m4
   m4/readlink.m4
   m4/readlinkat.m4
+  m4/readutmp.m4
   m4/realloc.m4
   m4/regex.m4
   m4/sha1.m4
diff --git a/m4/mktime.m4 b/m4/mktime.m4
index e9d31f35a46..69cce86da5a 100644
--- a/m4/mktime.m4
+++ b/m4/mktime.m4
@@ -1,4 +1,4 @@
-# serial 37
+# serial 38
 dnl Copyright (C) 2002-2003, 2005-2007, 2009-2023 Free Software Foundation,
 dnl Inc.
 dnl This file is free software; the Free Software Foundation
@@ -280,7 +280,6 @@ AC_DEFUN([gl_FUNC_MKTIME],
   AC_REQUIRE([AC_CANONICAL_HOST])
   AC_REQUIRE([gl_FUNC_MKTIME_WORKS])
 
-  REPLACE_MKTIME=0
   if test "$gl_cv_func_working_mktime" != yes; then
     REPLACE_MKTIME=1
     AC_DEFINE([NEED_MKTIME_WORKING], [1],
diff --git a/m4/nanosleep.m4 b/m4/nanosleep.m4
index e21a3e343cb..b7f22d7b606 100644
--- a/m4/nanosleep.m4
+++ b/m4/nanosleep.m4
@@ -1,4 +1,4 @@
-# serial 43
+# serial 44
 
 dnl From Jim Meyering.
 dnl Check for the nanosleep function.
@@ -126,9 +126,7 @@ AC_DEFUN([gl_FUNC_NANOSLEEP],
        ])
     ])
    case "$gl_cv_func_nanosleep" in
-     *yes)
-       REPLACE_NANOSLEEP=0
-       ;;
+     *yes) ;;
      *)
        REPLACE_NANOSLEEP=1
        case "$gl_cv_func_nanosleep" in
diff --git a/m4/ndk-build.m4 b/m4/ndk-build.m4
new file mode 100644
index 00000000000..ab4e88ca168
--- /dev/null
+++ b/m4/ndk-build.m4
@@ -0,0 +1,479 @@
+dnl Copyright (C) 2023 Free Software Foundation, Inc.
+dnl This file is part of GNU Emacs.
+
+dnl GNU Emacs is free software: you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation, either version 3 of the License, or
+dnl (at your option) any later version.
+
+dnl GNU Emacs is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+
+dnl You should have received a copy of the GNU General Public License
+dnl along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# Support for building Emacs with dependencies using the Android NDK
+# build system.
+
+AC_ARG_WITH([ndk_path],
+  [AS_HELP_STRING([--with-ndk-path],
+    [find Android libraries in these directories])])
+
+AC_ARG_WITH([ndk_cxx_shared],
+  [AS_HELP_STRING([--with-ndk-cxx-shared],
+    [name of the C++ standard library included with the NDK])])
+
+AC_ARG_WITH([ndk_cxx],
+  [AS_HELP_STRING([--with-ndk-cxx],
+    [name of the C++ compiler included with the NDK])])
+
+# ndk_INIT(ABI, API, DIR, CFLAGS)
+# -------------------------------
+# Initialize the Android NDK.  ABI is the ABI being built for.
+# API is the API version being built for.
+# CFLAGS is a list of compiler flags.
+# As a side effect, set the variable ndk_INITIALIZED to true.
+# DIR should be a directory containing the Makefile.in actually
+# implementing the Android NDK build system.
+
+AC_DEFUN([ndk_INIT],
+[
+# Look for Android.mk files.
+ndk_module_files=
+for file in $with_ndk_path; do
+  if test -f $file/Android.mk; then
+    ndk_module_files="$ndk_module_files$file/Android.mk "
+  fi
+done
+
+AC_REQUIRE_AUX_FILE([ndk-build-helper.mk])
+ndk_AUX_DIR=$ac_aux_dir
+ndk_ABI=$1
+ndk_MODULES=
+ndk_MAKEFILES=
+ndk_INITIALIZED=yes
+ndk_API=$2
+ndk_DIR=$3
+ndk_ANY_CXX=
+ndk_BUILD_CFLAGS="$4"
+ndk_working_cxx=no
+
+AS_CASE(["$ndk_ABI"],
+  [*arm64*], [ndk_ARCH=arm64],
+  [*arm*], [ndk_ARCH=arm],
+  [*x86_64*], [ndk_ARCH=x86_64],
+  [*x86*], [ndk_ARCH=x86],
+  [*mips64*], [ndk_ARCH=mips64],
+  [*mips*], [ndk_ARCH=mips],
+  [AC_MSG_ERROR([Failed to determine Android device architecture])])
+
+# This is a map between pkg-config style package names and Android
+# ones.
+
+ndk_package_map="libwebpdemux:webpdemux libxml-2.0:libxml2 jansson:libjansson"
+ndk_package_map="$ndk_package_map sqlite3:libsqlite_static_minimal"
+ndk_package_map="$ndk_package_map MagickWand:libmagickwand-7 lcms2:liblcms2"
+
+# Replace ndk_module with the appropriate Android module name if it is
+# found in ndk_package_map.
+
+ndk_replace_pkg_config_package () {
+  for ndk_stuff in $ndk_package_map; do
+    ndk_key=`AS_ECHO([$ndk_stuff]) | cut -d: -f1`
+    ndk_value=`AS_ECHO([$ndk_stuff]) | cut -d: -f2`
+
+    if test "$ndk_key" = "$ndk_module"; then
+      ndk_module="$ndk_value"
+      break
+    fi
+  done
+}
+
+# Run the Makefile helper script for the Android.mk file.
+
+ndk_run_test () {
+  # Figure out where the helper Makefile is.
+  ndk_build_helper_file="${ndk_AUX_DIR}ndk-build-helper.mk"
+  ndk_module_extract_awk="${ndk_AUX_DIR}ndk-module-extract.awk"
+  ndk_dir=`AS_DIRNAME([$ndk_android_mk])`
+
+  # Now call Make with the right arguments.
+  "$MAKE" -s -f "$ndk_build_helper_file" EMACS_SRCDIR=`pwd`            \
+    EMACS_ABI="$ndk_ABI" ANDROID_MAKEFILE="$ndk_android_mk"            \
+    NDK_BUILD_DIR="$ndk_DIR" NDK_ROOT="/tmp"                           \
+    ANDROID_MODULE_DIRECTORY="$ndk_dir" BUILD_AUXDIR=$ndk_AUX_DIR      \
+    NDK_BUILD_ARCH="$ndk_ARCH" 2>&AS_MESSAGE_LOG_FD >conftest.ndk
+
+  # Read the output.
+  cat conftest.ndk | awk -f "$ndk_module_extract_awk" MODULE="$ndk_module"
+
+  # Remove the temporary file.
+  rm -f conftest.ndk
+}
+
+# ndk_parse_pkg_config_string PKG_CONFIG_STRING
+# ---------------------------------------------
+# Parse a pkg-config style list of modules.  Place the resulting list
+# in ndk_modules.
+
+ndk_parse_pkg_config_string () {
+  ndk_input=[$]1
+  ndk_modules=
+  while test -n "$ndk_input"; do
+    ndk_str=`AS_ECHO_N(["$ndk_input"]) | cut -f1 -d' '`
+    ndk_input=`AS_ECHO_N(["$ndk_input"]) | cut -s -f2- -d' '`
+
+    if test "$ndk_str" = ">=" || test "$ndk_str" = "<=" \
+      || test "$ndk_str" = ">" || test "$ndk_str" = "<" \
+      || test "$ndk_str" = "!="; then
+      ndk_input=`AS_ECHO_N(["$ndk_input"]) | cut -s -f2- -d' '`
+    else
+      ndk_modules="$ndk_modules$ndk_str "
+    fi
+  done
+}
+
+# ndk_resolve_import_module MODULE
+# --------------------------------
+# Resolve MODULE, a single import.  Prepend its makefile to
+# ndk_MAKEFILES if found.  Also, prepend all includes to the variable
+# ndk_import_includes.
+
+ndk_resolve_import_module () {
+  module_name=
+  ndk_module=[$]1
+
+  AC_MSG_CHECKING([for imported $ndk_module])
+
+  for ndk_android_mk in $ndk_module_files; do
+    # Read this Android.mk file.  Set NDK_ROOT to /tmp: the Android in
+    # tree build system sets it to a meaning value, but build files
+    # just use it to test whether or not the NDK is being used.
+    ndk_commands=`ndk_run_test`
+    eval "$ndk_commands"
+
+    if test -n "$module_name"; then
+      break;
+    fi
+  done
+
+  AS_IF([test -z "$module_name"],
+    [AC_MSG_RESULT([no])
+     AC_MSG_ERROR([The module currently being built depends on [$]1, but \
+that could not be found in the list of directories specified in \
+`--with-ndk-path'.])])
+
+  if test -n "$module_cxx_deps"; then
+    ndk_ANY_CXX=yes
+  fi
+
+  AS_IF([test "$ndk_ANY_CXX" = "yes" && test -z "$with_ndk_cxx_shared"],
+    [AC_MSG_ERROR([The module [$]1 requires the C++ standard library \
+(libc++_shared.so), but it was not found.])])
+
+  AS_IF([test "$ndk_ANY_CXX" = "yes" && test "$ndk_working_cxx" != "yes"],
+    [AC_MSG_ERROR([The module [$]1 requires the C++ standard library \
+(libc++_shared.so), but a working C++ compiler was not found.])])
+
+  AC_MSG_RESULT([yes])
+
+  # Make sure the module is prepended.
+  ndk_MAKEFILES="$ndk_android_mk $ndk_MAKEFILES"
+  ndk_import_includes="$module_includes $ndk_import_includes"
+
+  # Now recursively resolve this module's imports.
+  for ndk_module in $module_imports; do
+    ndk_resolve_import_module $ndk_module
+  done
+}
+
+# ndk_filter_cc_for_cxx
+# ---------------------
+# Run through $CC, removing any options that are not suitable for
+# use in a C++ compiler.
+
+ndk_filter_cc_for_cxx () {
+  for ndk_word in $CC; do
+    AS_CASE([$ndk_word], [*-std=*], [],
+      [AS_ECHO_N(["$ndk_word "])])
+  done
+}
+
+# ndk_subst_cc_onto_cxx
+# ---------------------
+# Print the value of $CXX, followed by any innocent looking options
+# in $CC.
+
+ndk_subst_cc_onto_cxx () {
+  AS_ECHO_N(["$CXX "])
+  ndk_flag=
+  for ndk_word in `AS_ECHO_N(["$CC"]) | cut -s -f2- -d' '`; do
+    AS_IF([test "$ndk_flag" = "yes"],
+      [AS_ECHO_N(["$ndk_word "])
+       ndk_flag=no],
+      [AS_CASE([$ndk_word],
+        [*-sysroot=*],
+          [AS_ECHO_N(["$ndk_word "])],
+       [*-isystem*],
+          [AS_ECHO_N(["$ndk_word "])
+          ndk_flag=yes],
+       [*-sysroot*],
+         [AS_ECHO_N(["$ndk_word "])
+          ndk_flag=yes],
+       [-D__ANDROID_API__*],
+         [AS_ECHO_N(["$ndk_word "])])])
+  done
+}
+
+# Look for a suitable ar and ranlib in the same directory as the C
+# compiler.
+ndk_cc_firstword=`AS_ECHO(["$CC"]) | cut -d' ' -f1`
+ndk_where_cc=`which $ndk_cc_firstword`
+ndk_ar_search_path=$PATH
+ndk_ranlib_search_path=$RANLIB
+
+# First, try to find $host_alias-ar in PATH.
+AC_PATH_PROGS([AR], [$host_alias-ar], [], [$ndk_ar_search_path])
+
+AS_IF([test -z "$AR"],[
+  # Next, try finding either that or llvm-ar in the directory holding
+  # CC.
+  ndk_ar_search_path="`AS_DIRNAME([$ndk_where_cc])`:$ndk_ar_search_path"
+  AC_PATH_PROGS([AR], [$host_alias-ar llvm-ar], [], [$ndk_ar_search_path])])
+
+# First, try to find $host_alias-ranlib in PATH.
+AC_PATH_PROGS([RANLIB], [$host_alias-ranlib], [], [$ndk_ranlib_search_path])
+
+AS_IF([test -z "$RANLIB"],[
+  # Next, try finding either that or llvm-ranlib in the directory
+  # holding CC.
+  
ndk_ranlib_search_path="`AS_DIRNAME([$ndk_where_cc])`:$ndk_ranlib_search_path"
+  AC_PATH_PROGS([RANLIB], [$host_alias-ranlib llvm-ranlib], [],
+    [$ndk_ranlib_search_path])])
+
+NDK_BUILD_NASM=
+
+# Next, try to find nasm on x86.  This doesn't ship with the NDK.
+AS_IF([test "$ndk_ARCH" = "x86" || test "$ndk_ARCH" = "x86_64"],
+  [AC_CHECK_PROGS([NDK_BUILD_NASM], [nasm])])
+
+# Look for a file named ``libc++_shared.so'' in a subdirectory of
+# $ndk_where_cc if it was not specified.
+AC_MSG_CHECKING([for libc++_shared.so])
+
+ndk_where_toolchain=
+AS_IF([test -z "$with_ndk_cxx_shared" && test -n "$ndk_where_cc"],[
+  # Find the NDK root directory.  Go to $ndk_where_cc.
+  SAVE_PWD=`pwd`
+  cd `AS_DIRNAME(["$ndk_where_cc"])`
+
+  # Now, keep moving backwards until pwd ends with ``toolchains''.
+  while :; do
+    if test "`pwd`" = "/"; then
+      cd "$SAVE_PWD"
+      break
+    fi
+
+    ndk_pwd=`pwd`
+    if test "`AS_BASENAME([$ndk_pwd])`" = "toolchains"; then
+      ndk_where_toolchain=$ndk_pwd
+      cd "$SAVE_PWD"
+      break
+    fi
+
+    cd ..
+  done
+
+  ndk_matching_libcxx_shared_so=
+
+  # The toolchain directory should be in "$ndk_where_toolchain".
+  AS_IF([test -n "$ndk_where_toolchain"],[
+    # Now, look in the directory behind it.
+    ndk_cxx_shared_so=`find "$ndk_where_toolchain" -name libc++_shared.so`
+
+    # Look for one with the correct architecture.
+    for ndk_candidate in $ndk_cxx_shared_so; do
+      AS_CASE([$ndk_candidate],
+        [*arm-linux-android*],
+         [AS_IF([test "$ndk_ARCH" = "arm"],
+           [ndk_matching_libcxx_shared_so=$ndk_candidate])],
+       [*aarch64-linux-android*],
+         [AS_IF([test "$ndk_ARCH" = "arm64"],
+           [ndk_matching_libcxx_shared_so=$ndk_candidate])],
+       [*i[[3-6]]86-linux-android*],
+         [AS_IF([test "$ndk_ARCH" = "x86"],
+           [ndk_matching_libcxx_shared_so=$ndk_candidate])],
+       [*x86_64-linux-android*],
+         [AS_IF([test "$ndk_ARCH" = "x86_64"],
+           [ndk_matching_libcxx_shared_so=$ndk_candidate])])
+
+      AS_IF([test -n "$ndk_matching_libcxx_shared_so"],
+        [with_ndk_cxx_shared=$ndk_matching_libcxx_shared_so])
+    done])])
+
+AS_IF([test -z "$with_ndk_cxx_shared"],[AC_MSG_RESULT([no])
+  AC_MSG_WARN([The C++ standard library could not be found.  \
+If you try to build Emacs with a dependency that requires the C++ standard \
+library, Emacs will not build correctly, unless you manually specify the \
+name of an appropriate ``libc++_shared.so'' binary.])],
+  [AC_MSG_RESULT([$with_ndk_cxx_shared])])
+
+ndk_CXX_SHARED=$with_ndk_cxx_shared
+
+# These variables have now been found.  Now look for a C++ compiler.
+# Upon failure, pretend the C compiler is a C++ compiler and use that
+# instead.
+
+ndk_cc_name=`AS_BASENAME(["${ndk_cc_firstword}"])`
+ndk_cxx_name=
+
+AS_CASE([$ndk_cc_name], [*-gcc],
+  [ndk_cxx_name=`AS_ECHO([$ndk_cc_name]) | sed 's/gcc/g++/'`],
+  [ndk_cxx_name="${ndk_cc_name}++"])
+
+AS_IF([test -n "$with_ndk_cxx"], [CXX=$with_ndk_cxx],
+  [AC_PATH_PROGS([CXX], [$ndk_cxx_name],
+     [], [`AS_DIRNAME(["$ndk_where_cc"])`:$PATH])
+   AS_IF([test -z "$CXX"], [CXX=`ndk_filter_cc_for_cxx`],
+     [CXX=`ndk_subst_cc_onto_cxx`])])
+])
+
+# ndk_LATE
+# --------
+# Perform late initialization of the ndk-build system by checking for
+# required C and C++ headers.
+
+AC_DEFUN([ndk_LATE],
+[dnl
+dnl This calls AC_REQUIRE([AC_PROG_CXX]), leading to configure looking
+dnl for a C++ compiler.  However, the language is not restored
+dnl afterwards if not `$ndk_INITIALIZED'.
+AS_IF([test "$ndk_INITIALIZED" = "yes"],[
+  AS_IF([test -n "$CXX"], [AC_LANG_PUSH([C++])
+    AC_CHECK_HEADER([string], [ndk_working_cxx=yes],
+      [AC_MSG_WARN([Your C++ compiler is not properly set up, and\
+ the standard library headers could not be found.])])
+    AC_LANG_POP([C++])])])
+dnl Thus, manually switch back to C here.
+AC_LANG([C])
+])
+
+# ndk_SEARCH_MODULE(MODULE, NAME, ACTION-IF-FOUND, [ACTION-IF-NOT-FOUND])
+# -----------------------------------------------------------------------
+# Search for a module named MODULE in `with_ndk_path'.  Add the file
+# name of the module's Android.mk file to the variable ndk_MAKEFILES.
+# Set NAME_CFLAGS and NAME_LIBS to the appropriate values.  Then, call
+# ACTION-IF-FOUND, or ACTION-IF-NOT-FOUND upon failure.
+#
+# Resolve any imports specified by MODULE, and expand AC_MSG_ERROR
+# with a suitable error message if imports were not found.
+AC_DEFUN([ndk_SEARCH_MODULE],
+[
+module_name=
+ndk_module=$1
+ndk_replace_pkg_config_package
+AC_MSG_CHECKING([for Android.mk that builds $ndk_module])
+
+for ndk_android_mk in $ndk_module_files; do
+  # Read this Android.mk file.  Set NDK_ROOT to /tmp: the Android in
+  # tree build system sets it to a meaning value, but build files just
+  # use it to test whether or not the NDK is being used.
+  ndk_commands=`ndk_run_test`
+
+  eval "$ndk_commands"
+  if test -n "$module_name"; then
+    break;
+  fi
+done
+
+if test -z "$module_name"; then
+  AC_MSG_RESULT([no])
+  $4
+else
+  if test -n "$module_cxx_deps"; then
+    ndk_ANY_CXX=yes
+  fi
+
+  AS_IF([test "$ndk_ANY_CXX" = "yes" && test -z "$with_ndk_cxx_shared"],
+    [AC_MSG_ERROR([The module $1 requires the C++ standard library \
+(libc++_shared.so), but it was not found.])])
+
+  AS_IF([test "$ndk_ANY_CXX" = "yes" && test "$ndk_working_cxx" != "yes"],
+    [AC_MSG_ERROR([The module [$]1 requires the C++ standard library \
+(libc++_shared.so), but a working C++ compiler was not found.])])
+
+  $2[]_CFLAGS="[$]$2[]_CFLAGS $module_cflags $module_includes"
+  $2[]_LIBS="[$]$2[]_LIBS $module_ldflags"
+  ndk_MAKEFILES="$ndk_MAKEFILES $ndk_android_mk"
+  ndk_MODULES="$ndk_MODULES $module_target"
+  AC_MSG_RESULT([yes])
+  $3
+
+  # Now, resolve imports.  Make sure the imports' Makefiles comes
+  # before ndk_MAKEFILES; likewise for its includes.
+  ndk_import_includes=
+  for ndk_module in $module_imports; do
+    ndk_resolve_import_module $ndk_module
+    $2[]_CFLAGS="$ndk_import_includes [$]$2[]_CFLAGS"
+  done
+fi
+])
+
+# ndk_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+#   [ACTION-IF-NOT-FOUND])
+# --------------------------------------------------------------
+# Just like `PKG_CHECK_MODULES'.  However, it uses the ndk-build
+# system instead.
+
+AC_DEFUN([ndk_CHECK_MODULES],
+[
+  ndk_modules=
+  ndk_parse_pkg_config_string "$2"
+  ndk_found=no
+
+  for module in $ndk_modules; do
+    ndk_SEARCH_MODULE([$module], [$1], [ndk_found=yes], [ndk_found=no])
+  done
+
+  AS_IF([test "$ndk_found" = "yes"],[$3],[$4])
+])
+
+# ndk_CONFIG_FILES
+# -------------------------------------------------------------
+# Write out the NDK build Makefile with the appropriate variables
+# set if the NDK has been initialized.
+
+AC_DEFUN_ONCE([ndk_CONFIG_FILES],
+[
+  if test "$ndk_INITIALIZED" = "yes"; then
+    NDK_BUILD_ANDROID_MK="$ndk_MAKEFILES"
+    NDK_BUILD_ARCH=$ndk_ARCH
+    NDK_BUILD_ABI=$ndk_ABI
+    NDK_BUILD_SDK=$ndk_API
+    NDK_BUILD_CC=$CC
+    NDK_BUILD_CXX=$CXX
+    NDK_BUILD_AR=$AR
+    NDK_BUILD_MODULES="$ndk_MODULES"
+    NDK_BUILD_CXX_SHARED="$ndk_CXX_SHARED"
+    NDK_BUILD_ANY_CXX_MODULE=$ndk_ANY_CXX
+    NDK_BUILD_CFLAGS="$ndk_BUILD_CFLAGS"
+
+    AC_SUBST([NDK_BUILD_ANDROID_MK])
+    AC_SUBST([NDK_BUILD_ARCH])
+    AC_SUBST([NDK_BUILD_ABI])
+    AC_SUBST([NDK_BUILD_SDK])
+    AC_SUBST([NDK_BUILD_CC])
+    AC_SUBST([NDK_BUILD_CXX])
+    AC_SUBST([NDK_BUILD_AR])
+    AC_SUBST([NDK_BUILD_NASM])
+    AC_SUBST([NDK_BUILD_MODULES])
+    AC_SUBST([NDK_BUILD_CXX_SHARED])
+    AC_SUBST([NDK_BUILD_ANY_CXX_MODULE])
+    AC_SUBST([NDK_BUILD_CFLAGS])
+
+    AC_CONFIG_FILES([$ndk_DIR/Makefile])
+    AC_CONFIG_FILES([$ndk_DIR/ndk-build.mk])
+  fi
+])
diff --git a/m4/printf-posix-rpl.m4 b/m4/printf-posix-rpl.m4
new file mode 100644
index 00000000000..36156d43502
--- /dev/null
+++ b/m4/printf-posix-rpl.m4
@@ -0,0 +1,26 @@
+# printf-posix-rpl.m4 serial 4
+dnl Copyright (C) 2007-2023 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_PRINTF_POSIX],
+[
+  AC_REQUIRE([gl_FUNC_VFPRINTF_POSIX])
+  if test $gl_cv_func_vfprintf_posix = no; then
+    gl_REPLACE_PRINTF
+  fi
+])
+
+AC_DEFUN([gl_REPLACE_PRINTF],
+[
+  AC_REQUIRE([gl_STDIO_H_DEFAULTS])
+  AC_REQUIRE([gl_ASM_SYMBOL_PREFIX])
+  AC_LIBOBJ([printf])
+  REPLACE_PRINTF=1
+  AC_DEFINE([REPLACE_PRINTF_POSIX], [1],
+    [Define if printf is overridden by a POSIX compliant gnulib 
implementation.])
+  gl_PREREQ_PRINTF
+])
+
+AC_DEFUN([gl_PREREQ_PRINTF], [:])
diff --git a/m4/readutmp.m4 b/m4/readutmp.m4
new file mode 100644
index 00000000000..0a47f4bb77d
--- /dev/null
+++ b/m4/readutmp.m4
@@ -0,0 +1,121 @@
+# readutmp.m4 serial 30
+dnl Copyright (C) 2002-2023 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_READUTMP],
+[
+  AC_REQUIRE([gl_SYSTEMD_CHOICE])
+
+  dnl Set READUTMP_LIB to '-lsystemd' or '', depending on whether use of
+  dnl systemd APIs is possible and desired (only the systemd login API, here).
+  dnl AC_LIB_LINKFLAGS_BODY would be overkill here, since few people install
+  dnl libsystemd in non-system directories.
+  READUTMP_LIB=
+  if test "$SYSTEMD_CHOICE" = yes; then
+    AC_CHECK_HEADER([systemd/sd-login.h])
+    if test $ac_cv_header_systemd_sd_login_h = yes; then
+      AC_CACHE_CHECK([for libsystemd version >= 254],
+        [gl_cv_lib_readutmp_systemd],
+        [gl_save_LIBS="$LIBS"
+         LIBS="$LIBS -lsystemd"
+         AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM([[
+              #include <stdint.h>
+              #include <systemd/sd-login.h>
+              ]], [[
+              uint64_t st;
+              sd_session_get_start_time ("1", &st);
+              ]])
+           ],
+           [gl_cv_lib_readutmp_systemd=yes],
+           [gl_cv_lib_readutmp_systemd=no])
+         LIBS="$gl_save_LIBS"
+        ])
+      if test $gl_cv_lib_readutmp_systemd = yes; then
+        AC_DEFINE([READUTMP_USE_SYSTEMD], [1],
+          [Define if the readutmp module should use the systemd login API.])
+        READUTMP_LIB='-lsystemd'
+      fi
+    fi
+  fi
+  AC_SUBST([READUTMP_LIB])
+
+  gl_PREREQ_READUTMP_H
+])
+
+# Prerequisites of readutmp.h and boot-time-aux.h.
+AC_DEFUN_ONCE([gl_PREREQ_READUTMP_H],
+[
+  dnl Persuade utmpx.h to declare utmpxname
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_HEADERS_ONCE([utmp.h utmpx.h])
+  if test $ac_cv_header_utmp_h = yes || test $ac_cv_header_utmpx_h = yes; then
+    dnl Prerequisites of lib/readutmp.h and lib/readutmp.c.
+    AC_CHECK_FUNCS_ONCE([utmpname utmpxname])
+    AC_CHECK_DECLS([endutent],,,[[
+/* <sys/types.h> is a prerequisite of <utmp.h> on FreeBSD 8.0, OpenBSD 4.6.  */
+#include <sys/types.h>
+#ifdef HAVE_UTMP_H
+# include <utmp.h>
+#endif
+]])
+    utmp_includes="\
+AC_INCLUDES_DEFAULT
+#ifdef HAVE_UTMPX_H
+# include <utmpx.h>
+#endif
+#ifdef HAVE_UTMP_H
+# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
+   /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
+      defined, work around the duplicate struct utmp_data declaration.  */
+#  define utmp_data gl_aix_4_3_workaround_utmp_data
+# endif
+# include <utmp.h>
+#endif
+"
+    AC_CHECK_MEMBERS([struct utmpx.ut_user],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_user],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_name],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_name],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_type],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_type],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_pid],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_pid],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_tv],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_host],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_host],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_id],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_id],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_session],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_session],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_exit],,,[$utmp_includes])
+
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.ut_exit],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.e_exit],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_exit.e_exit],,,[$utmp_includes])
+
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.ut_termination],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.e_termination],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_exit.e_termination],,,[$utmp_includes])
+  fi
+
+  AC_CHECK_DECLS([sysinfo],,,[[
+    #include <sys/sysinfo.h>
+    ]])
+
+  AC_CHECK_HEADERS_ONCE([sys/param.h])
+  dnl <sys/sysctl.h> requires <sys/param.h> on OpenBSD 4.0.
+  AC_CHECK_HEADERS([sys/sysctl.h],,,
+    [AC_INCLUDES_DEFAULT
+     #if HAVE_SYS_PARAM_H
+     # include <sys/param.h>
+     #endif
+    ])
+  AC_CHECK_FUNCS([sysctl])
+
+  AC_CHECK_HEADERS_ONCE([OS.h])
+])
diff --git a/m4/stdalign.m4 b/m4/stdalign.m4
index 1a236d66d2f..6a39ffe7565 100644
--- a/m4/stdalign.m4
+++ b/m4/stdalign.m4
@@ -68,8 +68,10 @@ AC_DEFUN([gl_ALIGNASOF],
   dnl The "zz" puts this toward config.h's end, to avoid potential
   dnl collisions with other definitions.
   AH_VERBATIM([zzalignas],
-[#if !defined HAVE_C_ALIGNASOF && __cplusplus < 201103 && !defined alignof
-# if HAVE_STDALIGN_H
+[#if !defined HAVE_C_ALIGNASOF \
+    && !(defined __cplusplus && 201103 <= __cplusplus) \
+    && !defined alignof
+# if defined HAVE_STDALIGN_H
 #  include <stdalign.h>
 # endif
 
@@ -166,7 +168,7 @@ AC_DEFUN([gl_ALIGNASOF],
 #   define _Alignas(a) __declspec (align (a))
 #  endif
 # endif
-# if !HAVE_STDALIGN_H
+# if !defined HAVE_STDALIGN_H
 #  if ((defined _Alignas \
         && !(defined __cplusplus \
              && (201103 <= __cplusplus || defined _MSC_VER))) \
@@ -175,7 +177,7 @@ AC_DEFUN([gl_ALIGNASOF],
 #  endif
 # endif
 
-# if _GL_STDALIGN_NEEDS_STDDEF
+# if defined _GL_STDALIGN_NEEDS_STDDEF
 #  include <stddef.h>
 # endif
 #endif])
diff --git a/m4/stdint.m4 b/m4/stdint.m4
index d6961b0993e..b9f764d4c1c 100644
--- a/m4/stdint.m4
+++ b/m4/stdint.m4
@@ -1,4 +1,4 @@
-# stdint.m4 serial 61
+# stdint.m4 serial 62
 dnl Copyright (C) 2001-2023 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -150,7 +150,10 @@ intmax_t i = INTMAX_MAX;
 uintmax_t j = UINTMAX_MAX;
 
 /* Check that SIZE_MAX has the correct type, if possible.  */
-#if 201112 <= __STDC_VERSION__
+/* ISO C 11 mandates _Generic, but GCC versions < 4.9 lack it.  */
+#if 201112 <= __STDC_VERSION__ \
+    && (!defined __GNUC__ || 4 < __GNUC__ + (9 <= __GNUC_MINOR__) \
+        || defined __clang__)
 int k = _Generic (SIZE_MAX, size_t: 0);
 #elif (2 <= __GNUC__ || 4 <= __clang_major__ || defined __IBM__TYPEOF__ \
        || (0x5110 <= __SUNPRO_C && !__STDC__))
diff --git a/m4/time_h.m4 b/m4/time_h.m4
index 51d553a2f1a..632d18fc071 100644
--- a/m4/time_h.m4
+++ b/m4/time_h.m4
@@ -2,7 +2,7 @@
 
 # Copyright (C) 2000-2001, 2003-2007, 2009-2023 Free Software Foundation, Inc.
 
-# serial 22
+# serial 24
 
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -23,7 +23,10 @@ AC_DEFUN_ONCE([gl_TIME_H],
   dnl corresponding gnulib module is not in use.
   gl_WARN_ON_USE_PREPARE([[
 #include <time.h>
-    ]], [asctime_r ctime_r])
+    ]], [
+      asctime asctime_r ctime ctime_r gmtime_r localtime localtime_r mktime
+      nanosleep strftime strptime time timegm timespec_get timespec_getres 
tzset
+    ])
 
   AC_REQUIRE([AC_C_RESTRICT])
 
@@ -162,25 +165,15 @@ AC_DEFUN([gl_TIME_H_DEFAULTS],
   HAVE_TIMESPEC_GETRES=1;                AC_SUBST([HAVE_TIMESPEC_GETRES])
   dnl Even GNU libc does not have timezone_t yet.
   HAVE_TIMEZONE_T=0;                     AC_SUBST([HAVE_TIMEZONE_T])
-  dnl If another module says to replace or to not replace, do that.
-  dnl Otherwise, replace only if someone compiles with -DGNULIB_PORTCHECK;
-  dnl this lets maintainers check for portability.
-  REPLACE_CTIME=GNULIB_PORTCHECK;        AC_SUBST([REPLACE_CTIME])
-  REPLACE_LOCALTIME_R=GNULIB_PORTCHECK;  AC_SUBST([REPLACE_LOCALTIME_R])
-  REPLACE_MKTIME=GNULIB_PORTCHECK;       AC_SUBST([REPLACE_MKTIME])
-  REPLACE_NANOSLEEP=GNULIB_PORTCHECK;    AC_SUBST([REPLACE_NANOSLEEP])
-  REPLACE_STRFTIME=GNULIB_PORTCHECK;     AC_SUBST([REPLACE_STRFTIME])
-  REPLACE_TIME=0;                        AC_SUBST([REPLACE_TIME])
-  REPLACE_TIMEGM=GNULIB_PORTCHECK;       AC_SUBST([REPLACE_TIMEGM])
-  REPLACE_TIMESPEC_GET=GNULIB_PORTCHECK; AC_SUBST([REPLACE_TIMESPEC_GET])
-  REPLACE_TZSET=GNULIB_PORTCHECK;        AC_SUBST([REPLACE_TZSET])
-
-  dnl Hack so that the time module doesn't depend on the sys_time module.
-  dnl First, default GNULIB_GETTIMEOFDAY to 0 if sys_time is absent.
-  : ${GNULIB_GETTIMEOFDAY=0};            AC_SUBST([GNULIB_GETTIMEOFDAY])
-  dnl Second, it's OK to not use GNULIB_PORTCHECK for REPLACE_GMTIME
-  dnl and REPLACE_LOCALTIME, as portability to Solaris 2.6 and earlier
-  dnl is no longer a big deal.
+  REPLACE_CTIME=0;                       AC_SUBST([REPLACE_CTIME])
   REPLACE_GMTIME=0;                      AC_SUBST([REPLACE_GMTIME])
   REPLACE_LOCALTIME=0;                   AC_SUBST([REPLACE_LOCALTIME])
+  REPLACE_LOCALTIME_R=0;                 AC_SUBST([REPLACE_LOCALTIME_R])
+  REPLACE_MKTIME=0;                      AC_SUBST([REPLACE_MKTIME])
+  REPLACE_NANOSLEEP=0;                   AC_SUBST([REPLACE_NANOSLEEP])
+  REPLACE_STRFTIME=0;                    AC_SUBST([REPLACE_STRFTIME])
+  REPLACE_TIME=0;                        AC_SUBST([REPLACE_TIME])
+  REPLACE_TIMEGM=0;                      AC_SUBST([REPLACE_TIMEGM])
+  REPLACE_TIMESPEC_GET=0;                AC_SUBST([REPLACE_TIMESPEC_GET])
+  REPLACE_TZSET=0;                       AC_SUBST([REPLACE_TZSET])
 ])
diff --git a/m4/time_r.m4 b/m4/time_r.m4
index adce438abf1..4831eb26f90 100644
--- a/m4/time_r.m4
+++ b/m4/time_r.m4
@@ -57,9 +57,7 @@ AC_DEFUN([gl_TIME_R],
          [gl_cv_time_r_posix=yes],
          [gl_cv_time_r_posix=no])
       ])
-    if test $gl_cv_time_r_posix = yes; then
-      REPLACE_LOCALTIME_R=0
-    else
+    if test $gl_cv_time_r_posix != yes; then
       REPLACE_LOCALTIME_R=1
     fi
   else
diff --git a/m4/timegm.m4 b/m4/timegm.m4
index 8ab265e65fe..6da07807698 100644
--- a/m4/timegm.m4
+++ b/m4/timegm.m4
@@ -1,4 +1,4 @@
-# timegm.m4 serial 15
+# timegm.m4 serial 16
 dnl Copyright (C) 2003, 2007, 2009-2023 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -8,7 +8,6 @@ AC_DEFUN([gl_FUNC_TIMEGM],
 [
   AC_REQUIRE([gl_TIME_H_DEFAULTS])
   AC_REQUIRE([gl_FUNC_MKTIME_WORKS])
-  REPLACE_TIMEGM=0
   gl_CHECK_FUNCS_ANDROID([timegm], [[#include <time.h>]])
   if test $ac_cv_func_timegm = yes; then
     if test "$gl_cv_func_working_mktime" != yes; then
diff --git a/make-dist b/make-dist
index 80c672dbf3a..a3b7219af3a 100755
--- a/make-dist
+++ b/make-dist
@@ -356,7 +356,8 @@ possibly_non_vc_files="
   $top_level_ChangeLog
   MANIFEST aclocal.m4 configure
   admin/charsets/jisx2131-filter
-  src/config.in
+  src/config.in exec/configure
+  exec/config.h.in
 "$(
   find admin doc etc lisp \
    \( -name '*.el' -o -name '*.elc' -o -name '*.map' -o -name '*.stamp' \
diff --git a/msdos/sed1v2.inp b/msdos/sed1v2.inp
index 162ccb3e8d8..d299ec17574 100644
--- a/msdos/sed1v2.inp
+++ b/msdos/sed1v2.inp
@@ -56,6 +56,9 @@ s/ *@LIBPNG@//
 s/ *@LIBGIF@//
 s/ *@LIBXPM@//
 s/ *@WEBP_LIBS@//
+/^GIF_CFLAGS *=/s/@GIF_CFLAGS@//
+/^JPEG_CFLAGS *=/s/@JPEG_CFLAGS@//
+/^TIFF_CFLAGS *=/s/@TIFF_CFLAGS@//
 /^HAVE_NATIVE_COMP *=/s/@HAVE_NATIVE_COMP@/no/
 /^HAVE_PDUMPER *=/s/@HAVE_PDUMPER@/no/
 /^HAVE_BE_APP *=/s/@HAVE_BE_APP@/no/
@@ -183,6 +186,7 @@ s/ *@WEBP_LIBS@//
 /^HARFBUZZ_CFLAGS *=/s/@HARFBUZZ_CFLAGS@//
 /^HARFBUZZ_LIBS *=/s/@HARFBUZZ_LIBS@//
 /^QCOPY_ACL_LIB *=/s/@QCOPY_ACL_LIB@//
+/^TIMER_TIME_LIB *=/s/@TIMER_TIME_LIB@//
 /^LCMS2_CFLAGS *=/s/@LCMS2_CFLAGS@//
 /^LCMS2_LIBS *=/s/@LCMS2_LIBS@//
 /^LIBGMP *=/s/@LIBGMP@//
@@ -200,8 +204,20 @@ s/ *@WEBP_LIBS@//
 /^PAXCTL_dumped *=/s/=.*$/=/
 /^PAXCTL_notdumped *=/s/=.*$/=/
 /^DUMPING *=/s/@DUMPING@/unexec/
+/^ANDROID_OBJ *=/s/@ANDROID_OBJ@//
+/^ANDROID_LIBS *=/s/@ANDROID_LIBS@//
+/^ANDROID_LDFLAGS *=/s/@ANDROID_LDFLAGS@//
+/^ANDROID_BUILD_CFLAGS *=/s/@ANDROID_BUILD_CFLAGS@//
+/^LIBGMP_CFLAGS *=/s/@LIBGMP_CFLAGS@//
+/^SQLITE3_CFLAGS *=/s/@SQLITE3_CFLAGS@//
+/^LIBSELINUX_CFLAGS *=/s/@LIBSELINUX_CFLAGS@//
+/^XCONFIGURE *=/s/@XCONFIGURE@//
 /^[ \t]*MAKE_PDUMPER_FINGERPRINT = *$/c\
 MAKE_PDUMPER_FINGERPRINT =
+# While this variable is named abs_top_builddir, the distinction is
+# only relevant when Emacs is undergoing cross-compilation.
+/^abs_top_builddir =*/s/@abs_top_builddir@/../
+s/\$(abs_top_builddir)\/src\/lisp.mk/lisp.mk/
 /^lisp\.mk:/,/^$/c\
 lisp.mk: $(lispsource)/loadup.el\
        @rm -f $@\
@@ -283,3 +299,4 @@ s| -I\. -I\$(srcdir)| -I.|
 /^      *test "X/d
 /\$(CC) -o \$@.tmp/s/\$@.tmp/\$@/
 /mv \$@.tmp \$@/d
+/^top_builddir =*/s/@top_builddir@/../
diff --git a/msdos/sed3v2.inp b/msdos/sed3v2.inp
index 9688a27b066..0699fb68b02 100644
--- a/msdos/sed3v2.inp
+++ b/msdos/sed3v2.inp
@@ -57,3 +57,4 @@
 /^GETOPT_H *=/s!@GETOPT_H@!getopt.h!
 /^GETOPTOBJS *=/s!@GETOPTOBJS@!getopt.o getopt1.o!
 /^INSTALLABLES/s/emacsclient[^ ]* *//
+/^XCONFIGURE *=/s/@XCONFIGURE@//
diff --git a/msdos/sedlibcf.inp b/msdos/sedlibcf.inp
index 931ceb8f044..8966e799a38 100644
--- a/msdos/sedlibcf.inp
+++ b/msdos/sedlibcf.inp
@@ -20,3 +20,4 @@
 # ----------------------------------------------------------------------
 s/c++defs/cxxdefs/g
 s/\([a-zA-Z0-9_]*\)\.in\.h/\1.in-h/g
+/^XCONFIGURE *=/s/@XCONFIGURE@//
diff --git a/msdos/sedlibmk.inp b/msdos/sedlibmk.inp
index c3f410bd74d..664bfaf693c 100644
--- a/msdos/sedlibmk.inp
+++ b/msdos/sedlibmk.inp
@@ -156,6 +156,7 @@ s/@PACKAGE@/emacs/
 /^HYBRID_MALLOC *=/s/@HYBRID_MALLOC@//
 /^WARN_CFLAGS *=/s/@WARN_CFLAGS@//
 /^WERROR_CFLAGS *=/s/@WERROR_CFLAGS@//
+/^ANDROID_BUILD_CFLAGS *=/s/@ANDROID_BUILD_CFLAGS@//
 /^DEFS *=/s/@[^@\n]*@//
 /^DEPDIR *=/s/@[^@\n]*@/deps/
 /^ECHO_N *=/s/@[^@\n]*@/-n/
@@ -199,6 +200,9 @@ s/@PACKAGE@/emacs/
 /^GL_GNULIB_[^ =]* *= *@/s/@[^@\n]*@/0/
 /^GL_GSETTINGS_CFLAGS *=/s/@[^@\n]*@//
 /^GL_GSETTINGS_LIBS *=/s/@[^@\n]*@//
+# Miscellaneous variables.
+/^DIR_HAS_FD_MEMBER *=/s/@DIR_HAS_FD_MEMBER@/0/
+/^LOCALE_FR_UTF8 *=/s/@LOCALE_FR_UTF8@/none/
 #
 # Edit the HAVE_foo variables
 /^HAVE_ATOLL *=/s/@HAVE_ATOLL@/0/
@@ -299,8 +303,10 @@ s/@PACKAGE@/emacs/
 /^NEXT_DIRENT_H *=/s/@[^@\n]*@/<dirent.h>/
 /^NEXT_ERRNO_H *=/s/@[^@\n]*@//
 /^NEXT_FCNTL_H *=/s/@[^@\n]*@/<fcntl.h>/
+/^NEXT_FLOAT_H *=/s/@[^@\n]*@//
 /^NEXT_GETOPT_H *=/s/@[^@\n]*@/<getopt.h>/
 /^NEXT_LIMITS_H *=/s/@[^@\n]*@/<limits.h>/
+/^NEXT_MATH_H *=/s/@[^@\n]*@//
 /^NEXT_SIGNAL_H *=/s/@[^@\n]*@/<signal.h>/
 /^NEXT_STDDEF_H *=/s/@[^@\n]*@/<stddef.h>/
 /^NEXT_STDIO_H *=/s/@[^@\n]*@/<stdio.h>/
@@ -309,9 +315,11 @@ s/@PACKAGE@/emacs/
 /^NEXT_STRING_H *=/s/@[^@\n]*@/<string.h>/
 /^NEXT_SYS_SELECT_H *=/s/@[^@\n]*@//
 /^NEXT_SYS_STAT_H *=/s!@[^@\n]*@!<sys/stat.h>!
+/^NEXT_SYS_RANDOM_H *=/s/@[^@\n]*@//
 /^NEXT_SYS_TIME_H *=/s/@[^@\n]*@//
 /^NEXT_SYS_TYPES_H *=/s!@[^@\n]*@!<sys/types.h>!
 /^NEXT_TIME_H *=/s/@[^@\n]*@/<time.h>/
+/^NEXT_INTTYPES_H *=/s/@[^@\n]*@//
 /^NEXT_UNISTD_H *=/s/@[^@\n]*@/<unistd.h>/
 /^OBJEXT *=/s/@[^@\n]*@/o/
 /^PRAGMA_COLUMNS *=/s/@[^@\n]*@//
@@ -331,6 +339,7 @@ s/@PACKAGE@/emacs/
 /^DIRENT_H *=/s/@[^@\n]*@//
 /^ERRNO_H *=/s/@[^@\n]*@//
 /^EXECINFO_H *=/s/@[^@\n]*@/execinfo.h/
+/^FLOAT_H *=/s/@[^@\n]*@//
 /^GETOPT_CDEFS_H *=/s/@[^@\n]*@/getopt-cdefs.h/
 /^GMP_H *=/s/@[^@\n]*@/gmp.h/
 /^LIMITS_H *=/s/@[^@\n]*@/limits.h/
@@ -427,7 +436,6 @@ s/= @GL_GENERATE_STDDEF_H_CONDITION@/= 1/
 s/= @GL_GENERATE_STDINT_H_CONDITION@/= 1/
 s/= @GL_GENERATE_LIMITS_H_CONDITION@/= 1/
 s/= @GL_GENERATE_ERRNO_H_CONDITION@/= /
-s/= @GL_GENERATE_LIMITS_H_CONDITION@/= /
 s/= @GL_GENERATE_GETOPT_CDEFS_H_CONDITION@/= 1/
 s/= @GL_GENERATE_GETOPT_H_CONDITION@/= 1/
 s/= @GL_GENERATE_GMP_H_CONDITION@/= 1/
@@ -436,6 +444,8 @@ s/= @GL_GENERATE_MINI_GMP_H_CONDITION@/= 1/
 s/= @GL_GENERATE_STDCKDINT_H_CONDITION@/= 1/
 s/= @GL_COND_OBJ_STDIO_READ_CONDITION@/= /
 s/= @GL_COND_OBJ_STDIO_WRITE_CONDITION@/= /
+s/= @GL_COND_OBJ_STPNCPY_CONDITION@/= /
+s/= @GL_COND_OBJ_.*@/= 1/
 s/\$\(MKDIR_P\) malloc//
 #
 # Determine which modules to build and which to omit
@@ -455,6 +465,8 @@ OMIT_GNULIB_MODULE_fcntl = true\
 OMIT_GNULIB_MODULE_fdopendir = true\
 OMIT_GNULIB_MODULE_fstatat = true\
 OMIT_GNULIB_MODULE_fsync = true\
+OMIT_GNULIB_MODULE_getline = true\
+OMIT_GNULIB_MODULE_getdelim = true\
 OMIT_GNULIB_MODULE_getdtablesize = true\
 OMIT_GNULIB_MODULE_getgroups = true\
 OMIT_GNULIB_MODULE_gettimeofday = true\
@@ -462,6 +474,7 @@ OMIT_GNULIB_MODULE_group-member = true\
 OMIT_GNULIB_MODULE_inttypes-incomplete = true\
 OMIT_GNULIB_MODULE_localtime-buffer = true\
 OMIT_GNULIB_MODULE_lstat = true\
+OMIT_GNULIB_MODULE_math = true\
 OMIT_GNULIB_MODULE_nanosleep = true\
 OMIT_GNULIB_MODULE_open = true\
 OMIT_GNULIB_MODULE_pipe2 = true\
@@ -470,11 +483,13 @@ OMIT_GNULIB_MODULE_putenv = true\
 OMIT_GNULIB_MODULE_qcopy-acl = true\
 OMIT_GNULIB_MODULE_readlink = true\
 OMIT_GNULIB_MODULE_readlinkat = true\
+OMIT_GNULIB_MODULE_stpcpy = true\
 OMIT_GNULIB_MODULE_strtoimax = true\
 OMIT_GNULIB_MODULE_strtoll = true\
 OMIT_GNULIB_MODULE_symlink = true\
 OMIT_GNULIB_MODULE_sys_select = true\
 OMIT_GNULIB_MODULE_sys_time = true\
+OMIT_GNULIB_MODULE_boot-time = true\
 OMIT_GNULIB_MODULE_crypto\/md5 = true
 /^arg-nonnull\.h:/,/^[         ][      ]*mv /c\
 arg-nonnull.h: $(top_srcdir)/build-aux/snippet/arg-nonnull.h\
diff --git a/nt/gnulib-cfg.mk b/nt/gnulib-cfg.mk
index eca3778f203..fe29e942d80 100644
--- a/nt/gnulib-cfg.mk
+++ b/nt/gnulib-cfg.mk
@@ -44,34 +44,43 @@
 OMIT_GNULIB_MODULE_acl-permissions = true
 OMIT_GNULIB_MODULE_allocator = true
 OMIT_GNULIB_MODULE_at-internal = true
+OMIT_GNULIB_MODULE_canonicalize-lgpl = true
 OMIT_GNULIB_MODULE_careadlinkat = true
 OMIT_GNULIB_MODULE_dirent = true
 OMIT_GNULIB_MODULE_dirfd = true
+OMIT_GNULIB_MODULE_fchmodat = true
 OMIT_GNULIB_MODULE_fcntl = true
 OMIT_GNULIB_MODULE_fcntl-h = true
+OMIT_GNULIB_MODULE_file-has-acl = true
+OMIT_GNULIB_MODULE_float = true
+OMIT_GNULIB_MODULE_fpucw = true
 OMIT_GNULIB_MODULE_free-posix = true
+OMIT_GNULIB_MODULE_fseterr = true
 OMIT_GNULIB_MODULE_fsusage = true
+OMIT_GNULIB_MODULE_futimens = true
+OMIT_GNULIB_MODULE_getdelim = true
+OMIT_GNULIB_MODULE_getline = true
 OMIT_GNULIB_MODULE_inttypes-incomplete = true
+OMIT_GNULIB_MODULE_lchmod = true
 OMIT_GNULIB_MODULE_malloc-posix = true
+OMIT_GNULIB_MODULE_nanosleep = true
+OMIT_GNULIB_MODULE_nproc = true
 OMIT_GNULIB_MODULE_open = true
 OMIT_GNULIB_MODULE_pipe2 = true
 OMIT_GNULIB_MODULE_realloc-gnu = true
 OMIT_GNULIB_MODULE_realloc-posix = true
 OMIT_GNULIB_MODULE_secure_getenv = true
 OMIT_GNULIB_MODULE_signal-h = true
+OMIT_GNULIB_MODULE_signbit = true
+OMIT_GNULIB_MODULE_size_max = true
 OMIT_GNULIB_MODULE_stdio = true
 OMIT_GNULIB_MODULE_stdlib = true
+OMIT_GNULIB_MODULE_stpncpy = true
 OMIT_GNULIB_MODULE_sys_select = true
 OMIT_GNULIB_MODULE_sys_stat = true
 OMIT_GNULIB_MODULE_sys_time = true
 OMIT_GNULIB_MODULE_sys_types = true
 OMIT_GNULIB_MODULE_unistd = true
-OMIT_GNULIB_MODULE_canonicalize-lgpl = true
 OMIT_GNULIB_MODULE_utimens = true
-OMIT_GNULIB_MODULE_fchmodat = true
-OMIT_GNULIB_MODULE_lchmod = true
-OMIT_GNULIB_MODULE_futimens = true
 OMIT_GNULIB_MODULE_utimensat = true
-OMIT_GNULIB_MODULE_file-has-acl = true
-OMIT_GNULIB_MODULE_nproc = true
-OMIT_GNULIB_MODULE_nanosleep = true
+OMIT_GNULIB_MODULE_xsize = true
diff --git a/nt/inc/ms-w32.h b/nt/inc/ms-w32.h
index 58be1199345..fce15fcbd8c 100644
--- a/nt/inc/ms-w32.h
+++ b/nt/inc/ms-w32.h
@@ -111,18 +111,6 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 # endif
 #endif
 
-/* This isn't perfect, as some systems might have the page file in
-   another place.  Also, I suspect that the time stamp of that file
-   might also change when Windows enlarges the file due to
-   insufficient VM.  Still, this seems to be the most reliable way;
-   the alternative (of using GetSystemTimes) won't work on laptops
-   that hibernate, because the system clock is stopped then.  Other
-   possibility would be to run "net statistics workstation" and parse
-   the output, but that's gross.  So this should do; if the file is
-   not there, the boot time will be returned as zero, and filelock.c
-   already handles that.  */
-#define BOOT_TIME_FILE "C:/pagefile.sys"
-
 /* ============================================================ */
 
 /* Here, add any special hacks needed to make Emacs work on this
diff --git a/src/ChangeLog.12 b/src/ChangeLog.12
index 74df9809261..fdb7a2e659b 100644
--- a/src/ChangeLog.12
+++ b/src/ChangeLog.12
@@ -7077,7 +7077,7 @@
 
        * .gdbinit: Use "set $dummy = ..." to avoid warnings from GDB 7.5
        and later about non-assignments with no effect.  See discussion at
-       http://sourceware.org/ml/gdb-patches/2012-08/msg00518.html for
+       https://sourceware.org/ml/gdb-patches/2012-08/msg00518.html for
        details.
 
 2012-08-20  Dmitry Antipov  <dmantipov@yandex.ru>
diff --git a/src/ChangeLog.3 b/src/ChangeLog.3
index 31fb11b06d7..fbfb36fdb2a 100644
--- a/src/ChangeLog.3
+++ b/src/ChangeLog.3
@@ -1250,7 +1250,7 @@
 
        * data.c (Fdefine_function): New function (same code as Fdefalias).
 
-1993-04-28  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-28  Eric S. Raymond  (esr@thyrsus.com)
 
        * eval.c (do_autoload): Fixed the bug in the autoload-saving code.
 
@@ -1258,7 +1258,7 @@
 
        * keyboard.c (Fcurrent_input_mode): New function.
 
-1993-04-27  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-27  Eric S. Raymond  (esr@thyrsus.com)
 
        * eval.c (un_autoload): Don't try to save old autoload forms when
        we load something in.  Something about the code now conditioned
@@ -1311,7 +1311,7 @@
        * dispnew.c (Fsleep_for, Fsit_for): Allow SECONDS to be a
        floating point value.
 
-1993-04-26  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-26  Eric S. Raymond  (esr@thyrsus.com)
 
        * sysdep.c (read_pending_input):
        Fix the garbaged-modifiers bug under System Vs previous
@@ -1330,7 +1330,7 @@
        beginning or end of the original text to float to the
        corresponding position in the replacement text.
 
-1993-04-25  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-25  Eric S. Raymond  (esr@thyrsus.com)
 
        * window.c (Fset-window-buffer):
        Set horizontal-scrolling on a window to zero when
@@ -1351,7 +1351,7 @@
        (apply_modifiers): Don't abort if you see extra modifier bits,
        just remove them.
 
-1993-04-23  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-23  Eric S. Raymond  (esr@thyrsus.com)
 
        * data.c (Fdefine_function):
        Changed name back to Fdefalias, so we get things
@@ -1386,7 +1386,7 @@
        (eval_region, eval_buffer): Call readevalloop with new arg.
        (load_history): New variable.
 
-1993-04-16  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-04-16  Eric S. Raymond  (esr@thyrsus.com)
 
        * lread.c (readevalloop): New argument is the source file name (or
        nil if none).  All calls changed.  Do the two-step
@@ -1928,7 +1928,7 @@
        * xfns.c (Fx_display_color_p): Renamed from Fx_color_display_p.
        (syms_of_xfns): Use new name in defsubr.
 
-1993-03-19  Eric S. Raymond  (eric@geech.gnu.ai.mit.edu)
+1993-03-19  Eric S. Raymond  (esr@thyrsus.com)
 
        * Makefile.in (unlock, relock): New productions to assist with
        version control.
@@ -1960,7 +1960,7 @@
        * xfns.c (x_screen): Make this var file scope.
        (Fx_server_version): Use Fcons, not list3.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * xterm.c (term_get_fkeys): Less klugey version of the last fix.
 
@@ -1976,7 +1976,7 @@
        * xterm.c (x_display_box_cursor, x_display_bar_cursor): Don't
        display the cursor on garbaged frames.
 
-1993-03-17  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-17  Eric S. Raymond  (esr@thyrsus.com)
 
        * term.c (term_get_fkeys) Supply second args for all tgetstr calls.
 
@@ -2182,7 +2182,7 @@
        * cmd.c (internal_self_insert): Check that tab_width does not
        exceed 20, to be consistent with indent.c and xdisp.c.
 
-1993-03-12  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-12  Eric S. Raymond  (esr@thyrsus.com)
 
        * term.c (CONDITIONAL_REASSIGN): Fixed reference to tigetstr.
        This should have been tgetstr, but I typoed and tigetstr happens
@@ -2215,7 +2215,7 @@
        (make_lispy_event): Handle menu bar events.
        (read_key_sequence): Make dummy prefix `menu-bar' for menu bar events.
 
-1993-03-11  Eric S. Raymond  (eric@mole.gnu.ai.mit.edu)
+1993-03-11  Eric S. Raymond  (esr@thyrsus.com)
 
        * term.c (fkey_table): Added many more keycap cookies to the
        fkey_table; it now supports the full intersection of the set of X
@@ -2289,7 +2289,7 @@
        have the data_start symbol defined, so we'll just use the address
        of environ.
 
-       * s/usg5-4.h: Changes from Eric Raymond:
+       * s/usg5-4.h: Changes from Eric S. Raymond:
        If we're doing ordinary linking, define LIB_STANDARD appropriately.
        Give LIBS_DEBUG a null definition; usg5-4 has no -lg.
        #define LIBS_STANDARD as "-lc"; usg5-4 has no -lPW.
@@ -2297,7 +2297,7 @@
        #define HAVE_TERMIOS instead of HAVE_TCATTR.
        Provide our own definition of LIB_X11_LIB.
 
-       * s/usg5-3.h (LIBX11_SYSTEM): Eric Raymond says the libraries here
+       * s/usg5-3.h (LIBX11_SYSTEM): Eric S. Raymond says the libraries here
        were slightly wrong.
 
        * m/intel386.h (LIB_STANDARD): If USG5_4 is #defined, there's no
@@ -4335,7 +4335,7 @@
        * Makefile.in: Rearrange dependencies to make sure that xmakefile
        is built before we try to use it, even using a parallel make.
 
-       Changes for SYSV from Eric Raymond:
+       Changes for SYSV from Eric S. Raymond:
        * process.c [SYSV]: Don't include <termios.h>, <termio.h>, or
        <fcntl.h>.
        (process_send_signal): Don't try to send SIGTSTP
@@ -6187,7 +6187,7 @@
 
        * xterm.c: Doc fixes.
 
-       More SYSV portability changes from Eric Raymond:
+       More SYSV portability changes from Eric S. Raymond:
 
        * xterm.c [USG5]: Don't include <sys/types.h>.
 
@@ -6222,7 +6222,7 @@
 
 1992-08-14  Jim Blandy  (jimb@pogo.cs.oberlin.edu)
 
-       Applied SYSV portability changes from Eric Raymond:
+       Applied SYSV portability changes from Eric S. Raymond:
 
        * xrdb.c [USG5]: Define SYSV, and then include <unistd.h>.
        Apparently, Xlib.h include string.h if SYSV is defined, and
@@ -6279,7 +6279,7 @@
 
        * sysdep.c [USG5]: Don't include fcntl.h.
 
-       * s/usg5-3.h: Eric Raymond writes:
+       * s/usg5-3.h: Eric S. Raymond writes:
        Define HAVE_SELECT and BSTRINGS only if HAVE_X_WINDOWS is on,
        because that means we'll be linking in the shared libraries
        containing the BSD emulations.  Teach the file about the shared
@@ -6333,7 +6333,7 @@
        wasn't written portably, and it should probably go somewhere else
        anyway - say, funcall or eval.
 
-       End of changes from Eric Raymond.
+       End of changes from Eric S. Raymond.
 
        * xfns.c (Fx_create_frame): Make the default for the icon-type
        parameter nil, not t.  It seems to cause problems with some X
diff --git a/src/Makefile.in b/src/Makefile.in
index 1dae6376c0f..b14681f2537 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -33,6 +33,20 @@ top_builddir = @top_builddir@
 # MinGW CPPFLAGS may use this.
 abs_top_srcdir=@abs_top_srcdir@
 VPATH = $(srcdir)
+
+# This is not empty if this is a Makefile that will be copied to
+# cross/src.
+XCONFIGURE = @XCONFIGURE@
+
+ifneq ($(XCONFIGURE),)
+vpath %.c := $(srcdir)
+vpath %.h := $(srcdir)
+endif
+
+# abs_top_builddir is the name of the toplevel build directory under
+# cross-compiled builds.
+abs_top_builddir = @abs_top_builddir@
+
 CC = @CC@
 CXX = @CXX@
 CFLAGS = @CFLAGS@
@@ -48,6 +62,7 @@ LIBOBJS = @LIBOBJS@
 
 lispsource = $(top_srcdir)/lisp
 lib = ../lib
+hostlib = $(top_builddir)/lib
 libsrc = ../lib-src
 etc = ../etc
 oldXMenudir = ../oldXMenu
@@ -127,6 +142,10 @@ LIB_PTHREAD=@LIB_PTHREAD@
 
 LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@
 
+GIF_CFLAGS=@GIF_CFLAGS@
+JPEG_CFLAGS=@JPEG_CFLAGS@
+TIFF_CFLAGS=@TIFF_CFLAGS@
+
 XCB_LIBS=@XCB_LIBS@
 XFT_LIBS=@XFT_LIBS@
 XRENDER_LIBS=@XRENDER_LIBS@
@@ -148,7 +167,7 @@ CLOCK_TIME_LIB=@CLOCK_TIME_LIB@
 EUIDACCESS_LIBGEN=@EUIDACCESS_LIBGEN@
 NANOSLEEP_LIB=@NANOSLEEP_LIB@
 QCOPY_ACL_LIB=@QCOPY_ACL_LIB@
-LIB_TIMER_TIME=@LIB_TIMER_TIME@
+TIMER_TIME_LIB=@TIMER_TIME_LIB@
 
 DBUS_CFLAGS = @DBUS_CFLAGS@
 DBUS_LIBS = @DBUS_LIBS@
@@ -241,6 +260,7 @@ LIBXML2_LIBS = @LIBXML2_LIBS@
 LIBXML2_CFLAGS = @LIBXML2_CFLAGS@
 
 SQLITE3_LIBS = @SQLITE3_LIBS@
+SQLITE3_CFLAGS = @SQLITE3_CFLAGS@
 
 GETADDRINFO_A_LIBS = @GETADDRINFO_A_LIBS@
 
@@ -327,12 +347,13 @@ W32_RES_LINK=@W32_RES_LINK@
 ## if HAVE_HARFBUZZ, hbfont.o is added regardless of the rest
 FONT_OBJ=@FONT_OBJ@
 
-## Empty for MinGW, cm.o for the rest.
+## Empty for MinGW and Android, cm.o for the rest.
 CM_OBJ=@CM_OBJ@
 
 LIBGPM = @LIBGPM@
 
 LIBSELINUX_LIBS = @LIBSELINUX_LIBS@
+LIBSELINUX_CFLAGS = @LIBSELINUX_CFLAGS@
 
 LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@
 LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@
@@ -371,6 +392,13 @@ HAIKU_CXX_OBJ = @HAIKU_CXX_OBJ@
 HAIKU_LIBS = @HAIKU_LIBS@
 HAIKU_CFLAGS = @HAIKU_CFLAGS@
 
+ANDROID_OBJ = @ANDROID_OBJ@
+ANDROID_LIBS = @ANDROID_LIBS@
+ANDROID_LDFLAGS = @ANDROID_LDFLAGS@
+ANDROID_BUILD_CFLAGS = @ANDROID_BUILD_CFLAGS@
+
+LIBGMP_CFLAGS = @LIBGMP_CFLAGS@
+
 DUMPING=@DUMPING@
 CHECK_STRUCTS = @CHECK_STRUCTS@
 HAVE_PDUMPER = @HAVE_PDUMPER@
@@ -412,7 +440,9 @@ EMACS_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \
   $(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
   $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) $(XSYNC_CFLAGS) $(TREE_SITTER_CFLAGS) \
   $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \
-  $(WERROR_CFLAGS) $(HAIKU_CFLAGS) $(XCOMPOSITE_CFLAGS) $(XSHAPE_CFLAGS)
+  $(WERROR_CFLAGS) $(HAIKU_CFLAGS) $(XCOMPOSITE_CFLAGS) $(XSHAPE_CFLAGS) \
+  $(ANDROID_BUILD_CFLAGS) $(GIF_CFLAGS) $(JPEG_CFLAGS) $(SQLITE3_CFLAGS) \
+  $(LIBGMP_CFLAGS) $(TIFF_CFLAGS) $(LIBSELINUX_CFLAGS)
 ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS)
 ALL_OBJC_CFLAGS = $(EMACS_CFLAGS) \
   $(filter-out $(NON_OBJC_CFLAGS),$(WARN_CFLAGS)) $(CFLAGS) \
@@ -450,7 +480,7 @@ base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o 
$(XMENU_OBJ) window.o     \
        $(if $(HYBRID_MALLOC),sheap.o)                                         \
        $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ)        \
        $(W32_OBJ) $(WINDOW_SYSTEM_OBJ) $(XGSELOBJ) $(JSON_OBJ)                \
-       $(HAIKU_OBJ) $(PGTK_OBJ)
+       $(HAIKU_OBJ) $(PGTK_OBJ) $(ANDROID_OBJ)
 doc_obj = $(base_obj) $(NS_OBJC_OBJ)
 obj = $(doc_obj) $(HAIKU_CXX_OBJ)
 
@@ -467,7 +497,8 @@ SOME_MACHINE_OBJECTS = dosfns.o msdos.o \
   w32menu.o w32proc.o w32reg.o w32select.o w32term.o w32xfns.o \
   w16select.o widget.o xfont.o ftfont.o xftfont.o gtkutil.o \
   xsettings.o xgselect.o termcap.o hbfont.o \
-  haikuterm.o haikufns.o haikumenu.o haikufont.o
+  haikuterm.o haikufns.o haikumenu.o haikufont.o androidterm.o androidfns.o \
+  androidfont.o androidselect.c sfntfont-android.c sfntfont.c
 
 ## gmalloc.o if !SYSTEM_MALLOC && !DOUG_LEA_MALLOC, else empty.
 GMALLOC_OBJ=@GMALLOC_OBJ@
@@ -538,15 +569,19 @@ endif
 ## we need to remove leim-list, site-init, and site-load by hand.
 ## There's not much to choose between these two approaches,
 ## but the second one seems like it could be more future-proof.
+##
+## This list is placed in the toplevel build directory to prevent it
+## from being unnecessarily regenerated by the successive use of this
+## Makefile within cross/Makefile.
 shortlisp =
-lisp.mk: $(lispsource)/loadup.el
+$(abs_top_builddir)/src/lisp.mk: $(lispsource)/loadup.el
        ${AM_V_GEN}( printf 'shortlisp = \\\n'; \
        sed -n 's/^[ \t]*(load "\([^"]*\)".*/\1/p' $< | \
          sed -e 's/$$/.elc \\/' -e 's/\.el\.elc/.el/'; \
        echo "" ) > $@.tmp
        $(AM_V_at)mv -f $@.tmp $@
 
--include lisp.mk
+-include $(abs_top_builddir)/src/lisp.mk
 shortlisp_filter = leim/leim-list.el site-load.elc site-init.elc
 shortlisp := $(filter-out ${shortlisp_filter},${shortlisp})
 ## Place loaddefs.el first, so it gets generated first, since it is on
@@ -561,7 +596,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(PGTK_LIBS) 
$(LIBX_BASE) $(LIBIMAGE
    $(LIBX_OTHER) $(LIBSOUND) \
    $(RSVG_LIBS) $(IMAGEMAGICK_LIBS) $(LIB_ACL) $(CLOCK_TIME_LIB) \
    $(NANOSLEEP_LIB) $(QCOPY_ACL_LIB) $(WEBKIT_LIBS) \
-   $(EUIDACCESS_LIBGEN) $(LIB_TIMER_TIME) $(DBUS_LIBS) \
+   $(EUIDACCESS_LIBGEN) $(TIMER_TIME_LIB) $(DBUS_LIBS) \
    $(LIB_EXECINFO) $(XRANDR_LIBS) $(XINERAMA_LIBS) $(XFIXES_LIBS) \
    $(XDBE_LIBS) $(XSYNC_LIBS) \
    $(LIBXML2_LIBS) $(LIBGPM) $(LIBS_SYSTEM) $(CAIRO_LIBS) \
@@ -570,15 +605,18 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(PGTK_LIBS) 
$(LIBX_BASE) $(LIBIMAGE
    $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \
    $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS) \
    $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) $(XINPUT_LIBS) $(HAIKU_LIBS) \
-   $(TREE_SITTER_LIBS) $(SQLITE3_LIBS) $(XCOMPOSITE_LIBS) $(XSHAPE_LIBS)
+   $(TREE_SITTER_LIBS) $(SQLITE3_LIBS) $(XCOMPOSITE_LIBS) $(XSHAPE_LIBS) \
+   $(ANDROID_LIBS)
 
 ## FORCE it so that admin/unidata can decide whether this file is
 ## up-to-date.  Although since charprop depends on bootstrap-emacs,
 ## and emacs depends on charprop, in practice this rule was always run
 ## anyway.
+ifneq ($(XCONFIGURE),android)
 $(lispsource)/international/charprop.el: \
   FORCE | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
        $(MAKE) -C ../admin/unidata all EMACS="../$(bootstrap_exe)"
+endif
 
 ## We require charprop.el to exist before ucs-normalize.el is
 ## byte-compiled, because ucs-normalize.el needs to load 2 uni-*.el files.
@@ -612,7 +650,7 @@ SYSTEM_TYPE = @SYSTEM_TYPE@
 ## since not all pieces are used on all platforms.  But DOC depends
 ## on all of $lisp, and emacs depends on DOC, so it is ok to use $lisp here.
 emacs$(EXEEXT): temacs$(EXEEXT) \
-                lisp.mk $(etc)/DOC $(lisp) \
+                $(abs_top_builddir)/src/lisp.mk $(etc)/DOC $(lisp) \
                 $(lispsource)/international/charprop.el ${charsets}
 ifeq ($(SYSTEM_TYPE),cygwin)
        find ${top_builddir} -name '*.eln' | rebase -v -O -T -
@@ -659,7 +697,7 @@ $(etc)/DOC: $(libsrc)/make-docfile$(EXEEXT) $(doc_obj)
          $(SOME_MACHINE_OBJECTS) $(doc_obj) > $(etc)/DOC
 
 $(libsrc)/make-docfile$(EXEEXT) $(libsrc)/make-fingerprint$(EXEEXT): \
-  $(lib)/libgnu.a
+  $(hostlib)/libgnu.a
        $(MAKE) -C $(dir $@) $(notdir $@)
 
 buildobj.h: Makefile
@@ -720,6 +758,47 @@ ifeq ($(DUMPING),unexec)
   endif
 endif
 
+ifeq ($(XCONFIGURE),android)
+## The Android package internally links to a shared library named
+## `libemacs.so' at startup.  It is built almost the same way temacs
+## is.  But it is position independent, and is not dumped here.
+## Instead, it dumps itself the first time it starts on the user's
+## device.
+
+# Include ndk-build.mk in order to build Emacs dependencies.
+old_top_builddir := $(top_builddir)
+top_builddir := $(old_top_builddir)/..
+include $(old_top_builddir)/ndk-build/ndk-build.mk
+top_builddir := $(old_top_builddir)
+
+## Builds using libemacs.so (Android) don't dump Emacs within this
+## Makefile, but on device.  Make sure the library hash changes for
+## each change in shortlisp by linking an object that changes
+## accordingly to it.
+BUILD_COUNTER_OBJ = build-counter.o
+
+# This file is then compiled into libemacs.so
+build-counter.c: $(abs_top_builddir)/src/lisp.mk $(lisp)
+       $(AM_V_GEN) $(top_srcdir)/build-aux/makecounter.sh $@
+
+libemacs.so: $(ALLOBJS) $(BUILD_COUNTER_OBJ) $(LIBEGNU_ARCHIVE) \
+            $(EMACSRES) $(MAKE_PDUMPER_FINGERPRINT) \
+            $(NDK_BUILD_SHARED) $(NDK_BUILD_STATIC) $(etc)/DOC
+       $(AM_V_CCLD)$(CC) -o $@ $(ALL_CFLAGS) $(TEMACS_LDFLAGS) \
+         $(ANDROID_LDFLAGS) $(LDFLAGS) -shared $(ALLOBJS) \
+         $(BUILD_COUNTER_OBJ) $(LIBEGNU_ARCHIVE) $(LIBES)
+       $(AM_V_at)$(MAKE_PDUMPER_FINGERPRINT) $@
+
+# There is also a binary named `android-emacs' which simply calls
+# emacs.so.  It need not link against libemacs because app_process
+# will do that instead.
+
+android-emacs: android-emacs.c
+       $(AM_V_CCLD)$(CC) $(lastword $^) -o $@ \
+         $(ALL_CFLAGS) $(LDFLAGS)             \
+         $(LIBEGNU_ARCHIVE)
+endif
+
 ## The following oldxmenu-related rules are only (possibly) used if
 ## HAVE_X11 && !USE_GTK, but there is no harm in always defining them.
 $(lwlibdir)/liblw.a: $(config_h) globals.h lisp.h FORCE
@@ -748,7 +827,8 @@ ns-app: emacs$(EXEEXT) $(pdmp)
 .PHONY: versionclean
 
 mostlyclean:
-       rm -f temacs$(EXEEXT) core ./*.core \#* ./*.o
+       rm -f android-emacs libemacs.so
+       rm -f temacs$(EXEEXT) core ./*.core \#* ./*.o build-counter.c
        rm -f dmpstruct.h
        rm -f emacs.pdmp
        rm -f ../etc/DOC
@@ -837,10 +917,16 @@ tags: TAGS ../lisp/TAGS $(lwlibdir)/TAGS $(lib)/TAGS
 ## To solve the freshness issue, in the past we tried various clever tricks,
 ## but now that we require GNU make, we can simply specify
 ## bootstrap-emacs$(EXEEXT) as an order-only prerequisite.
+##
+## bootstrap-emacs doesn't have to be built when cross-compiling
+## libemacs.so for Android, however, as the Lisp files have already
+## been compiled by the top level `src' Makefile.
 
+ifneq ($(XCONFIGURE),android)
 %.elc: %.el | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
        @$(MAKE) $(AM_V_NO_PD) -C ../lisp EMACS="$(bootstrap_exe)"\
                THEFILE=$< $<c
+endif
 
 ifeq ($(HAVE_NATIVE_COMP):$(NATIVE_DISABLED),yes:)
 ## The following rules are used only when building a source tarball
@@ -894,8 +980,10 @@ NATIVE_COMPILATION_AOT = @NATIVE_COMPILATION_AOT@
        fi
 endif
 
+ifneq ($(XCONFIGURE),android)
 $(lispsource)/loaddefs.el: | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
        $(MAKE) -C ../lisp autoloads EMACS="$(bootstrap_exe)"
+endif
 
 ## Dump an Emacs executable named bootstrap-emacs containing the
 ## files from loadup.el in source form.
diff --git a/src/alloc.c b/src/alloc.c
index 17ca5c725d0..c77bdc6372d 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -50,6 +50,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#include "sfntfont.h"
+#endif
+
 #ifdef HAVE_TREE_SITTER
 #include "treesit.h"
 #endif
@@ -3346,6 +3350,15 @@ cleanup_vector (struct Lisp_Vector *vector)
              drv->close_font (font);
            }
        }
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+      /* The Android font driver needs the ability to associate extra
+        information with font entities.  */
+      if (((vector->header.size & PSEUDOVECTOR_SIZE_MASK)
+          == FONT_ENTITY_MAX)
+         && PSEUDOVEC_STRUCT (vector, font_entity)->is_android)
+       android_finalize_font_entity (PSEUDOVEC_STRUCT (vector, font_entity));
+#endif
     }
   else if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_THREAD))
     finalize_one_thread (PSEUDOVEC_STRUCT (vector, thread_state));
@@ -5645,6 +5658,22 @@ find_string_data_in_pure (const char *data, ptrdiff_t 
nbytes)
   if (pure_bytes_used_non_lisp <= nbytes)
     return NULL;
 
+  /* The Android GCC generates code like:
+
+   0xa539e755 <+52>:   lea    0x430(%esp),%esi
+=> 0xa539e75c <+59>:   movdqa %xmm0,0x0(%ebp)
+   0xa539e761 <+64>:   add    $0x10,%ebp
+
+   but data is not aligned appropriately, so a GP fault results.  */
+
+#if defined __i386__                           \
+  && defined HAVE_ANDROID                      \
+  && !defined ANDROID_STUBIFY                  \
+  && !defined (__clang__)
+  if ((intptr_t) data & 15)
+    return NULL;
+#endif
+
   /* Set up the Boyer-Moore table.  */
   skip = nbytes + 1;
   for (i = 0; i < 256; i++)
@@ -6155,16 +6184,45 @@ mark_pinned_objects (void)
     mark_object (pobj->object);
 }
 
+#if defined HAVE_ANDROID && !defined (__clang__)
+
+/* The Android gcc is broken and needs the following version of
+   make_lisp_symbol.  Otherwise a mysterious ICE pops up.  */
+
+#define make_lisp_symbol android_make_lisp_symbol
+
+static Lisp_Object
+android_make_lisp_symbol (struct Lisp_Symbol *sym)
+{
+  intptr_t symoffset;
+
+  symoffset = (intptr_t) sym;
+  INT_SUBTRACT_WRAPV (symoffset, (intptr_t) &lispsym,
+                     &symoffset);
+
+  {
+    Lisp_Object a = TAG_PTR (Lisp_Symbol, symoffset);
+    return a;
+  }
+}
+
+#endif
+
 static void
 mark_pinned_symbols (void)
 {
   struct symbol_block *sblk;
-  int lim = (symbol_block_pinned == symbol_block
-            ? symbol_block_index : SYMBOL_BLOCK_SIZE);
+  int lim;
+  struct Lisp_Symbol *sym, *end;
+
+  if (symbol_block_pinned == symbol_block)
+    lim = symbol_block_index;
+  else
+    lim = SYMBOL_BLOCK_SIZE;
 
   for (sblk = symbol_block_pinned; sblk; sblk = sblk->next)
     {
-      struct Lisp_Symbol *sym = sblk->symbols, *end = sym + lim;
+      sym = sblk->symbols, end = sym + lim;
       for (; sym < end; ++sym)
        if (sym->u.s.pinned)
          mark_object (make_lisp_symbol (sym));
@@ -6463,6 +6521,13 @@ garbage_collect (void)
   mark_xselect ();
 #endif
 
+#ifdef HAVE_ANDROID
+  mark_androidterm ();
+#ifndef ANDROID_STUBIFY
+  mark_sfntfont ();
+#endif
+#endif
+
 #ifdef HAVE_NS
   mark_nsterm ();
 #endif
@@ -6871,6 +6936,11 @@ static void
 mark_frame (struct Lisp_Vector *ptr)
 {
   struct frame *f = (struct frame *) ptr;
+#ifdef HAVE_TEXT_CONVERSION
+  struct text_conversion_action *tem;
+#endif
+
+
   mark_vectorlike (&ptr->header);
   mark_face_cache (f->face_cache);
 #ifdef HAVE_WINDOW_SYSTEM
@@ -6882,6 +6952,15 @@ mark_frame (struct Lisp_Vector *ptr)
         mark_vectorlike (&font->header);
     }
 #endif
+
+#ifdef HAVE_TEXT_CONVERSION
+  mark_object (f->conversion.compose_region_start);
+  mark_object (f->conversion.compose_region_end);
+  mark_object (f->conversion.compose_region_overlay);
+
+  for (tem = f->conversion.actions; tem; tem = tem->next)
+    mark_object (tem->data);
+#endif
 }
 
 static void
diff --git a/src/android-asset.h b/src/android-asset.h
new file mode 100644
index 00000000000..4fb309f1645
--- /dev/null
+++ b/src/android-asset.h
@@ -0,0 +1,422 @@
+/* Android initialization for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <android/log.h>
+
+/* This file contains an emulation of the Android asset manager API
+   used on builds for Android 2.2.  It is included by android.c
+   whenever appropriate.
+
+   The replacements in this file are not thread safe and must only be
+   called from the creating thread.  */
+
+struct android_asset_manager
+{
+  /* JNI environment.  */
+  JNIEnv *env;
+
+  /* Asset manager class and functions.  */
+  jclass class;
+  jmethodID open_fd;
+
+  /* Asset file descriptor class and functions.  */
+  jclass fd_class;
+  jmethodID get_length;
+  jmethodID create_input_stream;
+  jmethodID close;
+
+  /* Input stream class and functions.  */
+  jclass input_stream_class;
+  jmethodID read;
+  jmethodID stream_close;
+
+  /* Associated asset manager object.  */
+  jobject asset_manager;
+};
+
+typedef struct android_asset_manager AAssetManager;
+
+struct android_asset
+{
+  /* The asset manager.  */
+  AAssetManager *manager;
+
+  /* The length of the asset, or -1.  */
+  jlong length;
+
+  /* The asset file descriptor and input stream.  */
+  jobject fd, stream;
+
+  /* The mode.  */
+  int mode;
+};
+
+typedef struct android_asset AAsset;
+
+static AAssetManager *
+AAssetManager_fromJava (JNIEnv *env, jobject java_manager)
+{
+  AAssetManager *manager;
+  jclass temp;
+
+  manager = malloc (sizeof *manager);
+
+  if (!manager)
+    return NULL;
+
+  manager->env = env;
+  manager->asset_manager
+    = (*env)->NewGlobalRef (env, java_manager);
+
+  if (!manager->asset_manager)
+    {
+      free (manager);
+      return NULL;
+    }
+
+  manager->class
+    = (*env)->FindClass (env, "android/content/res/AssetManager");
+  assert (manager->class);
+
+  manager->open_fd
+    = (*env)->GetMethodID (env, manager->class, "openFd",
+                          "(Ljava/lang/String;)"
+                          "Landroid/content/res/AssetFileDescriptor;");
+  assert (manager->open);
+
+  manager->fd_class
+    = (*env)->FindClass (env, "android/content/res/AssetFileDescriptor");
+  assert (manager->fd_class);
+
+  manager->get_length
+    = (*env)->GetMethodID (env, manager->fd_class, "getLength",
+                          "()J");
+  assert (manager->get_length);
+
+  manager->create_input_stream
+    = (*env)->GetMethodID (env, manager->fd_class,
+                          "createInputStream",
+                          "()Ljava/io/FileInputStream;");
+  assert (manager->create_input_stream);
+
+  manager->close
+    = (*env)->GetMethodID (env, manager->fd_class,
+                          "close", "()V");
+  assert (manager->close);
+
+  manager->input_stream_class
+    = (*env)->FindClass (env, "java/io/InputStream");
+  assert (manager->input_stream_class);
+
+  manager->read
+    = (*env)->GetMethodID (env, manager->input_stream_class,
+                          "read", "([B)I");
+  assert (manager->read);
+
+  manager->stream_close
+    = (*env)->GetMethodID (env, manager->input_stream_class,
+                          "close", "()V");
+  assert (manager->stream_close);
+
+  /* Now convert all the class references to global ones.  */
+  temp = manager->class;
+  manager->class
+    = (*env)->NewGlobalRef (env, temp);
+  assert (manager->class);
+  (*env)->DeleteLocalRef (env, temp);
+  temp = manager->fd_class;
+  manager->fd_class
+    = (*env)->NewGlobalRef (env, temp);
+  assert (manager->fd_class);
+  (*env)->DeleteLocalRef (env, temp);
+  temp = manager->input_stream_class;
+  manager->input_stream_class
+    = (*env)->NewGlobalRef (env, temp);
+  assert (manager->input_stream_class);
+  (*env)->DeleteLocalRef (env, temp);
+
+  /* Return the asset manager.  */
+  return manager;
+}
+
+enum
+  {
+    AASSET_MODE_STREAMING = 0,
+    AASSET_MODE_BUFFER   = 1,
+  };
+
+static AAsset *
+AAssetManager_open (AAssetManager *manager, const char *c_name,
+                   int mode)
+{
+  jobject desc;
+  jstring name;
+  AAsset *asset;
+
+  /* Push a local frame.  */
+  asset = NULL;
+
+  (*(manager->env))->PushLocalFrame (manager->env, 3);
+
+  if ((*(manager->env))->ExceptionCheck (manager->env))
+    goto fail;
+
+  /* Encoding issues can be ignored for now as there are only ASCII
+     file names in Emacs.  */
+  name = (*(manager->env))->NewStringUTF (manager->env, c_name);
+
+  if (!name)
+    goto fail;
+
+  /* Now try to open an ``AssetFileDescriptor''.  */
+  desc = (*(manager->env))->CallObjectMethod (manager->env,
+                                             manager->asset_manager,
+                                             manager->open_fd,
+                                             name);
+
+  if (!desc)
+    goto fail;
+
+  /* Allocate the asset.  */
+  asset = calloc (1, sizeof *asset);
+
+  if (!asset)
+    {
+      (*(manager->env))->CallVoidMethod (manager->env,
+                                        desc,
+                                        manager->close);
+      goto fail;
+    }
+
+  /* Pop the local frame and return desc.  */
+  desc = (*(manager->env))->NewGlobalRef (manager->env, desc);
+
+  if (!desc)
+    goto fail;
+
+  (*(manager->env))->PopLocalFrame (manager->env, NULL);
+
+  asset->manager = manager;
+  asset->length = -1;
+  asset->fd = desc;
+  asset->mode = mode;
+
+  return asset;
+
+ fail:
+  (*(manager->env))->ExceptionClear (manager->env);
+  (*(manager->env))->PopLocalFrame (manager->env, NULL);
+  free (asset);
+
+  return NULL;
+}
+
+static AAsset *
+AAsset_close (AAsset *asset)
+{
+  JNIEnv *env;
+
+  env = asset->manager->env;
+
+  (*env)->CallVoidMethod (asset->manager->env,
+                         asset->fd,
+                         asset->manager->close);
+  (*env)->DeleteGlobalRef (asset->manager->env,
+                          asset->fd);
+
+  if (asset->stream)
+    {
+      (*env)->CallVoidMethod (asset->manager->env,
+                             asset->stream,
+                             asset->manager->stream_close);
+      (*env)->DeleteGlobalRef (asset->manager->env,
+                              asset->stream);
+    }
+
+  free (asset);
+}
+
+/* Create an input stream associated with the given ASSET.  Set
+   ASSET->stream to its global reference.
+
+   Value is 1 upon failure, else 0.  ASSET must not already have an
+   input stream.  */
+
+static int
+android_asset_create_stream (AAsset *asset)
+{
+  jobject stream;
+  JNIEnv *env;
+
+  env = asset->manager->env;
+  stream
+    = (*env)->CallObjectMethod (env, asset->fd,
+                               asset->manager->create_input_stream);
+
+  if (!stream)
+    {
+      (*env)->ExceptionClear (env);
+      return 1;
+    }
+
+  asset->stream
+    = (*env)->NewGlobalRef (env, stream);
+
+  if (!asset->stream)
+    {
+      (*env)->ExceptionClear (env);
+      (*env)->DeleteLocalRef (env, stream);
+      return 1;
+    }
+
+  (*env)->DeleteLocalRef (env, stream);
+  return 0;
+}
+
+/* Read NBYTES from the specified asset into the given BUFFER;
+
+   Internally, allocate a Java byte array containing 4096 elements and
+   copy the data to and from that array.
+
+   Value is the number of bytes actually read, 0 at EOF, or -1 upon
+   failure, in which case errno is set accordingly.  If NBYTES is
+   zero, behavior is undefined.  */
+
+static int
+android_asset_read_internal (AAsset *asset, int nbytes, char *buffer)
+{
+  jbyteArray stash;
+  JNIEnv *env;
+  jint bytes_read, total;
+
+  /* Allocate a suitable amount of storage.  Either nbytes or 4096,
+     whichever is larger.  */
+  env = asset->manager->env;
+  stash = (*env)->NewByteArray (env, MIN (nbytes, 4096));
+
+  if (!stash)
+    {
+      (*env)->ExceptionClear (env);
+      errno = ENOMEM;
+      return -1;
+    }
+
+  /* Try to create an input stream.  */
+
+  if (!asset->stream
+      && android_asset_create_stream (asset))
+    {
+      (*env)->DeleteLocalRef (env, stash);
+      errno = ENOMEM;
+      return -1;
+    }
+
+  /* Start reading.  */
+
+  total = 0;
+
+  while (nbytes)
+    {
+      bytes_read = (*env)->CallIntMethod (env, asset->stream,
+                                         asset->manager->read,
+                                         stash);
+
+      /* Detect error conditions.  */
+
+      if ((*env)->ExceptionCheck (env))
+       goto out;
+
+      /* Detect EOF.  */
+
+      if (bytes_read == -1)
+       goto out;
+
+      /* Finally write out the amount that was read.  */
+      bytes_read = MIN (bytes_read, nbytes);
+      (*env)->GetByteArrayRegion (env, stash, 0, bytes_read, buffer);
+
+      buffer += bytes_read;
+      total += bytes_read;
+      nbytes -= bytes_read;
+    }
+
+  /* Make sure the value of nbytes still makes sense.  */
+  assert (nbytes >= 0);
+
+ out:
+  (*env)->ExceptionClear (env);
+  (*env)->DeleteLocalRef (env, stash);
+  return total;
+}
+
+static long
+AAsset_getLength (AAsset *asset)
+{
+  JNIEnv *env;
+
+  if (asset->length != -1)
+    return asset->length;
+
+  env = asset->manager->env;
+  asset->length
+    = (*env)->CallLongMethod (env, asset->fd,
+                             asset->manager->get_length);
+  return asset->length;
+}
+
+static char *
+AAsset_getBuffer (AAsset *asset)
+{
+  long length;
+  char *buffer;
+
+  length = AAsset_getLength (asset);
+
+  if (!length)
+    return NULL;
+
+  buffer = malloc (length);
+
+  if (!buffer)
+    return NULL;
+
+  if (android_asset_read_internal (asset, length, buffer)
+      != length)
+    {
+      xfree (buffer);
+      return NULL;
+    }
+
+  return buffer;
+}
+
+static size_t
+AAsset_read (AAsset *asset, void *buffer, size_t size)
+{
+  return android_asset_read_internal (asset, MIN (size, INT_MAX),
+                                     buffer);
+}
+
+static off_t
+AAsset_seek (AAsset *asset, off_t offset, int whence)
+{
+  /* Java InputStreams don't support seeking at all.  */
+  errno = ESPIPE;
+  return -1;
+}
diff --git a/src/android-emacs.c b/src/android-emacs.c
new file mode 100644
index 00000000000..2c405795860
--- /dev/null
+++ b/src/android-emacs.c
@@ -0,0 +1,178 @@
+/* Android initialization for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <stdio.h>
+#include <alloca.h>
+#include <string.h>
+#include <unistd.h>
+
+/* android-emacs is a wrapper around /system/bin/app_process(64).
+   It invokes app_process(64) with the right class path and then
+   starts org.gnu.emacs.EmacsNoninteractive.
+
+   The main function in that class tries to load an activity thread
+   and obtain a context and asset manager before calling
+   android_emacs_init, which is required for Emacs to find required
+   preloaded Lisp.  */
+
+int
+main (int argc, char **argv)
+{
+  char **args;
+  int i;
+  char *bootclasspath, *emacs_class_path, *ld_library_path;
+
+  /* Allocate enough to hold the arguments to app_process.  */
+  args = alloca ((10 + argc) * sizeof *args);
+
+  /* Clear args.  */
+  memset (args, 0, (10 + argc) * sizeof *args);
+
+  /* First, figure out what program to start.  */
+#if defined __x86_64__ || defined __aarch64__ || defined __mips64
+  args[0] = (char *) "/system/bin/app_process64";
+#else /* i386 || regular mips || arm */
+  args[0] = (char *) "/system/bin/app_process";
+#endif /* __x86_64__ || __aarch64__ || __mips64 */
+
+  /* Machines with ART require the boot classpath to be manually
+     specified.  Machines with Dalvik however refuse to do so, as they
+     open the jars inside the BOOTCLASSPATH environment variable at
+     startup, resulting in the following crash:
+
+     W/dalvikvm( 1608): Refusing to reopen boot DEX
+     '/system/framework/core.jar'
+     W/dalvikvm( 1608): Refusing to reopen boot DEX
+     '/system/framework/bouncycastle.jar'
+     E/dalvikvm( 1608): Too many exceptions during init (failed on
+     'Ljava/io/IOException;' 'Re-opening BOOTCLASSPATH DEX files is
+     not allowed')
+     E/dalvikvm( 1608): VM aborting  */
+
+#if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL
+  if (android_get_device_api_level () < 21)
+    {
+      bootclasspath = NULL;
+      goto skip_setup;
+    }
+#else /* !HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL */
+  if (__ANDROID_API__ < 21)
+    {
+      bootclasspath = NULL;
+      goto skip_setup;
+    }
+#endif /* HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL */
+
+  /* Next, obtain the boot class path.  */
+  bootclasspath = getenv ("BOOTCLASSPATH");
+
+  if (!bootclasspath)
+    {
+      fprintf (stderr, "The BOOTCLASSPATH environment variable"
+              " is not set.  As a result, Emacs does not know"
+              " how to start app_process.\n"
+              "This is likely a change in the Android platform."
+              "  Please report this to bug-gnu-emacs@gnu.org.\n");
+      return 1;
+    }
+
+ skip_setup:
+
+  /* And the Emacs class path.  */
+  emacs_class_path = getenv ("EMACS_CLASS_PATH");
+
+  if (!emacs_class_path)
+    {
+      fprintf (stderr, "EMACS_CLASS_PATH not set."
+              "  Please make sure Emacs is being started"
+              " from within a running copy of Emacs.\n");
+      return 1;
+    }
+
+  /* Restore LD_LIBRARY_PATH to its original value, the app library
+     directory, to guarantee that it is possible for Java to find the
+     Emacs C code later.  */
+
+  ld_library_path = getenv ("EMACS_LD_LIBRARY_PATH");
+
+  if (ld_library_path)
+    setenv ("LD_LIBRARY_PATH", ld_library_path, 1);
+
+  if (bootclasspath)
+    {
+      if (asprintf (&bootclasspath, "-Djava.class.path=%s:%s",
+                   bootclasspath, emacs_class_path) < 0)
+       {
+         perror ("asprintf");
+         return 1;
+       }
+    }
+  else
+    {
+      if (asprintf (&bootclasspath, "-Djava.class.path=%s",
+                   emacs_class_path) < 0)
+       {
+         perror ("asprintf");
+         return 1;
+       }
+    }
+
+  args[1] = bootclasspath;
+  args[2] = (char *) "/system/bin";
+
+#if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL
+  /* I don't know exactly when --nice-name was introduced; this is
+     just a guess.  */
+  if (android_get_device_api_level () >= 26)
+    {
+      args[3] = (char *) "--nice-name=emacs";
+      args[4] = (char *) "org.gnu.emacs.EmacsNoninteractive";
+
+      /* Arguments from here on are passed to main in
+        EmacsNoninteractive.java.  */
+      args[5] = argv[0];
+
+      /* Now copy the rest of the arguments over.  */
+      for (i = 1; i < argc; ++i)
+       args[5 + i] = argv[i];
+    }
+  else
+    {
+#endif /* HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL */
+      args[3] = (char *) "org.gnu.emacs.EmacsNoninteractive";
+
+      /* Arguments from here on are passed to main in
+        EmacsNoninteractive.java.  */
+      args[4] = argv[0];
+
+      /* Now copy the rest of the arguments over.  */
+      for (i = 1; i < argc; ++i)
+       args[4 + i] = argv[i];
+#if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL
+    }
+#endif /* HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL */
+
+  /* Finally, try to start the app_process.  */
+  execvp (args[0], args);
+
+  /* If exit fails, return an error indication.  */
+  perror ("exec");
+  return 1;
+}
diff --git a/src/android.c b/src/android.c
new file mode 100644
index 00000000000..ed304baf0e6
--- /dev/null
+++ b/src/android.c
@@ -0,0 +1,6903 @@
+/* Android initialization for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <allocator.h>
+#include <assert.h>
+#include <careadlinkat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fingerprint.h>
+#include <intprops.h>
+#include <libgen.h>
+#include <limits.h>
+#include <math.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+#include <stdckdint.h>
+#include <string.h>
+#include <sys/param.h>
+#include <timespec.h>
+#include <unistd.h>
+
+/* Old NDK versions lack MIN and MAX.  */
+#include <minmax.h>
+
+#include "android.h"
+#include "androidgui.h"
+
+#include "lisp.h"
+#include "blockinput.h"
+#include "coding.h"
+#include "epaths.h"
+
+/* Whether or not Emacs is running inside the application process and
+   Android windowing should be enabled.  */
+bool android_init_gui;
+
+#ifndef ANDROID_STUBIFY
+
+#include <android/bitmap.h>
+#include <android/log.h>
+
+#include <linux/unistd.h>
+
+#include <sys/syscall.h>
+
+#ifdef __aarch64__
+#include <arm_neon.h>
+#endif /* __aarch64__ */
+
+struct android_emacs_pixmap
+{
+  jclass class;
+  jmethodID constructor;
+  jmethodID constructor_mutable;
+};
+
+struct android_graphics_point
+{
+  jclass class;
+  jmethodID constructor;
+};
+
+struct android_emacs_drawable
+{
+  jclass class;
+  jmethodID get_bitmap;
+  jmethodID damage_rect;
+};
+
+struct android_emacs_window
+{
+  jclass class;
+  jmethodID swap_buffers;
+  jmethodID toggle_on_screen_keyboard;
+  jmethodID lookup_string;
+  jmethodID set_fullscreen;
+  jmethodID change_window_background;
+  jmethodID reparent_to;
+  jmethodID map_window;
+  jmethodID unmap_window;
+  jmethodID resize_window;
+  jmethodID move_window;
+  jmethodID make_input_focus;
+  jmethodID raise;
+  jmethodID lower;
+  jmethodID get_window_geometry;
+  jmethodID translate_coordinates;
+  jmethodID set_dont_accept_focus;
+  jmethodID set_dont_focus_on_map;
+  jmethodID define_cursor;
+};
+
+struct android_emacs_cursor
+{
+  jclass class;
+  jmethodID constructor;
+};
+
+/* The API level of the current device.  */
+static int android_api_level;
+
+/* The directory used to store site-lisp.  */
+char *android_site_load_path;
+
+/* The directory used to store native libraries.  */
+char *android_lib_dir;
+
+/* The directory used to store game files.  */
+char *android_game_path;
+
+/* The directory used to store temporary files.  */
+char *android_cache_dir;
+
+/* The list of archive files within which the Java virtual macine
+   looks for class files.  */
+char *android_class_path;
+
+/* The display's pixel densities.  */
+double android_pixel_density_x, android_pixel_density_y;
+
+/* The display pixel density used to convert between point and pixel
+   font sizes.  */
+double android_scaled_pixel_density;
+
+/* The Android application data directory.  */
+static char *android_files_dir;
+
+/* The Java environment being used for the main thread.  */
+JNIEnv *android_java_env;
+
+/* The EmacsGC class.  */
+static jclass emacs_gc_class;
+
+/* Various fields.  */
+static jfieldID emacs_gc_foreground, emacs_gc_background;
+static jfieldID emacs_gc_function, emacs_gc_clip_rects;
+static jfieldID emacs_gc_clip_x_origin, emacs_gc_clip_y_origin;
+static jfieldID emacs_gc_stipple, emacs_gc_clip_mask;
+static jfieldID emacs_gc_fill_style, emacs_gc_ts_origin_x;
+static jfieldID emacs_gc_ts_origin_y;
+
+/* The constructor and one function.  */
+static jmethodID emacs_gc_constructor, emacs_gc_mark_dirty;
+
+/* The Rect class.  */
+static jclass android_rect_class;
+
+/* Its constructor.  */
+static jmethodID android_rect_constructor;
+
+/* The EmacsService object.  */
+jobject emacs_service;
+
+/* Various methods associated with the EmacsService.  */
+struct android_emacs_service service_class;
+
+/* Various methods associated with the EmacsPixmap class.  */
+static struct android_emacs_pixmap pixmap_class;
+
+/* Various methods associated with the Point class.  */
+static struct android_graphics_point point_class;
+
+/* Various methods associated with the EmacsDrawable class.  */
+static struct android_emacs_drawable drawable_class;
+
+/* Various methods associated with the EmacsWindow class.  */
+static struct android_emacs_window window_class;
+
+/* Various methods associated with the EmacsCursor class.  */
+static struct android_emacs_cursor cursor_class;
+
+/* The last event serial used.  This is a 32 bit value, but it is
+   stored in unsigned long to be consistent with X.  */
+unsigned int event_serial;
+
+#ifdef __i386__
+
+/* Unused pointer used to control compiler optimizations.  */
+void *unused_pointer;
+
+#endif /* __i386__ */
+
+/* Whether or not the default signal mask has been changed.  If so,
+   the signal mask must be restored before calling
+   android_emacs_init.  */
+static bool signal_mask_changed_p;
+
+/* The signal mask at the time Emacs was started.  */
+static sigset_t startup_signal_mask;
+
+
+
+/* Event handling functions.  Events are stored on a (circular) queue
+   that is read synchronously.  The Android port replaces pselect with
+   a function android_select, which runs pselect in a separate thread,
+   but more importantly also waits for events to be available on the
+   android event queue.  */
+
+struct android_event_container
+{
+  /* The next and last events in this queue.  */
+  struct android_event_container *next, *last;
+
+  /* The event itself.  */
+  union android_event event;
+};
+
+struct android_event_queue
+{
+  /* Mutex protecting the event queue.  */
+  pthread_mutex_t mutex;
+
+  /* Mutex protecting the select data.  */
+  pthread_mutex_t select_mutex;
+
+  /* The thread used to run select.  */
+  pthread_t select_thread;
+
+  /* Condition variables for the reading side.  */
+  pthread_cond_t read_var;
+
+  /* The number of events in the queue.  If this is greater than 1024,
+     writing will block.  */
+  int num_events;
+
+  /* Circular queue of events.  */
+  struct android_event_container events;
+};
+
+/* Arguments to pselect used by the select thread.  */
+static int android_pselect_nfds;
+static fd_set *android_pselect_readfds;
+static fd_set *android_pselect_writefds;
+static fd_set *android_pselect_exceptfds;
+static struct timespec *android_pselect_timeout;
+
+/* Value of pselect.  */
+static int android_pselect_rc;
+
+/* The global event queue.  */
+static struct android_event_queue event_queue;
+
+/* Semaphores used to signal select completion and start.  */
+static sem_t android_pselect_sem, android_pselect_start_sem;
+
+#if __ANDROID_API__ < 16
+
+/* Select self-pipe.  */
+static int select_pipe[2];
+
+#else
+
+/* Whether or not pselect has been interrupted.  */
+static volatile sig_atomic_t android_pselect_interrupted;
+
+#endif
+
+static void *
+android_run_select_thread (void *data)
+{
+  /* Apparently this is required too.  */
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  int rc;
+#if __ANDROID_API__ < 16
+  int nfds;
+  fd_set readfds;
+  char byte;
+#else
+  sigset_t signals, waitset;
+  int sig;
+#endif
+
+#if __ANDROID_API__ < 16
+  /* A completely different implementation is used when building for
+     Android versions earlier than 16, because pselect with a signal
+     mask does not work there.  Instead of blocking SIGUSR1 and
+     unblocking it inside pselect, a file descriptor is used instead.
+     Something is written to the file descriptor every time select is
+     supposed to return.  */
+
+  while (true)
+    {
+      /* Wait for the thread to be released.  */
+      while (sem_wait (&android_pselect_start_sem) < 0)
+       ;;
+
+      /* Get the select lock and call pselect.  API 8 does not have
+        working pselect in any sense.  Instead, pselect wakes up on
+        select_pipe[0].  */
+
+      pthread_mutex_lock (&event_queue.select_mutex);
+      nfds = android_pselect_nfds;
+
+      if (android_pselect_readfds)
+       readfds = *android_pselect_readfds;
+      else
+       FD_ZERO (&readfds);
+
+      if (nfds < select_pipe[0] + 1)
+       nfds = select_pipe[0] + 1;
+      FD_SET (select_pipe[0], &readfds);
+
+      rc = pselect (nfds, &readfds,
+                   android_pselect_writefds,
+                   android_pselect_exceptfds,
+                   android_pselect_timeout,
+                   NULL);
+
+      /* Subtract 1 from rc if readfds contains the select pipe, and
+        also remove it from that set.  */
+
+      if (rc != -1 && FD_ISSET (select_pipe[0], &readfds))
+       {
+         rc -= 1;
+         FD_CLR (select_pipe[0], &readfds);
+
+         /* If no file descriptors aside from the select pipe are
+            ready, then pretend that an error has occurred.  */
+         if (!rc)
+           rc = -1;
+       }
+
+      /* Save the read file descriptor set back again.  */
+
+      if (android_pselect_readfds)
+       *android_pselect_readfds = readfds;
+
+      android_pselect_rc = rc;
+      pthread_mutex_unlock (&event_queue.select_mutex);
+
+      /* Signal the main thread that there is now data to read.  Hold
+         the event queue lock during this process to make sure this
+         does not happen before the main thread begins to wait for the
+         condition variable.  */
+
+      pthread_mutex_lock (&event_queue.mutex);
+      pthread_cond_broadcast (&event_queue.read_var);
+      pthread_mutex_unlock (&event_queue.mutex);
+
+      /* Read a single byte from the select pipe.  */
+      read (select_pipe[0], &byte, 1);
+
+      /* Signal the Emacs thread that pselect is done.  If read_var
+        was signaled by android_write_event, event_queue.mutex could
+        still be locked, so this must come before.  */
+      sem_post (&android_pselect_sem);
+    }
+#else
+  if (pthread_sigmask (SIG_BLOCK, &signals, NULL))
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "pthread_sigmask: %s",
+                        strerror (errno));
+
+  sigfillset (&signals);
+  sigdelset (&signals, SIGUSR1);
+  sigemptyset (&waitset);
+  sigaddset (&waitset, SIGUSR1);
+
+  while (true)
+    {
+      /* Wait for the thread to be released.  */
+      while (sem_wait (&android_pselect_start_sem) < 0)
+       ;;
+
+      /* Clear the ``pselect interrupted'' flag.  This is safe because
+        right now, SIGUSR1 is blocked.  */
+      android_pselect_interrupted = 0;
+
+      /* Get the select lock and call pselect.  */
+      pthread_mutex_lock (&event_queue.select_mutex);
+      rc = pselect (android_pselect_nfds,
+                   android_pselect_readfds,
+                   android_pselect_writefds,
+                   android_pselect_exceptfds,
+                   android_pselect_timeout,
+                   &signals);
+      android_pselect_rc = rc;
+      pthread_mutex_unlock (&event_queue.select_mutex);
+
+      /* Signal the main thread that there is now data to read.  Hold
+         the event queue lock during this process to make sure this
+         does not happen before the main thread begins to wait for the
+         condition variable.  */
+
+      pthread_mutex_lock (&event_queue.mutex);
+      pthread_cond_broadcast (&event_queue.read_var);
+      pthread_mutex_unlock (&event_queue.mutex);
+
+      /* Check `android_pselect_interrupted' instead of rc and errno.
+
+         This is because `pselect' does not return an rc of -1 upon
+         being interrupted in some versions of Android, but does set
+         signal masks correctly.  */
+
+      if (!android_pselect_interrupted)
+       /* Now, wait for SIGUSR1, unless pselect was interrupted and
+          the signal was already delivered.  The Emacs thread will
+          always send this signal after read_var is triggered or the
+          UI thread has sent an event.  */
+       sigwait (&waitset, &sig);
+
+      /* Signal the Emacs thread that pselect is done.  If read_var
+        was signaled by android_write_event, event_queue.mutex could
+        still be locked, so this must come before.  */
+      sem_post (&android_pselect_sem);
+    }
+#endif
+
+  return NULL;
+}
+
+#if __ANDROID_API__ >= 16
+
+static void
+android_handle_sigusr1 (int sig, siginfo_t *siginfo, void *arg)
+{
+  /* Notice that pselect has been interrupted.  */
+  android_pselect_interrupted = 1;
+}
+
+#endif
+
+/* Semaphore used to indicate completion of a query.
+   This should ideally be defined further down.  */
+static sem_t android_query_sem;
+
+/* Set up the global event queue by initializing the mutex and two
+   condition variables, and the linked list of events.  This must be
+   called before starting the Emacs thread.  Also, initialize the
+   thread used to run pselect.
+
+   These functions must also use the C library malloc and free,
+   because xmalloc is not thread safe.  */
+
+static void
+android_init_events (void)
+{
+  struct sigaction sa;
+
+  if (pthread_mutex_init (&event_queue.mutex, NULL))
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "pthread_mutex_init: %s",
+                        strerror (errno));
+
+  if (pthread_mutex_init (&event_queue.select_mutex, NULL))
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "pthread_mutex_init: %s",
+                        strerror (errno));
+
+  if (pthread_cond_init (&event_queue.read_var, NULL))
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "pthread_cond_init: %s",
+                        strerror (errno));
+
+  sem_init (&android_pselect_sem, 0, 0);
+  sem_init (&android_pselect_start_sem, 0, 0);
+  sem_init (&android_query_sem, 0, 0);
+
+  event_queue.events.next = &event_queue.events;
+  event_queue.events.last = &event_queue.events;
+
+#if __ANDROID_API__ >= 16
+
+  /* Before starting the select thread, make sure the disposition for
+     SIGUSR1 is correct.  */
+  sigfillset (&sa.sa_mask);
+  sa.sa_sigaction = android_handle_sigusr1;
+  sa.sa_flags = SA_SIGINFO;
+
+#else
+
+  /* Set up the file descriptor used to wake up pselect.  */
+  if (pipe2 (select_pipe, O_CLOEXEC) < 0)
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "pipe2: %s", strerror (errno));
+
+  /* Make sure the read end will fit in fd_set.  */
+  if (select_pipe[0] >= FD_SETSIZE)
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "read end of select pipe"
+                        " lies outside FD_SETSIZE!");
+
+#endif
+
+  if (sigaction (SIGUSR1, &sa, NULL))
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "sigaction: %s",
+                        strerror (errno));
+
+  /* Start the select thread.  */
+  if (pthread_create (&event_queue.select_thread, NULL,
+                     android_run_select_thread, NULL))
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "pthread_create: %s",
+                        strerror (errno));
+}
+
+int
+android_pending (void)
+{
+  int i;
+
+  pthread_mutex_lock (&event_queue.mutex);
+  i = event_queue.num_events;
+  pthread_mutex_unlock (&event_queue.mutex);
+
+  return i;
+}
+
+/* Forward declaration.  */
+
+static void android_check_query (void);
+
+/* Wait for events to become available synchronously.  Return once an
+   event arrives.  Also, reply to the UI thread whenever it requires a
+   response.  */
+
+void
+android_wait_event (void)
+{
+  /* Run queries from the UI thread to the Emacs thread.  */
+  android_check_query ();
+
+  pthread_mutex_lock (&event_queue.mutex);
+
+  /* Wait for events to appear if there are none available to
+     read.  */
+  if (!event_queue.num_events)
+    pthread_cond_wait (&event_queue.read_var,
+                      &event_queue.mutex);
+
+  pthread_mutex_unlock (&event_queue.mutex);
+
+  /* Check for queries again.  If a query is sent after the call to
+     `android_check_query' above, `read_var' will be signaled.  */
+  android_check_query ();
+}
+
+void
+android_next_event (union android_event *event_return)
+{
+  struct android_event_container *container;
+
+  pthread_mutex_lock (&event_queue.mutex);
+
+  /* Wait for events to appear if there are none available to
+     read.  */
+  if (!event_queue.num_events)
+    pthread_cond_wait (&event_queue.read_var,
+                      &event_queue.mutex);
+
+  /* Obtain the event from the end of the queue.  */
+  container = event_queue.events.last;
+  eassert (container != &event_queue.events);
+
+  /* Remove the event from the queue and copy it to the caller
+     supplied buffer.  */
+  container->last->next = container->next;
+  container->next->last = container->last;
+  *event_return = container->event;
+  event_queue.num_events--;
+
+  /* Free the container.  */
+  free (container);
+
+  /* Unlock the queue.  */
+  pthread_mutex_unlock (&event_queue.mutex);
+}
+
+bool
+android_check_if_event (union android_event *event_return,
+                       bool (*predicate) (union android_event *,
+                                          void *),
+                       void *arg)
+{
+  struct android_event_container *container;
+
+  pthread_mutex_lock (&event_queue.mutex);
+
+  /* Loop over each event.  */
+  container = event_queue.events.last;
+  for (; container != &event_queue.events; container = container->last)
+    {
+      /* See if the predicate matches.  */
+      if ((*predicate) (&container->event, arg))
+       {
+         /* Copy out the event and return true.  */
+         *event_return = container->event;
+         --event_queue.num_events;
+
+         /* Unlink container.  */
+         container->last->next = container->next;
+         container->next->last = container->last;
+         free (container);
+         pthread_mutex_unlock (&event_queue.mutex);
+         return true;
+       }
+    }
+
+  pthread_mutex_unlock (&event_queue.mutex);
+  return false;
+}
+
+void
+android_write_event (union android_event *event)
+{
+  struct android_event_container *container;
+
+  container = malloc (sizeof *container);
+
+  if (!container)
+    return;
+
+  /* If the event queue hasn't been initialized yet, return false.  */
+  if (!event_queue.events.next)
+    return;
+
+  pthread_mutex_lock (&event_queue.mutex);
+  container->next = event_queue.events.next;
+  container->last = &event_queue.events;
+  container->next->last = container;
+  container->last->next = container;
+  container->event = *event;
+  event_queue.num_events++;
+  pthread_cond_broadcast (&event_queue.read_var);
+  pthread_mutex_unlock (&event_queue.mutex);
+
+  /* Now set pending_signals to true, and raise SIGIO to interrupt any
+     ongoing reads if the event is important.  */
+  pending_signals = true;
+
+  switch (event->type)
+    {
+      /* Key press and window action events are considered important,
+        as they either end up quitting or asking for responses to the
+        IME.  */
+    case ANDROID_KEY_PRESS:
+    case ANDROID_WINDOW_ACTION:
+      kill (getpid (), SIGIO);
+      break;
+
+    default:
+      break;
+    }
+}
+
+
+
+/* Whether or not the UI thread has been waiting for a significant
+   amount of time for a function to run in the main thread, and Emacs
+   should answer the query ASAP.  */
+static bool android_urgent_query;
+
+int
+android_select (int nfds, fd_set *readfds, fd_set *writefds,
+               fd_set *exceptfds, struct timespec *timeout)
+{
+  int nfds_return;
+#if __ANDROID_API__ < 16
+  static char byte;
+#endif
+
+  /* Since Emacs is reading keyboard input again, signify that queries
+     from input methods are no longer ``urgent''.  */
+
+  __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE);
+
+  /* Check for and run anything the UI thread wants to run on the main
+     thread.  */
+  android_check_query ();
+
+  pthread_mutex_lock (&event_queue.mutex);
+
+  if (event_queue.num_events)
+    {
+      pthread_mutex_unlock (&event_queue.mutex);
+      return 1;
+    }
+
+  nfds_return = 0;
+
+  pthread_mutex_lock (&event_queue.select_mutex);
+  android_pselect_nfds = nfds;
+  android_pselect_readfds = readfds;
+  android_pselect_writefds = writefds;
+  android_pselect_exceptfds = exceptfds;
+  android_pselect_timeout = timeout;
+  pthread_mutex_unlock (&event_queue.select_mutex);
+
+  /* Release the select thread.  */
+  sem_post (&android_pselect_start_sem);
+
+  /* Start waiting for the event queue condition to be set.  */
+  pthread_cond_wait (&event_queue.read_var, &event_queue.mutex);
+
+#if __ANDROID_API__ >= 16
+  /* Interrupt the select thread now, in case it's still in
+     pselect.  */
+  pthread_kill (event_queue.select_thread, SIGUSR1);
+#else
+  /* Interrupt the select thread by writing to the select pipe.  */
+  if (write (select_pipe[1], &byte, 1) != 1)
+    __android_log_print (ANDROID_LOG_FATAL, __func__,
+                        "write: %s", strerror (errno));
+#endif
+
+  /* Unlock the event queue mutex.  */
+  pthread_mutex_unlock (&event_queue.mutex);
+
+  /* Wait for pselect to return in any case.  This must be done with
+     the event queue mutex unlocked.  Otherwise, the pselect thread
+     can hang if it tries to lock the event queue mutex to signal
+     read_var after the UI thread has already done so.  */
+  while (sem_wait (&android_pselect_sem) < 0)
+    ;;
+
+  /* If there are now events in the queue, return 1.  */
+
+  pthread_mutex_lock (&event_queue.mutex);
+  if (event_queue.num_events)
+    nfds_return = 1;
+  pthread_mutex_unlock (&event_queue.mutex);
+
+  /* Add the return value of pselect if it has also found ready file
+     descriptors.  */
+
+  if (android_pselect_rc >= 0)
+    nfds_return += android_pselect_rc;
+  else if (!nfds_return)
+    /* If pselect was interrupted and nfds_return is 0 (meaning that
+       no events have been read), indicate that an error has taken
+       place.  */
+    nfds_return = android_pselect_rc;
+
+  if ((android_pselect_rc < 0) && nfds_return >= 0)
+    {
+      /* Clear the file descriptor sets if events will be delivered
+        but no file descriptors have become ready to prevent the
+        caller from misinterpreting a non-zero return value.  */
+
+      if (readfds)
+       FD_ZERO (readfds);
+
+      if (writefds)
+       FD_ZERO (writefds);
+
+      if (exceptfds)
+       FD_ZERO (exceptfds);
+    }
+
+  /* This is to shut up process.c when pselect gets EINTR.  */
+  if (nfds_return < 0)
+    errno = EINTR;
+
+  /* Now check for and run anything the UI thread wants to run in the
+     main thread.  */
+  android_check_query ();
+
+  return nfds_return;
+}
+
+
+
+static void *
+android_run_debug_thread (void *data)
+{
+  FILE *file;
+  int fd;
+  char *line;
+  size_t n;
+
+  fd = (int) (intptr_t) data;
+  file = fdopen (fd, "r");
+
+  if (!file)
+    return NULL;
+
+  line = NULL;
+
+  while (true)
+    {
+      if (getline (&line, &n, file) < 0)
+       {
+         free (line);
+         break;
+       }
+
+      __android_log_print (ANDROID_LOG_INFO, __func__, "%s", line);
+    }
+
+  fclose (file);
+  return NULL;
+}
+
+
+
+/* Intercept USER_FULL_NAME and return something that makes sense if
+   pw->pw_gecos is NULL.  */
+
+char *
+android_user_full_name (struct passwd *pw)
+{
+#ifdef HAVE_STRUCT_PASSWD_PW_GECOS
+  if (!pw->pw_gecos)
+    return (char *) "Android user";
+
+  return pw->pw_gecos;
+#else
+  return "Android user";
+#endif
+}
+
+
+
+/* Determine whether or not the specified file NAME describes a file
+   in the directory DIR, which should be an absolute file name.  NAME
+   must be in canonical form.
+
+   Value is NULL if not.  Otherwise, it is a pointer to the first
+   character in NAME after the part containing DIR and its trailing
+   directory separator.  */
+
+const char *
+android_is_special_directory (const char *name, const char *dir)
+{
+  size_t len;
+
+  /* Compare up to strlen (DIR) bytes of NAME with DIR.  */
+
+  len = strlen (dir);
+  if (strncmp (name, dir, len))
+    return NULL;
+
+  /* Now see if the character of NAME after len is either a directory
+     separator or a terminating NULL.  */
+
+  name += len;
+  switch (*name)
+    {
+    case '\0':
+      /* Return the empty string if this is the end of the file
+        name.  */
+      return name;
+
+    case '/':
+      /* Return NAME (with the separator removed) if it describes a
+        file.  */
+      return name + 1;
+
+    default:
+      /* The file name doesn't match.  */
+      return NULL;
+    }
+}
+
+#if 0
+
+/* URL-encode N bytes of the specified STRING into at most N bytes of
+   BUFFER; STRING is assumed to be encoded in a `utf-8-emacs'
+   compatible coding system.  Value is the number of bytes encoded
+   (excluding the trailing null byte placed at the end of the encoded
+   text) or -1 upon failure.  */
+
+static ssize_t
+android_url_encode (const char *restrict string, size_t length,
+                   char *restrict buffer, size_t n)
+{
+  int len, character;
+  size_t num_encoded;
+  char *end;
+  char format[1 + 25];
+
+  /* For each multibyte character... */
+
+  end = string + length;
+  num_encoded = 0;
+
+  while (string < end)
+    {
+      /* XXX: Android documentation claims that URIs is encoded
+        according to the ``Unicode'' scheme, but what this means in
+        reality is that the URI is encoded in UTF-8, and then
+        each of its bytes are encoded.  */
+      /* Find the length of the multibyte character at STRING.  */
+      len = /* multibyte_length (string, end, true, true) */ 1;
+
+      /* 0 means that STRING is not a valid multibyte string.  */
+      if (!len || string + len > end)
+       goto failure;
+
+      /* Now fetch the character and increment string.  */
+      /* character = /\* STRING_CHAR ((unsigned char *) string) *\/; */
+      character = *(unsigned char *) string;
+      string += len;
+
+      /* If CHARACTER is not a letter or an unreserved character,
+        escape it.  */
+
+      if (!((character >= 'A'
+            && character <= 'Z')
+           || (character >= 'a'
+               && character <= 'z')
+           || (character >= '0'
+               && character <= '9')
+           || character == '_'
+           || character == '-'
+           || character == '!'
+           || character == '.'
+           || character == '~'
+           || character == '\''
+           || character == '('
+           || character == ')'
+           || character == '*'))
+       {
+         len = sprintf (format, "%%%X", (unsigned int) character);
+         if (len < 0)
+           goto failure;
+
+         /* See if there is enough space left to hold the encoded
+            string.  */
+
+         if (n < len)
+           goto failure;
+
+         n -= len;
+         num_encoded += len;
+
+         /* Copy the encoded string to STRING.  */
+         memcpy (buffer, format, n);
+         buffer += len;
+       }
+      else
+       {
+         /* No more space within BUFFER.  */
+         if (!n)
+           goto failure;
+
+         /* Don't encode this ASCII character; just store it.  */
+         n--, num_encoded++;
+         *(buffer++) = character;
+       }
+    }
+
+  /* If there's no space for a trailing null byte or more bytes have
+     been encoded than representable in ssize_t, fail.  */
+
+  if (!n || num_encoded > SSIZE_MAX)
+    goto failure;
+
+  /* Store the terminating NULL byte.  */
+  *buffer = '\0';
+  return num_encoded;
+
+ failure:
+  return -1;
+}
+
+/* Return the content URI corresponding to a `/content' file name,
+   or NULL if it is not a content URI.
+
+   This function is not reentrant.  */
+
+static const char *
+android_get_content_name (const char *filename)
+{
+  static char buffer[PATH_MAX + 1];
+  char *head, *token, *next, *saveptr, *copy, *mark, *mark1;
+  ssize_t rc;
+  size_t n, length;
+
+  /* Find the file name described if it starts with `/content'.  If
+     just the directory is described, return content://.  */
+
+  filename = android_is_special_directory (filename, "/content");
+
+  if (!filename)
+    return NULL;
+
+  if (!*filename)
+    return "content://";
+
+  /* Now copy FILENAME into a buffer and convert it into a content
+     URI.  */
+
+  copy = xstrdup (filename);
+  mark = saveptr = NULL;
+  head = stpcpy (buffer, "content:/");
+
+  /* Split FILENAME by slashes.  */
+
+  token = strtok_r (copy, "/", &saveptr);
+
+  while (token)
+    {
+      /* Compute the number of bytes remaining in buffer excluding a
+        trailing null byte.  */
+      n = PATH_MAX - (head - buffer);
+
+      /* Write / to the buffer.  Return failure if there is no space
+        for it.  */
+
+      if (!n)
+       goto failure;
+
+      *head++ = '/';
+      n--;
+
+      /* Find the next token now.  */
+      next = strtok_r (NULL, "/", &saveptr);
+
+      /* Detect and avoid encoding an encoded URL query affixed to the
+        end of the last component within the content file name.
+
+         Content URIs can include a query describing parameters that
+         must be provided to the content provider.  They are separated
+         from the rest of the URI by a single question mark character,
+         which should not be encoded.
+
+         However, the distinction between the separator and question
+         marks that appear inside file name components is lost when a
+         content URI is decoded into a content path.  To compensate
+         for this loss of information, Emacs assumes that the last
+         question mark is always a URI separator, and suffixes content
+         file names which contain question marks with a trailing
+         question mark.  */
+
+      if (!next)
+       {
+         /* Find the last question mark character.  */
+
+         mark1 = strchr (token, '?');
+
+         while (mark1)
+           {
+             mark = mark1;
+             mark1 = strchr (mark + 1, '?');
+           }
+       }
+
+      if (mark)
+       {
+         /* First, encode the part leading to the question mark
+            character.  */
+
+         rc = 0;
+         if (mark > token)
+           rc = android_url_encode (token, mark - token,
+                                    head, n + 1);
+
+         /* If this fails, bail out.  */
+
+         if (rc < 0)
+           goto failure;
+
+         /* Copy mark to the file name.  */
+
+         n -= rc, head += rc;
+         length = strlen (mark);
+
+         if (n < length)
+           goto failure;
+
+         strcpy (head, mark);
+
+         /* Now break out of the loop, since this is the last
+            component anyway.  */
+         break;
+       }
+      else
+       /* Now encode this file name component into the buffer.  */
+       rc = android_url_encode (token, strlen (token),
+                                head, n + 1);
+
+      if (rc < 0)
+       goto failure;
+
+      head += rc;
+      token = next;
+    }
+
+  /* buffer must have been null terminated by
+     `android_url_encode'.  */
+  xfree (copy);
+  return buffer;
+
+ failure:
+  xfree (copy);
+  return NULL;
+}
+
+#endif /* 0 */
+
+/* Return the current user's ``home'' directory, which is actually the
+   app data directory on Android.  */
+
+const char *
+android_get_home_directory (void)
+{
+  return android_files_dir;
+}
+
+/* Return the name of the file behind a file descriptor FD by reading
+   /proc/self/fd/.  Value is allocated memory holding the file name
+   upon success, and 0 upon failure.  */
+
+static char *
+android_proc_name (int fd)
+{
+  char format[sizeof "/proc/self/fd/"
+             + INT_STRLEN_BOUND (int)];
+  static struct allocator allocator = {
+    /* Fill the allocator with C library malloc functions.  xmalloc
+       and so aren't thread safe.  */
+    malloc, realloc, free, NULL,
+  };
+
+  sprintf (format, "/proc/self/fd/%d", fd);
+  return careadlinkat (AT_FDCWD, format, NULL, 0,
+                      &allocator, readlinkat);
+}
+
+/* Try to guarantee the existence of the `lib' directory within the
+   parent directory of the application files directory.
+
+   If `/data/data/org.gnu.emacs/lib' (or
+   `/data/user/N/org.gnu.emacs/lib') does not exist or is a dangling
+   symbolic link, create a symlink from it to the library
+   directory.
+
+   Newer versions of Android don't create this link by default, making
+   it difficult to locate the directory containing Emacs library
+   files, particularly from scripts in other programs sharing the same
+   user ID as Emacs that don't have access to `exec-path'.  */
+
+static void
+android_create_lib_link (void)
+{
+  char *filename;
+  char lib_directory[PATH_MAX];
+  int fd;
+
+  /* Find the directory containing the files directory.  */
+  filename = dirname (android_files_dir);
+  if (!filename)
+    goto failure;
+
+  /* Now make `lib_directory' the name of the library directory
+     within.  */
+  snprintf (lib_directory, PATH_MAX, "%s/lib", filename);
+
+  /* Try to open this directory.  */
+  fd = open (lib_directory, O_DIRECTORY);
+
+  /* If the directory can be opened normally, close it and return
+     now.  */
+  if (fd >= 0)
+    goto success;
+
+  /* Try to unlink the directory in case it's a dangling symbolic
+     link.  */
+  unlink (lib_directory);
+
+  /* Otherwise, try to symlink lib_directory to the actual library
+     directory.  */
+
+  if (symlink (android_lib_dir, lib_directory))
+    /* Print a warning message if creating the link fails.  */
+    __android_log_print (ANDROID_LOG_WARN, __func__,
+                        "Failed to create symbolic link from"
+                        " application library directory `%s'"
+                        " to its actual location at `%s'",
+                        lib_directory, android_files_dir);
+
+ success:
+  close (fd);
+ failure:
+  return;
+}
+
+
+
+/* JNI functions called by Java.  */
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#else
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif
+
+JNIEXPORT jint JNICALL
+NATIVE_NAME (dup) (JNIEnv *env, jobject object, jint fd)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  return dup (fd);
+}
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getFingerprint) (JNIEnv *env, jobject object)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  char buffer[sizeof fingerprint * 2 + 1];
+
+  memset (buffer, 0, sizeof buffer);
+  hexbuf_digest (buffer, (char *) fingerprint,
+                sizeof fingerprint);
+
+  return (*env)->NewStringUTF (env, buffer);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
+                             jobject local_asset_manager,
+                             jobject files_dir, jobject libs_dir,
+                             jobject cache_dir,
+                             jfloat pixel_density_x,
+                             jfloat pixel_density_y,
+                             jfloat scaled_density,
+                             jobject class_path,
+                             jobject emacs_service_object,
+                             jint api_level)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  int pipefd[2];
+  pthread_t thread;
+  const char *java_string;
+
+  /* Set the Android API level early, as it is used by
+     `android_vfs_init'.  */
+  android_api_level = api_level;
+
+  /* This function should only be called from the main thread.  */
+
+  android_pixel_density_x = pixel_density_x;
+  android_pixel_density_y = pixel_density_y;
+  android_scaled_pixel_density = scaled_density;
+
+  __android_log_print (ANDROID_LOG_INFO, __func__,
+                      "Initializing "PACKAGE_STRING"...\nPlease report bugs to 
"
+                      PACKAGE_BUGREPORT".  Thanks.\n");
+
+  if (emacs_service_object)
+    {
+      /* Create a pipe and duplicate it to stdout and stderr.  Next,
+        make a thread that prints stderr to the system log.
+
+         Notice that this function is called in one of two ways.  The
+         first is when Emacs is being started as a GUI application by
+         the system, and the second is when Emacs is being started by
+         libandroid-emacs.so as an ordinary noninteractive Emacs.
+
+         In the second case, stderr is usually connected to a PTY, so
+         this is unnecessary.  */
+
+      if (pipe2 (pipefd, O_CLOEXEC) < 0)
+       emacs_abort ();
+
+      if (dup2 (pipefd[1], 2) < 0)
+       emacs_abort ();
+      close (pipefd[1]);
+
+      if (pthread_create (&thread, NULL, android_run_debug_thread,
+                         (void *) (intptr_t) pipefd[0]))
+       emacs_abort ();
+    }
+
+  /* Now set the path to the site load directory.  */
+
+  java_string = (*env)->GetStringUTFChars (env, (jstring) files_dir,
+                                          NULL);
+
+  if (!java_string)
+    emacs_abort ();
+
+  android_files_dir = strdup ((const char *) java_string);
+
+  if (!android_files_dir)
+    emacs_abort ();
+
+  (*env)->ReleaseStringUTFChars (env, (jstring) files_dir,
+                                java_string);
+
+  java_string = (*env)->GetStringUTFChars (env, (jstring) libs_dir,
+                                          NULL);
+
+  if (!java_string)
+    emacs_abort ();
+
+  android_lib_dir = strdup ((const char *) java_string);
+
+  if (!android_files_dir)
+    emacs_abort ();
+
+  (*env)->ReleaseStringUTFChars (env, (jstring) libs_dir,
+                                java_string);
+
+  java_string = (*env)->GetStringUTFChars (env, (jstring) cache_dir,
+                                          NULL);
+
+  if (!java_string)
+    emacs_abort ();
+
+  android_cache_dir = strdup ((const char *) java_string);
+
+  if (!android_files_dir)
+    emacs_abort ();
+
+  (*env)->ReleaseStringUTFChars (env, (jstring) cache_dir,
+                                java_string);
+
+  if (class_path)
+    {
+      java_string = (*env)->GetStringUTFChars (env, (jstring) class_path,
+                                              NULL);
+
+      if (!java_string)
+       emacs_abort ();
+
+      android_class_path = strdup ((const char *) java_string);
+
+      if (!android_files_dir)
+       emacs_abort ();
+
+      (*env)->ReleaseStringUTFChars (env, (jstring) class_path,
+                                    java_string);
+    }
+
+  /* Calculate the site-lisp path.  */
+
+  android_site_load_path = malloc (PATH_MAX + 1);
+
+  if (!android_site_load_path)
+    emacs_abort ();
+
+  android_game_path = malloc (PATH_MAX + 1);
+
+  if (!android_game_path)
+    emacs_abort ();
+
+  snprintf (android_site_load_path, PATH_MAX, "%s/site-lisp",
+           android_files_dir);
+  snprintf (android_game_path, PATH_MAX, "%s/scores", android_files_dir);
+
+  __android_log_print (ANDROID_LOG_INFO, __func__,
+                      "Site-lisp directory: %s\n"
+                      "Files directory: %s\n"
+                      "Native code directory: %s\n"
+                      "Game score path: %s\n"
+                      "Class path: %s\n",
+                      android_site_load_path,
+                      android_files_dir,
+                      android_lib_dir, android_game_path,
+                      (android_class_path
+                       ? android_class_path
+                       : "None"));
+
+  if (android_class_path)
+    /* Set EMACS_CLASS_PATH to the class path where
+       EmacsNoninteractive can be found.  */
+    setenv ("EMACS_CLASS_PATH", android_class_path, 1);
+
+  /* Set LD_LIBRARY_PATH to an appropriate value.  */
+  setenv ("LD_LIBRARY_PATH", android_lib_dir, 1);
+
+  /* EMACS_LD_LIBRARY_PATH records the location of the app library
+     directory.  android-emacs refers to this, since users have valid
+     reasons for changing LD_LIBRARY_PATH to a value that precludes
+     the possibility of Java locating libemacs later.  */
+  setenv ("EMACS_LD_LIBRARY_PATH", android_lib_dir, 1);
+
+  /* Make a reference to the Emacs service.  */
+
+  if (emacs_service_object)
+    {
+      emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
+
+      if (!emacs_service)
+       emacs_abort ();
+
+      /* If the service is set this Emacs is being initialized as part
+        of the Emacs application itself.
+
+         Try to create a symlink from where scripts expect Emacs to
+         place its library files to the directory that actually holds
+         them; earlier versions of Android used to do this
+         automatically, but that feature has been removed.  */
+
+      android_create_lib_link ();
+    }
+
+  /* Set up events.  */
+  android_init_events ();
+
+  /* Set up the Android virtual filesystem layer.  */
+  android_vfs_init (env, local_asset_manager);
+
+  /* OK, setup is now complete.  The caller may call initEmacs
+     now.  */
+}
+
+JNIEXPORT jobject JNICALL
+NATIVE_NAME (getProcName) (JNIEnv *env, jobject object, jint fd)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  char *buffer;
+  size_t length;
+  jbyteArray array;
+
+  buffer = android_proc_name (fd);
+  if (!buffer)
+    return NULL;
+
+  /* Return a byte array, as Java strings cannot always encode file
+     names.  */
+  length = strlen (buffer);
+  array = (*env)->NewByteArray (env, length);
+  if (!array)
+    goto finish;
+
+  (*env)->SetByteArrayRegion (env, array, 0, length,
+                             (jbyte *) buffer);
+
+ finish:
+  free (buffer);
+  return array;
+}
+
+/* Initialize service_class, aborting if something goes wrong.  */
+
+static void
+android_init_emacs_service (void)
+{
+  jclass old;
+
+  service_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsService");
+  eassert (service_class.class);
+
+  old = service_class.class;
+  service_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!service_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  service_class.c_name                                         \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       service_class.class,    \
+                                       name, signature);       \
+  assert (service_class.c_name);
+
+  FIND_METHOD (fill_rectangle, "fillRectangle",
+              "(Lorg/gnu/emacs/EmacsDrawable;"
+              "Lorg/gnu/emacs/EmacsGC;IIII)V");
+  FIND_METHOD (fill_polygon, "fillPolygon",
+              "(Lorg/gnu/emacs/EmacsDrawable;"
+              "Lorg/gnu/emacs/EmacsGC;"
+              "[Landroid/graphics/Point;)V");
+  FIND_METHOD (draw_rectangle, "drawRectangle",
+              "(Lorg/gnu/emacs/EmacsDrawable;"
+              "Lorg/gnu/emacs/EmacsGC;IIII)V");
+  FIND_METHOD (draw_line, "drawLine",
+              "(Lorg/gnu/emacs/EmacsDrawable;"
+              "Lorg/gnu/emacs/EmacsGC;IIII)V");
+  FIND_METHOD (draw_point, "drawPoint",
+              "(Lorg/gnu/emacs/EmacsDrawable;"
+              "Lorg/gnu/emacs/EmacsGC;II)V");
+  FIND_METHOD (clear_window, "clearWindow",
+              "(Lorg/gnu/emacs/EmacsWindow;)V");
+  FIND_METHOD (clear_area, "clearArea",
+              "(Lorg/gnu/emacs/EmacsWindow;IIII)V");
+  FIND_METHOD (ring_bell, "ringBell", "()V");
+  FIND_METHOD (query_tree, "queryTree",
+              "(Lorg/gnu/emacs/EmacsWindow;)[S");
+  FIND_METHOD (get_screen_width, "getScreenWidth", "(Z)I");
+  FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I");
+  FIND_METHOD (detect_mouse, "detectMouse", "()Z");
+  FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;");
+  FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;Z)"
+              "Ljava/lang/String;");
+  FIND_METHOD (restart_emacs, "restartEmacs", "()V");
+  FIND_METHOD (update_ic, "updateIC",
+              "(Lorg/gnu/emacs/EmacsWindow;IIII)V");
+  FIND_METHOD (reset_ic, "resetIC",
+              "(Lorg/gnu/emacs/EmacsWindow;I)V");
+  FIND_METHOD (open_content_uri, "openContentUri",
+              "([BZZZ)I");
+  FIND_METHOD (check_content_uri, "checkContentUri",
+              "(Ljava/lang/String;ZZ)Z");
+  FIND_METHOD (query_battery, "queryBattery", "()[J");
+  FIND_METHOD (update_extracted_text, "updateExtractedText",
+              "(Lorg/gnu/emacs/EmacsWindow;"
+              "Landroid/view/inputmethod/ExtractedText;I)V");
+  FIND_METHOD (update_cursor_anchor_info, "updateCursorAnchorInfo",
+              "(Lorg/gnu/emacs/EmacsWindow;FFFF)V");
+  FIND_METHOD (get_document_authorities, "getDocumentAuthorities",
+              "()[Ljava/lang/String;");
+  FIND_METHOD (request_directory_access, "requestDirectoryAccess",
+              "()I");
+  FIND_METHOD (get_document_trees, "getDocumentTrees",
+              "([B)[Ljava/lang/String;");
+  FIND_METHOD (document_id_from_name, "documentIdFromName",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "[Ljava/lang/String;)I");
+  FIND_METHOD (get_tree_uri, "getTreeUri",
+              "(Ljava/lang/String;Ljava/lang/String;)"
+              "Ljava/lang/String;");
+  FIND_METHOD (stat_document, "statDocument",
+              "(Ljava/lang/String;Ljava/lang/String;Z)[J");
+  FIND_METHOD (access_document, "accessDocument",
+              "(Ljava/lang/String;Ljava/lang/String;Z)I");
+  FIND_METHOD (open_document_directory, "openDocumentDirectory",
+              "(Ljava/lang/String;Ljava/lang/String;)"
+              "Landroid/database/Cursor;");
+  FIND_METHOD (read_directory_entry, "readDirectoryEntry",
+              "(Landroid/database/Cursor;)Lorg/gnu/emacs/"
+              "EmacsDirectoryEntry;");
+  FIND_METHOD (open_document, "openDocument",
+              "(Ljava/lang/String;Ljava/lang/String;ZZZ)"
+              "Landroid/os/ParcelFileDescriptor;");
+  FIND_METHOD (create_document, "createDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;)Ljava/lang/String;");
+  FIND_METHOD (create_directory, "createDirectory",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;)Ljava/lang/String;");
+  FIND_METHOD (delete_document, "deleteDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;)I");
+  FIND_METHOD (rename_document, "renameDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;Ljava/lang/String;)I");
+  FIND_METHOD (move_document, "moveDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;)Ljava/lang/String;");
+  FIND_METHOD (valid_authority, "validAuthority",
+              "(Ljava/lang/String;)Z");
+#undef FIND_METHOD
+}
+
+static void
+android_init_emacs_pixmap (void)
+{
+  jclass old;
+
+  pixmap_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsPixmap");
+  eassert (pixmap_class.class);
+
+  old = pixmap_class.class;
+  pixmap_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!pixmap_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  pixmap_class.c_name                                          \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       pixmap_class.class,     \
+                                       name, signature);       \
+  assert (pixmap_class.c_name);
+
+  FIND_METHOD (constructor, "<init>", "(S[IIII)V");
+  FIND_METHOD (constructor_mutable, "<init>", "(SIII)V");
+
+#undef FIND_METHOD
+}
+
+static void
+android_init_graphics_point (void)
+{
+  jclass old;
+
+  point_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "android/graphics/Point");
+  eassert (point_class.class);
+
+  old = point_class.class;
+  point_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!point_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  point_class.c_name                                           \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       point_class.class,      \
+                                       name, signature);       \
+  assert (point_class.c_name);
+
+  FIND_METHOD (constructor, "<init>", "(II)V");
+#undef FIND_METHOD
+}
+
+static void
+android_init_emacs_drawable (void)
+{
+  jclass old;
+
+  drawable_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsDrawable");
+  eassert (drawable_class.class);
+
+  old = drawable_class.class;
+  drawable_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!drawable_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  drawable_class.c_name                                                \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       drawable_class.class,   \
+                                       name, signature);       \
+  assert (drawable_class.c_name);
+
+  FIND_METHOD (get_bitmap, "getBitmap", "()Landroid/graphics/Bitmap;");
+  FIND_METHOD (damage_rect, "damageRect", "(Landroid/graphics/Rect;)V");
+#undef FIND_METHOD
+}
+
+static void
+android_init_emacs_window (void)
+{
+  jclass old;
+
+  window_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsWindow");
+  eassert (window_class.class);
+
+  old = window_class.class;
+  window_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!window_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  window_class.c_name                                          \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       window_class.class,     \
+                                       name, signature);       \
+  assert (window_class.c_name);
+
+  FIND_METHOD (swap_buffers, "swapBuffers", "()V");
+  FIND_METHOD (toggle_on_screen_keyboard,
+              "toggleOnScreenKeyboard", "(Z)V");
+  FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;");
+  FIND_METHOD (set_fullscreen, "setFullscreen", "(Z)V");
+  FIND_METHOD (change_window_background, "changeWindowBackground",
+              "(I)V");
+  FIND_METHOD (reparent_to, "reparentTo",
+              "(Lorg/gnu/emacs/EmacsWindow;II)V");
+  FIND_METHOD (map_window, "mapWindow", "()V");
+  FIND_METHOD (unmap_window, "unmapWindow", "()V");
+  FIND_METHOD (resize_window, "resizeWindow", "(II)V");
+  FIND_METHOD (move_window, "moveWindow", "(II)V");
+  FIND_METHOD (make_input_focus, "makeInputFocus", "(J)V");
+  FIND_METHOD (raise, "raise", "()V");
+  FIND_METHOD (lower, "lower", "()V");
+  FIND_METHOD (get_window_geometry, "getWindowGeometry",
+              "()[I");
+  FIND_METHOD (translate_coordinates, "translateCoordinates",
+              "(II)[I");
+  FIND_METHOD (set_dont_focus_on_map, "setDontFocusOnMap", "(Z)V");
+  FIND_METHOD (set_dont_accept_focus, "setDontAcceptFocus", "(Z)V");
+  FIND_METHOD (define_cursor, "defineCursor",
+              "(Lorg/gnu/emacs/EmacsCursor;)V");
+#undef FIND_METHOD
+}
+
+static void
+android_init_emacs_cursor (void)
+{
+  jclass old;
+
+  cursor_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsCursor");
+  eassert (cursor_class.class);
+
+  old = cursor_class.class;
+  cursor_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!cursor_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  cursor_class.c_name                                          \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       cursor_class.class,     \
+                                       name, signature);       \
+  assert (cursor_class.c_name);
+
+  FIND_METHOD (constructor, "<init>", "(SI)V");
+#undef FIND_METHOD
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
+                        jobject dump_file_object)
+{
+  /* android_emacs_init is not main, so GCC is not nice enough to add
+     the stack alignment prologue.
+
+     Unfortunately for us, dalvik on Android 4.0.x calls native code
+     with a 4 byte aligned stack, so this prologue must be inserted
+     before each function exported via JNI.  */
+
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  char **c_argv;
+  jsize nelements, i;
+  jobject argument;
+  const char *c_argument;
+  char *dump_file;
+
+  android_java_env = env;
+
+  nelements = (*env)->GetArrayLength (env, argv);
+  c_argv = alloca (sizeof *c_argv * nelements);
+
+  for (i = 0; i < nelements; ++i)
+    {
+      argument = (*env)->GetObjectArrayElement (env, argv, i);
+      c_argument = (*env)->GetStringUTFChars (env, (jstring) argument,
+                                             NULL);
+
+      if (!c_argument)
+       emacs_abort ();
+
+      /* Note that c_argument is in ``modified UTF-8 encoding'', but
+        we don't care as NUL bytes are not being specified inside.  */
+      c_argv[i] = alloca (strlen (c_argument) + 1);
+      strcpy (c_argv[i], c_argument);
+      (*env)->ReleaseStringUTFChars (env, (jstring) argument, c_argument);
+    }
+
+  android_init_emacs_service ();
+  android_init_emacs_pixmap ();
+  android_init_graphics_point ();
+  android_init_emacs_drawable ();
+  android_init_emacs_window ();
+  android_init_emacs_cursor ();
+
+  /* Set HOME to the app data directory.  */
+  setenv ("HOME", android_files_dir, 1);
+
+  /* Set TMPDIR to the temporary files directory.  */
+  setenv ("TMPDIR", android_cache_dir, 1);
+
+  /* And finally set "SHELL" to /system/bin/sh.  Otherwise, some
+     programs will look for /bin/sh, which is problematic.  */
+  setenv ("SHELL", "/system/bin/sh", 1);
+
+  /* Set the cwd to that directory as well.  */
+  if (chdir (android_files_dir))
+    __android_log_print (ANDROID_LOG_WARN, __func__,
+                        "chdir: %s", strerror (errno));
+
+  /* Initialize the Android GUI as long as the service object was
+     set.  */
+
+  if (emacs_service)
+    android_init_gui = true;
+
+  /* Now see if a dump file has been specified and should be used.  */
+  dump_file = NULL;
+
+  if (dump_file_object)
+    {
+      c_argument
+       = (*env)->GetStringUTFChars (env, (jstring) dump_file_object,
+                                    NULL);
+
+      /* Copy the Java string data once.  */
+      dump_file = strdup (c_argument);
+
+      /* Release the Java string data.  */
+      (*env)->ReleaseStringUTFChars (env, (jstring) dump_file_object,
+                                    c_argument);
+    }
+
+  /* Delete local references to objects that are no longer needed.  */
+  ANDROID_DELETE_LOCAL_REF (argv);
+  ANDROID_DELETE_LOCAL_REF (dump_file_object);
+
+  /* Restore the signal mask at the time of startup if it was changed
+     to block unwanted signals from reaching system threads.  */
+
+  if (signal_mask_changed_p)
+    pthread_sigmask (SIG_SETMASK, &startup_signal_mask, NULL);
+
+  /* Now start Emacs proper.  */
+  android_emacs_init (nelements, c_argv, dump_file);
+
+  /* android_emacs_init should never return.  */
+  emacs_abort ();
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  emacs_abort ();
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (quit) (JNIEnv *env, jobject object)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  __android_log_print (ANDROID_LOG_VERBOSE, __func__,
+                      "Sending SIGIO and setting Vquit_flag");
+
+  /* Raise sigio to interrupt anything that could be reading
+     input.  */
+  Vquit_flag = Qt;
+  kill (getpid (), SIGIO);
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
+                                  jshort window, jlong time,
+                                  jint x, jint y, jint width,
+                                  jint height)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xconfigure.type = ANDROID_CONFIGURE_NOTIFY;
+  event.xconfigure.serial = ++event_serial;
+  event.xconfigure.window = window;
+  event.xconfigure.time = time;
+  event.xconfigure.x = x;
+  event.xconfigure.y = y;
+  event.xconfigure.width = width;
+  event.xconfigure.height = height;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
+                           jshort window, jlong time,
+                           jint state, jint keycode,
+                           jint unicode_char)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xkey.type = ANDROID_KEY_PRESS;
+  event.xkey.serial = ++event_serial;
+  event.xkey.window = window;
+  event.xkey.time = time;
+  event.xkey.state = state;
+  event.xkey.keycode = keycode;
+  event.xkey.unicode_char = unicode_char;
+  event.xkey.counter = 0;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
+                             jshort window, jlong time,
+                             jint state, jint keycode,
+                             jint unicode_char)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xkey.type = ANDROID_KEY_RELEASE;
+  event.xkey.serial = ++event_serial;
+  event.xkey.window = window;
+  event.xkey.time = time;
+  event.xkey.state = state;
+  event.xkey.keycode = keycode;
+  event.xkey.unicode_char = unicode_char;
+  event.xkey.counter = 0;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
+                          jshort window, jlong time)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xfocus.type = ANDROID_FOCUS_IN;
+  event.xfocus.serial = ++event_serial;
+  event.xfocus.window = window;
+  event.xfocus.time = time;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
+                           jshort window, jlong time)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xfocus.type = ANDROID_FOCUS_OUT;
+  event.xfocus.serial = ++event_serial;
+  event.xfocus.window = window;
+  event.xfocus.time = time;
+
+  android_write_event (&event);
+  return ++event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
+                               jshort window, jint action)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xaction.type = ANDROID_WINDOW_ACTION;
+  event.xaction.serial = ++event_serial;
+  event.xaction.window = window;
+  event.xaction.action = action;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
+                              jshort window, jint x, jint y,
+                              jlong time)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xcrossing.type = ANDROID_ENTER_NOTIFY;
+  event.xcrossing.serial = ++event_serial;
+  event.xcrossing.window = window;
+  event.xcrossing.x = x;
+  event.xcrossing.y = y;
+  event.xcrossing.time = time;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
+                              jshort window, jint x, jint y,
+                              jlong time)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xcrossing.type = ANDROID_LEAVE_NOTIFY;
+  event.xcrossing.serial = ++event_serial;
+  event.xcrossing.window = window;
+  event.xcrossing.x = x;
+  event.xcrossing.y = y;
+  event.xcrossing.time = time;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
+                               jshort window, jint x, jint y,
+                               jlong time)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xmotion.type = ANDROID_MOTION_NOTIFY;
+  event.xmotion.serial = ++event_serial;
+  event.xmotion.window = window;
+  event.xmotion.x = x;
+  event.xmotion.y = y;
+  event.xmotion.time = time;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
+                              jshort window, jint x, jint y,
+                              jlong time, jint state,
+                              jint button)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xbutton.type = ANDROID_BUTTON_PRESS;
+  event.xbutton.serial = ++event_serial;
+  event.xbutton.window = window;
+  event.xbutton.x = x;
+  event.xbutton.y = y;
+  event.xbutton.time = time;
+  event.xbutton.state = state;
+  event.xbutton.button = button;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
+                                jshort window, jint x, jint y,
+                                jlong time, jint state,
+                                jint button)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xbutton.type = ANDROID_BUTTON_RELEASE;
+  event.xbutton.serial = ++event_serial;
+  event.xbutton.window = window;
+  event.xbutton.x = x;
+  event.xbutton.y = y;
+  event.xbutton.time = time;
+  event.xbutton.state = state;
+  event.xbutton.button = button;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
+                            jshort window, jint x, jint y,
+                            jlong time, jint pointer_id,
+                            jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.touch.type = ANDROID_TOUCH_DOWN;
+  event.touch.serial = ++event_serial;
+  event.touch.window = window;
+  event.touch.x = x;
+  event.touch.y = y;
+  event.touch.time = time;
+  event.touch.pointer_id = pointer_id;
+  event.touch.flags = flags;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
+                          jshort window, jint x, jint y,
+                          jlong time, jint pointer_id,
+                          jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.touch.type = ANDROID_TOUCH_UP;
+  event.touch.serial = ++event_serial;
+  event.touch.window = window;
+  event.touch.x = x;
+  event.touch.y = y;
+  event.touch.time = time;
+  event.touch.pointer_id = pointer_id;
+  event.touch.flags = flags;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
+                            jshort window, jint x, jint y,
+                            jlong time, jint pointer_id,
+                            jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.touch.type = ANDROID_TOUCH_MOVE;
+  event.touch.serial = ++event_serial;
+  event.touch.window = window;
+  event.touch.x = x;
+  event.touch.y = y;
+  event.touch.time = time;
+  event.touch.pointer_id = pointer_id;
+  event.touch.flags = flags;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
+                        jshort window, jint x, jint y,
+                        jlong time, jint state,
+                        jfloat x_delta, jfloat y_delta)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.wheel.type = ANDROID_WHEEL;
+  event.wheel.serial = ++event_serial;
+  event.wheel.window = window;
+  event.wheel.x = x;
+  event.wheel.y = y;
+  event.wheel.time = time;
+  event.wheel.state = state;
+  event.wheel.x_delta = x_delta;
+  event.wheel.y_delta = y_delta;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
+                            jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.iconified.type = ANDROID_ICONIFIED;
+  event.iconified.serial = ++event_serial;
+  event.iconified.window = window;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
+                              jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.iconified.type = ANDROID_DEICONIFIED;
+  event.iconified.serial = ++event_serial;
+  event.iconified.window = window;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
+                              jshort window, jint menu_event_id,
+                              jint menu_event_serial)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.menu.type = ANDROID_CONTEXT_MENU;
+  event.menu.serial = ++event_serial;
+  event.menu.window = window;
+  event.menu.menu_event_id = menu_event_id;
+  event.menu.menu_event_serial = menu_event_serial;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jlong JNICALL
+NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
+                         jshort window, jint x, jint y,
+                         jint width, jint height)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.xexpose.type = ANDROID_EXPOSE;
+  event.xexpose.serial = ++event_serial;
+  event.xexpose.window = window;
+  event.xexpose.x = x;
+  event.xexpose.y = y;
+  event.xexpose.width = width;
+  event.xexpose.height = height;
+
+  android_write_event (&event);
+  return event_serial;
+}
+
+JNIEXPORT jboolean JNICALL
+NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env,
+                                             jobject object)
+{
+  /* Yes, android_pass_multimedia_buttons_to_system is being
+     read from the UI thread.  */
+  return !android_pass_multimedia_buttons_to_system;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (blitRect) (JNIEnv *env, jobject object,
+                       jobject src, jobject dest,
+                       jint x1, jint y1, jint x2, jint y2)
+{
+  AndroidBitmapInfo src_info, dest_info;
+  unsigned char *src_data_1, *dest_data_1;
+  void *src_data, *dest_data;
+
+  /* N.B. that X2 and Y2 represent the pixel past the edge of the
+     rectangle; thus, the width is x2 - x1 and the height is y2 -
+     y1.  */
+
+  memset (&src_info, 0, sizeof src_info);
+  memset (&dest_info, 0, sizeof dest_info);
+  AndroidBitmap_getInfo (env, src, &src_info);
+  AndroidBitmap_getInfo (env, dest, &dest_info);
+
+  /* If the stride is 0 after a call to `getInfo', assume it
+     failed.  */
+
+  if (!src_info.stride || !dest_info.stride)
+    return;
+
+  /* If formats differ, abort.  */
+  eassert (src_info.format == dest_info.format
+          && src_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);
+
+  /* Lock the image data.  */
+  src_data = NULL;
+  AndroidBitmap_lockPixels (env, src, &src_data);
+
+  if (!src_data)
+    return;
+
+  dest_data = NULL;
+  AndroidBitmap_lockPixels (env, dest, &dest_data);
+
+  if (!dest_data)
+    goto fail1;
+
+  /* Now clip the rectangle to the bounds of the source and
+     destination bitmap.  */
+
+  x1 = MAX (x1, 0);
+  y1 = MAX (y1, 0);
+  x2 = MAX (x2, 0);
+  y2 = MAX (y2, 0);
+
+
+  if (x1 >= src_info.width
+      || x1 >= dest_info.width)
+    x1 = MIN (dest_info.width - 1, src_info.width - 1);
+
+  if (x2 > src_info.width
+      || x2 > dest_info.width)
+    x2 = MIN (src_info.width, dest_info.width);
+
+  if (y1 >= src_info.height
+      || y1 >= dest_info.height)
+    y1 = MIN (dest_info.height - 1, src_info.height - 1);
+
+  if (y2 > src_info.height
+      || y2 > dest_info.height)
+    y2 = MIN (src_info.height, dest_info.height);
+
+  if (x1 >= x2 || y1 >= y2)
+    goto fail2;
+
+  /* Determine the address of the first line to copy.  */
+
+  src_data_1 = src_data;
+  dest_data_1 = dest_data;
+  src_data_1 += x1 * 4;
+  src_data_1 += y1 * src_info.stride;
+  dest_data_1 += x1 * 4;
+  dest_data_1 += y1 * dest_info.stride;
+
+  /* Start copying each line.  */
+
+  while (y1 != y2)
+    {
+      memcpy (dest_data_1, src_data_1, (x2 - x1) * 4);
+      src_data_1 += src_info.stride;
+      dest_data_1 += dest_info.stride;
+      y1++;
+    }
+
+  /* Complete the copy and unlock the bitmap.  */
+
+ fail2:
+  AndroidBitmap_unlockPixels (env, dest);
+ fail1:
+  AndroidBitmap_unlockPixels (env, src);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (notifyPixelsChanged) (JNIEnv *env, jobject object,
+                                  jobject bitmap)
+{
+  void *data;
+
+  /* Lock and unlock the bitmap.  This calls
+     SkBitmap->notifyPixelsChanged.  */
+
+  if (AndroidBitmap_lockPixels (env, bitmap, &data) < 0)
+    /* The return value is less than 0 if an error occurs.
+       Good luck finding this in the documentation.  */
+    return;
+
+  AndroidBitmap_unlockPixels (env, bitmap);
+}
+
+/* Forward declarations of deadlock prevention functions.  */
+
+static void android_begin_query (void);
+static void android_end_query (void);
+static void android_answer_query_spin (void);
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (beginSynchronous) (JNIEnv *env, jobject object)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  android_begin_query ();
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (endSynchronous) (JNIEnv *env, jobject object)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  android_end_query ();
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (answerQuerySpin) (JNIEnv *env, jobject object)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  android_answer_query_spin ();
+}
+
+
+
+/* System thread setup.  Android doesn't always block signals Emacs is
+   interested in from being received by the UI or render threads,
+   which can lead to problems when those signals then interrupt one of
+   those threads.  */
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setupSystemThread) (void)
+{
+  sigset_t sigset;
+
+  /* Block everything except for SIGSEGV and SIGBUS; those two are
+     used by the runtime.  */
+
+  sigfillset (&sigset);
+  sigdelset (&sigset, SIGSEGV);
+  sigdelset (&sigset, SIGBUS);
+
+  /* Save the signal mask that was previously used.  It will be
+     restored in `initEmacs'.  */
+
+  if (pthread_sigmask (SIG_BLOCK, &sigset, &startup_signal_mask))
+    __android_log_print (ANDROID_LOG_WARN, __func__,
+                        "pthread_sigmask: %s", strerror (errno));
+  else
+    signal_mask_changed_p = true;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#else
+#pragma GCC diagnostic pop
+#endif
+
+
+
+/* Java functions called by C.
+
+   Because all C code runs in the native function initEmacs, ALL LOCAL
+   REFERENCES WILL PERSIST!
+
+   This means that every local reference must be explicitly destroyed
+   with DeleteLocalRef.  A helper macro is provided to do this.  */
+
+struct android_handle_entry
+{
+  /* The type.  */
+  enum android_handle_type type;
+
+  /* The handle.  */
+  jobject handle;
+};
+
+/* Table of handles MAX_HANDLE long.  */
+struct android_handle_entry android_handles[USHRT_MAX];
+
+/* The largest handle ID currently known, but subject to
+   wraparound.  */
+static android_handle max_handle;
+
+/* Allocate a new, unused, handle identifier.  If Emacs is out of
+   identifiers, return 0.  */
+
+static android_handle
+android_alloc_id (void)
+{
+  android_handle handle;
+
+  /* 0 is never a valid handle ID.  */
+
+  if (!max_handle)
+    max_handle++;
+
+  /* See if the handle is already occupied.  */
+
+  if (android_handles[max_handle].handle)
+    {
+      /* Look for a fresh unoccupied handle.  */
+
+      handle = max_handle;
+      max_handle++;
+
+      while (handle != max_handle)
+       {
+         ++max_handle;
+
+         /* Make sure the handle is valid.  */
+         if (!max_handle)
+           ++max_handle;
+
+         if (!android_handles[max_handle].handle)
+           return max_handle++;
+       }
+
+      return ANDROID_NONE;
+    }
+
+  return max_handle++;
+}
+
+/* Destroy the specified handle and mark it as free on the Java side
+   as well.  */
+
+static void
+android_destroy_handle (android_handle handle)
+{
+  static jclass old, class;
+  static jmethodID method;
+
+  if (!android_handles[handle].handle)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Trying to destroy free handle!");
+      emacs_abort ();
+    }
+
+  if (!class)
+    {
+      class
+       = (*android_java_env)->FindClass (android_java_env,
+                                         "org/gnu/emacs/EmacsHandleObject");
+      assert (class != NULL);
+
+      method
+       = (*android_java_env)->GetMethodID (android_java_env, class,
+                                           "destroyHandle", "()V");
+      assert (method != NULL);
+
+      old = class;
+      class
+       = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                     (jobject) class);
+      android_exception_check_1 (old);
+      ANDROID_DELETE_LOCAL_REF (old);
+    }
+
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      android_handles[handle].handle,
+                                      method);
+
+  /* Just clear any exception thrown.  If destroying the handle
+     fails from an out-of-memory error, then Emacs loses some
+     resources, but that is not as big deal as signalling.  */
+  (*android_java_env)->ExceptionClear (android_java_env);
+
+  /* Delete the global reference regardless of any error.  */
+  (*android_java_env)->DeleteGlobalRef (android_java_env,
+                                       android_handles[handle].handle);
+  android_handles[handle].handle = NULL;
+}
+
+jobject
+android_resolve_handle (android_handle handle,
+                       enum android_handle_type type)
+{
+  if (!handle)
+    /* ANDROID_NONE.  */
+    return NULL;
+
+  /* CheckJNI will normally ensure that the handle exists and is
+     the right type, but with a less informative error message.
+     Don't waste cycles doing our own checking here.  */
+
+#ifdef ENABLE_CHECKING
+
+  if (!android_handles[handle].handle)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Trying to resolve free handle!");
+      emacs_abort ();
+    }
+
+  if (android_handles[handle].type != type)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Handle has wrong type!");
+      emacs_abort ();
+    }
+
+#endif /* ENABLE_CHECKING */
+
+  return android_handles[handle].handle;
+}
+
+static jobject
+android_resolve_handle2 (android_handle handle,
+                        enum android_handle_type type,
+                        enum android_handle_type type2)
+{
+  if (!handle)
+    return NULL;
+
+  /* CheckJNI will normally ensure that the handle exists and is
+     the right type, but with a less informative error message.
+     Don't waste cycles doing our own checking here.  */
+
+#ifdef ENABLE_CHECKING
+
+  if (!android_handles[handle].handle)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Trying to resolve free handle!");
+      emacs_abort ();
+    }
+
+  if (android_handles[handle].type != type
+      && android_handles[handle].type != type2)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Handle has wrong type!");
+      emacs_abort ();
+    }
+
+#endif /* ENABLE_CHECKING */
+
+  return android_handles[handle].handle;
+}
+
+void
+android_change_window_attributes (android_window handle,
+                                 enum android_window_value_mask value_mask,
+                                 struct android_set_window_attributes *attrs)
+{
+  jmethodID method;
+  jobject window;
+  jint pixel;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+
+  if (value_mask & ANDROID_CW_BACK_PIXEL)
+    {
+      method = window_class.change_window_background;
+      pixel = (jint) attrs->background_pixel;
+      (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                    window,
+                                                    window_class.class,
+                                                    method, pixel);
+      android_exception_check ();
+    }
+}
+
+/* Create a new window with the given width, height and
+   attributes.  */
+
+android_window
+android_create_window (android_window parent, int x, int y,
+                      int width, int height,
+                      enum android_window_value_mask value_mask,
+                      struct android_set_window_attributes *attrs)
+{
+  static jclass class;
+  static jmethodID constructor;
+  jobject object, parent_object, old;
+  android_window window;
+  android_handle prev_max_handle;
+  bool override_redirect;
+
+  parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
+
+  prev_max_handle = max_handle;
+  window = android_alloc_id ();
+
+  if (!window)
+    error ("Out of window handles!");
+
+  if (!class)
+    {
+      class = (*android_java_env)->FindClass (android_java_env,
+                                             "org/gnu/emacs/EmacsWindow");
+      assert (class != NULL);
+
+      constructor
+       = (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
+                                           "(SLorg/gnu/emacs/EmacsWindow;"
+                                           "IIIIZ)V");
+      assert (constructor != NULL);
+
+      old = class;
+      class = (*android_java_env)->NewGlobalRef (android_java_env, class);
+      android_exception_check_1 (old);
+      ANDROID_DELETE_LOCAL_REF (old);
+    }
+
+  /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
+     creation time.  */
+  override_redirect = ((value_mask
+                       & ANDROID_CW_OVERRIDE_REDIRECT)
+                      && attrs->override_redirect);
+
+  object = (*android_java_env)->NewObject (android_java_env, class,
+                                          constructor, (jshort) window,
+                                          parent_object, (jint) x, (jint) y,
+                                          (jint) width, (jint) height,
+                                          (jboolean) override_redirect);
+  if (!object)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      max_handle = prev_max_handle;
+      memory_full (0);
+    }
+
+  android_handles[window].type = ANDROID_HANDLE_WINDOW;
+  android_handles[window].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env,
+                                        object);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+  if (!android_handles[window].handle)
+    memory_full (0);
+
+  android_change_window_attributes (window, value_mask, attrs);
+  return window;
+}
+
+void
+android_set_window_background (android_window window, unsigned long pixel)
+{
+  struct android_set_window_attributes attrs;
+
+  attrs.background_pixel = pixel;
+  android_change_window_attributes (window, ANDROID_CW_BACK_PIXEL,
+                                   &attrs);
+}
+
+void
+android_destroy_window (android_window window)
+{
+  if (android_handles[window].type != ANDROID_HANDLE_WINDOW)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Trying to destroy something not a window!");
+      emacs_abort ();
+    }
+
+  android_destroy_handle (window);
+}
+
+static void
+android_init_android_rect_class (void)
+{
+  jclass old;
+
+  if (android_rect_class)
+    /* Already initialized.  */
+    return;
+
+  android_rect_class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "android/graphics/Rect");
+  assert (android_rect_class);
+
+  android_rect_constructor
+    = (*android_java_env)->GetMethodID (android_java_env, android_rect_class,
+                                       "<init>", "(IIII)V");
+  assert (emacs_gc_constructor);
+
+  old = android_rect_class;
+  android_rect_class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) android_rect_class);
+  android_exception_check_1 (old);
+  ANDROID_DELETE_LOCAL_REF (old);
+}
+
+static void
+android_init_emacs_gc_class (void)
+{
+  jclass old;
+
+  if (emacs_gc_class)
+    /* Already initialized.  */
+    return;
+
+  emacs_gc_class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsGC");
+  assert (emacs_gc_class);
+
+  emacs_gc_constructor
+    = (*android_java_env)->GetMethodID (android_java_env,
+                                       emacs_gc_class,
+                                       "<init>", "(S)V");
+  assert (emacs_gc_constructor);
+
+  emacs_gc_mark_dirty
+    = (*android_java_env)->GetMethodID (android_java_env,
+                                       emacs_gc_class,
+                                       "markDirty", "(Z)V");
+  assert (emacs_gc_mark_dirty);
+
+  old = emacs_gc_class;
+  emacs_gc_class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) emacs_gc_class);
+  android_exception_check_1 (old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  emacs_gc_foreground
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "foreground", "I");
+  emacs_gc_background
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "background", "I");
+  emacs_gc_function
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "function", "I");
+  emacs_gc_clip_rects
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "clip_rects",
+                                      "[Landroid/graphics/Rect;");
+  emacs_gc_clip_x_origin
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "clip_x_origin", "I");
+  emacs_gc_clip_y_origin
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "clip_y_origin", "I");
+  emacs_gc_stipple
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "stipple",
+                                      "Lorg/gnu/emacs/EmacsPixmap;");
+  emacs_gc_clip_mask
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "clip_mask",
+                                      "Lorg/gnu/emacs/EmacsPixmap;");
+  emacs_gc_fill_style
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "fill_style", "I");
+  emacs_gc_ts_origin_x
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "ts_origin_x", "I");
+  emacs_gc_ts_origin_y
+    = (*android_java_env)->GetFieldID (android_java_env,
+                                      emacs_gc_class,
+                                      "ts_origin_y", "I");
+}
+
+struct android_gc *
+android_create_gc (enum android_gc_value_mask mask,
+                  struct android_gc_values *values)
+{
+  struct android_gc *gc;
+  android_handle prev_max_handle;
+  jobject object;
+
+  android_init_emacs_gc_class ();
+
+  gc = xmalloc (sizeof *gc);
+  prev_max_handle = max_handle;
+  gc->gcontext = android_alloc_id ();
+  gc->foreground = 0;
+  gc->background = 0xffffff;
+  gc->clip_rects = NULL;
+
+  /* This means to not apply any clipping.  */
+  gc->num_clip_rects = -1;
+
+  /* Apply the other default values.  */
+  gc->function = ANDROID_GC_COPY;
+  gc->fill_style = ANDROID_FILL_SOLID;
+  gc->clip_x_origin = 0;
+  gc->clip_y_origin = 0;
+  gc->clip_mask = ANDROID_NONE;
+  gc->stipple = ANDROID_NONE;
+  gc->ts_x_origin = 0;
+  gc->ts_y_origin = 0;
+
+  if (!gc->gcontext)
+    {
+      xfree (gc);
+      error ("Out of GContext handles!");
+    }
+
+  object = (*android_java_env)->NewObject (android_java_env,
+                                          emacs_gc_class,
+                                          emacs_gc_constructor,
+                                          (jshort) gc->gcontext);
+
+  if (!object)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      max_handle = prev_max_handle;
+      memory_full (0);
+    }
+
+  android_handles[gc->gcontext].type = ANDROID_HANDLE_GCONTEXT;
+  android_handles[gc->gcontext].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env, object);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+  if (!android_handles[gc->gcontext].handle)
+    memory_full (0);
+
+  android_change_gc (gc, mask, values);
+  return gc;
+}
+
+void
+android_free_gc (struct android_gc *gc)
+{
+  android_destroy_handle (gc->gcontext);
+
+  xfree (gc->clip_rects);
+  xfree (gc);
+}
+
+void
+android_change_gc (struct android_gc *gc,
+                  enum android_gc_value_mask mask,
+                  struct android_gc_values *values)
+{
+  jobject what, gcontext;
+  jboolean clip_changed;
+
+  clip_changed = false;
+
+  android_init_emacs_gc_class ();
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  if (mask & ANDROID_GC_FOREGROUND)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_foreground,
+                                       values->foreground);
+      gc->foreground = values->foreground;
+    }
+
+  if (mask & ANDROID_GC_BACKGROUND)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_background,
+                                       values->background);
+      gc->background = values->background;
+    }
+
+  if (mask & ANDROID_GC_FUNCTION)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_function,
+                                       values->function);
+      gc->function = values->function;
+    }
+
+  if (mask & ANDROID_GC_CLIP_X_ORIGIN)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_clip_x_origin,
+                                       values->clip_x_origin);
+      gc->clip_x_origin = values->clip_x_origin;
+      clip_changed = true;
+    }
+
+  if (mask & ANDROID_GC_CLIP_Y_ORIGIN)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_clip_y_origin,
+                                       values->clip_y_origin);
+      gc->clip_y_origin = values->clip_y_origin;
+      clip_changed = true;
+    }
+
+  if (mask & ANDROID_GC_CLIP_MASK)
+    {
+      what = android_resolve_handle (values->clip_mask,
+                                    ANDROID_HANDLE_PIXMAP);
+      (*android_java_env)->SetObjectField (android_java_env,
+                                          gcontext,
+                                          emacs_gc_clip_mask,
+                                          what);
+      gc->clip_mask = values->clip_mask;
+
+      /* Changing GCClipMask also clears the clip rectangles.  */
+      (*android_java_env)->SetObjectField (android_java_env,
+                                          gcontext,
+                                          emacs_gc_clip_rects,
+                                          NULL);
+
+      xfree (gc->clip_rects);
+      gc->clip_rects = NULL;
+      gc->num_clip_rects = -1;
+      clip_changed = true;
+    }
+
+  if (mask & ANDROID_GC_STIPPLE)
+    {
+      what = android_resolve_handle (values->stipple,
+                                    ANDROID_HANDLE_PIXMAP);
+      (*android_java_env)->SetObjectField (android_java_env,
+                                          gcontext,
+                                          emacs_gc_stipple,
+                                          what);
+      gc->stipple = values->stipple;
+    }
+
+  if (mask & ANDROID_GC_FILL_STYLE)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_fill_style,
+                                       values->fill_style);
+      gc->fill_style = values->fill_style;
+    }
+
+  if (mask & ANDROID_GC_TILE_STIP_X_ORIGIN)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_ts_origin_x,
+                                       values->ts_x_origin);
+      gc->ts_x_origin = values->ts_x_origin;
+    }
+
+  if (mask & ANDROID_GC_TILE_STIP_Y_ORIGIN)
+    {
+      (*android_java_env)->SetIntField (android_java_env,
+                                       gcontext,
+                                       emacs_gc_ts_origin_y,
+                                       values->ts_y_origin);
+      gc->ts_y_origin = values->ts_y_origin;
+    }
+
+  if (mask)
+    {
+      (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                    gcontext,
+                                                    emacs_gc_class,
+                                                    emacs_gc_mark_dirty,
+                                                    (jboolean) clip_changed);
+      android_exception_check ();
+    }
+}
+
+void
+android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin,
+                            int clip_y_origin,
+                            struct android_rectangle *clip_rects,
+                            int n_clip_rects)
+{
+  jobjectArray array;
+  jobject rect, gcontext;
+  int i;
+
+  android_init_android_rect_class ();
+  android_init_emacs_gc_class ();
+
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  array = (*android_java_env)->NewObjectArray (android_java_env,
+                                              n_clip_rects,
+                                              android_rect_class,
+                                              NULL);
+  android_exception_check ();
+
+  for (i = 0; i < n_clip_rects; ++i)
+    {
+      rect = (*android_java_env)->NewObject (android_java_env,
+                                            android_rect_class,
+                                            android_rect_constructor,
+                                            (jint) clip_rects[i].x,
+                                            (jint) clip_rects[i].y,
+                                            (jint) (clip_rects[i].x
+                                                    + clip_rects[i].width),
+                                            (jint) (clip_rects[i].y
+                                                    + clip_rects[i].height));
+
+      /* The meaning of this call is to check whether or not an
+        allocation error happened, and to delete ARRAY and signal an
+        out-of-memory error if that is the case.  */
+      android_exception_check_1 (array);
+
+      (*android_java_env)->SetObjectArrayElement (android_java_env,
+                                                 array, i, rect);
+      ANDROID_DELETE_LOCAL_REF (rect);
+    }
+
+  (*android_java_env)->SetObjectField (android_java_env,
+                                      gcontext,
+                                      emacs_gc_clip_rects,
+                                      (jobject) array);
+  ANDROID_DELETE_LOCAL_REF (array);
+
+  (*android_java_env)->SetIntField (android_java_env,
+                                   gcontext,
+                                   emacs_gc_clip_x_origin,
+                                   clip_x_origin);
+  (*android_java_env)->SetIntField (android_java_env,
+                                   gcontext,
+                                   emacs_gc_clip_y_origin,
+                                   clip_y_origin);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                gcontext,
+                                                emacs_gc_class,
+                                                emacs_gc_mark_dirty,
+                                                (jboolean) true);
+  android_exception_check ();
+
+  /* Cache the clip rectangles on the C side for
+     sfntfont-android.c.  */
+  if (gc->clip_rects)
+    xfree (gc->clip_rects);
+
+  /* If gc->num_clip_rects is 0, then no drawing will be performed at
+     all.  */
+  gc->clip_rects = xmalloc (sizeof *gc->clip_rects
+                           * n_clip_rects);
+  gc->num_clip_rects = n_clip_rects;
+  memcpy (gc->clip_rects, clip_rects,
+         n_clip_rects * sizeof *gc->clip_rects);
+}
+
+void
+android_reparent_window (android_window w, android_window parent_handle,
+                        int x, int y)
+{
+  jobject window, parent;
+  jmethodID method;
+
+  window = android_resolve_handle (w, ANDROID_HANDLE_WINDOW);
+  parent = android_resolve_handle (parent_handle,
+                                  ANDROID_HANDLE_WINDOW);
+
+  method = window_class.reparent_to;
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
+                                                window_class.class, method,
+                                                parent, (jint) x, (jint) y);
+  android_exception_check ();
+}
+
+void
+android_clear_window (android_window handle)
+{
+  jobject window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.clear_window,
+                                                window);
+  android_exception_check ();
+}
+
+void
+android_map_window (android_window handle)
+{
+  jobject window;
+  jmethodID map_window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  map_window = window_class.map_window;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                map_window);
+  android_exception_check ();
+}
+
+void
+android_unmap_window (android_window handle)
+{
+  jobject window;
+  jmethodID unmap_window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  unmap_window = window_class.unmap_window;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                unmap_window);
+  android_exception_check ();
+}
+
+void
+android_resize_window (android_window handle, unsigned int width,
+                      unsigned int height)
+{
+  jobject window;
+  jmethodID resize_window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  resize_window = window_class.resize_window;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                resize_window,
+                                                (jint) width,
+                                                (jint) height);
+  android_exception_check ();
+}
+
+void
+android_move_window (android_window handle, int x, int y)
+{
+  jobject window;
+  jmethodID move_window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  move_window = window_class.move_window;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                move_window,
+                                                (jint) x, (jint) y);
+  android_exception_check ();
+}
+
+void
+android_swap_buffers (struct android_swap_info *swap_info,
+                     int num_windows)
+{
+  jobject window;
+  int i;
+
+  for (i = 0; i < num_windows; ++i)
+    {
+      window = android_resolve_handle (swap_info[i].swap_window,
+                                      ANDROID_HANDLE_WINDOW);
+      (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                    window,
+                                                    window_class.class,
+                                                    window_class.swap_buffers);
+      android_exception_check ();
+    }
+}
+
+void
+android_get_gc_values (struct android_gc *gc,
+                      enum android_gc_value_mask mask,
+                      struct android_gc_values *values)
+{
+  if (mask & ANDROID_GC_FOREGROUND)
+    /* GCs never have 32 bit colors, so we don't have to worry about
+       sign extension here.  */
+    values->foreground = gc->foreground;
+
+  if (mask & ANDROID_GC_BACKGROUND)
+    values->background = gc->background;
+
+  if (mask & ANDROID_GC_FUNCTION)
+    values->function = gc->function;
+
+  if (mask & ANDROID_GC_CLIP_X_ORIGIN)
+    values->clip_x_origin = gc->clip_x_origin;
+
+  if (mask & ANDROID_GC_CLIP_Y_ORIGIN)
+    values->clip_y_origin = gc->clip_y_origin;
+
+  if (mask & ANDROID_GC_FILL_STYLE)
+    values->fill_style = gc->fill_style;
+
+  if (mask & ANDROID_GC_TILE_STIP_X_ORIGIN)
+    values->ts_x_origin = gc->ts_x_origin;
+
+  if (mask & ANDROID_GC_TILE_STIP_Y_ORIGIN)
+    values->ts_y_origin = gc->ts_y_origin;
+
+  /* Fields involving handles are not used by Emacs, and thus not
+     implemented */
+}
+
+void
+android_set_foreground (struct android_gc *gc, unsigned long foreground)
+{
+  struct android_gc_values gcv;
+
+  gcv.foreground = foreground;
+  android_change_gc (gc, ANDROID_GC_FOREGROUND, &gcv);
+}
+
+void
+android_fill_rectangle (android_drawable handle, struct android_gc *gc,
+                       int x, int y, unsigned int width,
+                       unsigned int height)
+{
+  jobject drawable, gcontext;
+
+  drawable = android_resolve_handle2 (handle,
+                                     ANDROID_HANDLE_WINDOW,
+                                     ANDROID_HANDLE_PIXMAP);
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.fill_rectangle,
+                                                drawable,
+                                                gcontext,
+                                                (jint) x, (jint) y,
+                                                (jint) width,
+                                                (jint) height);
+}
+
+android_pixmap
+android_create_pixmap_from_bitmap_data (char *data, unsigned int width,
+                                       unsigned int height,
+                                       unsigned long foreground,
+                                       unsigned long background,
+                                       unsigned int depth)
+{
+  android_handle prev_max_handle;
+  jobject object;
+  jintArray colors;
+  android_pixmap pixmap;
+  unsigned int x, y;
+  jint *region;
+
+  USE_SAFE_ALLOCA;
+
+  /* Create the color array holding the data.  */
+  colors = (*android_java_env)->NewIntArray (android_java_env,
+                                            width * height);
+  android_exception_check ();
+
+  SAFE_NALLOCA (region, sizeof *region, width);
+
+  for (y = 0; y < height; ++y)
+    {
+      for (x = 0; x < width; ++x)
+       {
+         if (depth == 24)
+           {
+             /* The alpha channels must be set, or otherwise, the
+                pixmap will be created entirely transparent.  */
+
+             if (data[x / 8] & (1 << (x % 8)))
+               region[x] = foreground | 0xff000000;
+             else
+               region[x] = background | 0xff000000;
+           }
+         else
+           {
+             if (data[x / 8] & (1 << (x % 8)))
+               region[x] = foreground;
+             else
+               region[x] = background;
+           }
+       }
+
+      (*android_java_env)->SetIntArrayRegion (android_java_env,
+                                             colors,
+                                             width * y, width,
+                                             region);
+      data += width / 8;
+    }
+
+  /* First, allocate the pixmap handle.  */
+  prev_max_handle = max_handle;
+  pixmap = android_alloc_id ();
+
+  if (!pixmap)
+    {
+      ANDROID_DELETE_LOCAL_REF ((jobject) colors);
+      error ("Out of pixmap handles!");
+    }
+
+  object = (*android_java_env)->NewObject (android_java_env,
+                                          pixmap_class.class,
+                                          pixmap_class.constructor,
+                                          (jshort) pixmap, colors,
+                                          (jint) width, (jint) height,
+                                          (jint) depth);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF ((jobject) colors);
+
+  if (!object)
+    {
+      max_handle = prev_max_handle;
+      memory_full (0);
+    }
+
+  android_handles[pixmap].type = ANDROID_HANDLE_PIXMAP;
+  android_handles[pixmap].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env, object);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+  if (!android_handles[pixmap].handle)
+    memory_full (0);
+
+  SAFE_FREE ();
+  return pixmap;
+}
+
+void
+android_set_clip_mask (struct android_gc *gc, android_pixmap pixmap)
+{
+  struct android_gc_values gcv;
+
+  gcv.clip_mask = pixmap;
+  android_change_gc (gc, ANDROID_GC_CLIP_MASK, &gcv);
+}
+
+void
+android_set_fill_style (struct android_gc *gc,
+                       enum android_fill_style fill_style)
+{
+  struct android_gc_values gcv;
+
+  gcv.fill_style = fill_style;
+  android_change_gc (gc, ANDROID_GC_FILL_STYLE, &gcv);
+}
+
+
+
+/* Pixmap bit blit implementation.  This exists as `Canvas.drawBitmap'
+   seems to have trouble with copying bitmap data from one bitmap back
+   to itself on Android 8.0.  */
+
+/* Function called to actually perform the copy.  */
+
+typedef void (*android_blit_func) (int, int, int, int, int, int,
+                                  struct android_gc *,
+                                  unsigned char *, AndroidBitmapInfo *,
+                                  unsigned char *, AndroidBitmapInfo *,
+                                  unsigned char *, AndroidBitmapInfo *);
+
+
+
+#ifdef __aarch64__
+
+/* Copy N pixels from SRC to DST, using MASK as a depth 1 clip
+   mask.  */
+
+static void
+android_neon_mask_line (unsigned int *src, unsigned int *dst,
+                       unsigned char *mask, int n)
+{
+  uint32x4_t src_low, src_high, dst_low, dst_high;
+  int16x8_t vmask;
+  int32x4_t ext_mask_low, ext_mask_high, low, high;
+  int rem, i;
+
+  /* Calculate the remainder.  */
+  rem = n & 7, n &= ~7;
+
+  /* Process eight pixels at a time.  */
+
+  if (n)
+    {
+    again:
+      /* Load the low and high four pixels from the source.  */
+      src_low = vld1q_u32 (src);
+      src_high = vld1q_u32 (src + 4);
+
+      /* Do the same with the destination.  */
+      dst_low = vld1q_u32 (dst);
+      dst_high = vld1q_u32 (dst + 4);
+
+      /* Load and sign extend the mask.  */
+      vmask = vmovl_s8 (vld1_u8 (mask));
+      ext_mask_low = vmovl_s16 (vget_low_s16 (vmask));
+      ext_mask_high = vmovl_s16 (vget_high_s16 (vmask));
+
+      /* Reinterpret the mask.  */
+      low = vreinterpretq_u32_s32 (ext_mask_low);
+      high = vreinterpretq_u32_s32 (ext_mask_high);
+
+      /* Apply the mask.  */
+      dst_low = vbicq_u32 (dst_low, low);
+      src_low = vandq_u32 (src_low, low);
+      dst_high = vbicq_u32 (dst_high, high);
+      src_high = vandq_u32 (src_high, high);
+
+      /* Write the result after combining both masked vectors.  */
+      vst1q_u32 (dst, vorrq_u32 (dst_low, src_low));
+      vst1q_u32 (dst + 4, vorrq_u32 (dst_high, src_high));
+
+      /* Adjust src, dst and mask.  */
+      dst += 8;
+      src += 8;
+      mask += 8;
+
+      /* See if this loop should continue.  */
+      n -= 8;
+      if (n > 0)
+       goto again;
+    }
+
+  /* Process the remaining pixels.  */
+
+  for (i = 0; i < rem; ++i)
+    {
+      /* Sign extend the mask.  */
+      n = ((signed char *) mask)[i];
+
+      /* Combine src and dst.  */
+      dst[i] = ((src[i] & n) | (dst[i] & ~n));
+    }
+}
+
+#endif /* __aarch64__ */
+
+
+
+/* Copy a rectangle SRC_X, SRC_Y, WIDTH and HEIGHT from SRC, described
+   by SRC_INFO, to DST_X and DST_Y in DST, as described by DST_INFO.
+
+   If MASK is set, mask the source data using MASK_INFO, translating
+   it by GC->clip_x_origin and GC->clip_y_origin.  MASK must be a
+   pixmap of depth 1.
+
+   N.B. that currently only copies between bitmaps of depth 24 are
+   implemented.  */
+
+static void
+android_blit_copy (int src_x, int src_y, int width, int height,
+                  int dst_x, int dst_y, struct android_gc *gc,
+                  unsigned char *src, AndroidBitmapInfo *src_info,
+                  unsigned char *dst, AndroidBitmapInfo *dst_info,
+                  unsigned char *mask, AndroidBitmapInfo *mask_info)
+{
+  uintptr_t start, end;
+  int mask_offset;
+  size_t pixel, offset, offset1;
+  unsigned char *src_current, *dst_current;
+  unsigned char *mask_current;
+  int overflow, temp, i;
+#ifndef __aarch64__
+  int j;
+#endif /* __aarch64__ */
+  bool backwards;
+  unsigned int *long_src, *long_dst;
+
+  /* Assert that the specified coordinates are within bounds.  */
+  eassert (src_x >= 0 && src_y >= 0
+          && dst_x >= 0 && dst_y >= 0);
+  eassert (src_x + width <= src_info->width);
+  eassert (src_y + height <= src_info->height);
+  eassert (dst_x + width <= dst_info->width);
+  eassert (dst_y + height <= dst_info->height);
+
+  /* Now check that each bitmap has the correct format.  */
+  eassert (src_info->format == dst_info->format
+          && src_info->format == ANDROID_BITMAP_FORMAT_RGBA_8888);
+  pixel = sizeof (unsigned int);
+
+  /* Android doesn't have A1 bitmaps, so A8 is used to represent
+     packed bitmaps of depth 1.  */
+  eassert (!mask || mask_info->format == ANDROID_BITMAP_FORMAT_A_8);
+
+  /* Calculate the address of the first pixel of the first row to be
+     copied in both src and dst.  Compare them to determine the
+     direction in which the copy is to take place.  */
+
+  overflow  = ckd_mul (&start, src_y, src_info->stride);
+  overflow |= ckd_mul (&end, src_x, pixel);
+  overflow |= ckd_add (&start, end, start);
+  overflow |= ckd_add (&start, (uintptr_t) src, start);
+
+  if (overflow)
+    return;
+
+  src_current = (unsigned char *) start;
+
+  overflow  = ckd_mul (&start, dst_y, dst_info->stride);
+  overflow |= ckd_mul (&end, dst_x, pixel);
+  overflow |= ckd_add (&start, end, start);
+  overflow |= ckd_add (&start, (uintptr_t) dst, start);
+
+  if (overflow)
+    return;
+
+  dst_current = (unsigned char *) start;
+  backwards = false;
+
+  /* Now see if copying should proceed from the bottom up.  */
+
+  if (src == dst && dst_current >= src_current)
+    {
+      backwards = true;
+
+      /* Walk src and dst from bottom to top, in order to avoid
+        overlap.  Calculate the coordinate of the last pixel of the
+        last row in both src and dst.  */
+
+      overflow = ckd_mul (&start, src_y + height - 1,
+                         src_info->stride);
+
+      if (mask)
+       /* If a mask is set, put the pointers before the end of the
+          row.  */
+       overflow |= ckd_mul (&end, src_x + width - 1, pixel);
+      else
+       end = src_x * pixel;
+
+      overflow |= ckd_add (&start, start, end);
+      overflow |= ckd_add (&start, (uintptr_t) src, start);
+
+      if (overflow)
+       return;
+
+      src_current = (unsigned char *) start;
+
+      overflow = ckd_mul (&start, dst_y + height - 1,
+                         dst_info->stride);
+
+      if (mask)
+       /* If a mask is set, put the pointers before the end of the
+          row.  */
+       overflow |= ckd_mul (&end, dst_x + width - 1, pixel);
+      else
+       end = dst_x * pixel;
+
+      overflow |= ckd_add (&start, start, end);
+      overflow |= ckd_add (&start, (uintptr_t) dst, start);
+
+      if (overflow)
+       return;
+
+      dst_current = (unsigned char *) start;
+    }
+
+  if (!mask)
+    {
+      /* Change the direction of the copy depending on how SRC and DST
+        overlap.  */
+
+      for (i = 0; i < height; ++i)
+       {
+         memmove (dst_current, src_current,
+                  width * pixel);
+
+         if (backwards)
+           {
+             /* Proceed to the last row.  */
+             src_current -= src_info->stride;
+             dst_current -= dst_info->stride;
+           }
+         else
+           {
+             /* Proceed to the next row.  */
+             src_current += src_info->stride;
+             dst_current += dst_info->stride;
+           }
+       }
+    }
+  else
+    {
+      /* Adjust the source and destination Y.  The start is MAX
+         (dst_y, gc->clip_y_origin); the difference between that value
+         and dst_y is the offset to apply to src_y. */
+
+      temp    = dst_y;
+      dst_y   = MAX (dst_y, gc->clip_y_origin);
+      src_y  += dst_y - temp;
+      height -= dst_y - temp;
+
+      /* Verify that the bounds are correct.  */
+      eassert (dst_y + height
+              <= gc->clip_y_origin + mask_info->height);
+      eassert (dst_y >= gc->clip_y_origin);
+
+      /* There is a mask.  For each scan line... */
+
+      if (backwards)
+       {
+         /* Calculate the number of pixels at the end of the
+            mask.  */
+
+         mask_offset  = dst_x + width;
+         mask_offset -= mask_info->width + gc->clip_x_origin;
+
+         if (mask_offset < 0)
+           mask_offset = 0;
+
+         /* Calculate the last column of the mask that will be
+            consulted.  */
+
+         temp = dst_x - gc->clip_x_origin;
+         temp += MIN (mask_info->width - temp,
+                      width - mask_offset);
+
+         if (temp < 0)
+           return;
+
+         /* Now calculate the last row of the mask that will be
+            consulted.  */
+         i = dst_y - gc->clip_y_origin + height;
+
+         /* Turn both into offsets.  */
+
+         if (INT_MULTIPLY_WRAPV (temp, pixel, &offset)
+             || INT_MULTIPLY_WRAPV (i, mask_info->stride, &offset1)
+             || INT_ADD_WRAPV (offset, offset1, &offset)
+             || INT_ADD_WRAPV ((uintptr_t) mask, offset, &start))
+           return;
+
+         if (height <= 0)
+           return;
+
+         mask = mask_current = (unsigned char *) start;
+
+         while (height--)
+           {
+             /* Skip backwards past the end of the mask.  */
+
+             long_src = (unsigned int *) (src_current - mask_offset * pixel);
+             long_dst = (unsigned int *) (dst_current - mask_offset * pixel);
+             mask = mask_current;
+
+             /* For each pixel covered by the mask... */
+             temp = MIN (mask_info->width - temp, width - mask_offset);
+             while (temp--)
+               {
+                 /* Copy the destination it to the source, masked by
+                    the mask.  */
+
+                 /* Sign extend the mask.  */
+                 i = *(signed char *) mask--;
+
+                 /* Apply the mask.  */
+                 *long_dst = ((*long_src & i) | (*long_dst & ~i));
+
+                 long_dst--;
+                 long_src--;
+               }
+
+             /* Return to the last row.  */
+             src_current -= src_info->stride;
+             dst_current -= dst_info->stride;
+             mask_current -= mask_info->stride;
+           }
+       }
+      else
+       {
+         /* Calculate the first column of the mask that will be
+            consulted.  */
+
+         mask_offset = dst_x - gc->clip_x_origin;
+
+         /* Adjust the mask by that much.  */
+
+         if (mask_offset > 0)
+           mask += mask_offset;
+         else
+           {
+             /* Offset src and dst by the mask offset.  */
+             src_current += -mask_offset * pixel;
+             dst_current += -mask_offset * pixel;
+             width += mask_offset;
+           }
+
+         /* Make sure it's not out of bounds.  */
+
+         eassert (dst_y - gc->clip_y_origin >= 0);
+         if ((dst_y - gc->clip_y_origin) + height > mask_info->height
+             || width <= 0)
+           return;
+
+         /* Now move mask to the position of the first row.  */
+
+         mask += ((dst_y - gc->clip_y_origin)
+                  * mask_info->stride);
+
+         /* Determine how many bytes need to be copied.  */
+
+         if (mask_offset > 0)
+           temp = MIN (mask_info->width - mask_offset, width);
+         else
+           temp = MIN (mask_info->width, width);
+
+         if (temp <= 0 || height <= 0)
+           return;
+
+         /* Copy bytes according to the mask.  */
+
+         while (height--)
+           {
+             long_src = (unsigned int *) src_current;
+             long_dst = (unsigned int *) dst_current;
+             mask_current = mask;
+
+#ifndef __aarch64__
+             for (j = 0; j < temp; ++j)
+               {
+                 /* Sign extend the mask.  */
+                 i = *(signed char *) mask_current++;
+
+                 /* Apply the mask.  */
+                 *long_dst = ((*long_src & i) | (*long_dst & ~i));
+                 long_dst++;
+                 long_src++;
+               }
+#else /* __aarch64__ */
+             android_neon_mask_line (long_src, long_dst, mask, temp);
+#endif /* __aarch64__ */
+
+             src_current += src_info->stride;
+             dst_current += dst_info->stride;
+             mask        += mask_info->stride;
+           }
+       }
+    }
+}
+
+
+/* Xor a rectangle SRC_X, SRC_Y, WIDTH and HEIGHT from SRC, described
+   by SRC_INFO, to DST_X and DST_Y in DST, as described by DST_INFO.
+
+   Ignore the alpha channel when computing the exclusive-or of the
+   destination pixel.
+
+   If MASK is set, mask the source data using MASK_INFO, translating
+   it by GC->clip_x_origin and GC->clip_y_origin.  MASK must be a
+   pixmap of depth 1.
+
+   N.B. that currently only copies between bitmaps of depth 24 are
+   implemented.  */
+
+static void
+android_blit_xor (int src_x, int src_y, int width, int height,
+                 int dst_x, int dst_y, struct android_gc *gc,
+                 unsigned char *src, AndroidBitmapInfo *src_info,
+                 unsigned char *dst, AndroidBitmapInfo *dst_info,
+                 unsigned char *mask, AndroidBitmapInfo *mask_info)
+{
+#if 0
+  uintptr_t start, end;
+  int mask_offset;
+  size_t pixel, offset, offset1;
+  unsigned char *src_current, *dst_current;
+  unsigned char *mask_current;
+  int overflow, temp, i;
+  bool backwards;
+  unsigned int *long_src, *long_dst;
+#endif /* 0 */
+
+  /* Note that this alu hasn't been tested -- it probably does not
+     work! */
+  emacs_abort ();
+
+#if 0
+  /* Assert that the specified coordinates are within bounds.  */
+  eassert (src_x >= 0 && src_y >= 0
+          && dst_x >= 0 && dst_y >= 0);
+  eassert (src_x + width <= src_info->width);
+  eassert (src_y + height <= src_info->height);
+  eassert (dst_x + width <= dst_info->width);
+  eassert (dst_y + height <= dst_info->height);
+
+  /* Now check that each bitmap has the correct format.  */
+  eassert (src_info->format == dst_info->format
+          && src_info->format == ANDROID_BITMAP_FORMAT_RGBA_8888);
+  pixel = sizeof (unsigned int);
+
+  /* Android doesn't have A1 bitmaps, so A8 is used to represent
+     packed bitmaps of depth 1.  */
+  eassert (!mask || mask_info->format == ANDROID_BITMAP_FORMAT_A_8);
+
+  /* Calculate the address of the first pixel of the first row to be
+     copied in both src and dst.  Compare them to determine the
+     direction in which the copy is to take place.  */
+
+  overflow  = ckd_mul (&start, src_y, src_info->stride);
+  overflow |= ckd_mul (&end, src_x, pixel);
+  overflow |= ckd_add (&start, (uintptr_t) src, start);
+
+  if (overflow)
+    return;
+
+  src_current = (unsigned char *) start;
+
+  overflow  = ckd_mul (&start, dst_y, src_info->stride);
+  overflow |= ckd_mul (&end, dst_x, pixel);
+  overflow |= ckd_add (&start, (uintptr_t) dst, start);
+
+  if (overflow)
+    return;
+
+  dst_current = (unsigned char *) start;
+  backwards = false;
+
+  /* Now see if copying should proceed from the bottom up.  */
+
+  if (src == dst && dst_current >= src_current)
+    {
+      backwards = true;
+
+      /* Walk src and dst from bottom to top, in order to avoid
+        overlap.  Calculate the coordinate of the last pixel of the
+        last row in both src and dst.  */
+
+      overflow  = ckd_mul (&start, src_y + height - 1,
+                          src_info->stride);
+      if (mask) /* If a mask is set, put the pointers before the end
+                  of the row.  */
+       overflow |= ckd_mul (&end, src_x + width - 1, pixel);
+      else
+       overflow |= ckd_mul (&end, src_x, pixel);
+      overflow |= ckd_add (&start, start, end);
+      overflow |= ckd_add (&start, (uintptr_t) src, start);
+
+      if (overflow)
+       return;
+
+      src_current = (unsigned char *) start;
+
+      overflow  = ckd_mul (&start, dst_y + height - 1,
+                          dst_info->stride);
+      if (mask) /* If a mask is set, put the pointers before the end
+                  of the row.  */
+       overflow |= ckd_mul (&end, dst_x + width - 1, pixel);
+      else
+       overflow |= ckd_mul (&end, dst_x, pixel);
+      overflow |= ckd_add (&start, start, end);
+      overflow |= ckd_add (&start, (uintptr_t) dst, start);
+
+      if (overflow)
+       return;
+
+      dst_current = (unsigned char *) start;
+    }
+
+  if (!mask)
+    {
+      /* Change the direction of the copy depending on how SRC and DST
+        overlap.  */
+
+      for (i = 0; i < height; ++i)
+       {
+         if (backwards)
+           {
+             for (i = width - 1; i <= 0; --i)
+               (((unsigned int *) dst_current)[i])
+                 /* Keep the alpha channel intact.  */
+                 ^= (((unsigned int *) src_current)[i]) & 0xffffff;
+
+             /* Proceed to the last row.  */
+             src_current -= src_info->stride;
+             dst_current -= dst_info->stride;
+           }
+         else
+           {
+             for (i = 0; i < width; ++i)
+               (((unsigned int *) dst_current)[i])
+                 /* Keep the alpha channel intact.  */
+                 ^= (((unsigned int *) src_current)[i]) & 0xffffff;
+
+             /* Proceed to the next row.  */
+             src_current += src_info->stride;
+             dst_current += dst_info->stride;
+           }
+       }
+    }
+  else
+    {
+      /* Adjust the source and destination Y.  The start is MAX
+         (dst_y, gc->clip_y_origin); the difference between that value
+         and dst_y is the offset to apply to src_y. */
+
+      temp    = dst_y;
+      dst_y   = MAX (dst_y, gc->clip_y_origin);
+      src_y  += dst_y - temp;
+      height -= dst_y - temp;
+
+      /* Verify that the bounds are correct.  */
+      eassert (dst_y + height
+              <= gc->clip_y_origin + mask_info->height);
+      eassert (dst_y >= gc->clip_y_origin);
+
+      /* There is a mask.  For each scan line... */
+
+      if (backwards)
+       {
+         /* Calculate the number of pixels at the end of the
+            mask.  */
+
+         mask_offset  = dst_x + width;
+         mask_offset -= mask_info->width + gc->clip_x_origin;
+
+         if (mask_info < 0)
+           mask_info = 0;
+
+         /* Calculate the last column of the mask that will be
+            consulted.  */
+
+         temp = dst_x - gc->clip_x_origin;
+         temp += MIN (mask_info->width - temp,
+                      width - mask_offset);
+
+         if (temp < 0)
+           return;
+
+         /* Now calculate the last row of the mask that will be
+            consulted.  */
+         i = dst_y - gc->clip_y_origin + height;
+
+         /* Turn both into offsets.  */
+
+         if (INT_MULTIPLY_WRAPV (temp, pixel, &offset)
+             || INT_MULTIPLY_WRAPV (i, mask_info->stride, &offset1)
+             || INT_ADD_WRAPV (offset, offset1, &offset)
+             || INT_ADD_WRAPV ((uintptr_t) mask, offset, &start))
+           return;
+
+         mask = mask_current = (unsigned char *) start;
+
+         for (i = 0; i < height; ++i)
+           {
+             /* Skip backwards past the end of the mask.  */
+
+             long_src = (unsigned int *) (src_current - mask_offset * pixel);
+             long_dst = (unsigned int *) (dst_current - mask_offset * pixel);
+             mask = mask_current;
+
+             /* For each pixel covered by the mask... */
+             temp = MIN (mask_info->width - temp, width - mask_offset);
+             while (temp--)
+               /* XOR the source to the destination, masked by the
+                  mask.  */
+               *long_dst-- ^= ((*(long_src--) & (0u - (*(mask--) & 1)))
+                               & 0xffffff);
+
+             /* Return to the last row.  */
+             src_current -= src_info->stride;
+             dst_current -= dst_info->stride;
+             mask_current -= mask_info->stride;
+           }
+       }
+      else
+       {
+         /* Calculate the first column of the mask that will be
+            consulted.  */
+
+         mask_offset = dst_x - gc->clip_x_origin;
+
+         /* Adjust the mask by that much.  */
+
+         if (mask_offset > 0)
+           mask += mask_offset;
+         else
+           {
+             /* Offset src and dst by the mask offset.  */
+             src_current += -mask_offset * pixel;
+             dst_current += -mask_offset * pixel;
+             width -= mask_offset;
+           }
+
+         /* Now move mask to the position of the first row.  */
+
+         mask += gc->clip_y_origin * mask_info->stride;
+
+         for (i = 0; i < height; ++i)
+           {
+             long_src = (unsigned int *) src_current;
+             long_dst = (unsigned int *) dst_current;
+             mask_current = mask;
+
+             if (mask_offset > 0)
+               {
+                 /* Copy bytes according to the mask.  */
+                 temp = MIN (mask_info->width - mask_offset, width);
+                 while (temp--)
+                   *long_dst++ ^= ((*(long_src++)
+                                    & (0u - (*(mask_current++) & 1)))
+                                   & 0xffffff);
+               }
+             else
+               {
+                 /* Copy bytes according to the mask.  */
+                 temp = MIN (mask_info->width, width);
+                 while (temp--)
+                   *long_dst++ = ((*(long_src++)
+                                   & (0u - (*(mask_current++) & 1)))
+                                  & 0xffffff);
+               }
+
+             src_current += src_info->stride;
+             dst_current += dst_info->stride;
+             mask        += mask_info->stride;
+           }
+       }
+    }
+#endif /* 0 */
+}
+
+void
+android_copy_area (android_drawable src, android_drawable dest,
+                  struct android_gc *gc, int src_x, int src_y,
+                  unsigned int width, unsigned int height,
+                  int dest_x, int dest_y)
+{
+  jobject src_object, dest_object, mask;
+  android_blit_func do_blit;
+  AndroidBitmapInfo src_info, dest_info, mask_info;
+  void *src_data, *dest_data, *mask_data;
+  int n_clip_rects, i;
+  bool flag;
+  struct android_rectangle bounds, rect, temp, *clip_rectangles;
+
+  /* Perform the copy.  Loop over each clip rectangle, unless none are
+     set.  Also, obtain bitmaps for src and dst, and possibly the mask
+     as well if it is present.  */
+
+  src_data = android_lock_bitmap (src, &src_info, &src_object);
+  if (!src_data)
+    return;
+
+  mask_data = mask = NULL;
+
+  if (src != dest)
+    {
+      dest_data = android_lock_bitmap (dest, &dest_info, &dest_object);
+      if (!dest_data)
+       goto fail;
+    }
+  else
+    {
+      dest_data = src_data;
+      dest_info = src_info;
+    }
+
+  /* Obtain the bitmap for the mask if necessary.  */
+
+  if (gc->clip_mask)
+    {
+      mask_data = android_lock_bitmap (gc->clip_mask,
+                                      &mask_info, &mask);
+      if (!mask_data)
+       goto fail1;
+    }
+
+  /* Calculate the number of clip rectangles.  */
+  n_clip_rects = gc->num_clip_rects;
+
+  /* If n_clip_rects is -1, then no clipping is in effect.  Set rect
+     to the bounds of the destination.  */
+
+  flag = n_clip_rects == -1;
+  if (flag)
+    {
+      n_clip_rects = 1;
+      clip_rectangles = &rect;
+    }
+  else if (!n_clip_rects)
+    goto fail2;
+  else
+    clip_rectangles = gc->clip_rects;
+
+  /* Set rect to the bounds of the destination.  */
+
+  rect.x = 0;
+  rect.y = 0;
+  rect.width = dest_info.width;
+  rect.height = dest_info.height;
+
+  if (mask_data)
+    {
+      /* Clip width and height to that of the mask.  */
+
+      if (src_x + width > mask_info.width)
+       width = mask_info.width - src_x;
+
+      if (src_y + height > mask_info.height)
+       height = mask_info.height - src_y;
+    }
+
+  /* Clip width and height to that of the source.  */
+
+  if (src_x + width > src_info.width)
+    width = src_info.width - src_x;
+
+  if (src_y + height > src_info.height)
+    height = src_info.height - src_y;
+
+  /* Return if the copy is outside the source.  */
+
+  if (width <= 0 || height <= 0)
+    goto fail2;
+
+  /* Look up the right function for the alu.  */
+
+  switch (gc->function)
+    {
+    case ANDROID_GC_COPY:
+      do_blit = android_blit_copy;
+      break;
+
+    case ANDROID_GC_XOR:
+      do_blit = android_blit_xor;
+      break;
+
+    default:
+      emacs_abort ();
+    }
+
+  /* Load the bounds of the destination rectangle.  */
+  bounds.x = dest_x;
+  bounds.y = dest_y;
+  bounds.width = width;
+  bounds.height = height;
+
+  /* For each clip rectangle... */
+  for (i = 0; i < n_clip_rects; ++i)
+    {
+      /* Calculate its intersection with the destination
+        rectangle.  */
+
+      if (!gui_intersect_rectangles (&clip_rectangles[i], &bounds,
+                                    &temp))
+       continue;
+
+      /* And that of the destination itself.  */
+
+      if (!flag && !gui_intersect_rectangles (&temp, &rect, &temp))
+       continue;
+
+      /* Now perform the copy.  */
+      (*do_blit) (src_x + temp.x - dest_x,     /* temp.x relative to src_x */
+                 src_y + temp.y - dest_y,      /* temp.y relative to src_y */
+                 temp.width,                   /* Width of area to copy.  */
+                 temp.height,                  /* Height of area to copy.  */
+                 temp.x, temp.y,               /* Coordinates to copy to.  */
+                 gc,                           /* GC.  */
+                 src_data, &src_info,          /* Source drawable.  */
+                 dest_data, &dest_info,        /* Destination drawable.  */
+                 mask_data, &mask_info);       /* Mask drawable.  */
+    }
+
+  /* Now damage the destination drawable accordingly, should it be a
+     window.  */
+
+  if (android_handles[dest].type == ANDROID_HANDLE_WINDOW)
+    android_damage_window (dest, &bounds);
+
+ fail2:
+  if (mask)
+    {
+      AndroidBitmap_unlockPixels (android_java_env, mask);
+      ANDROID_DELETE_LOCAL_REF (mask);
+    }
+ fail1:
+  if (src != dest)
+    {
+      AndroidBitmap_unlockPixels (android_java_env, dest_object);
+      ANDROID_DELETE_LOCAL_REF (dest_object);
+    }
+ fail:
+  AndroidBitmap_unlockPixels (android_java_env, src_object);
+  ANDROID_DELETE_LOCAL_REF (src_object);
+}
+
+
+
+void
+android_free_pixmap (android_pixmap pixmap)
+{
+  android_destroy_handle (pixmap);
+}
+
+void
+android_set_background (struct android_gc *gc, unsigned long background)
+{
+  struct android_gc_values gcv;
+
+  gcv.background = background;
+  android_change_gc (gc, ANDROID_GC_BACKGROUND, &gcv);
+}
+
+void
+android_fill_polygon (android_drawable drawable, struct android_gc *gc,
+                     struct android_point *points, int npoints,
+                     enum android_shape shape, enum android_coord_mode mode)
+{
+  jobjectArray array;
+  jobject point, drawable_object, gcontext;
+  int i;
+
+  drawable_object = android_resolve_handle2 (drawable,
+                                            ANDROID_HANDLE_WINDOW,
+                                            ANDROID_HANDLE_PIXMAP);
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  array = (*android_java_env)->NewObjectArray (android_java_env,
+                                              npoints,
+                                              point_class.class,
+                                              NULL);
+  android_exception_check ();
+
+  for (i = 0; i < npoints; ++i)
+    {
+      point = (*android_java_env)->NewObject (android_java_env,
+                                             point_class.class,
+                                             point_class.constructor,
+                                             (jint) points[i].x,
+                                             (jint) points[i].y);
+      android_exception_check_1 (array);
+
+      (*android_java_env)->SetObjectArrayElement (android_java_env,
+                                                 array, i, point);
+      ANDROID_DELETE_LOCAL_REF (point);
+    }
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.fill_polygon,
+                                                drawable_object,
+                                                gcontext, array);
+  ANDROID_DELETE_LOCAL_REF (array);
+}
+
+void
+android_draw_rectangle (android_drawable handle, struct android_gc *gc,
+                       int x, int y, unsigned int width, unsigned int height)
+{
+  jobject drawable, gcontext;
+
+  drawable = android_resolve_handle2 (handle,
+                                     ANDROID_HANDLE_WINDOW,
+                                     ANDROID_HANDLE_PIXMAP);
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.draw_rectangle,
+                                                drawable, gcontext,
+                                                (jint) x, (jint) y,
+                                                (jint) width, (jint) height);
+}
+
+void
+android_draw_point (android_drawable handle, struct android_gc *gc,
+                   int x, int y)
+{
+  jobject drawable, gcontext;
+
+  drawable = android_resolve_handle2 (handle,
+                                     ANDROID_HANDLE_WINDOW,
+                                     ANDROID_HANDLE_PIXMAP);
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.draw_point,
+                                                drawable, gcontext,
+                                                (jint) x, (jint) y);
+}
+
+void
+android_draw_line (android_drawable handle, struct android_gc *gc,
+                  int x, int y, int x2, int y2)
+{
+  jobject drawable, gcontext;
+
+  drawable = android_resolve_handle2 (handle,
+                                     ANDROID_HANDLE_WINDOW,
+                                     ANDROID_HANDLE_PIXMAP);
+  gcontext = android_resolve_handle (gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.draw_line,
+                                                drawable, gcontext,
+                                                (jint) x, (jint) y,
+                                                (jint) x2, (jint) y2);
+}
+
+android_pixmap
+android_create_pixmap (unsigned int width, unsigned int height,
+                      int depth)
+{
+  android_handle prev_max_handle;
+  jobject object;
+  android_pixmap pixmap;
+
+  /* First, allocate the pixmap handle.  */
+  prev_max_handle = max_handle;
+  pixmap = android_alloc_id ();
+
+  if (!pixmap)
+    error ("Out of pixmap handles!");
+
+  object = (*android_java_env)->NewObject (android_java_env,
+                                          pixmap_class.class,
+                                          pixmap_class.constructor_mutable,
+                                          (jshort) pixmap,
+                                          (jint) width, (jint) height,
+                                          (jint) depth);
+
+  if (!object)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      max_handle = prev_max_handle;
+      memory_full (0);
+    }
+
+  android_handles[pixmap].type = ANDROID_HANDLE_PIXMAP;
+  android_handles[pixmap].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env, object);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+  if (!android_handles[pixmap].handle)
+    memory_full (0);
+
+  return pixmap;
+}
+
+void
+android_set_ts_origin (struct android_gc *gc, int x, int y)
+{
+  struct android_gc_values gcv;
+
+  gcv.ts_x_origin = x;
+  gcv.ts_y_origin = y;
+  android_change_gc (gc, (ANDROID_GC_TILE_STIP_X_ORIGIN
+                         | ANDROID_GC_TILE_STIP_Y_ORIGIN),
+                    &gcv);
+}
+
+void
+android_clear_area (android_window handle, int x, int y,
+                   unsigned int width, unsigned int height)
+{
+  jobject window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.clear_area,
+                                                window, (jint) x, (jint) y,
+                                                (jint) width, (jint) height);
+}
+
+android_pixmap
+android_create_bitmap_from_data (char *bits, unsigned int width,
+                                unsigned int height)
+{
+  return android_create_pixmap_from_bitmap_data (bits, 1, 0,
+                                                width, height, 1);
+}
+
+struct android_image *
+android_create_image (unsigned int depth, enum android_image_format format,
+                     char *data, unsigned int width, unsigned int height)
+{
+  struct android_image *image;
+
+  image = xmalloc (sizeof *image);
+
+  /* Fill in the fields required by image.c.  N.B. that
+     android_destroy_image ostensibly will free data, but image.c
+     mostly sets and frees data itself.  */
+  image->width = width;
+  image->height = height;
+  image->data = data;
+  image->depth = depth;
+  image->format = format;
+
+  /* Now fill in the image dimensions.  There are only two depths
+     supported by this function.  */
+
+  if (depth == 1)
+    {
+      image->bytes_per_line = (width + 7) / 8;
+      image->bits_per_pixel = 1;
+    }
+  else if (depth == 24)
+    {
+      image->bytes_per_line = width * 4;
+      image->bits_per_pixel = 32;
+    }
+  else
+    emacs_abort ();
+
+  return image;
+}
+
+void
+android_destroy_image (struct android_image *ximg)
+{
+  /* If XIMG->data is NULL, then it has already been freed by
+     image.c.  */
+
+  if (ximg->data)
+    xfree (ximg->data);
+  xfree (ximg);
+}
+
+void
+android_put_pixel (struct android_image *ximg, int x, int y,
+                  unsigned long pixel)
+{
+  char *byte, *word;
+  unsigned int r, g, b;
+  unsigned int pixel_int;
+
+  /* Ignore out-of-bounds accesses.  */
+
+  if (x >= ximg->width || y >= ximg->height || x < 0 || y < 0)
+    return;
+
+  switch (ximg->depth)
+    {
+    case 1:
+      byte = ximg->data + y * ximg->bytes_per_line + x / 8;
+
+      if (pixel)
+       *byte |= (1 << x % 8);
+      else
+       *byte &= ~(1 << x % 8);
+      break;
+
+    case 24:
+      /* Unaligned accesses are problematic on Android devices.  */
+      word = ximg->data + y * ximg->bytes_per_line + x * 4;
+
+      /* Swizzle the pixel into ABGR format.  Android uses Skia's
+        ``native color type'', which is ABGR.  This is despite the
+        format being named ``ARGB'', and more confusingly
+        `ANDROID_BITMAP_FORMAT_RGBA_8888' in bitmap.h.  */
+      r = pixel & 0x00ff0000;
+      g = pixel & 0x0000ff00;
+      b = pixel & 0x000000ff;
+      pixel = (r >> 16) | g | (b << 16) | 0xff000000;
+
+      pixel_int = pixel;
+      memcpy (word, &pixel_int, sizeof pixel_int);
+      break;
+    }
+}
+
+unsigned long
+android_get_pixel (struct android_image *ximg, int x, int y)
+{
+  char *byte, *word;
+  unsigned int pixel, r, g, b;
+
+  if (x >= ximg->width || y >= ximg->height
+      || x < 0 || y < 0)
+    return 0;
+
+  switch (ximg->depth)
+    {
+    case 1:
+      byte = ximg->data + y * ximg->bytes_per_line + x / 8;
+      return (*byte & (1 << x % 8)) ? 1 : 0;
+
+    case 24:
+      word = ximg->data + y * ximg->bytes_per_line + x * 4;
+      memcpy (&pixel, word, sizeof pixel);
+
+      /* Convert the pixel back to RGB.  */
+      b = pixel & 0x00ff0000;
+      g = pixel & 0x0000ff00;
+      r = pixel & 0x000000ff;
+      pixel = ((r << 16) | g | (b >> 16)) & ~0xff000000;
+
+      return pixel;
+    }
+
+  emacs_abort ();
+}
+
+struct android_image *
+android_get_image (android_drawable handle,
+                  enum android_image_format format)
+{
+  jobject drawable, bitmap;
+  AndroidBitmapInfo bitmap_info;
+  size_t byte_size;
+  void *data;
+  struct android_image *image;
+  unsigned char *data1, *data2;
+  int i, x;
+
+  drawable = android_resolve_handle2 (handle, ANDROID_HANDLE_WINDOW,
+                                     ANDROID_HANDLE_PIXMAP);
+
+  /* Look up the drawable and get the bitmap corresponding to it.
+     Then, lock the bitmap's bits.  */
+  bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 drawable,
+                                                 drawable_class.get_bitmap);
+  android_exception_check ();
+
+  /* Clear the bitmap info structure.  */
+  memset (&bitmap_info, 0, sizeof bitmap_info);
+
+  /* The NDK doc seems to imply this function can fail but doesn't say
+     what value it gives when it does! */
+  AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info);
+
+  if (!bitmap_info.stride)
+    {
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      memory_full (0);
+    }
+
+  /* Compute how big the image data will be.  Fail if it would be too
+     big.  */
+
+  if (bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8)
+    {
+      if (INT_MULTIPLY_WRAPV ((size_t) bitmap_info.stride,
+                             (size_t) bitmap_info.height,
+                             &byte_size))
+       {
+         ANDROID_DELETE_LOCAL_REF (bitmap);
+         memory_full (0);
+       }
+    }
+  else
+    /* This A8 image will be packed into A1 later on.  */
+    byte_size = (bitmap_info.width + 7) / 8;
+
+  /* Lock the image data.  Once again, the NDK documentation says the
+     call can fail, but does not say how to determine whether or not
+     it has failed, nor how the address is aligned.  */
+  data = NULL;
+  AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
+
+  if (!data)
+    {
+      /* Take a NULL pointer to mean that AndroidBitmap_lockPixels
+        failed.  */
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      memory_full (0);
+    }
+
+  /* Copy the data into a new struct android_image.  */
+  image = xmalloc (sizeof *image);
+  image->width = bitmap_info.width;
+  image->height = bitmap_info.height;
+  image->data = malloc (byte_size);
+
+  if (!image->data)
+    {
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      xfree (image);
+      memory_full (byte_size);
+    }
+
+  /* Use the format of the bitmap to determine the image depth.  */
+  switch (bitmap_info.format)
+    {
+    case ANDROID_BITMAP_FORMAT_RGBA_8888:
+      image->depth = 24;
+      image->bits_per_pixel = 32;
+      break;
+
+      /* A8 images are used by Emacs to represent bitmaps.  They have
+        to be packed manually.  */
+    case ANDROID_BITMAP_FORMAT_A_8:
+      image->depth = 1;
+      image->bits_per_pixel = 1;
+      break;
+
+      /* Other formats are currently not supported.  */
+    default:
+      emacs_abort ();
+    }
+
+  image->format = format;
+
+  if (image->depth == 24)
+    {
+      image->bytes_per_line = bitmap_info.stride;
+
+      /* Copy the bitmap data over.  */
+      memcpy (image->data, data, byte_size);
+    }
+  else
+    {
+      /* Pack the A8 image data into bits manually.  */
+      image->bytes_per_line = (image->width + 7) / 8;
+
+      data1 = (unsigned char *) image->data;
+      data2 = data;
+
+      for (i = 0; i < image->height; ++i)
+       {
+         for (x = 0; x < image->width; ++x)
+           /* Some bits in data1 might be initialized at this point,
+              but they will all be set properly later.  */
+           data1[x / 8] = (data2[x]
+                           ? (data1[x / 8] | (1 << (x % 8)))
+                           : (data1[x / 8] & ~(1 << (x % 8))));
+
+         data1 += image->bytes_per_line;
+         data2 += bitmap_info.stride;
+       }
+    }
+
+  /* Unlock the bitmap pixels.  */
+  AndroidBitmap_unlockPixels (android_java_env, bitmap);
+
+  /* Delete the bitmap reference.  */
+  ANDROID_DELETE_LOCAL_REF (bitmap);
+  return image;
+}
+
+void
+android_put_image (android_pixmap handle, struct android_image *image)
+{
+  jobject drawable, bitmap;
+  AndroidBitmapInfo bitmap_info;
+  void *data;
+  unsigned char *data_1, *data_2;
+  int i, x;
+
+  drawable = android_resolve_handle (handle, ANDROID_HANDLE_PIXMAP);
+
+  /* Look up the drawable and get the bitmap corresponding to it.
+     Then, lock the bitmap's bits.  */
+  bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 drawable,
+                                                 drawable_class.get_bitmap);
+  android_exception_check ();
+
+  /* Clear the bitmap info structure.  */
+  memset (&bitmap_info, 0, sizeof bitmap_info);
+
+  /* The NDK doc seems to imply this function can fail but doesn't say
+     what value it gives when it does! */
+  AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info);
+
+  if (!bitmap_info.stride)
+    {
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      memory_full (0);
+    }
+
+  if (bitmap_info.width != image->width
+      || bitmap_info.height != image->height)
+    /* This is not yet supported.  */
+    emacs_abort ();
+
+  /* Make sure the bitmap formats are compatible with each other.  */
+
+  if ((image->depth == 24
+       && bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
+      || (image->depth == 1
+         && bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8))
+    emacs_abort ();
+
+  /* Lock the image data.  Once again, the NDK documentation says the
+     call can fail, but does not say how to determine whether or not
+     it has failed, nor how the address is aligned.  */
+  data = NULL;
+  AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
+
+  if (!data)
+    {
+      /* Take a NULL pointer to mean that AndroidBitmap_lockPixels
+        failed.  */
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      memory_full (0);
+    }
+
+  data_1 = data;
+  data_2 = (unsigned char *) image->data;
+
+  /* Copy the bitmap data over scanline-by-scanline.  */
+  for (i = 0; i < image->height; ++i)
+    {
+      if (image->depth != 1)
+       memcpy (data_1, data_2,
+               image->width * (image->bits_per_pixel / 8));
+      else
+       {
+         /* Android internally uses a 1 byte-per-pixel format for
+            ALPHA_8 images.  Expand the image from the 1
+            bit-per-pixel X format correctly.  */
+
+         for (x = 0; x < image->width; ++x)
+           data_1[x] = (data_2[x / 8] & (1 << x % 8)) ? 0xff : 0;
+       }
+
+      data_1 += bitmap_info.stride;
+      data_2 += image->bytes_per_line;
+    }
+
+  /* Unlock the bitmap pixels.  */
+  AndroidBitmap_unlockPixels (android_java_env, bitmap);
+
+  /* Delete the bitmap reference.  */
+  ANDROID_DELETE_LOCAL_REF (bitmap);
+}
+
+void
+android_bell (void)
+{
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.ring_bell);
+  android_exception_check ();
+}
+
+void
+android_set_input_focus (android_window handle, unsigned long time)
+{
+  jobject window;
+  jmethodID make_input_focus;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  make_input_focus = window_class.make_input_focus;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                make_input_focus,
+                                                (jlong) time);
+  android_exception_check ();
+}
+
+void
+android_raise_window (android_window handle)
+{
+  jobject window;
+  jmethodID raise;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  raise = window_class.raise;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                raise);
+  android_exception_check ();
+}
+
+void
+android_lower_window (android_window handle)
+{
+  jobject window;
+  jmethodID lower;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  lower = window_class.lower;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window,
+                                                window_class.class,
+                                                lower);
+  android_exception_check ();
+}
+
+int
+android_query_tree (android_window handle, android_window *root_return,
+                   android_window *parent_return,
+                   android_window **children_return,
+                   unsigned int *nchildren_return)
+{
+  jobject window, array;
+  jsize nelements, i;
+  android_window *children;
+  jshort *shorts;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+
+  /* window can be NULL, so this is a service method.  */
+  array
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            emacs_service,
+                                            service_class.query_tree,
+                                            window);
+  android_exception_check ();
+
+  /* The first element of the array is the parent window.  The rest
+     are the children.  */
+  nelements = (*android_java_env)->GetArrayLength (android_java_env,
+                                                  array);
+  eassert (nelements);
+
+  /* Now fill in the children.  */
+  children = xnmalloc (nelements - 1, sizeof *children);
+
+  shorts
+    = (*android_java_env)->GetShortArrayElements (android_java_env, array,
+                                                 NULL);
+  android_exception_check_nonnull (shorts, array);
+
+  for (i = 1; i < nelements; ++i)
+    /* Subtract one from the index into children, since the parent is
+       not included.  */
+    children[i - 1] = shorts[i];
+
+  /* Finally, return the parent and other values.  */
+  *root_return = 0;
+  *parent_return = shorts[0];
+  *children_return = children;
+  *nchildren_return = nelements - 1;
+
+  /* Release the array contents.  */
+  (*android_java_env)->ReleaseShortArrayElements (android_java_env, array,
+                                                 shorts, JNI_ABORT);
+
+  ANDROID_DELETE_LOCAL_REF (array);
+  return 1;
+}
+
+void
+android_get_geometry (android_window handle,
+                     android_window *root_return,
+                     int *x_return, int *y_return,
+                     unsigned int *width_return,
+                     unsigned int *height_return,
+                     unsigned int *border_width_return)
+{
+  jobject window;
+  jarray window_geometry;
+  jmethodID get_geometry;
+  jint *ints;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  get_geometry = window_class.get_window_geometry;
+
+  window_geometry
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            window,
+                                            get_geometry);
+  android_exception_check ();
+
+  /* window_geometry is an array containing x, y, width and
+     height.  border_width is always 0 on Android.  */
+  eassert ((*android_java_env)->GetArrayLength (android_java_env,
+                                               window_geometry)
+          == 4);
+
+  *root_return = 0;
+  *border_width_return = 0;
+
+  ints
+    = (*android_java_env)->GetIntArrayElements (android_java_env,
+                                               window_geometry,
+                                               NULL);
+  android_exception_check_nonnull (ints, window_geometry);
+
+  *x_return = ints[0];
+  *y_return = ints[1];
+  *width_return = ints[2];
+  *height_return = ints[3];
+
+  (*android_java_env)->ReleaseIntArrayElements (android_java_env,
+                                               window_geometry,
+                                               ints, JNI_ABORT);
+
+  /* Now free the local reference.  */
+  ANDROID_DELETE_LOCAL_REF (window_geometry);
+}
+
+void
+android_move_resize_window (android_window window, int x, int y,
+                           unsigned int width, unsigned int height)
+{
+  android_move_window (window, x, y);
+  android_resize_window (window, width, height);
+}
+
+void
+android_map_raised (android_window window)
+{
+  android_raise_window (window);
+  android_map_window (window);
+}
+
+void
+android_translate_coordinates (android_window src, int x,
+                              int y, int *root_x, int *root_y)
+{
+  jobject window;
+  jarray coordinates;
+  jmethodID method;
+  jint *ints;
+
+  window = android_resolve_handle (src, ANDROID_HANDLE_WINDOW);
+  method = window_class.translate_coordinates;
+  coordinates
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            window, method,
+                                            (jint) x, (jint) y);
+  android_exception_check ();
+
+  /* The array must contain two elements: X, Y translated to the root
+     window.  */
+  eassert ((*android_java_env)->GetArrayLength (android_java_env,
+                                               coordinates)
+          == 2);
+
+  /* Obtain the coordinates from the array.  */
+  ints = (*android_java_env)->GetIntArrayElements (android_java_env,
+                                                  coordinates, NULL);
+  android_exception_check_nonnull (ints, coordinates);
+
+  *root_x = ints[0];
+  *root_y = ints[1];
+
+  /* Release the coordinates.  */
+  (*android_java_env)->ReleaseIntArrayElements (android_java_env,
+                                               coordinates, ints,
+                                               JNI_ABORT);
+
+  /* And free the local reference.  */
+  ANDROID_DELETE_LOCAL_REF (coordinates);
+}
+
+int
+android_wc_lookup_string (android_key_pressed_event *event,
+                         wchar_t *buffer_return, int wchars_buffer,
+                         int *keysym_return,
+                         enum android_lookup_status *status_return)
+{
+  enum android_lookup_status status;
+  int rc;
+  jobject window, string;
+  const jchar *characters;
+  jsize size;
+  size_t i;
+
+  status = ANDROID_LOOKUP_NONE;
+  rc = 0;
+
+  /* See if an actual lookup has to be made.  Note that while
+     BUFFER_RETURN is wchar_t, the returned characters are always in
+     UCS.  */
+
+  if (event->unicode_char != (uint32_t) -1)
+    {
+      if (event->unicode_char)
+       {
+         if (wchars_buffer < 1)
+           {
+             *status_return = ANDROID_BUFFER_OVERFLOW;
+             return 0;
+           }
+         else
+           {
+             buffer_return[0] = event->unicode_char;
+             status = ANDROID_LOOKUP_CHARS;
+             rc = 1;
+           }
+       }
+
+      *keysym_return = event->keycode;
+
+      if (status == ANDROID_LOOKUP_CHARS)
+       status = ANDROID_LOOKUP_BOTH;
+      else
+       {
+         status = ANDROID_LOOKUP_KEYSYM;
+         rc = 0;
+       }
+
+      *status_return = status;
+
+      return rc;
+    }
+
+  /* Now look up the window.  */
+  rc = 0;
+
+  if (!android_handles[event->window].handle
+      || (android_handles[event->window].type
+         != ANDROID_HANDLE_WINDOW))
+    status = ANDROID_LOOKUP_NONE;
+  else
+    {
+      window = android_handles[event->window].handle;
+      string
+       = (*android_java_env)->CallObjectMethod (android_java_env, window,
+                                                window_class.lookup_string,
+                                                (jint) event->serial);
+      android_exception_check ();
+
+      if (!string)
+       status = ANDROID_LOOKUP_NONE;
+      else
+       {
+         /* Now return this input method string.  */
+         characters = (*android_java_env)->GetStringChars (android_java_env,
+                                                           string, NULL);
+         android_exception_check_nonnull ((void *) characters, string);
+
+         /* Figure out how big the string is.  */
+         size = (*android_java_env)->GetStringLength (android_java_env,
+                                                      string);
+
+         /* Copy over the string data.  */
+         for (i = 0; i < MIN ((unsigned int) wchars_buffer, size); ++i)
+           buffer_return[i] = characters[i];
+
+         if (i < size)
+           status = ANDROID_BUFFER_OVERFLOW;
+         else
+           status = ANDROID_LOOKUP_CHARS;
+
+         /* Return the number of characters that should have been
+            written.  */
+
+         if (size > INT_MAX)
+           rc = INT_MAX;
+         else
+           rc = size;
+
+         (*android_java_env)->ReleaseStringChars (android_java_env, string,
+                                                  characters);
+         ANDROID_DELETE_LOCAL_REF (string);
+       }
+    }
+
+  *status_return = status;
+  return rc;
+}
+
+
+
+/* Low level drawing primitives.  */
+
+/* Lock the bitmap corresponding to the drawable DRAWABLE.  Return the
+   bitmap data upon success, and store the bitmap object in
+   BITMAP_RETURN.  Value is NULL upon failure.
+
+   The caller must take care to unlock the bitmap data afterwards.  */
+
+unsigned char *
+android_lock_bitmap (android_window drawable,
+                    AndroidBitmapInfo *bitmap_info,
+                    jobject *bitmap_return)
+{
+  jobject object, bitmap;
+  void *data;
+
+  object = android_resolve_handle2 (drawable, ANDROID_HANDLE_WINDOW,
+                                   ANDROID_HANDLE_PIXMAP);
+
+  /* Look up the drawable and get the bitmap corresponding to it.
+     Then, lock the bitmap's bits.  */
+  bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 object,
+                                                 drawable_class.get_bitmap);
+  if (!bitmap)
+    /* NULL is returned when the bitmap does not currently exist due
+       to ongoing reconfiguration on the main thread.  */
+    return NULL;
+
+  memset (bitmap_info, 0, sizeof *bitmap_info);
+
+  /* Get the bitmap info.  */
+  AndroidBitmap_getInfo (android_java_env, bitmap, bitmap_info);
+
+  if (!bitmap_info->stride)
+    {
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      return NULL;
+    }
+
+  /* Now lock the image data.  */
+  data = NULL;
+  AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
+
+  if (!data)
+    {
+      ANDROID_DELETE_LOCAL_REF (bitmap);
+      return NULL;
+    }
+
+  /* Give the bitmap to the caller.  */
+  *bitmap_return = bitmap;
+
+  /* The bitmap data is now locked.  */
+  return data;
+}
+
+/* Damage the window HANDLE by the given damage rectangle.  */
+
+void
+android_damage_window (android_drawable handle,
+                      struct android_rectangle *damage)
+{
+  jobject drawable, rect;
+
+  drawable = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+
+  /* Now turn DAMAGE into a Java rectangle.  */
+  rect = (*android_java_env)->NewObject (android_java_env,
+                                        android_rect_class,
+                                        android_rect_constructor,
+                                        (jint) damage->x,
+                                        (jint) damage->y,
+                                        (jint) (damage->x
+                                                + damage->width),
+                                        (jint) (damage->y
+                                                + damage->height));
+  android_exception_check ();
+
+  /* Post the damage to the drawable.  */
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      drawable,
+                                      drawable_class.damage_rect,
+                                      rect);
+  android_exception_check_1 (rect);
+  ANDROID_DELETE_LOCAL_REF (rect);
+}
+
+
+
+/* Other misc system routines.  */
+
+int
+android_get_screen_width (void)
+{
+  int rc;
+  jmethodID method;
+
+  method = service_class.get_screen_width;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method,
+                                                    (jboolean) false);
+  android_exception_check ();
+  return rc;
+}
+
+int
+android_get_screen_height (void)
+{
+  int rc;
+  jmethodID method;
+
+  method = service_class.get_screen_height;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method,
+                                                    (jboolean) false);
+  android_exception_check ();
+  return rc;
+}
+
+int
+android_get_mm_width (void)
+{
+  int rc;
+  jmethodID method;
+
+  method = service_class.get_screen_width;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method,
+                                                    (jboolean) true);
+  android_exception_check ();
+  return rc;
+}
+
+int
+android_get_mm_height (void)
+{
+  int rc;
+  jmethodID method;
+
+  method = service_class.get_screen_height;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method,
+                                                    (jboolean) true);
+  android_exception_check ();
+  return rc;
+}
+
+bool
+android_detect_mouse (void)
+{
+  bool rc;
+  jmethodID method;
+
+  method = service_class.detect_mouse;
+  rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env,
+                                                        emacs_service,
+                                                        service_class.class,
+                                                        method);
+  android_exception_check ();
+  return rc;
+}
+
+void
+android_set_dont_focus_on_map (android_window handle,
+                              bool no_focus_on_map)
+{
+  jmethodID method;
+  jobject window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  method = window_class.set_dont_focus_on_map;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
+                                                window_class.class,
+                                                method,
+                                                (jboolean) no_focus_on_map);
+  android_exception_check ();
+}
+
+void
+android_set_dont_accept_focus (android_window handle,
+                              bool no_accept_focus)
+{
+  jmethodID method;
+  jobject window;
+
+  window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW);
+  method = window_class.set_dont_accept_focus;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
+                                                window_class.class,
+                                                method,
+                                                (jboolean) no_accept_focus);
+  android_exception_check ();
+}
+
+void
+android_get_keysym_name (int keysym, char *name_return, size_t size)
+{
+  jobject string;
+  const char *buffer;
+
+  string = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 emacs_service,
+                                                 service_class.name_keysym,
+                                                 (jint) keysym);
+  android_exception_check ();
+
+  buffer = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                  (jstring) string,
+                                                  NULL);
+  android_exception_check_nonnull ((void *) buffer, string);
+  strncpy (name_return, buffer, size - 1);
+  name_return[size] = '\0';
+
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) string,
+                                             buffer);
+  ANDROID_DELETE_LOCAL_REF (string);
+}
+
+/* Display the on screen keyboard on window WINDOW, or hide it if SHOW
+   is false.  Ask the system to bring up or hide the on-screen
+   keyboard on behalf of WINDOW.  The request may be rejected by the
+   system, especially when the window does not have the input
+   focus.  */
+
+void
+android_toggle_on_screen_keyboard (android_window window, bool show)
+{
+  jobject object;
+  jmethodID method;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+  method = window_class.toggle_on_screen_keyboard;
+
+  /* Now display the on screen keyboard.  */
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, object,
+                                                window_class.class,
+                                                method, (jboolean) show);
+
+  /* Check for out of memory errors.  */
+  android_exception_check ();
+}
+
+
+
+/* emacs_abort implementation for Android.  This logs a stack
+   trace.  */
+
+void
+emacs_abort (void)
+{
+  volatile char *foo;
+
+  __android_log_print (ANDROID_LOG_FATAL, __func__,
+                      "emacs_abort called, please review the ensuing"
+                      " stack trace");
+
+  /* Cause a NULL pointer dereference to make debuggerd generate a
+     tombstone.  */
+  foo = NULL;
+  *foo = '\0';
+
+  abort ();
+}
+
+
+
+/* Return whether or not TEXT, a string without multibyte
+   characters, has no bytes with the 8th bit set.  */
+
+static bool
+android_check_string (Lisp_Object text)
+{
+  ptrdiff_t i;
+
+  for (i = 0; i < SBYTES (text); ++i)
+    {
+      if (SREF (text, i) & 128)
+       return false;
+    }
+
+  return true;
+}
+
+/* Verify that the specified NULL-terminated STRING is a valid JNI
+   ``UTF-8'' string.  Return 0 if so, 1 otherwise.
+
+   Do not perform GC, enabling NAME to be a direct reference to string
+   data.
+
+   The native coding system used by the JVM to store strings derives
+   from UTF-8, but deviates from it in two aspects in an attempt to
+   better represent the UCS-16 based Java String format, and to let
+   strings contain NULL characters while remaining valid C strings:
+   NULL bytes are encoded as two-byte sequences, and Unicode surrogate
+   pairs encoded as two-byte sequences are prefered to four-byte
+   sequences when encoding characters above the BMP.  */
+
+int
+android_verify_jni_string (const char *name)
+{
+  const unsigned char *chars;
+
+  chars = (unsigned char *) name;
+  while (*chars)
+    {
+      /* Switch on the high 4 bits.  */
+
+      switch (*chars++ >> 4)
+       {
+       case 0 ... 7:
+         /* The 8th bit is clean, so this is a regular C
+            character.  */
+         break;
+
+       case 8 ... 0xb:
+         /* Invalid starting byte! */
+         return 1;
+
+       case 0xf:
+         /* The start of a four byte sequence.  These aren't allowed
+            in Java.  */
+         return 1;
+
+       case 0xe:
+         /* The start of a three byte sequence.  Verify that its
+            continued.  */
+
+         if ((*chars++ & 0xc0) != 0x80)
+           return 1;
+
+         FALLTHROUGH;
+
+       case 0xc ... 0xd:
+         /* The start of a two byte sequence.  Verify that the
+            next byte exists and has its high bit set.  */
+
+         if ((*chars++ & 0xc0) != 0x80)
+           return 1;
+
+         break;
+       }
+    }
+
+  return 0;
+}
+
+/* Given a Lisp string TEXT, return a local reference to an equivalent
+   Java string.  */
+
+jstring
+android_build_string (Lisp_Object text)
+{
+  Lisp_Object encoded;
+  jstring string;
+  size_t nchars;
+  jchar *characters;
+  USE_SAFE_ALLOCA;
+
+  /* Directly encode TEXT if it contains no non-ASCII characters, or
+     is multibyte and a valid Modified UTF-8 string.  This is okay
+     because the Java extended UTF format is compatible with
+     ASCII.  */
+
+  if ((SBYTES (text) == SCHARS (text)
+       && android_check_string (text))
+      /* If TEXT is a multibyte string, then it's using Emacs's
+        internal UTF-8 coding system, a significant subset of which
+        is compatible with JNI.  */
+      || (STRING_MULTIBYTE (text)
+         && !android_verify_jni_string (SSDATA (text))))
+    {
+      string = (*android_java_env)->NewStringUTF (android_java_env,
+                                                 SSDATA (text));
+      android_exception_check ();
+      SAFE_FREE ();
+
+      return string;
+    }
+
+  encoded = code_convert_string_norecord (text, Qutf_16le,
+                                         true);
+  nchars = (SBYTES (encoded) / sizeof (jchar));
+
+  /* Encode the string as UTF-16 prior to creating the string.
+     Copy the string to a separate buffer in order to preserve
+     alignment.  */
+
+  characters = SAFE_ALLOCA (SBYTES (encoded));
+  memcpy (characters, SDATA (encoded), SBYTES (encoded));
+
+  /* Create the string.  */
+  string
+    = (*android_java_env)->NewString (android_java_env,
+                                     characters, nchars);
+  android_exception_check ();
+
+  SAFE_FREE ();
+  return string;
+}
+
+/* Do the same, except TEXT is constant string data in ASCII or
+   UTF-8 containing no characters outside the Basic Multilingual
+   Plane.  */
+
+jstring
+android_build_jstring (const char *text)
+{
+  jstring string;
+
+  /* Note that Java expects this string to be in ``modified UTF
+     encoding'', which is actually UTF-8, except with NUL
+     encoded as a two-byte sequence, and surrogate pairs encoded
+     in the three-byte extended encoding.  The only consequence
+     of passing an actual UTF-8 string is that NUL bytes and
+     characters requiring surrogate pairs cannot be represented,
+     which is not really of consequence.  */
+
+  string = (*android_java_env)->NewStringUTF (android_java_env,
+                                             text);
+  android_exception_check ();
+
+  return string;
+}
+
+
+
+/* Exception checking functions.  Most JNI functions which allocate
+   memory return NULL upon failure; they also set the JNI
+   environment's pending exception to an OutOfMemoryError.
+
+   These functions check for such errors and call memory_full wherever
+   appropriate.  Three variants are provided: one which releases no
+   local references, one which releases a single local reference
+   before calling memory_full, and one which releases two local
+   references.
+
+   Typically, you use these functions by calling them immediately
+   after a JNI function which allocates memory, passing it any local
+   references that are already valid but should be deleted after
+   leaving the current scope.  For example, to allocate foo, make
+   global_foo its global reference, and then release foo, you write:
+
+     jobject foo, global_foo;
+
+     foo = (*android_java_env)->New...;
+     android_exception_check ();
+
+     global_foo = (*android_java_env)->NewGlobalRef (..., foo);
+     android_exception_check_1 (foo);
+     ANDROID_DELETE_LOCAL_REF (foo);
+
+   where the first android_exception_check ensures that foo has been
+   allocated correctly, while the call to android_exception_check_1,
+   and the call to ANDROID_DELETE_LOCAL_REF afterwards, together
+   ensure the same of global_foo, and also that foo is released both
+   if global_foo cannot be allocated, and after the global reference
+   is created.  */
+
+#if __GNUC__ >= 3
+#define likely(cond)   __builtin_expect ((cond), 1)
+#else /* __GNUC__ < 3 */
+#define likely(cond)   (cond)
+#endif /* __GNUC__ >= 3 */
+
+/* Check for JNI exceptions and call memory_full in that
+   situation.  */
+
+void
+android_exception_check (void)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  memory_full (0);
+}
+
+/* Check for JNI exceptions.  If there is one such exception, clear
+   it, then delete the local reference to OBJECT and call
+   memory_full.  */
+
+void
+android_exception_check_1 (jobject object)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+  memory_full (0);
+}
+
+/* Like android_exception_check_1, except it takes more than one local
+   reference argument.  */
+
+void
+android_exception_check_2 (jobject object, jobject object1)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+  ANDROID_DELETE_LOCAL_REF (object1);
+  memory_full (0);
+}
+
+/* Like android_exception_check_2, except it takes more than two local
+   reference arguments.  */
+
+void
+android_exception_check_3 (jobject object, jobject object1,
+                          jobject object2)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+  ANDROID_DELETE_LOCAL_REF (object1);
+  ANDROID_DELETE_LOCAL_REF (object2);
+  memory_full (0);
+}
+
+/* Like android_exception_check_3, except it takes more than three
+   local reference arguments.  */
+
+void
+android_exception_check_4 (jobject object, jobject object1,
+                          jobject object2, jobject object3)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+  ANDROID_DELETE_LOCAL_REF (object1);
+  ANDROID_DELETE_LOCAL_REF (object2);
+  ANDROID_DELETE_LOCAL_REF (object3);
+  memory_full (0);
+}
+
+/* Check for JNI problems based on the value of OBJECT.
+
+   Signal out of memory if OBJECT is NULL.  OBJECT1 means the
+   same as in `android_exception_check_1'.
+
+   This function is useful when checking for errors from JNI
+   functions that do not set exceptions on failure, such as
+   `GetIntArrayElements'.  */
+
+void
+android_exception_check_nonnull (void *object, jobject object1)
+{
+  if (likely (object != NULL))
+    return;
+
+  if (object1)
+    ANDROID_DELETE_LOCAL_REF (object1);
+
+  memory_full (0);
+}
+
+/* Check for JNI problems based on the value of OBJECT.
+
+   Signal out of memory if OBJECT is NULL.  OBJECT1 and OBJECT2 mean
+   the same as in `android_exception_check_2'.  */
+
+void
+android_exception_check_nonnull_1 (void *object, jobject object1,
+                                  jobject object2)
+{
+  if (likely (object != NULL))
+    return;
+
+  if (object1)
+    ANDROID_DELETE_LOCAL_REF (object1);
+
+  if (object2)
+    ANDROID_DELETE_LOCAL_REF (object2);
+
+  memory_full (0);
+}
+
+
+
+/* Native image transforms.  */
+
+/* Transform the coordinates X and Y by the specified affine
+   transformation MATRIX.  Place the result in *XOUT and *YOUT.  */
+
+static void
+android_transform_coordinates (int x, int y,
+                              struct android_transform *transform,
+                              float *xout, float *yout)
+{
+  /* Apply the specified affine transformation.
+     A transform looks like:
+
+       M1 M2 M3     X
+       M4 M5 M6   * Y
+
+       =
+
+       M1*X + M2*Y + M3*1 = X1
+       M4*X + M5*Y + M6*1 = Y1
+
+     (In most transforms, there is another row at the bottom for
+     mathematical reasons.  Since Z1 is always 1.0, the row is simply
+     implied to be 0 0 1, because 0 * x + 0 * y + 1 * 1 = 1.0.  See
+     the definition of matrix3x3 in image.c for some more explanations
+     about this.) */
+
+  *xout = transform->m1 * x + transform->m2 * y + transform->m3;
+  *yout = transform->m4 * x + transform->m5 * y + transform->m6;
+}
+
+/* Return the interpolation of the four pixels TL, TR, BL, and BR,
+   according to the weights DISTX and DISTY.  */
+
+static unsigned int
+android_four_corners_bilinear (unsigned int tl, unsigned int tr,
+                              unsigned int bl, unsigned int br,
+                              int distx, int disty)
+{
+  int distxy, distxiy, distixy, distixiy;
+  uint32_t f, r;
+
+  distxy = distx * disty;
+  distxiy = (distx << 8) - distxy;
+  distixy = (disty << 8) - distxy;
+  distixiy = (256 * 256 - (disty << 8)
+             - (distx << 8) + distxy);
+
+  /* Red */
+  r = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy
+       + (bl & 0x000000ff) * distixy  + (br & 0x000000ff) * distxy);
+
+  /* Green */
+  f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy
+       + (bl & 0x0000ff00) * distixy  + (br & 0x0000ff00) * distxy);
+  r |= f & 0xff000000;
+
+  /* Now do the upper two components.  */
+  tl >>= 16;
+  tr >>= 16;
+  bl >>= 16;
+  br >>= 16;
+  r >>= 16;
+
+  /* Blue */
+  f = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy
+       + (bl & 0x000000ff) * distixy  + (br & 0x000000ff) * distxy);
+  r |= f & 0x00ff0000;
+
+  /* Alpha */
+  f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy
+       + (bl & 0x0000ff00) * distixy  + (br & 0x0000ff00) * distxy);
+  r |= f & 0xff000000;
+
+  return r;
+}
+
+/* Return the interpolation of the four pixels closest to at X, Y in
+   IMAGE, according to weights in both axes computed from X and Y.
+   IMAGE must be depth 24, or the behavior is undefined.  */
+
+static unsigned int
+android_fetch_pixel_bilinear (struct android_image *image,
+                             float x, float y)
+{
+  int x1, y1, x2, y2;
+  float distx, disty;
+  unsigned int top_left, top_right;
+  unsigned int bottom_left, bottom_right;
+  char *word;
+
+  /* Compute the four closest corners to X and Y.  */
+  x1 = (int) x;
+  x2 = x1 + 1;
+  y1 = (int) y;
+  y2 = y1 + 1;
+
+  /* Make sure all four corners are within range.  */
+  x1 = MAX (0, MIN (image->width - 1, x1));
+  y1 = MAX (0, MIN (image->height - 1, y1));
+  x2 = MAX (0, MIN (image->width - 1, x2));
+  y2 = MAX (0, MIN (image->height - 1, y2));
+
+  /* Compute the X and Y biases.  These are numbers between 0f and
+     1f.  */
+  distx = x - x1;
+  disty = y - y1;
+
+  /* Fetch the four closest pixels.  */
+  word = image->data + y1 * image->bytes_per_line + x1 * 4;
+  memcpy (&top_left, word, sizeof top_left);
+  word = image->data + y1 * image->bytes_per_line + x2 * 4;
+  memcpy (&top_right, word, sizeof top_right);
+  word = image->data + y2 * image->bytes_per_line + x1 * 4;
+  memcpy (&bottom_left, word, sizeof bottom_left);
+  word = image->data + y2 * image->bytes_per_line + x2 * 4;
+  memcpy (&bottom_right, word, sizeof bottom_right);
+
+  /* Do the interpolation.  */
+  return android_four_corners_bilinear (top_left, top_right, bottom_left,
+                                       bottom_right, distx * 256,
+                                       disty * 256);
+}
+
+/* Transform the depth 24 image IMAGE by the 3x2 affine transformation
+   matrix MATRIX utilizing a bilinear filter.  Place the result in
+   OUT.  The matrix maps from the coordinate space of OUT to
+   IMAGE.  */
+
+void
+android_project_image_bilinear (struct android_image *image,
+                               struct android_image *out,
+                               struct android_transform *transform)
+{
+  int x, y;
+  unsigned int pixel;
+  float xout, yout;
+  char *word;
+
+  /* Loop through each pixel in OUT.  Transform it by TRANSFORM, then
+     interpolate it to IMAGE, and place the result back in OUT.  */
+
+  for (y = 0; y < out->height; ++y)
+    {
+      for (x = 0; x < out->width; ++x)
+       {
+         /* Transform the coordinates by TRANSFORM.  */
+         android_transform_coordinates (x, y, transform,
+                                        &xout, &yout);
+
+         /* Interpolate back to IMAGE.  */
+         pixel = android_fetch_pixel_bilinear (image, xout, yout);
+
+         /* Put the pixel back in OUT.  */
+         word = out->data + y * out->bytes_per_line + x * 4;
+         memcpy (word, &pixel, sizeof pixel);
+       }
+    }
+}
+
+/* Return the interpolation of X, Y to IMAGE, a depth 24 image.  */
+
+static unsigned int
+android_fetch_pixel_nearest_24 (struct android_image *image, float x,
+                               float y)
+{
+  int x1, y1;
+  char *word;
+  unsigned int pixel;
+
+  x1 = MAX (0, MIN (image->width - 1, (int) roundf (x)));
+  y1 = MAX (0, MIN (image->height - 1, (int) roundf (y)));
+
+  word = image->data + y1 * image->bytes_per_line + x1 * 4;
+  memcpy (&pixel, word, sizeof pixel);
+
+  return pixel;
+}
+
+/* Return the interpolation of X, Y to IMAGE, a depth 1 image.  */
+
+static unsigned int
+android_fetch_pixel_nearest_1 (struct android_image *image, float x,
+                              float y)
+{
+  int x1, y1;
+  char *byte;
+
+  x1 = MAX (0, MIN (image->width - 1, (int) roundf (x)));
+  y1 = MAX (0, MIN (image->height - 1, (int) roundf (y)));
+
+  byte = image->data + y1 * image->bytes_per_line;
+  return (byte[x1 / 8] & (1 << x1 % 8)) ? 1 : 0;
+}
+
+/* Transform the depth 24 or 1 image IMAGE by the 3x2 affine
+   transformation matrix MATRIX.  Place the result in OUT.  The matrix
+   maps from the coordinate space of OUT to IMAGE.  Use a
+   nearest-neighbor filter.  */
+
+void
+android_project_image_nearest (struct android_image *image,
+                              struct android_image *out,
+                              struct android_transform *transform)
+{
+  int x, y;
+  unsigned int pixel;
+  float xout, yout;
+  char *word, *byte;
+
+  if (image->depth == 1)
+    {
+      for (y = 0; y < out->height; ++y)
+       {
+         for (x = 0; x < out->width; ++x)
+           {
+             /* Transform the coordinates by TRANSFORM.  */
+             android_transform_coordinates (x, y, transform,
+                                            &xout, &yout);
+
+             /* Interpolate back to IMAGE.  */
+             pixel = android_fetch_pixel_nearest_1 (image, xout, yout);
+
+             /* Put the pixel back in OUT.  */
+             byte = out->data + y * out->bytes_per_line + x / 8;
+
+             if (pixel)
+               *byte |= (1 << x % 8);
+             else
+               *byte &= ~(1 << x % 8);
+           }
+       }
+
+      return;
+    }
+
+  for (y = 0; y < out->height; ++y)
+    {
+      for (x = 0; x < out->width; ++x)
+       {
+         /* Transform the coordinates by TRANSFORM.  */
+         android_transform_coordinates (x, y, transform,
+                                        &xout, &yout);
+
+         /* Interpolate back to IMAGE.  */
+         pixel = android_fetch_pixel_nearest_24 (image, xout, yout);
+
+         /* Put the pixel back in OUT.  */
+         word = out->data + y * out->bytes_per_line + x * 4;
+         memcpy (word, &pixel, sizeof pixel);
+       }
+    }
+}
+
+
+
+/* Other miscellaneous functions.  */
+
+/* Ask the system to start browsing the specified URL.  Upon failure,
+   return a string describing the error.  Else, value is nil.  URL
+   should be encoded unless SEND.
+
+   If SEND, open the URL with applications that can ``send'' or
+   ``share'' the URL (through mail, for example.)  */
+
+Lisp_Object
+android_browse_url (Lisp_Object url, Lisp_Object send)
+{
+  jobject value, string;
+  Lisp_Object tem;
+  const char *buffer;
+
+  string = android_build_string (url);
+  value = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.browse_url,
+                                                string,
+                                                (jboolean) !NILP (send));
+  android_exception_check ();
+
+  ANDROID_DELETE_LOCAL_REF (string);
+
+  /* If no string was returned, return Qnil.  */
+  if (!value)
+    return Qnil;
+
+  buffer = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                  (jstring) value,
+                                                  NULL);
+  android_exception_check_1 (string);
+
+  /* Otherwise, build the string describing the error.  */
+  tem = build_string_from_utf8 (buffer);
+
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) value,
+                                             buffer);
+
+  /* And return it.  */
+  ANDROID_DELETE_LOCAL_REF (value);
+  return tem;
+}
+
+/* Tell the system to restart Emacs in a short amount of time, and
+   then kill Emacs.  Never return.  This is used to implement
+   `restart-emacs'.  */
+
+_Noreturn void
+android_restart_emacs (void)
+{
+  /* Try to call the Java side function.  Normally, this should call
+     System.exit to terminate this process.  */
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.restart_emacs);
+
+  /* Exit anyway, in case EmacsService did not do so.  */
+  exit (0);
+}
+
+/* Return a number from 1 to 33 describing the version of Android
+   Emacs is running on.
+
+   This is different from __ANDROID_API__, as that describes the
+   minimum version of Android this build of Emacs will run on, and in
+   turn which APIs Emacs can safely use.  */
+
+int
+(android_get_current_api_level) (void)
+{
+  return android_api_level;
+}
+
+/* Query the status of the battery, and place it in *STATUS.
+   Value is 1 upon failure, else 0.  */
+
+int
+android_query_battery (struct android_battery_state *status)
+{
+  jlongArray array;
+  jlong *longs;
+
+  array = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.query_battery);
+  android_exception_check ();
+
+  /* A NULL return with no exception means that battery information
+     could not be obtained.  */
+
+  if (!array)
+    return 1;
+
+  longs = (*android_java_env)->GetLongArrayElements (android_java_env,
+                                                    array, NULL);
+  android_exception_check_nonnull (longs, array);
+
+  status->capacity = longs[0];
+  status->charge_counter = longs[1];
+  status->current_average = longs[2];
+  status->current_now = longs[3];
+  status->remaining = longs[4];
+  status->status = longs[5];
+  status->plugged = longs[6];
+  status->temperature = longs[7];
+
+  (*android_java_env)->ReleaseLongArrayElements (android_java_env,
+                                                array, longs,
+                                                JNI_ABORT);
+  ANDROID_DELETE_LOCAL_REF (array);
+
+  return 0;
+}
+
+/* Display a file panel and grant Emacs access to the SAF directory
+   within it.  Value is 1 upon failure and 0 upon success (which only
+   indicates that the panel has been displayed successfully; the panel
+   may still be dismissed without a file being selected.)  */
+
+int
+android_request_directory_access (void)
+{
+  jint rc;
+  jmethodID method;
+
+  method = service_class.request_directory_access;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method);
+  android_exception_check ();
+
+  return rc;
+}
+
+
+
+/* The thread from which a query against a thread is currently being
+   made, if any.  Value is 0 if no query is in progress, 1 if a query
+   is being made from the UI thread to the main thread, and 2 if a
+   query is being made the other way around.  */
+static char android_servicing_query;
+
+/* Function that is waiting to be run in the Emacs thread.  */
+static void (*android_query_function) (void *);
+
+/* Context for that function.  */
+static void *android_query_context;
+
+/* Deadlock protection.  The UI thread and the Emacs thread must
+   sometimes make synchronous queries to each other, which are
+   normally answered inside each thread's respective event loop.
+   Deadlocks can happen when both threads simultaneously make such
+   synchronous queries and block waiting for each others responses.
+
+   The Emacs thread can be interrupted to service any queries made by
+   the UI thread, but is not possible the other way around.
+
+   To avoid such deadlocks, an atomic counter is provided.  This
+   counter is set to two every time a query starts from the main
+   thread, and is set to zero every time one ends.  If the UI thread
+   tries to make a query and sees that the counter is two, it simply
+   returns so that its event loop can proceed to perform and respond
+   to the query.  If the Emacs thread sees that the counter is one,
+   then it stops to service all queries being made by the input
+   method, then proceeds to make its query with the counter set to
+   2.
+
+   The memory synchronization is simple: all writes to
+   `android_query_context' and `android_query_function' are depended
+   on by writes to the atomic counter.  Loads of the new value from
+   the counter are then guaranteed to make those writes visible.  The
+   separate flag `android_urgent_query' does not depend on anything
+   itself; however, the input signal handler executes a memory fence
+   to ensure that all query related writes become visible.  */
+
+/* Run any function that the UI thread has asked to run, and then
+   signal its completion.  */
+
+static void
+android_check_query (void)
+{
+  void (*proc) (void *);
+  void *closure;
+
+  if (!__atomic_load_n (&android_servicing_query, __ATOMIC_ACQUIRE))
+    return;
+
+  /* First, load the procedure and closure.  */
+  closure = android_query_context;
+  proc = android_query_function;
+
+  if (!proc)
+    return;
+
+  proc (closure);
+
+  /* Finish the query.  */
+  android_query_context = NULL;
+  android_query_function = NULL;
+  __atomic_store_n (&android_servicing_query, 0, __ATOMIC_RELEASE);
+  __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE);
+
+  /* Signal completion.  */
+  sem_post (&android_query_sem);
+}
+
+/* Run any function that the UI thread has asked to run, if the UI
+   thread has been waiting for more than two seconds.
+
+   Call this from `process_pending_signals' to ensure that the UI
+   thread always receives an answer within a reasonable amount of
+   time.  */
+
+void
+android_check_query_urgent (void)
+{
+  void (*proc) (void *);
+  void *closure;
+
+  if (!__atomic_load_n (&android_urgent_query, __ATOMIC_ACQUIRE))
+    return;
+
+  __android_log_print (ANDROID_LOG_VERBOSE, __func__,
+                      "Responding to urgent query...");
+
+  if (!__atomic_load_n (&android_servicing_query, __ATOMIC_ACQUIRE))
+    return;
+
+  /* First, load the procedure and closure.  */
+  closure = android_query_context;
+  proc = android_query_function;
+
+  if (!proc)
+    return;
+
+  proc (closure);
+
+  /* Finish the query.  Don't clear `android_urgent_query'; instead,
+     do that the next time Emacs enters the keyboard loop.  */
+
+  android_query_context = NULL;
+  android_query_function = NULL;
+  __atomic_store_n (&android_servicing_query, 0, __ATOMIC_RELEASE);
+
+  /* Signal completion.  */
+  sem_post (&android_query_sem);
+}
+
+/* Run the function that the UI thread has asked to run, and then
+   signal its completion.  Do not change `android_servicing_query'
+   after it completes.  */
+
+static void
+android_answer_query (void)
+{
+  void (*proc) (void *);
+  void *closure;
+
+  eassert (__atomic_load_n (&android_servicing_query,
+                           __ATOMIC_ACQUIRE)
+          == 1);
+
+  /* First, load the procedure and closure.  */
+  closure = android_query_context;
+  proc = android_query_function;
+
+  if (!proc)
+    return;
+
+  proc (closure);
+
+  /* Finish the query.  */
+  android_query_context = NULL;
+  android_query_function = NULL;
+  __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE);
+
+  /* Signal completion.  */
+  sem_post (&android_query_sem);
+}
+
+/* Like `android_answer_query'.  However, the query may not have
+   begun; spin until it has.  */
+
+static void
+android_answer_query_spin (void)
+{
+  int n;
+
+  while (!(n = __atomic_load_n (&android_servicing_query,
+                               __ATOMIC_ACQUIRE)))
+    eassert (!n);
+
+  /* Note that this function is supposed to be called before
+     `android_begin_query' starts, so clear the service flag.  */
+  android_check_query ();
+}
+
+/* Notice that the Emacs thread will start blocking waiting for a
+   response from the UI thread.  Process any pending queries from the
+   UI thread.
+
+   This function may be called from Java.  */
+
+static void
+android_begin_query (void)
+{
+  char old;
+
+  /* Load the previous value of `android_servicing_query' and then set
+     it to 2.  */
+
+  old = __atomic_exchange_n (&android_servicing_query,
+                            2, __ATOMIC_ACQ_REL);
+
+  /* See if a query was previously in progress.  */
+  if (old == 1)
+    {
+      /* Answer the query that is currently being made.  */
+      assert (android_query_function != NULL);
+      android_answer_query ();
+    }
+
+  /* `android_servicing_query' is now 2.  */
+}
+
+/* Notice that a query has stopped.  This function may be called from
+   Java.  */
+
+static void
+android_end_query (void)
+{
+  __atomic_store_n (&android_servicing_query, 0, __ATOMIC_RELEASE);
+  __atomic_clear (&android_urgent_query, __ATOMIC_RELEASE);
+}
+
+/* Synchronously ask the Emacs thread to run the specified PROC with
+   the given CLOSURE.  Return if this fails, or once PROC is run.
+
+   PROC may be run from inside maybe_quit.
+
+   It is not okay to run Lisp code which signals or performs non
+   trivial tasks inside PROC.
+
+   Return 1 if the Emacs thread is currently waiting for the UI thread
+   to respond and PROC could not be run, or 0 otherwise.  */
+
+int
+android_run_in_emacs_thread (void (*proc) (void *), void *closure)
+{
+  union android_event event;
+  char old;
+  int rc;
+  struct timespec timeout;
+
+  event.xaction.type = ANDROID_WINDOW_ACTION;
+  event.xaction.serial = ++event_serial;
+  event.xaction.window = 0;
+  event.xaction.action = 0;
+
+  /* Set android_query_function and android_query_context.  */
+  android_query_context = closure;
+  android_query_function = proc;
+
+  /* Don't allow deadlocks to happen; make sure the Emacs thread is
+     not waiting for something to be done (in that case,
+     `android_query_context' is 2.)  */
+
+  old = 0;
+  if (!__atomic_compare_exchange_n (&android_servicing_query, &old,
+                                   1, false, __ATOMIC_ACQ_REL,
+                                   __ATOMIC_ACQUIRE))
+    {
+      android_query_context = NULL;
+      android_query_function = NULL;
+
+      /* The two variables above may still be non-NULL from the POV of
+        the main thread, as no happens-before constraint is placed on
+         those stores wrt a future load from `android_servicing_query'.  */
+
+      return 1;
+    }
+
+  /* Send a dummy event.  `android_check_query' will be called inside
+     wait_reading_process_output after the event arrives.
+
+     Otherwise, android_select will call android_check_thread the next
+     time it is entered.  */
+  android_write_event (&event);
+
+  /* Start waiting for the function to be executed.  First, wait two
+     seconds for the query to execute normally.  */
+
+  timeout.tv_sec = 2;
+  timeout.tv_nsec = 0;
+  timeout = timespec_add (current_timespec (), timeout);
+
+  /* See if an urgent query was recently answered without entering the
+     keyboard loop in between.  When that happens, raise SIGIO to
+     continue processing queries as soon as possible.  */
+
+  if (__atomic_load_n (&android_urgent_query, __ATOMIC_ACQUIRE))
+    kill (getpid (), SIGIO);
+
+ again:
+  rc = sem_timedwait (&android_query_sem, &timeout);
+
+  if (rc < 0)
+    {
+      if (errno == EINTR)
+       goto again;
+
+      eassert (errno == ETIMEDOUT);
+
+      __android_log_print (ANDROID_LOG_VERBOSE, __func__,
+                          "Timed out waiting for response"
+                          " from main thread...");
+
+      /* The query timed out.  At this point, set
+        `android_urgent_query' to true.  */
+      __atomic_store_n (&android_urgent_query, true,
+                       __ATOMIC_RELEASE);
+
+    kill_again:
+
+      /* And raise SIGIO.  Now that the query is considered urgent,
+        the main thread will reply while reading async input.
+
+        Normally, the main thread waits for the keyboard loop to be
+        entered before responding, in order to avoid responding with
+        inaccurate results taken during command executioon.  */
+      kill (getpid (), SIGIO);
+
+      /* Wait for the query to complete.  `android_urgent_query' is
+        only cleared by either `android_select' or
+        `android_check_query', so there's no need to worry about the
+        flag being cleared before the query is processed.
+
+        Send SIGIO again periodically until the query is answered, on
+        the off chance that SIGIO arrived too late to preempt a
+        system call, but too early for it to return EINTR.  */
+
+      timeout.tv_sec = 4;
+      timeout.tv_nsec = 0;
+      timeout = timespec_add (current_timespec (), timeout);
+
+      while (sem_timedwait (&android_query_sem, &timeout) < 0)
+       {
+         /* If waiting timed out, send SIGIO to the main thread
+            again.  */
+
+         if (errno == ETIMEDOUT)
+           goto kill_again;
+
+         /* Otherwise, continue waiting.  */
+         eassert (errno == EINTR);
+       }
+    }
+
+  /* At this point, `android_servicing_query' should either be zero if
+     the query was answered or two if the main thread has started a
+     query.  */
+
+  eassert (!__atomic_load_n (&android_servicing_query,
+                            __ATOMIC_ACQUIRE)
+          || (__atomic_load_n (&android_servicing_query,
+                               __ATOMIC_ACQUIRE) == 2));
+
+  return 0;
+}
+
+
+
+/* Input method related functions.  */
+
+/* Change WINDOW's active selection to the characters between
+   SELECTION_START and SELECTION_END.
+
+   Also, update the composing region to COMPOSING_REGION_START and
+   COMPOSING_REGION_END.
+
+   If any value cannot fit in jint, then the behavior of the input
+   method is undefined.  */
+
+void
+android_update_ic (android_window window, ptrdiff_t selection_start,
+                  ptrdiff_t selection_end, ptrdiff_t composing_region_start,
+                  ptrdiff_t composing_region_end)
+{
+  jobject object;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.update_ic,
+                                                object,
+                                                (jint) selection_start,
+                                                (jint) selection_end,
+                                                (jint) composing_region_start,
+                                                (jint) composing_region_end);
+  android_exception_check ();
+}
+
+/* Reinitialize any ongoing input method connection on WINDOW.
+
+   Any input method that is connected to WINDOW will invalidate its
+   cache of the buffer contents.
+
+   MODE controls certain aspects of the input method's behavior:
+
+     - If MODE is ANDROID_IC_MODE_NULL, the input method will be
+       deactivated, and an ASCII only keyboard will be displayed
+       instead.
+
+     - If MODE is ANDROID_IC_MODE_ACTION, the input method will
+       edit text normally, but send ``return'' as a key event.
+       This is useful inside the mini buffer.
+
+     - If MODE is ANDROID_IC_MODE_TEXT, the input method is free
+       to behave however it wants.  */
+
+void
+android_reset_ic (android_window window, enum android_ic_mode mode)
+{
+  jobject object;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                service_class.reset_ic,
+                                                object, (jint) mode);
+  android_exception_check ();
+}
+
+/* Make updates to extracted text known to the input method on
+   WINDOW.  TEXT should be a local reference to the new
+   extracted text.  TOKEN should be the token specified by the
+   input method.  */
+
+void
+android_update_extracted_text (android_window window, void *text,
+                              int token)
+{
+  jobject object;
+  jmethodID method;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+  method = service_class.update_extracted_text;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                method, object,
+                                                /* N.B. that text is
+                                                   not jobject,
+                                                   because that type
+                                                   is not available
+                                                   in
+                                                   androidgui.h.  */
+                                                (jobject) text,
+                                                (jint) token);
+  android_exception_check_1 (text);
+}
+
+/* Report the position of the cursor to the input method connection on
+   WINDOW.
+
+   X is the horizontal position of the end of the insertion marker.  Y
+   is the top of the insertion marker.  Y_BASELINE is the baseline of
+   the row containing the insertion marker, and Y_BOTTOM is the bottom
+   of the insertion marker.  */
+
+void
+android_update_cursor_anchor_info (android_window window, float x,
+                                  float y, float y_baseline,
+                                  float y_bottom)
+{
+  jobject object;
+  jmethodID method;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+  method = service_class.update_cursor_anchor_info;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                emacs_service,
+                                                service_class.class,
+                                                method,
+                                                object,
+                                                (jfloat) x,
+                                                (jfloat) y,
+                                                (jfloat) y_baseline,
+                                                (jfloat) y_bottom);
+  android_exception_check ();
+}
+
+
+
+/* Window decoration management functions.  */
+
+/* Make the specified WINDOW fullscreen, i.e. obscure all of the
+   system navigation and status bars.  If not FULLSCREEN, make it
+   maximized instead.
+
+   Value is 1 if the system does not support this, else 0.  */
+
+int
+android_set_fullscreen (android_window window, bool fullscreen)
+{
+  jobject object;
+
+  /* Android 4.0 and earlier don't support fullscreen windows.  */
+
+  if (android_api_level < 16)
+    return 1;
+
+  object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                object,
+                                                window_class.class,
+                                                window_class.set_fullscreen,
+                                                (jboolean) fullscreen);
+  android_exception_check ();
+  return 0;
+}
+
+
+
+/* Window cursor support.  */
+
+android_cursor
+android_create_font_cursor (enum android_cursor_shape shape)
+{
+  android_cursor id;
+  short prev_max_handle;
+  jobject object;
+
+  /* First, allocate the cursor handle.  */
+  prev_max_handle = max_handle;
+  id = android_alloc_id ();
+
+  if (!id)
+    error ("Out of cursor handles!");
+
+  /* Next, create the cursor.  */
+  object = (*android_java_env)->NewObject (android_java_env,
+                                          cursor_class.class,
+                                          cursor_class.constructor,
+                                          (jshort) id,
+                                          (jint) shape);
+  if (!object)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      max_handle = prev_max_handle;
+      memory_full (0);
+    }
+
+  android_handles[id].type = ANDROID_HANDLE_CURSOR;
+  android_handles[id].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env, object);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+  if (!android_handles[id].handle)
+    memory_full (0);
+
+  return id;
+}
+
+void
+android_define_cursor (android_window window, android_cursor cursor)
+{
+  jobject window1, cursor1;
+  jmethodID method;
+
+  window1 = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+  cursor1 = android_resolve_handle (cursor, ANDROID_HANDLE_CURSOR);
+  method = window_class.define_cursor;
+
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                window1,
+                                                window_class.class,
+                                                method, cursor1);
+  android_exception_check ();
+}
+
+void
+android_free_cursor (android_cursor cursor)
+{
+  if (android_handles[cursor].type != ANDROID_HANDLE_CURSOR)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "Trying to destroy something not a CURSOR!");
+      emacs_abort ();
+    }
+
+  android_destroy_handle (cursor);
+}
+
+
+
+/* Process execution.
+
+   Newer Android systems use SELinux to restrict user programs from
+   executing programs installed in the application data directory for
+   security reasons.  Emacs uses a `loader' binary installed in the
+   application data directory to manually load executables and replace
+   the `execve' system call.  */
+
+enum
+  {
+    /* Maximum number of arguments available.  */
+    MAXARGS = 1024,
+  };
+
+/* Rewrite the command line given in *ARGV to utilize the `exec1'
+   bootstrap binary if necessary.
+
+   Value is 0 upon success, else 1.  Set errno upon failure.
+
+   ARGV holds a pointer to a NULL-terminated array of arguments given
+   to `emacs_spawn'.  */
+
+int
+android_rewrite_spawn_argv (const char ***argv)
+{
+  static const char *new_args[MAXARGS];
+  static char exec1_name[PATH_MAX], loader_name[PATH_MAX];
+  size_t i, nargs;
+
+  /* This isn't required on Android 9 or earlier.  */
+
+  if (android_api_level < 29 || !android_use_exec_loader)
+    return 0;
+
+  /* Get argv[0]; this should never be NULL.
+     Then, verify that it exists and is executable.  */
+
+  eassert (**argv);
+  if (access (**argv, R_OK | X_OK))
+    return 1;
+
+  /* Count the number of arguments in *argv.  */
+
+  nargs = 0;
+  while ((*argv)[nargs])
+    ++nargs;
+
+  /* nargs now holds the number of arguments in argv.  If it's larger
+     than MAXARGS, return failure.  */
+
+  if (nargs + 2 > MAXARGS)
+    {
+      errno = E2BIG;
+      return 1;
+    }
+
+  /* Fill in the name of `libexec1.so'.  */
+  snprintf (exec1_name, PATH_MAX, "%s/libexec1.so",
+           android_lib_dir);
+
+  /* And libloader.so.  */
+  snprintf (loader_name, PATH_MAX, "%s/libloader.so",
+           android_lib_dir);
+
+  /* Now fill in the first two arguments.  */
+  new_args[0] = exec1_name;
+  new_args[1] = loader_name;
+
+  /* And insert the rest, including the trailing NULL.  */
+  for (i = 0; i < nargs + 1; ++i)
+    new_args[i + 2] = (*argv)[i];
+
+  /* Replace argv.  */
+  *argv = new_args;
+
+  /* Return success.  */
+  return 0;
+}
+
+
+
+#else /* ANDROID_STUBIFY */
+
+/* X emulation functions for Android.  */
+
+struct android_gc *
+android_create_gc (enum android_gc_value_mask mask,
+                  struct android_gc_values *values)
+{
+  /* This function should never be called when building stubs.  */
+  emacs_abort ();
+}
+
+void
+android_free_gc (struct android_gc *gc)
+{
+  /* This function should never be called when building stubs.  */
+  emacs_abort ();
+}
+
+struct android_image *
+android_create_image (unsigned int depth, enum android_image_format format,
+                     char *data, unsigned int width, unsigned int height)
+{
+  emacs_abort ();
+}
+
+void
+android_destroy_image (struct android_image *ximg)
+{
+  emacs_abort ();
+}
+
+void
+android_put_pixel (struct android_image *ximg, int x, int y,
+                  unsigned long pixel)
+{
+  emacs_abort ();
+}
+
+unsigned long
+android_get_pixel (struct android_image *ximg, int x, int y)
+{
+  emacs_abort ();
+}
+
+struct android_image *
+android_get_image (android_drawable drawable,
+                  enum android_image_format format)
+{
+  emacs_abort ();
+}
+
+void
+android_put_image (android_pixmap pixmap,
+                  struct android_image *image)
+{
+  emacs_abort ();
+}
+
+void
+android_project_image_bilinear (struct android_image *image,
+                               struct android_image *out,
+                               struct android_transform *transform)
+{
+  emacs_abort ();
+}
+
+void
+android_project_image_nearest (struct android_image *image,
+                              struct android_image *out,
+                              struct android_transform *transform)
+{
+  emacs_abort ();
+}
+
+#endif /* !ANDROID_STUBIFY */
diff --git a/src/android.h b/src/android.h
new file mode 100644
index 00000000000..e865d7da665
--- /dev/null
+++ b/src/android.h
@@ -0,0 +1,325 @@
+/* Android initialization for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* On Android, Emacs is built as a shared library loaded from Java
+   using the Java Native Interface.  Emacs's `main' function is
+   renamed `android_emacs_init', and runs with some modifications
+   inside a separate thread, communicating with the Java code through
+   a table of function pointers.  */
+
+#ifndef _ANDROID_H_
+#ifndef ANDROID_STUBIFY
+#include <jni.h>
+#include <pwd.h>
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <stdio.h>
+
+#include <android/bitmap.h>
+
+#include "androidgui.h"
+#include "lisp.h"
+#endif /* ANDROID_STUBIFY */
+
+extern bool android_init_gui;
+
+#ifndef ANDROID_STUBIFY
+
+extern char *android_cache_dir;
+
+extern int android_emacs_init (int, char **, char *);
+extern int android_select (int, fd_set *, fd_set *, fd_set *,
+                          struct timespec *);
+extern char *android_user_full_name (struct passwd *);
+
+
+
+/* File I/O operations.  Many of these are defined in
+   androidvfs.c.  */
+
+extern const char *android_is_special_directory (const char *, const char *);
+extern const char *android_get_home_directory (void);
+
+extern void android_vfs_init (JNIEnv *, jobject);
+
+extern int android_open (const char *, int, mode_t);
+extern int android_fstat (int, struct stat *);
+extern int android_fstatat (int, const char *restrict,
+                           struct stat *restrict, int);
+extern int android_faccessat (int, const char *, int, int);
+extern int android_close (int);
+extern FILE *android_fdopen (int, const char *);
+extern int android_fclose (FILE *);
+extern int android_unlink (const char *);
+extern int android_symlink (const char *, const char *);
+extern int android_rmdir (const char *);
+extern int android_mkdir (const char *, mode_t);
+extern int android_renameat_noreplace (int, const char *,
+                                      int, const char *);
+extern int android_rename (const char *, const char *);
+extern int android_fchmodat (int, const char *, mode_t, int);
+extern ssize_t android_readlinkat (int, const char *restrict, char *restrict,
+                                  size_t);
+
+
+
+extern double android_pixel_density_x, android_pixel_density_y;
+extern double android_scaled_pixel_density;
+
+enum android_handle_type
+  {
+    ANDROID_HANDLE_WINDOW,
+    ANDROID_HANDLE_GCONTEXT,
+    ANDROID_HANDLE_PIXMAP,
+    ANDROID_HANDLE_CURSOR,
+  };
+
+extern jobject android_resolve_handle (android_handle,
+                                      enum android_handle_type);
+extern unsigned char *android_lock_bitmap (android_drawable,
+                                          AndroidBitmapInfo *,
+                                          jobject *);
+extern void android_damage_window (android_window,
+                                  struct android_rectangle *);
+extern int android_get_screen_width (void);
+extern int android_get_screen_height (void);
+extern int android_get_mm_width (void);
+extern int android_get_mm_height (void);
+extern bool android_detect_mouse (void);
+
+extern void android_set_dont_focus_on_map (android_window, bool);
+extern void android_set_dont_accept_focus (android_window, bool);
+
+extern int android_verify_jni_string (const char *);
+extern jstring android_build_string (Lisp_Object);
+extern jstring android_build_jstring (const char *);
+extern void android_exception_check (void);
+extern void android_exception_check_1 (jobject);
+extern void android_exception_check_2 (jobject, jobject);
+extern void android_exception_check_3 (jobject, jobject, jobject);
+extern void android_exception_check_4 (jobject, jobject, jobject, jobject);
+extern void android_exception_check_nonnull (void *, jobject);
+extern void android_exception_check_nonnull_1 (void *, jobject, jobject);
+
+extern void android_get_keysym_name (int, char *, size_t);
+extern void android_wait_event (void);
+extern void android_toggle_on_screen_keyboard (android_window, bool);
+extern _Noreturn void android_restart_emacs (void);
+extern int android_request_directory_access (void);
+extern int android_get_current_api_level (void)
+  __attribute__ ((pure));
+
+/* Define `android_get_current_api_level' to a macro that the compiler
+   knows will always return at least __ANDROID_API__.  */
+
+#define android_get_current_api_level()                                \
+  ({ int value;                                                        \
+                                                               \
+     value = (android_get_current_api_level) ();               \
+     eassume (value >= __ANDROID_API__); value; })
+
+
+
+/* Directory listing emulation.  */
+
+struct android_vdir;
+
+extern struct android_vdir *android_opendir (const char *);
+extern int android_dirfd (struct android_vdir *);
+extern struct dirent *android_readdir (struct android_vdir *);
+extern void android_closedir (struct android_vdir *);
+
+
+
+/* External asset manager interface.  */
+
+struct android_fd_or_asset
+{
+  /* The file descriptor.  */
+  int fd;
+
+  /* The asset.  If set, FD is not a real file descriptor.  */
+  void *asset;
+};
+
+extern struct android_fd_or_asset android_open_asset (const char *,
+                                                     int, mode_t);
+extern int android_close_asset (struct android_fd_or_asset);
+extern ssize_t android_asset_read_quit (struct android_fd_or_asset,
+                                       void *, size_t);
+extern ssize_t android_asset_read (struct android_fd_or_asset,
+                                  void *, size_t);
+extern off_t android_asset_lseek (struct android_fd_or_asset, off_t, int);
+extern int android_asset_fstat (struct android_fd_or_asset,
+                               struct stat *);
+
+
+
+/* Very miscellaneous functions.  */
+
+struct android_battery_state
+{
+  /* Battery charge level in integer percentage.  */
+  intmax_t capacity;
+
+  /* Battery charge level in microampere-hours.  */
+  intmax_t charge_counter;
+
+  /* Battery current in microampere-hours.  */
+  intmax_t current_average;
+
+  /* Instantaneous battery current in microampere-hours.  */
+  intmax_t current_now;
+
+  /* Estimate as to the amount of time remaining until the battery is
+     charged, in milliseconds.  */
+  intmax_t remaining;
+
+  /* Battery status.  The value is either:
+
+       2, if the battery is charging.
+       3, if the battery is discharging.
+       5, if the battery is full.
+       4, if the battery is not full or discharging,
+          but is not charging either.
+       1, if the battery state is unknown.  */
+  int status;
+
+  /* The power source of the battery.  Value is:
+
+       0, if on battery power.
+       1, for line power.
+       8, for dock power.
+       2, for USB power.
+       4, for wireless power.  */
+  int plugged;
+
+  /* The temperature of the battery in 10 * degrees centigrade.  */
+  int temperature;
+};
+
+extern Lisp_Object android_browse_url (Lisp_Object, Lisp_Object);
+extern int android_query_battery (struct android_battery_state *);
+extern void android_display_toast (const char *);
+
+
+
+/* Event loop functions.  */
+
+extern void android_check_query_urgent (void);
+extern int android_run_in_emacs_thread (void (*) (void *), void *);
+extern void android_write_event (union android_event *);
+
+extern unsigned int event_serial;
+
+
+
+/* Process related functions.  */
+extern int android_rewrite_spawn_argv (const char ***);
+
+#else /* ANDROID_STUBIFY */
+
+/* Define a substitute for use during Emacs compilation.  */
+
+#define android_is_special_directory(name, dir) ((const char *) NULL)
+
+#endif /* !ANDROID_STUBIFY */
+
+/* JNI functions should not be built when Emacs is stubbed out for the
+   build.  These should be documented in EmacsNative.java.  */
+
+#ifndef ANDROID_STUBIFY
+#include <jni.h>
+
+struct android_emacs_service
+{
+  jclass class;
+  jmethodID fill_rectangle;
+  jmethodID fill_polygon;
+  jmethodID draw_rectangle;
+  jmethodID draw_line;
+  jmethodID draw_point;
+  jmethodID clear_window;
+  jmethodID clear_area;
+  jmethodID ring_bell;
+  jmethodID query_tree;
+  jmethodID get_screen_width;
+  jmethodID get_screen_height;
+  jmethodID detect_mouse;
+  jmethodID name_keysym;
+  jmethodID browse_url;
+  jmethodID restart_emacs;
+  jmethodID update_ic;
+  jmethodID reset_ic;
+  jmethodID open_content_uri;
+  jmethodID check_content_uri;
+  jmethodID query_battery;
+  jmethodID update_extracted_text;
+  jmethodID update_cursor_anchor_info;
+  jmethodID get_document_authorities;
+  jmethodID request_directory_access;
+  jmethodID get_document_trees;
+  jmethodID document_id_from_name;
+  jmethodID get_tree_uri;
+  jmethodID stat_document;
+  jmethodID access_document;
+  jmethodID open_document_directory;
+  jmethodID read_directory_entry;
+  jmethodID open_document;
+  jmethodID create_document;
+  jmethodID create_directory;
+  jmethodID delete_document;
+  jmethodID rename_document;
+  jmethodID move_document;
+  jmethodID valid_authority;
+};
+
+extern JNIEnv *android_java_env;
+
+/* The EmacsService object.  */
+extern jobject emacs_service;
+
+/* Various methods associated with the EmacsService.  */
+extern struct android_emacs_service service_class;
+
+#define ANDROID_DELETE_LOCAL_REF(ref)                          \
+  ((*android_java_env)->DeleteLocalRef (android_java_env,      \
+                                       (ref)))
+
+#define NATIVE_NAME(name) Java_org_gnu_emacs_EmacsNative_##name
+
+/* Prologue which must be inserted before each JNI function.
+   See initEmacs for why.  */
+
+#if defined __i386__
+extern void *unused_pointer;
+
+#define JNI_STACK_ALIGNMENT_PROLOGUE                           \
+  __attribute__ ((aligned (32))) char stack_align_buffer[32];  \
+                                                               \
+  /* Trick GCC into not optimizing this variable away.  */     \
+  unused_pointer = stack_align_buffer;
+
+#else /* !__i386__ */
+#define JNI_STACK_ALIGNMENT_PROLOGUE ((void) 0)
+#endif /* __i386__ */
+
+#endif /* !ANDROID_STUBIFY */
+#endif /* _ANDROID_H_ */
diff --git a/src/androidfns.c b/src/androidfns.c
new file mode 100644
index 00000000000..9e8372f524b
--- /dev/null
+++ b/src/androidfns.c
@@ -0,0 +1,3271 @@
+/* Communication module for Android terminals.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <math.h>
+
+#include "lisp.h"
+#include "android.h"
+#include "androidterm.h"
+#include "blockinput.h"
+#include "keyboard.h"
+#include "buffer.h"
+#include "androidgui.h"
+
+#ifndef ANDROID_STUBIFY
+
+/* Some kind of reference count for the image cache.  */
+static ptrdiff_t image_cache_refcount;
+
+/* The frame of the currently visible tooltip, or nil if none.  */
+static Lisp_Object tip_frame;
+
+/* The window-system window corresponding to the frame of the
+   currently visible tooltip.  */
+static android_window tip_window;
+
+/* The X and Y deltas of the last call to `x-show-tip'.  */
+static Lisp_Object tip_dx, tip_dy;
+
+/* A timer that hides or deletes the currently visible tooltip when it
+   fires.  */
+static Lisp_Object tip_timer;
+
+/* STRING argument of last `x-show-tip' call.  */
+static Lisp_Object tip_last_string;
+
+/* Normalized FRAME argument of last `x-show-tip' call.  */
+static Lisp_Object tip_last_frame;
+
+/* PARMS argument of last `x-show-tip' call.  */
+static Lisp_Object tip_last_parms;
+
+#endif
+
+static struct android_display_info *
+android_display_info_for_name (Lisp_Object name)
+{
+  struct android_display_info *dpyinfo;
+
+  CHECK_STRING (name);
+
+  for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
+    {
+      if (!NILP (Fstring_equal (XCAR (dpyinfo->name_list_element),
+                               name)))
+       return dpyinfo;
+    }
+
+  error ("Cannot connect to Android if it was not initialized"
+        " at startup");
+}
+
+static struct android_display_info *
+check_android_display_info (Lisp_Object object)
+{
+  struct android_display_info *dpyinfo;
+  struct frame *sf, *f;
+  struct terminal *t;
+
+  if (NILP (object))
+    {
+      sf = XFRAME (selected_frame);
+
+      if (FRAME_ANDROID_P (sf) && FRAME_LIVE_P (sf))
+       dpyinfo = FRAME_DISPLAY_INFO (sf);
+      else if (x_display_list)
+       dpyinfo = x_display_list;
+      else
+       error ("Android windows are not in use or not initialized");
+    }
+  else if (TERMINALP (object))
+    {
+      t = decode_live_terminal (object);
+
+      if (t->type != output_android)
+        error ("Terminal %d is not an Android display", t->id);
+
+      dpyinfo = t->display_info.android;
+    }
+  else if (STRINGP (object))
+    dpyinfo = android_display_info_for_name (object);
+  else
+    {
+      f = decode_window_system_frame (object);
+      dpyinfo = FRAME_DISPLAY_INFO (f);
+    }
+
+  return dpyinfo;
+}
+
+Display_Info *
+check_x_display_info (Lisp_Object object)
+{
+  return check_android_display_info (object);
+}
+
+
+
+#ifndef ANDROID_STUBIFY
+
+void
+gamma_correct (struct frame *f, Emacs_Color *color)
+{
+  if (f->gamma)
+    {
+      color->red = pow (color->red / 65535.0, f->gamma) * 65535.0 + 0.5;
+      color->green = pow (color->green / 65535.0, f->gamma) * 65535.0 + 0.5;
+      color->blue = pow (color->blue / 65535.0, f->gamma) * 65535.0 + 0.5;
+    }
+}
+
+/* Decide if color named COLOR_NAME is valid for use on frame F.  If
+   so, return the RGB values in COLOR.  If ALLOC_P, allocate the
+   color.  Value is false if COLOR_NAME is invalid, or no color could
+   be allocated.  MAKE_INDEX is some mysterious argument used on
+   NS. */
+
+bool
+android_defined_color (struct frame *f, const char *color_name,
+                      Emacs_Color *color, bool alloc_p,
+                      bool make_index)
+{
+  bool success_p;
+
+  success_p = false;
+
+  block_input ();
+  success_p = android_parse_color (f, color_name, color);
+  if (success_p && alloc_p)
+    success_p = android_alloc_nearest_color (f, color);
+  unblock_input ();
+
+  return success_p;
+}
+
+/* Return the pixel color value for color COLOR_NAME on frame F.  If F
+   is a monochrome frame, return MONO_COLOR regardless of what ARG
+   says.  Signal an error if color can't be allocated.  */
+
+static unsigned long
+android_decode_color (struct frame *f, Lisp_Object color_name, int mono_color)
+{
+  Emacs_Color cdef;
+
+  CHECK_STRING (color_name);
+
+  if (android_defined_color (f, SSDATA (color_name), &cdef,
+                            true, false))
+    return cdef.pixel;
+
+  signal_error ("Undefined color", color_name);
+}
+
+static void
+android_set_parent_frame (struct frame *f, Lisp_Object new_value,
+                         Lisp_Object old_value)
+{
+  struct frame *p;
+
+  p = NULL;
+
+  if (!NILP (new_value)
+      && (!FRAMEP (new_value)
+         || !FRAME_LIVE_P (p = XFRAME (new_value))
+         || !FRAME_ANDROID_P (p)))
+    {
+      store_frame_param (f, Qparent_frame, old_value);
+      error ("Invalid specification of `parent-frame'");
+    }
+
+  if (p != FRAME_PARENT_FRAME (f))
+    {
+      block_input ();
+      android_reparent_window (FRAME_ANDROID_WINDOW (f),
+                              (p ? FRAME_ANDROID_WINDOW (p)
+                               : FRAME_DISPLAY_INFO (f)->root_window),
+                              f->left_pos, f->top_pos);
+      unblock_input ();
+
+      fset_parent_frame (f, new_value);
+    }
+
+  /* Update the fullscreen frame parameter as well.  */
+  FRAME_TERMINAL (f)->fullscreen_hook (f);
+}
+
+void
+android_implicitly_set_name (struct frame *f, Lisp_Object arg,
+                            Lisp_Object oldval)
+{
+
+}
+
+void
+android_explicitly_set_name (struct frame *f, Lisp_Object arg,
+                            Lisp_Object oldval)
+{
+
+}
+
+/* Set the number of lines used for the tool bar of frame F to VALUE.
+   VALUE not an integer, or < 0 means set the lines to zero.  OLDVAL
+   is the old number of tool bar lines.  This function changes the
+   height of all windows on frame F to match the new tool bar height.
+   The frame's height doesn't change.  */
+
+static void
+android_set_tool_bar_lines (struct frame *f, Lisp_Object value,
+                           Lisp_Object oldval)
+{
+  int nlines;
+
+  /* Treat tool bars like menu bars.  */
+  if (FRAME_MINIBUF_ONLY_P (f))
+    return;
+
+  /* Use VALUE only if an int >= 0.  */
+  if (RANGED_FIXNUMP (0, value, INT_MAX))
+    nlines = XFIXNAT (value);
+  else
+    nlines = 0;
+
+  android_change_tool_bar_height (f, nlines * FRAME_LINE_HEIGHT (f));
+}
+
+static void
+android_set_tool_bar_position (struct frame *f,
+                              Lisp_Object new_value,
+                              Lisp_Object old_value)
+{
+  if (!EQ (new_value, Qtop) && !EQ (new_value, Qbottom))
+    error ("Tool bar position must be either `top' or `bottom'");
+
+  if (EQ (new_value, old_value))
+    return;
+
+  /* Set the tool bar position.  */
+  fset_tool_bar_position (f, new_value);
+
+  /* Now reconfigure frame glyphs to place the tool bar at the
+     bottom.  While the inner height has not changed, call
+     `resize_frame_windows' to place each of the windows at its
+     new position.  */
+
+  adjust_frame_size (f, -1, -1, 3, false, Qtool_bar_position);
+  adjust_frame_glyphs (f);
+  SET_FRAME_GARBAGED (f);
+
+  if (FRAME_ANDROID_WINDOW (f))
+    android_clear_under_internal_border (f);
+}
+
+void
+android_change_tool_bar_height (struct frame *f, int height)
+{
+  int unit = FRAME_LINE_HEIGHT (f);
+  int old_height = FRAME_TOOL_BAR_HEIGHT (f);
+  int lines = (height + unit - 1) / unit;
+  Lisp_Object fullscreen = get_frame_param (f, Qfullscreen);
+
+  /* Make sure we redisplay all windows in this frame.  */
+  fset_redisplay (f);
+
+  FRAME_TOOL_BAR_HEIGHT (f) = height;
+  FRAME_TOOL_BAR_LINES (f) = lines;
+  store_frame_param (f, Qtool_bar_lines, make_fixnum (lines));
+
+  if (FRAME_ANDROID_WINDOW (f) && FRAME_TOOL_BAR_HEIGHT (f) == 0)
+    {
+      clear_frame (f);
+      clear_current_matrices (f);
+    }
+
+  if ((height < old_height) && WINDOWP (f->tool_bar_window))
+    clear_glyph_matrix (XWINDOW (f->tool_bar_window)->current_matrix);
+
+  if (!f->tool_bar_resized)
+    {
+      /* As long as tool_bar_resized is false, effectively try to change
+        F's native height.  */
+      if (NILP (fullscreen) || EQ (fullscreen, Qfullwidth))
+       adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f),
+                          1, false, Qtool_bar_lines);
+      else
+       adjust_frame_size (f, -1, -1, 4, false, Qtool_bar_lines);
+
+      f->tool_bar_resized =  f->tool_bar_redisplayed;
+    }
+  else
+    /* Any other change may leave the native size of F alone.  */
+    adjust_frame_size (f, -1, -1, 3, false, Qtool_bar_lines);
+
+  /* adjust_frame_size might not have done anything, garbage frame
+     here.  */
+  adjust_frame_glyphs (f);
+  SET_FRAME_GARBAGED (f);
+}
+
+/* Set the number of lines used for the tab bar of frame F to VALUE.
+   VALUE not an integer, or < 0 means set the lines to zero.  OLDVAL
+   is the old number of tab bar lines.  This function may change the
+   height of all windows on frame F to match the new tab bar height.
+   The frame's height may change if frame_inhibit_implied_resize was
+   set accordingly.  */
+
+static void
+android_set_tab_bar_lines (struct frame *f, Lisp_Object value,
+                          Lisp_Object oldval)
+{
+  int olines;
+  int nlines;
+
+  olines = FRAME_TAB_BAR_LINES (f);
+
+  /* Treat tab bars like menu bars.  */
+  if (FRAME_MINIBUF_ONLY_P (f))
+    return;
+
+  /* Use VALUE only if an int >= 0.  */
+  if (RANGED_FIXNUMP (0, value, INT_MAX))
+    nlines = XFIXNAT (value);
+  else
+    nlines = 0;
+
+  if (nlines != olines && (olines == 0 || nlines == 0))
+    android_change_tab_bar_height (f, nlines * FRAME_LINE_HEIGHT (f));
+}
+
+void
+android_change_tab_bar_height (struct frame *f, int height)
+{
+  int unit, old_height, lines;
+  Lisp_Object fullscreen;
+
+  unit = FRAME_LINE_HEIGHT (f);
+  old_height = FRAME_TAB_BAR_HEIGHT (f);
+  fullscreen = get_frame_param (f, Qfullscreen);
+
+  /* This differs from the tool bar code in that the tab bar height is
+     not rounded up.  Otherwise, if redisplay_tab_bar decides to grow
+     the tab bar by even 1 pixel, FRAME_TAB_BAR_LINES will be changed,
+     leading to the tab bar height being incorrectly set upon the next
+     call to android_set_font.  (bug#59285) */
+  lines = height / unit;
+
+  /* Make sure we redisplay all windows in this frame.  */
+  fset_redisplay (f);
+
+  /* Recalculate tab bar and frame text sizes.  */
+  FRAME_TAB_BAR_HEIGHT (f) = height;
+  FRAME_TAB_BAR_LINES (f) = lines;
+  store_frame_param (f, Qtab_bar_lines, make_fixnum (lines));
+
+  if (FRAME_ANDROID_WINDOW (f) && FRAME_TAB_BAR_HEIGHT (f) == 0)
+    {
+      clear_frame (f);
+      clear_current_matrices (f);
+    }
+
+  if ((height < old_height) && WINDOWP (f->tab_bar_window))
+    clear_glyph_matrix (XWINDOW (f->tab_bar_window)->current_matrix);
+
+  if (!f->tab_bar_resized)
+    {
+      /* As long as tab_bar_resized is false, effectively try to change
+        F's native height.  */
+      if (NILP (fullscreen) || EQ (fullscreen, Qfullwidth))
+       adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f),
+                          1, false, Qtab_bar_lines);
+      else
+       adjust_frame_size (f, -1, -1, 4, false, Qtab_bar_lines);
+
+      f->tab_bar_resized = f->tab_bar_redisplayed;
+    }
+  else
+    /* Any other change may leave the native size of F alone.  */
+    adjust_frame_size (f, -1, -1, 3, false, Qtab_bar_lines);
+
+  /* adjust_frame_size might not have done anything, garbage frame
+     here.  */
+  adjust_frame_glyphs (f);
+  SET_FRAME_GARBAGED (f);
+}
+
+void
+android_set_scroll_bar_default_height (struct frame *f)
+{
+  int height;
+
+  height = FRAME_LINE_HEIGHT (f);
+
+  /* The height of a non-toolkit scrollbar is 14 pixels.  */
+  FRAME_CONFIG_SCROLL_BAR_LINES (f) = (14 + height - 1) / height;
+
+  /* Use all of that space (aside from required margins) for the
+     scroll bar.  */
+  FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) = 14;
+}
+
+void
+android_set_scroll_bar_default_width (struct frame *f)
+{
+  int unit;
+
+  unit = FRAME_COLUMN_WIDTH (f);
+
+  FRAME_CONFIG_SCROLL_BAR_COLS (f) = (14 + unit - 1) / unit;
+  FRAME_CONFIG_SCROLL_BAR_WIDTH (f)
+    = FRAME_CONFIG_SCROLL_BAR_COLS (f) * unit;
+}
+
+
+/* Verify that the icon position args for this window are valid.  */
+
+static void
+android_icon_verify (struct frame *f, Lisp_Object parms)
+{
+  Lisp_Object icon_x, icon_y;
+
+  /* Set the position of the icon.  Note that twm groups all
+     icons in an icon window.  */
+  icon_x = gui_frame_get_and_record_arg (f, parms, Qicon_left, 0, 0,
+                                        RES_TYPE_NUMBER);
+  icon_y = gui_frame_get_and_record_arg (f, parms, Qicon_top, 0, 0,
+                                        RES_TYPE_NUMBER);
+
+  if (!BASE_EQ (icon_x, Qunbound) && !BASE_EQ (icon_y, Qunbound))
+    {
+      CHECK_FIXNUM (icon_x);
+      CHECK_FIXNUM (icon_y);
+    }
+  else if (!BASE_EQ (icon_x, Qunbound) || !BASE_EQ (icon_y, Qunbound))
+    error ("Both left and top icon corners of icon must be specified");
+}
+
+/* Handle the icon stuff for this window.  Perhaps later we might
+   want an x_set_icon_position which can be called interactively as
+   well.  */
+
+static void
+android_icon (struct frame *f, Lisp_Object parms)
+{
+  /* Set the position of the icon.  Note that twm groups all
+     icons in an icon window.  */
+  Lisp_Object icon_x
+    = gui_frame_get_and_record_arg (f, parms, Qicon_left, 0, 0,
+                                   RES_TYPE_NUMBER);
+  Lisp_Object icon_y
+    = gui_frame_get_and_record_arg (f, parms, Qicon_top, 0, 0,
+                                   RES_TYPE_NUMBER);
+
+  bool xgiven = !BASE_EQ (icon_x, Qunbound);
+  bool ygiven = !BASE_EQ (icon_y, Qunbound);
+
+  if (xgiven != ygiven)
+    error ("Both left and top icon corners of icon must be specified");
+
+  if (xgiven)
+    {
+      check_integer_range (icon_x, INT_MIN, INT_MAX);
+      check_integer_range (icon_y, INT_MIN, INT_MAX);
+    }
+
+  /* Now return as this is not supported on Android.  */
+}
+
+/* Make the GCs needed for this window, setting the background
+   color.  */
+
+static void
+android_make_gc (struct frame *f)
+{
+  struct android_gc_values gc_values;
+
+  block_input ();
+
+  /* Create the GCs of this frame.
+     Note that many default values are used.  */
+
+  gc_values.foreground = FRAME_FOREGROUND_PIXEL (f);
+  gc_values.background = FRAME_BACKGROUND_PIXEL (f);
+  f->output_data.android->normal_gc
+    = android_create_gc (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND,
+                        &gc_values);
+
+  /* Reverse video style.  */
+  gc_values.foreground = FRAME_BACKGROUND_PIXEL (f);
+  gc_values.background = FRAME_FOREGROUND_PIXEL (f);
+  f->output_data.android->reverse_gc
+    = android_create_gc (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND,
+                        &gc_values);
+
+  /* Cursor has cursor-color background, background-color foreground.  */
+  gc_values.foreground = FRAME_BACKGROUND_PIXEL (f);
+  gc_values.background = f->output_data.android->cursor_pixel;
+  f->output_data.android->cursor_gc
+    = android_create_gc (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND,
+                        &gc_values);
+  unblock_input ();
+}
+
+
+/* Free what was allocated in android_make_gc.  */
+
+void
+android_free_gcs (struct frame *f)
+{
+  block_input ();
+
+  if (f->output_data.android->normal_gc)
+    {
+      android_free_gc (f->output_data.android->normal_gc);
+      f->output_data.android->normal_gc = 0;
+    }
+
+  if (f->output_data.android->reverse_gc)
+    {
+      android_free_gc (f->output_data.android->reverse_gc);
+      f->output_data.android->reverse_gc = 0;
+    }
+
+  if (f->output_data.android->cursor_gc)
+    {
+      android_free_gc (f->output_data.android->cursor_gc);
+      f->output_data.android->cursor_gc = 0;
+    }
+
+  unblock_input ();
+}
+
+/* Handler for signals raised during x_create_frame and
+   Fx_create_tip_frame.  FRAME is the frame which is partially
+   constructed.  */
+
+static Lisp_Object
+unwind_create_frame (Lisp_Object frame)
+{
+  struct frame *f = XFRAME (frame);
+
+  /* If frame is already dead, nothing to do.  This can happen if the
+     display is disconnected after the frame has become official, but
+     before Fx_create_frame removes the unwind protect.  */
+  if (!FRAME_LIVE_P (f))
+    return Qnil;
+
+  /* If frame is ``official'', nothing to do.  */
+  if (NILP (Fmemq (frame, Vframe_list)))
+    {
+      /* If the frame's image cache refcount is still the same as our
+        private shadow variable, it means we are unwinding a frame
+        for which we didn't yet call init_frame_faces, where the
+        refcount is incremented.  Therefore, we increment it here, so
+        that free_frame_faces, called in x_free_frame_resources
+        below, will not mistakenly decrement the counter that was not
+        incremented yet to account for this new frame.  */
+      if (FRAME_IMAGE_CACHE (f) != NULL
+         && FRAME_IMAGE_CACHE (f)->refcount == image_cache_refcount)
+       FRAME_IMAGE_CACHE (f)->refcount++;
+
+      android_free_frame_resources (f);
+      free_glyphs (f);
+      return Qt;
+    }
+
+  return Qnil;
+}
+
+static void
+do_unwind_create_frame (Lisp_Object frame)
+{
+  unwind_create_frame (frame);
+}
+
+void
+android_default_font_parameter (struct frame *f, Lisp_Object parms)
+{
+  struct android_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  Lisp_Object font_param = gui_display_get_arg (dpyinfo, parms, Qfont, NULL, 
NULL,
+                                                RES_TYPE_STRING);
+  Lisp_Object font = Qnil;
+  if (BASE_EQ (font_param, Qunbound))
+    font_param = Qnil;
+
+  if (NILP (font))
+    font = (!NILP (font_param)
+           ? font_param
+           : gui_display_get_arg (dpyinfo, parms,
+                                  Qfont, "font", "Font",
+                                  RES_TYPE_STRING));
+
+  if (! FONTP (font) && ! STRINGP (font))
+    {
+      const char *names[] = {
+       "Droid Sans Mono-12",
+       "Monospace-12",
+       "DroidSansMono-12",
+       NULL
+      };
+      int i;
+
+      for (i = 0; names[i]; i++)
+       {
+         font = font_open_by_name (f, build_unibyte_string (names[i]));
+         if (! NILP (font))
+           break;
+       }
+
+      if (NILP (font))
+       error ("No suitable font was found");
+    }
+
+  gui_default_parameter (f, parms, Qfont, font, "font", "Font", 
RES_TYPE_STRING);
+}
+
+static void
+android_create_frame_window (struct frame *f)
+{
+  struct android_set_window_attributes attributes;
+  enum android_window_value_mask attribute_mask;
+
+  attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f);
+  attribute_mask = ANDROID_CW_BACK_PIXEL;
+
+  block_input ();
+  FRAME_ANDROID_WINDOW (f)
+    = android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
+                            f->left_pos,
+                            f->top_pos,
+                            FRAME_PIXEL_WIDTH (f),
+                            FRAME_PIXEL_HEIGHT (f),
+                            attribute_mask, &attributes);
+  unblock_input ();
+}
+
+#endif /* ANDROID_STUBIFY */
+
+
+
+DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
+       1, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object parms)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  struct frame *f;
+  Lisp_Object frame, tem;
+  Lisp_Object name;
+  bool minibuffer_only;
+  bool undecorated, override_redirect;
+  long window_prompting;
+  specpdl_ref count;
+  Lisp_Object display;
+  struct android_display_info *dpyinfo;
+  Lisp_Object parent, parent_frame;
+  struct kboard *kb;
+
+  minibuffer_only = false;
+  undecorated = false;
+  override_redirect = false;
+  window_prompting = 0;
+  count = SPECPDL_INDEX ();
+  dpyinfo = NULL;
+
+  /* Not actually used, but be consistent with X.  */
+  ((void) window_prompting);
+
+  parms = Fcopy_alist (parms);
+
+  /* Use this general default value to start with
+     until we know if this frame has a specified name.  */
+  Vx_resource_name = Vinvocation_name;
+
+  display = gui_display_get_arg (dpyinfo, parms, Qterminal, 0, 0,
+                                 RES_TYPE_NUMBER);
+  if (BASE_EQ (display, Qunbound))
+    display = gui_display_get_arg (dpyinfo, parms, Qdisplay, 0, 0,
+                                   RES_TYPE_STRING);
+  if (BASE_EQ (display, Qunbound))
+    display = Qnil;
+  dpyinfo = check_android_display_info (display);
+  kb = dpyinfo->terminal->kboard;
+
+  if (!dpyinfo->terminal->name)
+    error ("Terminal is not live, can't create new frames on it");
+
+  name = gui_display_get_arg (dpyinfo, parms, Qname, "name", "Name",
+                              RES_TYPE_STRING);
+  if (!STRINGP (name)
+      && ! BASE_EQ (name, Qunbound)
+      && ! NILP (name))
+    error ("Invalid frame name--not a string or nil");
+
+  if (STRINGP (name))
+    Vx_resource_name = name;
+
+  /* See if parent window is specified.  */
+  parent = gui_display_get_arg (dpyinfo, parms, Qparent_id, NULL, NULL,
+                                RES_TYPE_NUMBER);
+  if (BASE_EQ (parent, Qunbound))
+    parent = Qnil;
+  if (! NILP (parent))
+    CHECK_FIXNUM (parent);
+
+  frame = Qnil;
+  tem = gui_display_get_arg (dpyinfo,
+                             parms, Qminibuffer, "minibuffer", "Minibuffer",
+                             RES_TYPE_SYMBOL);
+  if (EQ (tem, Qnone) || NILP (tem))
+    f = make_frame_without_minibuffer (Qnil, kb, display);
+  else if (EQ (tem, Qonly))
+    {
+      f = make_minibuffer_frame ();
+      minibuffer_only = true;
+    }
+  else if (WINDOWP (tem))
+    f = make_frame_without_minibuffer (tem, kb, display);
+  else
+    f = make_frame (true);
+
+  parent_frame = gui_display_get_arg (dpyinfo,
+                                      parms,
+                                      Qparent_frame,
+                                      NULL,
+                                      NULL,
+                                      RES_TYPE_SYMBOL);
+  /* Accept parent-frame iff parent-id was not specified.  */
+  if (!NILP (parent)
+      || BASE_EQ (parent_frame, Qunbound)
+      || NILP (parent_frame)
+      || !FRAMEP (parent_frame)
+      || !FRAME_LIVE_P (XFRAME (parent_frame))
+      || !FRAME_ANDROID_P (XFRAME (parent_frame)))
+    parent_frame = Qnil;
+
+  fset_parent_frame (f, parent_frame);
+  store_frame_param (f, Qparent_frame, parent_frame);
+
+  if (!NILP (tem = (gui_display_get_arg (dpyinfo,
+                                         parms,
+                                         Qundecorated,
+                                         NULL,
+                                         NULL,
+                                         RES_TYPE_BOOLEAN)))
+      && !(BASE_EQ (tem, Qunbound)))
+    undecorated = true;
+
+  FRAME_UNDECORATED (f) = undecorated;
+  store_frame_param (f, Qundecorated, undecorated ? Qt : Qnil);
+
+  if (!NILP (tem = (gui_display_get_arg (dpyinfo,
+                                         parms,
+                                         Qoverride_redirect,
+                                         NULL,
+                                         NULL,
+                                         RES_TYPE_BOOLEAN)))
+      && !(BASE_EQ (tem, Qunbound)))
+    override_redirect = true;
+
+  FRAME_OVERRIDE_REDIRECT (f) = override_redirect;
+  store_frame_param (f, Qoverride_redirect, override_redirect ? Qt : Qnil);
+
+  XSETFRAME (frame, f);
+
+  f->terminal = dpyinfo->terminal;
+
+  f->output_method = output_android;
+  f->output_data.android = xzalloc (sizeof *f->output_data.android);
+  FRAME_FONTSET (f) = -1;
+  f->output_data.android->scroll_bar_foreground_pixel = -1;
+  f->output_data.android->scroll_bar_background_pixel = -1;
+  f->output_data.android->white_relief.pixel = -1;
+  f->output_data.android->black_relief.pixel = -1;
+
+  fset_icon_name (f, gui_display_get_arg (dpyinfo,
+                                          parms,
+                                          Qicon_name,
+                                          "iconName",
+                                          "Title",
+                                          RES_TYPE_STRING));
+  if (! STRINGP (f->icon_name))
+    fset_icon_name (f, Qnil);
+
+  FRAME_DISPLAY_INFO (f) = dpyinfo;
+
+  /* With FRAME_DISPLAY_INFO set up, this unwind-protect is safe.  */
+  record_unwind_protect (do_unwind_create_frame, frame);
+
+  /* These colors will be set anyway later, but it's important
+     to get the color reference counts right, so initialize them!
+
+     (Not really on Android, but it's best to be consistent with
+     X.) */
+  {
+    Lisp_Object black;
+
+    /* Function x_decode_color can signal an error.  Make
+       sure to initialize color slots so that we won't try
+       to free colors we haven't allocated.  */
+    FRAME_FOREGROUND_PIXEL (f) = -1;
+    FRAME_BACKGROUND_PIXEL (f) = -1;
+    f->output_data.android->cursor_pixel = -1;
+    f->output_data.android->cursor_foreground_pixel = -1;
+    f->output_data.android->mouse_pixel = -1;
+
+    black = build_string ("black");
+    FRAME_FOREGROUND_PIXEL (f)
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    FRAME_BACKGROUND_PIXEL (f)
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    f->output_data.android->cursor_pixel
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    f->output_data.android->cursor_foreground_pixel
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    f->output_data.android->mouse_pixel
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+  }
+
+  /* Set the name; the functions to which we pass f expect the name to
+     be set.  */
+  if (BASE_EQ (name, Qunbound) || NILP (name))
+    {
+      fset_name (f, build_string ("GNU Emacs"));
+      f->explicit_name = false;
+    }
+  else
+    {
+      fset_name (f, name);
+      f->explicit_name = true;
+      /* Use the frame's title when getting resources for this frame.  */
+      specbind (Qx_resource_name, name);
+    }
+
+  register_font_driver (&androidfont_driver, f);
+  register_font_driver (&android_sfntfont_driver, f);
+
+  image_cache_refcount = (FRAME_IMAGE_CACHE (f)
+                         ? FRAME_IMAGE_CACHE (f)->refcount
+                         : 0);
+
+  gui_default_parameter (f, parms, Qfont_backend, Qnil,
+                         "fontBackend", "FontBackend", RES_TYPE_STRING);
+
+  /* Extract the window parameters from the supplied values
+     that are needed to determine window geometry.  */
+  android_default_font_parameter (f, parms);
+  if (!FRAME_FONT (f))
+    {
+      delete_frame (frame, Qnoelisp);
+      error ("Invalid frame font");
+    }
+
+  if (NILP (Fassq (Qinternal_border_width, parms)))
+    {
+      Lisp_Object value;
+
+      value = gui_display_get_arg (dpyinfo, parms, Qinternal_border_width,
+                                   "internalBorder", "internalBorder",
+                                   RES_TYPE_NUMBER);
+      if (! BASE_EQ (value, Qunbound))
+       parms = Fcons (Fcons (Qinternal_border_width, value),
+                      parms);
+    }
+
+  gui_default_parameter (f, parms, Qinternal_border_width,
+                        make_fixnum (0),
+                        "internalBorderWidth", "internalBorderWidth",
+                        RES_TYPE_NUMBER);
+
+  /* Same for child frames.  */
+  if (NILP (Fassq (Qchild_frame_border_width, parms)))
+    {
+      Lisp_Object value;
+
+      value = gui_display_get_arg (dpyinfo, parms, Qchild_frame_border_width,
+                                   "childFrameBorder", "childFrameBorder",
+                                   RES_TYPE_NUMBER);
+      if (! BASE_EQ (value, Qunbound))
+       parms = Fcons (Fcons (Qchild_frame_border_width, value),
+                      parms);
+    }
+
+  gui_default_parameter (f, parms, Qchild_frame_border_width, Qnil,
+                        "childFrameBorderWidth", "childFrameBorderWidth",
+                        RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qright_divider_width, make_fixnum (0),
+                         NULL, NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0),
+                         NULL, NULL, RES_TYPE_NUMBER);
+
+  /* `vertical-scroll-bars' defaults to nil on Android as a
+     consequence of scroll bars not being supported at all.  */
+
+  gui_default_parameter (f, parms, Qvertical_scroll_bars, Qnil,
+                         "verticalScrollBars", "ScrollBars",
+                         RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil,
+                         "horizontalScrollBars", "ScrollBars",
+                         RES_TYPE_SYMBOL);
+
+  /* Also do the stuff which must be set before the window exists.  */
+  gui_default_parameter (f, parms, Qforeground_color, build_string ("black"),
+                         "foreground", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qbackground_color, build_string ("white"),
+                         "background", "Background", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qmouse_color, build_string ("black"),
+                         "pointerColor", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qborder_color, build_string ("black"),
+                         "borderColor", "BorderColor", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qscreen_gamma, Qnil,
+                         "screenGamma", "ScreenGamma", RES_TYPE_FLOAT);
+  gui_default_parameter (f, parms, Qline_spacing, Qnil,
+                         "lineSpacing", "LineSpacing", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qleft_fringe, Qnil,
+                         "leftFringe", "LeftFringe", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qright_fringe, Qnil,
+                         "rightFringe", "RightFringe", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qno_special_glyphs, Qnil,
+                         NULL, NULL, RES_TYPE_BOOLEAN);
+
+#if 0
+  android_default_scroll_bar_color_parameter (f, parms, Qscroll_bar_foreground,
+                                             "scrollBarForeground",
+                                             "ScrollBarForeground", true);
+  android_default_scroll_bar_color_parameter (f, parms, Qscroll_bar_background,
+                                             "scrollBarBackground",
+                                             "ScrollBarBackground", false);
+#endif
+
+  /* Init faces before gui_default_parameter is called for the
+     scroll-bar-width parameter because otherwise we end up in
+     init_iterator with a null face cache, which should not
+     happen.  */
+
+  init_frame_faces (f);
+
+  tem = gui_display_get_arg (dpyinfo, parms, Qmin_width, NULL, NULL,
+                             RES_TYPE_NUMBER);
+  if (FIXNUMP (tem))
+    store_frame_param (f, Qmin_width, tem);
+  tem = gui_display_get_arg (dpyinfo, parms, Qmin_height, NULL, NULL,
+                             RES_TYPE_NUMBER);
+  if (FIXNUMP (tem))
+    store_frame_param (f, Qmin_height, tem);
+
+  adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f),
+                    FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), 5, true,
+                    Qx_create_frame_1);
+
+  /* Set the menu-bar-lines and tool-bar-lines parameters.  We don't
+     look up the X resources controlling the menu-bar and tool-bar
+     here; they are processed specially at startup, and reflected in
+     the values of the mode variables.  */
+
+  gui_default_parameter (f, parms, Qmenu_bar_lines,
+                         NILP (Vmenu_bar_mode)
+                         ? make_fixnum (0) : make_fixnum (1),
+                         NULL, NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qtab_bar_lines,
+                         NILP (Vtab_bar_mode)
+                         ? make_fixnum (0) : make_fixnum (1),
+                         NULL, NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qtool_bar_lines,
+                         NILP (Vtool_bar_mode)
+                         ? make_fixnum (0) : make_fixnum (1),
+                         NULL, NULL, RES_TYPE_NUMBER);
+
+  gui_default_parameter (f, parms, Qbuffer_predicate, Qnil,
+                         "bufferPredicate", "BufferPredicate",
+                         RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qtitle, Qnil,
+                         "title", "Title", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qwait_for_wm, Qt,
+                         "waitForWM", "WaitForWM", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qtool_bar_position,
+                         FRAME_TOOL_BAR_POSITION (f), 0, 0, RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qinhibit_double_buffering, Qnil,
+                         "inhibitDoubleBuffering", "InhibitDoubleBuffering",
+                         RES_TYPE_BOOLEAN);
+
+  /* Compute the size of the X window.  */
+  window_prompting = gui_figure_window_size (f, parms, true, true);
+
+  tem = gui_display_get_arg (dpyinfo, parms, Qunsplittable, 0, 0,
+                             RES_TYPE_BOOLEAN);
+  f->no_split = minibuffer_only || EQ (tem, Qt);
+
+  android_icon_verify (f, parms);
+  android_create_frame_window (f);
+  android_icon (f, parms);
+  android_make_gc (f);
+
+  /* Now consider the frame official.  */
+  f->terminal->reference_count++;
+  Vframe_list = Fcons (frame, Vframe_list);
+
+  /* We need to do this after creating the window, so that the
+     icon-creation functions can say whose icon they're
+     describing.  */
+  gui_default_parameter (f, parms, Qicon_type, Qt,
+                         "bitmapIcon", "BitmapIcon", RES_TYPE_BOOLEAN);
+
+  gui_default_parameter (f, parms, Qauto_raise, Qnil,
+                         "autoRaise", "AutoRaiseLower", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qauto_lower, Qnil,
+                         "autoLower", "AutoRaiseLower", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qcursor_type, Qbox,
+                         "cursorType", "CursorType", RES_TYPE_SYMBOL);
+  /* Scroll bars are not supported on Android, as they are near
+     useless.  */
+  gui_default_parameter (f, parms, Qscroll_bar_width, Qnil,
+                         "scrollBarWidth", "ScrollBarWidth",
+                         RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qscroll_bar_height, Qnil,
+                         "scrollBarHeight", "ScrollBarHeight",
+                         RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qalpha, Qnil,
+                         "alpha", "Alpha", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qalpha_background, Qnil,
+                         "alphaBackground", "AlphaBackground", 
RES_TYPE_NUMBER);
+
+  if (!NILP (parent_frame))
+    {
+      struct frame *p = XFRAME (parent_frame);
+
+      block_input ();
+      android_reparent_window (FRAME_ANDROID_WINDOW (f),
+                              FRAME_ANDROID_WINDOW (p),
+                              f->left_pos, f->top_pos);
+      unblock_input ();
+    }
+
+  gui_default_parameter (f, parms, Qno_focus_on_map, Qnil,
+                         NULL, NULL, RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qno_accept_focus, Qnil,
+                         NULL, NULL, RES_TYPE_BOOLEAN);
+
+  /* Consider frame official, now.  */
+  f->can_set_window_size = true;
+
+  adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f),
+                    0, true, Qx_create_frame_2);
+
+  /* Process fullscreen parameter here in the hope that normalizing a
+     fullheight/fullwidth frame will produce the size set by the last
+     adjust_frame_size call.  Note that Android only supports the
+     `maximized' state.  */
+  gui_default_parameter (f, parms, Qfullscreen, Qmaximized,
+                         "fullscreen", "Fullscreen", RES_TYPE_SYMBOL);
+
+  /* When called from `x-create-frame-with-faces' visibility is
+     always explicitly nil.  */
+  Lisp_Object visibility
+    = gui_display_get_arg (dpyinfo, parms, Qvisibility, 0, 0,
+                          RES_TYPE_SYMBOL);
+  Lisp_Object height
+    = gui_display_get_arg (dpyinfo, parms, Qheight, 0, 0, RES_TYPE_NUMBER);
+  Lisp_Object width
+    = gui_display_get_arg (dpyinfo, parms, Qwidth, 0, 0, RES_TYPE_NUMBER);
+
+  if (EQ (visibility, Qicon))
+    {
+      f->was_invisible = true;
+      android_iconify_frame (f);
+    }
+  else
+    {
+      if (BASE_EQ (visibility, Qunbound))
+       visibility = Qt;
+
+      if (!NILP (visibility))
+       android_make_frame_visible (f);
+      else
+       f->was_invisible = true;
+    }
+
+  /* Leave f->was_invisible true only if height or width were
+     specified too.  This takes effect only when we are not called
+     from `x-create-frame-with-faces' (see above comment).  */
+  f->was_invisible
+    = (f->was_invisible
+       && (!BASE_EQ (height, Qunbound) || !BASE_EQ (width, Qunbound)));
+
+  store_frame_param (f, Qvisibility, visibility);
+
+  /* Set whether or not frame synchronization is enabled.  */
+  gui_default_parameter (f, parms, Quse_frame_synchronization, Qt,
+                        NULL, NULL, RES_TYPE_BOOLEAN);
+
+  /* Works iff frame has been already mapped.  */
+  gui_default_parameter (f, parms, Qskip_taskbar, Qnil,
+                         NULL, NULL, RES_TYPE_BOOLEAN);
+  /* The `z-group' parameter works only for visible frames.  */
+  gui_default_parameter (f, parms, Qz_group, Qnil,
+                         NULL, NULL, RES_TYPE_SYMBOL);
+
+  /* Initialize `default-minibuffer-frame' in case this is the first
+     frame on this terminal.  */
+  if (FRAME_HAS_MINIBUF_P (f)
+      && (!FRAMEP (KVAR (kb, Vdefault_minibuffer_frame))
+          || !FRAME_LIVE_P (XFRAME (KVAR (kb, Vdefault_minibuffer_frame)))))
+    kset_default_minibuffer_frame (kb, frame);
+
+  /* All remaining specified parameters, which have not been "used" by
+     gui_display_get_arg and friends, now go in the misc. alist of the
+     frame.  */
+  for (tem = parms; CONSP (tem); tem = XCDR (tem))
+    if (CONSP (XCAR (tem)) && !NILP (XCAR (XCAR (tem))))
+      fset_param_alist (f, Fcons (XCAR (tem), f->param_alist));
+
+  /* Make sure windows on this frame appear in calls to next-window
+     and similar functions.  */
+  Vwindow_list = Qnil;
+
+  return unbind_to (count, frame);
+#endif
+}
+
+DEFUN ("xw-color-defined-p", Fxw_color_defined_p, Sxw_color_defined_p,
+       1, 2, 0, doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object color, Lisp_Object frame)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  Emacs_Color foo;
+  struct frame *f;
+
+  f = decode_window_system_frame (frame);
+
+  CHECK_STRING (color);
+
+  if (android_defined_color (f, SSDATA (color), &foo, false, false))
+    return Qt;
+  else
+    return Qnil;
+#endif
+}
+
+DEFUN ("xw-color-values", Fxw_color_values, Sxw_color_values, 1, 2,
+       0, doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object color, Lisp_Object frame)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  Emacs_Color foo;
+  struct frame *f;
+
+  f = decode_window_system_frame (frame);
+
+  CHECK_STRING (color);
+
+  if (android_defined_color (f, SSDATA (color), &foo, false, false))
+    return list3i (foo.red, foo.green, foo.blue);
+  else
+    return Qnil;
+#endif
+}
+
+DEFUN ("xw-display-color-p", Fxw_display_color_p,
+       Sxw_display_color_p, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  return Qt;
+}
+
+DEFUN ("x-display-grayscale-p", Fx_display_grayscale_p,
+       Sx_display_grayscale_p, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  return Qnil;
+}
+
+DEFUN ("x-display-pixel-width", Fx_display_pixel_width,
+       Sx_display_pixel_width, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  return make_fixnum (android_get_screen_width ());
+#endif
+}
+
+DEFUN ("x-display-pixel-height", Fx_display_pixel_height,
+       Sx_display_pixel_height, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  return make_fixnum (android_get_screen_height ());
+#endif
+}
+
+DEFUN ("x-display-planes", Fx_display_planes, Sx_display_planes,
+       0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  struct android_display_info *dpyinfo;
+
+  dpyinfo = check_android_display_info (terminal);
+
+  return make_fixnum (dpyinfo->n_planes);
+}
+
+DEFUN ("x-display-color-cells", Fx_display_color_cells, Sx_display_color_cells,
+       0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  struct android_display_info *dpyinfo;
+  int nr_planes;
+
+  dpyinfo = check_android_display_info (terminal);
+  nr_planes = dpyinfo->n_planes;
+
+  /* Truncate nr_planes to 24 to avoid integer overflow.  */
+
+  if (nr_planes > 24)
+    nr_planes = 24;
+
+  return make_fixnum (1 << nr_planes);
+}
+
+DEFUN ("x-server-vendor", Fx_server_vendor, Sx_server_vendor, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  check_android_display_info (terminal);
+  return Vandroid_build_manufacturer;
+#endif
+}
+
+DEFUN ("x-server-version", Fx_server_version, Sx_server_version, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  check_android_display_info (terminal);
+  return list3i (android_get_current_api_level (), 0, 0);
+#endif
+}
+
+DEFUN ("x-display-screens", Fx_display_screens, Sx_display_screens,
+       0, 1, 0, doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  check_android_display_info (terminal);
+  return make_fixnum (1);
+}
+
+DEFUN ("x-display-mm-width", Fx_display_mm_width, Sx_display_mm_width,
+       0, 1, 0, doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  return make_fixnum (android_get_mm_width ());
+#endif
+}
+
+DEFUN ("x-display-mm-height", Fx_display_mm_height, Sx_display_mm_height,
+       0, 1, 0, doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  return make_fixnum (android_get_mm_height ());
+#endif
+}
+
+DEFUN ("x-display-backing-store", Fx_display_backing_store,
+       Sx_display_backing_store, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  check_android_display_info (terminal);
+
+  /* The Java part is implemented in a way that it always does the
+     equivalent of backing store.  */
+  return Qalways;
+}
+
+DEFUN ("x-display-visual-class", Fx_display_visual_class,
+       Sx_display_visual_class, 0, 1, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object terminal)
+{
+  check_android_display_info (terminal);
+
+  return Qtrue_color;
+}
+
+#ifndef ANDROID_STUBIFY
+
+static Lisp_Object
+android_make_monitor_attribute_list (struct MonitorInfo *monitors,
+                                    int n_monitors,
+                                    int primary_monitor)
+{
+  Lisp_Object monitor_frames;
+  Lisp_Object frame, rest;
+  struct frame *f;
+
+  monitor_frames = make_nil_vector (n_monitors);
+
+  FOR_EACH_FRAME (rest, frame)
+    {
+      f = XFRAME (frame);
+
+      /* Associate all frames with the primary monitor.  */
+
+      if (FRAME_WINDOW_P (f)
+         && !FRAME_TOOLTIP_P (f))
+       ASET (monitor_frames, primary_monitor,
+             Fcons (frame, AREF (monitor_frames,
+                                 primary_monitor)));
+    }
+
+  return make_monitor_attribute_list (monitors, n_monitors,
+                                     primary_monitor,
+                                      monitor_frames, NULL);
+}
+
+#endif
+
+DEFUN ("android-display-monitor-attributes-list",
+       Fandroid_display_monitor_attributes_list,
+       Sandroid_display_monitor_attributes_list,
+       0, 1, 0,
+       doc: /* Return a list of physical monitor attributes on the X display 
TERMINAL.
+
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be a terminal object, a frame or a display name (a string).
+If omitted or nil, that stands for the selected frame's display.
+
+Internal use only, use `display-monitor-attributes-list' instead.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  struct MonitorInfo monitor;
+
+  memset (&monitor, 0, sizeof monitor);
+  monitor.geom.width = android_get_screen_width ();
+  monitor.geom.height = android_get_screen_height ();
+  monitor.mm_width = android_get_mm_width ();
+  monitor.mm_height = android_get_mm_height ();
+  monitor.work = monitor.geom;
+  monitor.name = (char *) "Android device monitor";
+
+  return android_make_monitor_attribute_list (&monitor, 1, 0);
+#endif
+}
+
+#ifndef ANDROID_STUBIFY
+
+static Lisp_Object
+frame_geometry (Lisp_Object frame, Lisp_Object attribute)
+{
+  struct frame *f = decode_live_frame (frame);
+  android_window rootw;
+  unsigned int native_width, native_height, x_border_width = 0;
+  int x_native = 0, y_native = 0, xptr = 0, yptr = 0;
+  int left_off = 0, right_off = 0, top_off = 0, bottom_off = 0;
+  int outer_left, outer_top, outer_right, outer_bottom;
+  int native_left, native_top, native_right, native_bottom;
+  int inner_left, inner_top, inner_right, inner_bottom;
+  int internal_border_width;
+  bool menu_bar_external = false, tool_bar_external = false;
+  int menu_bar_height = 0, menu_bar_width = 0;
+  int tab_bar_height = 0, tab_bar_width = 0;
+  int tool_bar_height = 0, tool_bar_width = 0;
+
+  if (FRAME_INITIAL_P (f) || !FRAME_ANDROID_P (f)
+      || !FRAME_ANDROID_WINDOW (f))
+    return Qnil;
+
+  block_input ();
+  android_get_geometry (FRAME_ANDROID_WINDOW (f),
+                       &rootw, &x_native, &y_native,
+                       &native_width, &native_height, &x_border_width);
+  unblock_input ();
+
+  if (FRAME_PARENT_FRAME (f))
+    {
+      Lisp_Object parent, edges;
+
+      XSETFRAME (parent, FRAME_PARENT_FRAME (f));
+      edges = Fandroid_frame_edges (parent, Qnative_edges);
+      if (!NILP (edges))
+       {
+         x_native += XFIXNUM (Fnth (make_fixnum (0), edges));
+         y_native += XFIXNUM (Fnth (make_fixnum (1), edges));
+       }
+
+      outer_left = x_native;
+      outer_top = y_native;
+      outer_right = outer_left + native_width + 2 * x_border_width;
+      outer_bottom = outer_top + native_height + 2 * x_border_width;
+
+      native_left = x_native + x_border_width;
+      native_top = y_native + x_border_width;
+      native_right = native_left + native_width;
+      native_bottom = native_top + native_height;
+    }
+  else
+    {
+      outer_left = xptr;
+      outer_top = yptr;
+      outer_right = outer_left + left_off + native_width + right_off;
+      outer_bottom = outer_top + top_off + native_height + bottom_off;
+
+      native_left = outer_left + left_off;
+      native_top = outer_top + top_off;
+      native_right = native_left + native_width;
+      native_bottom = native_top + native_height;
+    }
+
+  internal_border_width = FRAME_INTERNAL_BORDER_WIDTH (f);
+  inner_left = native_left + internal_border_width;
+  inner_top = native_top + internal_border_width;
+  inner_right = native_right - internal_border_width;
+  inner_bottom = native_bottom - internal_border_width;
+
+  menu_bar_height = FRAME_MENU_BAR_HEIGHT (f);
+  inner_top += menu_bar_height;
+  menu_bar_width = menu_bar_height ? native_width : 0;
+
+  tab_bar_height = FRAME_TAB_BAR_HEIGHT (f);
+  tab_bar_width = (tab_bar_height
+                  ? native_width - 2 * internal_border_width
+                   : 0);
+  inner_top += tab_bar_height;
+
+  tool_bar_height = FRAME_TOOL_BAR_HEIGHT (f);
+  tool_bar_width = (tool_bar_height
+                   ? native_width - 2 * internal_border_width
+                   : 0);
+
+  /* Subtract or add to the inner dimensions based on the tool bar
+     position.  */
+
+  if (EQ (FRAME_TOOL_BAR_POSITION (f), Qtop))
+    inner_top += tool_bar_height;
+  else
+    inner_bottom -= tool_bar_height;
+
+  /* Construct list.  */
+  if (EQ (attribute, Qouter_edges))
+    return list4i (outer_left, outer_top, outer_right, outer_bottom);
+  else if (EQ (attribute, Qnative_edges))
+    return list4i (native_left, native_top, native_right, native_bottom);
+  else if (EQ (attribute, Qinner_edges))
+    return list4i (inner_left, inner_top, inner_right, inner_bottom);
+  else
+    return
+       list (Fcons (Qouter_position,
+                   Fcons (make_fixnum (outer_left),
+                          make_fixnum (outer_top))),
+            Fcons (Qouter_size,
+                   Fcons (make_fixnum (outer_right - outer_left),
+                          make_fixnum (outer_bottom - outer_top))),
+            /* Approximate.  */
+            Fcons (Qexternal_border_size,
+                   Fcons (make_fixnum (right_off),
+                          make_fixnum (bottom_off))),
+            Fcons (Qouter_border_width, make_fixnum (x_border_width)),
+            /* Approximate.  */
+            Fcons (Qtitle_bar_size,
+                   Fcons (make_fixnum (0),
+                          make_fixnum (top_off - bottom_off))),
+            Fcons (Qmenu_bar_external, menu_bar_external ? Qt : Qnil),
+            Fcons (Qmenu_bar_size,
+                   Fcons (make_fixnum (menu_bar_width),
+                          make_fixnum (menu_bar_height))),
+            Fcons (Qtab_bar_size,
+                   Fcons (make_fixnum (tab_bar_width),
+                          make_fixnum (tab_bar_height))),
+            Fcons (Qtool_bar_external, tool_bar_external ? Qt : Qnil),
+            Fcons (Qtool_bar_position, FRAME_TOOL_BAR_POSITION (f)),
+            Fcons (Qtool_bar_size,
+                   Fcons (make_fixnum (tool_bar_width),
+                          make_fixnum (tool_bar_height))),
+            Fcons (Qinternal_border_width,
+                   make_fixnum (internal_border_width)));
+}
+
+#endif
+
+DEFUN ("android-frame-geometry", Fandroid_frame_geometry,
+       Sandroid_frame_geometry,
+       0, 1, 0,
+       doc: /* Return geometric attributes of FRAME.
+FRAME must be a live frame and defaults to the selected one.  The return
+value is an association list of the attributes listed below.  All height
+and width values are in pixels.
+
+`outer-position' is a cons of the outer left and top edges of FRAME
+  relative to the origin - the position (0, 0) - of FRAME's display.
+
+`outer-size' is a cons of the outer width and height of FRAME.  The
+  outer size includes the title bar and the external borders as well as
+  any menu and/or tool bar of frame.
+
+`external-border-size' is a cons of the horizontal and vertical width of
+  FRAME's external borders as supplied by the window manager.
+
+`title-bar-size' is a cons of the width and height of the title bar of
+  FRAME as supplied by the window manager.  If both of them are zero,
+  FRAME has no title bar.  If only the width is zero, Emacs was not
+  able to retrieve the width information.
+
+`menu-bar-external', if non-nil, means the menu bar is external (never
+  included in the inner edges of FRAME).
+
+`menu-bar-size' is a cons of the width and height of the menu bar of
+  FRAME.
+
+`tool-bar-external', if non-nil, means the tool bar is external (never
+  included in the inner edges of FRAME).
+
+`tool-bar-position' tells on which side the tool bar on FRAME is and can
+  be one of `left', `top', `right' or `bottom'.  If this is nil, FRAME
+  has no tool bar.
+
+`tool-bar-size' is a cons of the width and height of the tool bar of
+  FRAME.
+
+`internal-border-width' is the width of the internal border of
+  FRAME.  */)
+  (Lisp_Object frame)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  return frame_geometry (frame, Qnil);
+#endif
+}
+
+DEFUN ("android-frame-edges", Fandroid_frame_edges, Sandroid_frame_edges, 0, 
2, 0,
+       doc: /* Return edge coordinates of FRAME.
+FRAME must be a live frame and defaults to the selected one.  The return
+value is a list of the form (LEFT, TOP, RIGHT, BOTTOM).  All values are
+in pixels relative to the origin - the position (0, 0) - of FRAME's
+display.
+
+If optional argument TYPE is the symbol `outer-edges', return the outer
+edges of FRAME.  The outer edges comprise the decorations of the window
+manager (like the title bar or external borders) as well as any external
+menu or tool bar of FRAME.  If optional argument TYPE is the symbol
+`native-edges' or nil, return the native edges of FRAME.  The native
+edges exclude the decorations of the window manager and any external
+menu or tool bar of FRAME.  If TYPE is the symbol `inner-edges', return
+the inner edges of FRAME.  These edges exclude title bar, any borders,
+menu bar or tool bar of FRAME.  */)
+  (Lisp_Object frame, Lisp_Object type)
+{
+#ifndef ANDROID_STUBIFY
+  return frame_geometry (frame, ((EQ (type, Qouter_edges)
+                                 || EQ (type, Qinner_edges))
+                                ? type
+                                : Qnative_edges));
+#else
+  return Qnil;
+#endif
+}
+
+#ifndef ANDROID_STUBIFY
+
+static Lisp_Object
+android_frame_list_z_order (struct android_display_info *dpyinfo,
+                           android_window window)
+{
+  android_window root, parent, *children;
+  unsigned int nchildren;
+  unsigned long i;
+  Lisp_Object frames;
+
+  frames = Qnil;
+
+  if (android_query_tree (window, &root, &parent,
+                         &children, &nchildren))
+    {
+      for (i = 0; i < nchildren; i++)
+       {
+         Lisp_Object frame, tail;
+
+         FOR_EACH_FRAME (tail, frame)
+            {
+              struct frame *cf = XFRAME (frame);
+
+              if (FRAME_ANDROID_P (cf)
+                  && (FRAME_ANDROID_WINDOW (cf) == children[i]))
+                frames = Fcons (frame, frames);
+            }
+       }
+
+      if (children)
+       xfree (children);
+    }
+
+  return frames;
+}
+
+#endif
+
+DEFUN ("android-frame-list-z-order", Fandroid_frame_list_z_order,
+       Sandroid_frame_list_z_order, 0, 1, 0,
+       doc: /* Return list of Emacs' frames, in Z (stacking) order.
+The optional argument TERMINAL specifies which display to ask about.
+TERMINAL should be either a frame or a display name (a string).  If
+omitted or nil, that stands for the selected frame's display.  Return
+nil if TERMINAL contains no Emacs frame.
+
+As a special case, if TERMINAL is non-nil and specifies a live frame,
+return the child frames of that frame in Z (stacking) order.
+
+Frames are listed from topmost (first) to bottommost (last).
+
+On Android, the order of the frames returned is undefined unless
+TERMINAL is a frame.  */)
+  (Lisp_Object terminal)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  struct android_display_info *dpyinfo;
+  android_window window;
+
+  dpyinfo = check_android_display_info (terminal);
+
+  if (FRAMEP (terminal) && FRAME_LIVE_P (XFRAME (terminal)))
+    window = FRAME_ANDROID_WINDOW (XFRAME (terminal));
+  else
+    window = dpyinfo->root_window;
+
+  return android_frame_list_z_order (dpyinfo, window);
+#endif
+}
+
+DEFUN ("android-frame-restack", Fandroid_frame_restack,
+       Sandroid_frame_restack, 2, 3, 0,
+       doc: /* Restack FRAME1 below FRAME2.
+This means that if both frames are visible and the display areas of
+these frames overlap, FRAME2 (partially) obscures FRAME1.  If optional
+third argument ABOVE is non-nil, restack FRAME1 above FRAME2.  This
+means that if both frames are visible and the display areas of these
+frames overlap, FRAME1 (partially) obscures FRAME2.
+
+This may be thought of as an atomic action performed in two steps: The
+first step removes FRAME1's window-step window from the display.  The
+second step reinserts FRAME1's window below (above if ABOVE is true)
+that of FRAME2.  Hence the position of FRAME2 in its display's Z
+\(stacking) order relative to all other frames excluding FRAME1 remains
+unaltered.
+
+The Android system refuses to restack windows, so this does not
+work.  */)
+  (Lisp_Object frame1, Lisp_Object frame2, Lisp_Object frame3)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  /* This is not supported on Android because of limitations in the
+     platform that prevent ViewGroups from restacking
+     SurfaceViews.  */
+  return Qnil;
+#endif
+}
+
+DEFUN ("android-mouse-absolute-pixel-position",
+       Fandroid_mouse_absolute_pixel_position,
+       Sandroid_mouse_absolute_pixel_position, 0, 0, 0,
+       doc: /* Return absolute position of mouse cursor in pixels.
+The position is returned as a cons cell (X . Y) of the coordinates of
+the mouse cursor position in pixels relative to a position (0, 0) of the
+selected frame's display.  This does not work on Android.  */)
+  (void)
+{
+  /* This cannot be implemented on Android.  */
+  return Qnil;
+}
+
+DEFUN ("android-set-mouse-absolute-pixel-position",
+       Fandroid_set_mouse_absolute_pixel_position,
+       Sandroid_set_mouse_absolute_pixel_position, 2, 2, 0,
+       doc: /* Move mouse pointer to a pixel position at (X, Y).  The
+coordinates X and Y are interpreted to start from the top-left corner
+of the screen.  This does not work on Android.  */)
+  (Lisp_Object x, Lisp_Object y)
+{
+  /* This cannot be implemented on Android.  */
+  return Qnil;
+}
+
+DEFUN ("android-get-connection", Fandroid_get_connection,
+       Sandroid_get_connection, 0, 0, 0,
+       doc: /* Get the connection to the display server.
+Return the terminal if it exists, else nil.
+
+Emacs cannot open a connection to the display server itself under
+Android, so there is no equivalent of `x-open-connection'.  */)
+  (void)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  Lisp_Object terminal;
+
+  terminal = Qnil;
+
+  if (x_display_list)
+    XSETTERMINAL (terminal, x_display_list->terminal);
+
+  return terminal;
+#endif
+}
+
+DEFUN ("x-display-list", Fx_display_list, Sx_display_list, 0, 0, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (void)
+{
+  Lisp_Object result;
+
+  result = Qnil;
+
+  if (x_display_list)
+    result = Fcons (XCAR (x_display_list->name_list_element),
+                   result);
+
+  return result;
+}
+
+#ifndef ANDROID_STUBIFY
+
+static void
+unwind_create_tip_frame (Lisp_Object frame)
+{
+  Lisp_Object deleted;
+
+  deleted = unwind_create_frame (frame);
+  if (EQ (deleted, Qt))
+    {
+      tip_window = ANDROID_NONE;
+      tip_frame = Qnil;
+    }
+}
+
+static Lisp_Object
+android_create_tip_frame (struct android_display_info *dpyinfo,
+                         Lisp_Object parms)
+{
+  struct frame *f;
+  Lisp_Object frame;
+  Lisp_Object name;
+  specpdl_ref count = SPECPDL_INDEX ();
+  bool face_change_before = face_change;
+
+  if (!dpyinfo->terminal->name)
+    error ("Terminal is not live, can't create new frames on it");
+
+  parms = Fcopy_alist (parms);
+
+  /* Get the name of the frame to use for resource lookup.  */
+  name = gui_display_get_arg (dpyinfo, parms, Qname, "name", "Name",
+                              RES_TYPE_STRING);
+  if (!STRINGP (name)
+      && !BASE_EQ (name, Qunbound)
+      && !NILP (name))
+    error ("Invalid frame name--not a string or nil");
+
+  frame = Qnil;
+  f = make_frame (false);
+  f->wants_modeline = false;
+  XSETFRAME (frame, f);
+  record_unwind_protect (unwind_create_tip_frame, frame);
+
+  f->terminal = dpyinfo->terminal;
+
+  /* By setting the output method, we're essentially saying that
+     the frame is live, as per FRAME_LIVE_P.  If we get a signal
+     from this point on, x_destroy_window might screw up reference
+     counts etc.  */
+  f->output_method = output_android;
+  f->output_data.android = xzalloc (sizeof *f->output_data.android);
+  FRAME_FONTSET (f) = -1;
+  f->output_data.android->white_relief.pixel = -1;
+  f->output_data.android->black_relief.pixel = -1;
+
+  f->tooltip = true;
+  fset_icon_name (f, Qnil);
+  FRAME_DISPLAY_INFO (f) = dpyinfo;
+  f->output_data.android->parent_desc = FRAME_DISPLAY_INFO (f)->root_window;
+
+  /* These colors will be set anyway later, but it's important
+     to get the color reference counts right, so initialize them!  */
+  {
+    Lisp_Object black;
+
+    /* Function android_decode_color can signal an error.  Make sure
+       to initialize color slots so that we won't try to free colors
+       we haven't allocated.  */
+    FRAME_FOREGROUND_PIXEL (f) = -1;
+    FRAME_BACKGROUND_PIXEL (f) = -1;
+    f->output_data.android->cursor_pixel = -1;
+    f->output_data.android->cursor_foreground_pixel = -1;
+    f->output_data.android->mouse_pixel = -1;
+
+    black = build_string ("black");
+    FRAME_FOREGROUND_PIXEL (f)
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    FRAME_BACKGROUND_PIXEL (f)
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    f->output_data.android->cursor_pixel
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    f->output_data.android->cursor_foreground_pixel
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+    f->output_data.android->mouse_pixel
+      = android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
+  }
+
+  /* Set the name; the functions to which we pass f expect the name to
+     be set.  */
+  if (BASE_EQ (name, Qunbound) || NILP (name))
+    f->explicit_name = false;
+  else
+    {
+      fset_name (f, name);
+      f->explicit_name = true;
+      /* use the frame's title when getting resources for this frame.  */
+      specbind (Qx_resource_name, name);
+    }
+
+  register_font_driver (&androidfont_driver, f);
+  register_font_driver (&android_sfntfont_driver, f);
+
+  image_cache_refcount
+    = FRAME_IMAGE_CACHE (f) ? FRAME_IMAGE_CACHE (f)->refcount : 0;
+#ifdef GLYPH_DEBUG
+  dpyinfo_refcount = dpyinfo->reference_count;
+#endif /* GLYPH_DEBUG */
+
+  gui_default_parameter (f, parms, Qfont_backend, Qnil,
+                         "fontBackend", "FontBackend", RES_TYPE_STRING);
+
+  /* Extract the window parameters from the supplied values that are
+     needed to determine window geometry.  */
+  android_default_font_parameter (f, parms);
+
+  gui_default_parameter (f, parms, Qborder_width, make_fixnum (0),
+                         "borderWidth", "BorderWidth", RES_TYPE_NUMBER);
+
+  /* This defaults to 1 in order to match xterm.  We recognize either
+     internalBorderWidth or internalBorder (which is what xterm calls
+     it).  */
+  if (NILP (Fassq (Qinternal_border_width, parms)))
+    {
+      Lisp_Object value;
+
+      value = gui_display_get_arg (dpyinfo, parms, Qinternal_border_width,
+                                   "internalBorder", "internalBorder",
+                                   RES_TYPE_NUMBER);
+      if (! BASE_EQ (value, Qunbound))
+       parms = Fcons (Fcons (Qinternal_border_width, value),
+                      parms);
+    }
+
+  gui_default_parameter (f, parms, Qinternal_border_width, make_fixnum (1),
+                         "internalBorderWidth", "internalBorderWidth",
+                         RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qright_divider_width, make_fixnum (0),
+                         NULL, NULL, RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0),
+                         NULL, NULL, RES_TYPE_NUMBER);
+
+  /* Also do the stuff which must be set before the window exists.  */
+  gui_default_parameter (f, parms, Qforeground_color, build_string ("black"),
+                         "foreground", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qbackground_color, build_string ("white"),
+                         "background", "Background", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qmouse_color, build_string ("black"),
+                         "pointerColor", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qcursor_color, build_string ("black"),
+                         "cursorColor", "Foreground", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qborder_color, build_string ("black"),
+                         "borderColor", "BorderColor", RES_TYPE_STRING);
+  gui_default_parameter (f, parms, Qno_special_glyphs, Qnil,
+                         NULL, NULL, RES_TYPE_BOOLEAN);
+
+  {
+    struct android_set_window_attributes attrs;
+    unsigned long mask;
+
+    block_input ();
+    mask = ANDROID_CW_OVERRIDE_REDIRECT | ANDROID_CW_BACK_PIXEL;
+
+    attrs.override_redirect = true;
+    attrs.background_pixel = FRAME_BACKGROUND_PIXEL (f);
+    tip_window
+      = FRAME_ANDROID_WINDOW (f)
+      = android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
+                              /* x, y, width, height, value-mask,
+                                 attrs.  */
+                              0, 0, 1, 1, mask, &attrs);
+    unblock_input ();
+  }
+
+  /* Init faces before gui_default_parameter is called for the
+     scroll-bar-width parameter because otherwise we end up in
+     init_iterator with a null face cache, which should not happen.  */
+  init_frame_faces (f);
+
+  gui_default_parameter (f, parms, Qinhibit_double_buffering, Qnil,
+                         "inhibitDoubleBuffering", "InhibitDoubleBuffering",
+                         RES_TYPE_BOOLEAN);
+
+  gui_figure_window_size (f, parms, false, false);
+
+  f->output_data.android->parent_desc = FRAME_DISPLAY_INFO (f)->root_window;
+
+  android_make_gc (f);
+
+  gui_default_parameter (f, parms, Qauto_raise, Qnil,
+                         "autoRaise", "AutoRaiseLower", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qauto_lower, Qnil,
+                         "autoLower", "AutoRaiseLower", RES_TYPE_BOOLEAN);
+  gui_default_parameter (f, parms, Qcursor_type, Qbox,
+                         "cursorType", "CursorType", RES_TYPE_SYMBOL);
+  gui_default_parameter (f, parms, Qalpha, Qnil,
+                         "alpha", "Alpha", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qalpha_background, Qnil,
+                         "alphaBackground", "AlphaBackground", 
RES_TYPE_NUMBER);
+
+  /* Add `tooltip' frame parameter's default value. */
+  if (NILP (Fframe_parameter (frame, Qtooltip)))
+    {
+      AUTO_FRAME_ARG (arg, Qtooltip, Qt);
+      Fmodify_frame_parameters (frame, arg);
+    }
+
+  /* FIXME - can this be done in a similar way to normal frames?
+     https://lists.gnu.org/r/emacs-devel/2007-10/msg00641.html */
+
+  /* Set the `display-type' frame parameter before setting up faces. */
+  {
+    Lisp_Object disptype;
+
+    disptype = Qcolor;
+
+    if (NILP (Fframe_parameter (frame, Qdisplay_type)))
+      {
+       AUTO_FRAME_ARG (arg, Qdisplay_type, disptype);
+       Fmodify_frame_parameters (frame, arg);
+      }
+  }
+
+  /* Set up faces after all frame parameters are known.  This call
+     also merges in face attributes specified for new frames.  */
+  {
+    Lisp_Object bg = Fframe_parameter (frame, Qbackground_color);
+
+    call2 (Qface_set_after_frame_default, frame, Qnil);
+
+    if (!EQ (bg, Fframe_parameter (frame, Qbackground_color)))
+      {
+       AUTO_FRAME_ARG (arg, Qbackground_color, bg);
+       Fmodify_frame_parameters (frame, arg);
+      }
+  }
+
+  f->no_split = true;
+
+  /* Now that the frame will be official, it counts as a reference to
+     its display and terminal.  */
+  f->terminal->reference_count++;
+
+  /* It is now ok to make the frame official even if we get an error
+     below.  And the frame needs to be on Vframe_list or making it
+     visible won't work.  */
+  Vframe_list = Fcons (frame, Vframe_list);
+  f->can_set_window_size = true;
+  adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f),
+                    0, true, Qtip_frame);
+
+  /* Setting attributes of faces of the tooltip frame from resources
+     and similar will set face_change, which leads to the clearing of
+     all current matrices.  Since this isn't necessary here, avoid it
+     by resetting face_change to the value it had before we created
+     the tip frame.  */
+  face_change = face_change_before;
+
+  /* Discard the unwind_protect.  */
+  return unbind_to (count, frame);
+}
+
+static Lisp_Object
+android_hide_tip (bool delete)
+{
+  if (!NILP (tip_timer))
+    {
+      call1 (Qcancel_timer, tip_timer);
+      tip_timer = Qnil;
+    }
+
+  if (NILP (tip_frame)
+      || (!delete
+         && !NILP (tip_frame)
+         && FRAME_LIVE_P (XFRAME (tip_frame))
+         && !FRAME_VISIBLE_P (XFRAME (tip_frame))))
+    return Qnil;
+  else
+    {
+      Lisp_Object was_open = Qnil;
+
+      specpdl_ref count = SPECPDL_INDEX ();
+      specbind (Qinhibit_redisplay, Qt);
+      specbind (Qinhibit_quit, Qt);
+
+      if (!NILP (tip_frame))
+       {
+         struct frame *f = XFRAME (tip_frame);
+
+         if (FRAME_LIVE_P (f))
+           {
+             if (delete)
+               {
+                 delete_frame (tip_frame, Qnil);
+                 tip_frame = Qnil;
+               }
+             else
+               android_make_frame_invisible (XFRAME (tip_frame));
+
+             was_open = Qt;
+           }
+         else
+           tip_frame = Qnil;
+       }
+      else
+       tip_frame = Qnil;
+
+      return unbind_to (count, was_open);
+    }
+}
+
+static void
+compute_tip_xy (struct frame *f, Lisp_Object parms, Lisp_Object dx,
+               Lisp_Object dy, int width, int height, int *root_x,
+               int *root_y)
+{
+  Lisp_Object left, top, right, bottom;
+  int min_x, min_y, max_x, max_y = -1;
+  android_window window;
+  struct frame *mouse_frame;
+
+  /* Initialize these values in case there is no mouse frame.  */
+  *root_x = 0;
+  *root_y = 0;
+
+  /* User-specified position?  */
+  left = CDR (Fassq (Qleft, parms));
+  top  = CDR (Fassq (Qtop, parms));
+  right = CDR (Fassq (Qright, parms));
+  bottom = CDR (Fassq (Qbottom, parms));
+
+  /* Move the tooltip window where the mouse pointer was last seen.
+     Resize and show it.  */
+  if ((!FIXNUMP (left) && !FIXNUMP (right))
+      || (!FIXNUMP (top) && !FIXNUMP (bottom)))
+    {
+      if (x_display_list->last_mouse_motion_frame)
+       {
+         *root_x = x_display_list->last_mouse_motion_x;
+         *root_y = x_display_list->last_mouse_motion_y;
+         mouse_frame = x_display_list->last_mouse_motion_frame;
+         window = FRAME_ANDROID_WINDOW (mouse_frame);
+
+         /* Translate the coordinates to the screen.  */
+         android_translate_coordinates (window, *root_x, *root_y,
+                                        root_x, root_y);
+       }
+    }
+
+  min_x = 0;
+  min_y = 0;
+  max_x = android_get_screen_width ();
+  max_y = android_get_screen_height ();
+
+  if (FIXNUMP (top))
+    *root_y = XFIXNUM (top);
+  else if (FIXNUMP (bottom))
+    *root_y = XFIXNUM (bottom) - height;
+  else if (*root_y + XFIXNUM (dy) <= min_y)
+    *root_y = min_y; /* Can happen for negative dy */
+  else if (*root_y + XFIXNUM (dy) + height <= max_y)
+    /* It fits below the pointer */
+    *root_y += XFIXNUM (dy);
+  else if (height + XFIXNUM (dy) + min_y <= *root_y)
+    /* It fits above the pointer.  */
+    *root_y -= height + XFIXNUM (dy);
+  else
+    /* Put it on the top.  */
+    *root_y = min_y;
+
+  if (FIXNUMP (left))
+    *root_x = XFIXNUM (left);
+  else if (FIXNUMP (right))
+    *root_x = XFIXNUM (right) - width;
+  else if (*root_x + XFIXNUM (dx) <= min_x)
+    *root_x = 0; /* Can happen for negative dx */
+  else if (*root_x + XFIXNUM (dx) + width <= max_x)
+    /* It fits to the right of the pointer.  */
+    *root_x += XFIXNUM (dx);
+  else if (width + XFIXNUM (dx) + min_x <= *root_x)
+    /* It fits to the left of the pointer.  */
+    *root_x -= width + XFIXNUM (dx);
+  else
+    /* Put it left justified on the screen -- it ought to fit that way.  */
+    *root_x = min_x;
+}
+
+#endif
+
+DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (Lisp_Object string, Lisp_Object frame, Lisp_Object parms,
+   Lisp_Object timeout, Lisp_Object dx, Lisp_Object dy)
+{
+#ifdef ANDROID_STUBIFY
+  error ("Android cross-compilation stub called!");
+  return Qnil;
+#else
+  struct frame *f, *tip_f;
+  struct window *w;
+  int root_x, root_y;
+  struct buffer *old_buffer;
+  struct text_pos pos;
+  int width, height;
+  int old_windows_or_buffers_changed = windows_or_buffers_changed;
+  specpdl_ref count = SPECPDL_INDEX ();
+  Lisp_Object window, size, tip_buf;
+  bool displayed;
+#ifdef ENABLE_CHECKING
+  struct glyph_row *row, *end;
+#endif
+  AUTO_STRING (tip, " *tip*");
+
+  specbind (Qinhibit_redisplay, Qt);
+
+  CHECK_STRING (string);
+  if (SCHARS (string) == 0)
+    string = make_unibyte_string (" ", 1);
+
+  if (NILP (frame))
+    frame = selected_frame;
+  f = decode_window_system_frame (frame);
+
+  if (NILP (timeout))
+    timeout = Vx_show_tooltip_timeout;
+  CHECK_FIXNAT (timeout);
+
+  if (NILP (dx))
+    dx = make_fixnum (5);
+  else
+    CHECK_FIXNUM (dx);
+
+  if (NILP (dy))
+    dy = make_fixnum (-10);
+  else
+    CHECK_FIXNUM (dy);
+
+  tip_dx = dx;
+  tip_dy = dy;
+
+  if (!NILP (tip_frame) && FRAME_LIVE_P (XFRAME (tip_frame)))
+    {
+      if (FRAME_VISIBLE_P (XFRAME (tip_frame))
+         && !NILP (Fequal_including_properties (tip_last_string,
+                                                string))
+         && !NILP (Fequal (tip_last_parms, parms)))
+       {
+         /* Only DX and DY have changed.  */
+         tip_f = XFRAME (tip_frame);
+         if (!NILP (tip_timer))
+           {
+             call1 (Qcancel_timer, tip_timer);
+             tip_timer = Qnil;
+           }
+
+         block_input ();
+         compute_tip_xy (tip_f, parms, dx, dy, FRAME_PIXEL_WIDTH (tip_f),
+                         FRAME_PIXEL_HEIGHT (tip_f), &root_x, &root_y);
+         android_move_window (FRAME_ANDROID_WINDOW (tip_f),
+                              root_x, root_y);
+         unblock_input ();
+
+         goto start_timer;
+       }
+      else
+       android_hide_tip (true);
+    }
+  else
+    android_hide_tip (true);
+
+  tip_last_frame = frame;
+  tip_last_string = string;
+  tip_last_parms = parms;
+
+  if (NILP (tip_frame) || !FRAME_LIVE_P (XFRAME (tip_frame)))
+    {
+      /* Add default values to frame parameters.  */
+      if (NILP (Fassq (Qname, parms)))
+       parms = Fcons (Fcons (Qname, build_string ("tooltip")), parms);
+      if (NILP (Fassq (Qinternal_border_width, parms)))
+       parms = Fcons (Fcons (Qinternal_border_width, make_fixnum (3)),
+                      parms);
+      if (NILP (Fassq (Qborder_width, parms)))
+       parms = Fcons (Fcons (Qborder_width, make_fixnum (1)), parms);
+      if (NILP (Fassq (Qborder_color, parms)))
+       parms = Fcons (Fcons (Qborder_color, build_string ("lightyellow")),
+                      parms);
+      if (NILP (Fassq (Qbackground_color, parms)))
+       parms = Fcons (Fcons (Qbackground_color,
+                             build_string ("lightyellow")),
+                      parms);
+
+      /* Create a frame for the tooltip, and record it in the global
+        variable tip_frame.  */
+      if (NILP (tip_frame = android_create_tip_frame (FRAME_DISPLAY_INFO (f),
+                                                     parms)))
+       /* Creating the tip frame failed.  */
+       return unbind_to (count, Qnil);
+    }
+
+  tip_f = XFRAME (tip_frame);
+  window = FRAME_ROOT_WINDOW (tip_f);
+  tip_buf = Fget_buffer_create (tip, Qnil);
+  /* We will mark the tip window a "pseudo-window" below, and such
+     windows cannot have display margins.  */
+  bset_left_margin_cols (XBUFFER (tip_buf), make_fixnum (0));
+  bset_right_margin_cols (XBUFFER (tip_buf), make_fixnum (0));
+  set_window_buffer (window, tip_buf, false, false);
+  w = XWINDOW (window);
+  w->pseudo_window_p = true;
+  /* Try to avoid that `other-window' select us (Bug#47207).  */
+  Fset_window_parameter (window, Qno_other_window, Qt);
+
+  /* Set up the frame's root window.  Note: The following code does not
+     try to size the window or its frame correctly.  Its only purpose is
+     to make the subsequent text size calculations work.  The right
+     sizes should get installed when the toolkit gets back to us.  */
+  w->left_col = 0;
+  w->top_line = 0;
+  w->pixel_left = 0;
+  w->pixel_top = 0;
+
+  if (CONSP (Vx_max_tooltip_size)
+      && RANGED_FIXNUMP (1, XCAR (Vx_max_tooltip_size), INT_MAX)
+      && RANGED_FIXNUMP (1, XCDR (Vx_max_tooltip_size), INT_MAX))
+    {
+      w->total_cols = XFIXNAT (XCAR (Vx_max_tooltip_size));
+      w->total_lines = XFIXNAT (XCDR (Vx_max_tooltip_size));
+    }
+  else
+    {
+      w->total_cols = 80;
+      w->total_lines = 40;
+    }
+
+  w->pixel_width = w->total_cols * FRAME_COLUMN_WIDTH (tip_f);
+  w->pixel_height = w->total_lines * FRAME_LINE_HEIGHT (tip_f);
+  FRAME_TOTAL_COLS (tip_f) = w->total_cols;
+  adjust_frame_glyphs (tip_f);
+
+  /* Insert STRING into root window's buffer and fit the frame to the
+     buffer.  */
+  specpdl_ref count_1 = SPECPDL_INDEX ();
+  old_buffer = current_buffer;
+  set_buffer_internal_1 (XBUFFER (w->contents));
+  bset_truncate_lines (current_buffer, Qnil);
+  specbind (Qinhibit_read_only, Qt);
+  specbind (Qinhibit_modification_hooks, Qt);
+  specbind (Qinhibit_point_motion_hooks, Qt);
+  Ferase_buffer ();
+  Finsert (1, &string);
+  clear_glyph_matrix (w->desired_matrix);
+  clear_glyph_matrix (w->current_matrix);
+  SET_TEXT_POS (pos, BEGV, BEGV_BYTE);
+  displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE);
+
+  if (!displayed && NILP (Vx_max_tooltip_size))
+    {
+#ifdef ENABLE_CHECKING
+      row = w->desired_matrix->rows;
+      end = w->desired_matrix->rows + w->desired_matrix->nrows;
+
+      while (row < end)
+       {
+         if (!row->displays_text_p
+             || row->ends_at_zv_p)
+           break;
+         ++row;
+       }
+
+      eassert (row < end && row->ends_at_zv_p);
+#endif
+    }
+
+  /* Calculate size of tooltip window.  */
+  size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil,
+                                 make_fixnum (w->pixel_height), Qnil,
+                                 Qnil);
+  /* Add the frame's internal border to calculated size.  */
+  width = XFIXNUM (CAR (size)) + 2 * FRAME_INTERNAL_BORDER_WIDTH (tip_f);
+  height = XFIXNUM (CDR (size)) + 2 * FRAME_INTERNAL_BORDER_WIDTH (tip_f);
+
+  /* Calculate position of tooltip frame.  */
+  compute_tip_xy (tip_f, parms, dx, dy, width, height, &root_x, &root_y);
+
+  /* Show tooltip frame.  */
+  block_input ();
+  android_move_resize_window (FRAME_ANDROID_WINDOW (tip_f),
+                             root_x, root_y, width,
+                             height);
+  android_map_raised (FRAME_ANDROID_WINDOW (tip_f));
+  unblock_input ();
+
+  /* Garbage the tip frame too.  */
+  SET_FRAME_GARBAGED (tip_f);
+
+  w->must_be_updated_p = true;
+  update_single_window (w);
+  flush_frame (tip_f);
+  set_buffer_internal_1 (old_buffer);
+  unbind_to (count_1, Qnil);
+  windows_or_buffers_changed = old_windows_or_buffers_changed;
+
+  /* MapNotify events are not sent on Android, so make the frame
+     visible.  */
+
+  SET_FRAME_VISIBLE (tip_f, true);
+
+ start_timer:
+  /* Let the tip disappear after timeout seconds.  */
+  tip_timer = call3 (Qrun_at_time, timeout, Qnil,
+                    Qx_hide_tip);
+
+  return unbind_to (count, Qnil);
+#endif
+}
+
+DEFUN ("x-hide-tip", Fx_hide_tip, Sx_hide_tip, 0, 0, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (void)
+{
+#ifdef ANDROID_STUBIFY
+  /* Fx_hide_tip is called from pre-command-hook (in turn called from
+     the tests.)  Since signaling here prevents any tests from being
+     run, refrain from protesting if this stub is called.  */
+#if 0
+  error ("Android cross-compilation stub called!");
+#endif /* 0 */
+  return Qnil;
+#else /* !ANDROID_STUBIFY */
+  return android_hide_tip (true);
+#endif /* ANDROID_STUBIFY */
+}
+
+DEFUN ("android-detect-mouse", Fandroid_detect_mouse,
+       Sandroid_detect_mouse, 0, 0, 0,
+       doc: /* Figure out whether or not there is a mouse.
+Return non-nil if a mouse is connected to this computer, and nil if
+there is no mouse.  */)
+  (void)
+{
+#ifndef ANDROID_STUBIFY
+  /* If no display connection is present, just return nil.  */
+
+  if (!android_init_gui)
+    return Qnil;
+
+  return android_detect_mouse () ? Qt : Qnil;
+#else
+  return Qnil;
+#endif
+}
+
+DEFUN ("android-toggle-on-screen-keyboard",
+       Fandroid_toggle_on_screen_keyboard,
+       Sandroid_toggle_on_screen_keyboard, 2, 2, 0,
+       doc: /* Display or hide the on-screen keyboard.
+If HIDE is non-nil, hide the on screen keyboard if it is currently
+being displayed.  Else, request that the system display it on behalf
+of FRAME.  This request may be rejected if FRAME does not have the
+input focus.  */)
+  (Lisp_Object frame, Lisp_Object hide)
+{
+#ifndef ANDROID_STUBIFY
+  struct frame *f;
+
+  f = decode_window_system_frame (frame);
+
+  block_input ();
+  android_toggle_on_screen_keyboard (FRAME_ANDROID_WINDOW (f),
+                                    NILP (hide));
+  unblock_input ();
+#endif
+
+  return Qnil;
+}
+
+
+
+#ifndef ANDROID_STUBIFY
+
+static void
+android_set_background_color (struct frame *f, Lisp_Object arg,
+                             Lisp_Object oldval)
+{
+  struct android_output *x;
+  unsigned long bg;
+
+  x = f->output_data.android;
+  bg = android_decode_color (f, arg, WHITE_PIX_DEFAULT (f));
+  FRAME_BACKGROUND_PIXEL (f) = bg;
+
+  if (FRAME_ANDROID_WINDOW (f) != 0)
+    {
+      block_input ();
+      android_set_background (x->normal_gc, bg);
+      android_set_foreground (x->reverse_gc, bg);
+      android_set_window_background (FRAME_ANDROID_WINDOW (f), bg);
+      android_set_foreground (x->cursor_gc, bg);
+      unblock_input ();
+
+      update_face_from_frame_parameter (f, Qbackground_color, arg);
+
+      if (FRAME_VISIBLE_P (f))
+        redraw_frame (f);
+    }
+}
+
+static void
+android_set_border_color (struct frame *f, Lisp_Object arg,
+                         Lisp_Object oldval)
+{
+  /* Left unimplemented because Android has no window borders.  */
+  CHECK_STRING (arg);
+  android_decode_color (f, arg, BLACK_PIX_DEFAULT (f));
+  update_face_from_frame_parameter (f, Qborder_color, arg);
+}
+
+static void
+android_set_cursor_color (struct frame *f, Lisp_Object arg,
+                         Lisp_Object oldval)
+{
+  unsigned long fore_pixel, pixel;
+  struct android_output *x;
+
+  x = f->output_data.android;
+
+  if (!NILP (Vx_cursor_fore_pixel))
+    fore_pixel = android_decode_color (f, Vx_cursor_fore_pixel,
+                                      WHITE_PIX_DEFAULT (f));
+  else
+    fore_pixel = FRAME_BACKGROUND_PIXEL (f);
+
+  pixel = android_decode_color (f, arg, BLACK_PIX_DEFAULT (f));
+
+  /* Make sure that the cursor color differs from the background color.  */
+  if (pixel == FRAME_BACKGROUND_PIXEL (f))
+    {
+      pixel = FRAME_FOREGROUND_PIXEL (f);
+      if (pixel == fore_pixel)
+       fore_pixel = FRAME_BACKGROUND_PIXEL (f);
+    }
+
+  x->cursor_foreground_pixel = fore_pixel;
+  x->cursor_pixel = pixel;
+
+  if (FRAME_ANDROID_WINDOW (f) != 0)
+    {
+      block_input ();
+      android_set_background (x->cursor_gc, x->cursor_pixel);
+      android_set_foreground (x->cursor_gc, fore_pixel);
+      unblock_input ();
+
+      if (FRAME_VISIBLE_P (f))
+       {
+         gui_update_cursor (f, false);
+         gui_update_cursor (f, true);
+       }
+    }
+
+  update_face_from_frame_parameter (f, Qcursor_color, arg);
+}
+
+static void
+android_set_cursor_type (struct frame *f, Lisp_Object arg,
+                        Lisp_Object oldval)
+{
+  set_frame_cursor_types (f, arg);
+}
+
+static void
+android_set_foreground_color (struct frame *f, Lisp_Object arg,
+                             Lisp_Object oldval)
+{
+  struct android_output *x;
+  unsigned long fg, old_fg;
+
+  x = f->output_data.android;
+
+  fg = android_decode_color (f, arg, BLACK_PIX_DEFAULT (f));
+  old_fg = FRAME_FOREGROUND_PIXEL (f);
+  FRAME_FOREGROUND_PIXEL (f) = fg;
+
+  if (FRAME_ANDROID_WINDOW (f) != 0)
+    {
+      block_input ();
+      android_set_foreground (x->normal_gc, fg);
+      android_set_background (x->reverse_gc, fg);
+
+      if (x->cursor_pixel == old_fg)
+       {
+         x->cursor_pixel = fg;
+         android_set_background (x->cursor_gc, x->cursor_pixel);
+       }
+
+      unblock_input ();
+
+      update_face_from_frame_parameter (f, Qforeground_color, arg);
+
+      if (FRAME_VISIBLE_P (f))
+        redraw_frame (f);
+    }
+}
+
+static void
+android_set_child_frame_border_width (struct frame *f, Lisp_Object arg,
+                                     Lisp_Object oldval)
+{
+  int border;
+
+  if (NILP (arg))
+    border = -1;
+  else if (RANGED_FIXNUMP (0, arg, INT_MAX))
+    border = XFIXNAT (arg);
+  else
+    signal_error ("Invalid child frame border width", arg);
+
+  if (border != FRAME_CHILD_FRAME_BORDER_WIDTH (f))
+    {
+      f->child_frame_border_width = border;
+
+      if (FRAME_ANDROID_WINDOW (f))
+       {
+         adjust_frame_size (f, -1, -1, 3, false, Qchild_frame_border_width);
+         android_clear_under_internal_border (f);
+       }
+    }
+}
+
+static void
+android_set_internal_border_width (struct frame *f, Lisp_Object arg,
+                                  Lisp_Object oldval)
+{
+  int border = check_int_nonnegative (arg);
+
+  if (border != FRAME_INTERNAL_BORDER_WIDTH (f))
+    {
+      f->internal_border_width = border;
+
+      if (FRAME_ANDROID_WINDOW (f))
+       {
+         adjust_frame_size (f, -1, -1, 3, false, Qinternal_border_width);
+         android_clear_under_internal_border (f);
+       }
+    }
+}
+
+static void
+android_set_menu_bar_lines (struct frame *f, Lisp_Object value,
+                           Lisp_Object oldval)
+{
+  int nlines;
+  int olines = FRAME_MENU_BAR_LINES (f);
+
+  /* Right now, menu bars don't work properly in minibuf-only frames;
+     most of the commands try to apply themselves to the minibuffer
+     frame itself, and get an error because you can't switch buffers
+     in or split the minibuffer window.  */
+  if (FRAME_MINIBUF_ONLY_P (f) || FRAME_PARENT_FRAME (f))
+    return;
+
+  if (TYPE_RANGED_FIXNUMP (int, value))
+    nlines = XFIXNUM (value);
+  else
+    nlines = 0;
+
+  /* Make sure we redisplay all windows in this frame.  */
+  fset_redisplay (f);
+
+  FRAME_MENU_BAR_LINES (f) = nlines;
+  FRAME_MENU_BAR_HEIGHT (f) = nlines * FRAME_LINE_HEIGHT (f);
+  if (FRAME_ANDROID_WINDOW (f))
+    android_clear_under_internal_border (f);
+
+  /* If the menu bar height gets changed, the internal border below
+     the top margin has to be cleared.  Also, if the menu bar gets
+     larger, the area for the added lines has to be cleared except for
+     the first menu bar line that is to be drawn later.  */
+  if (nlines != olines)
+    {
+      int height = FRAME_INTERNAL_BORDER_WIDTH (f);
+      int width = FRAME_PIXEL_WIDTH (f);
+      int y;
+
+      adjust_frame_size (f, -1, -1, 3, true, Qmenu_bar_lines);
+
+      /* height can be zero here. */
+      if (FRAME_ANDROID_WINDOW (f) && height > 0 && width > 0)
+       {
+         y = FRAME_TOP_MARGIN_HEIGHT (f);
+
+         block_input ();
+         android_clear_area (FRAME_ANDROID_DRAWABLE (f),
+                             0, y, width, height);
+         unblock_input ();
+       }
+
+      if (nlines > 1 && nlines > olines)
+       {
+         y = (olines == 0 ? 1 : olines) * FRAME_LINE_HEIGHT (f);
+         height = nlines * FRAME_LINE_HEIGHT (f) - y;
+
+         block_input ();
+         android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, y,
+                             width, height);
+         unblock_input ();
+       }
+
+      if (nlines == 0 && WINDOWP (f->menu_bar_window))
+       clear_glyph_matrix (XWINDOW (f->menu_bar_window)->current_matrix);
+    }
+
+  adjust_frame_glyphs (f);
+}
+
+
+
+/* These enums must stay in sync with the mouse_cursor_types array
+   below!  */
+
+enum mouse_cursor
+  {
+    mouse_cursor_text,
+    mouse_cursor_nontext,
+    mouse_cursor_hourglass,
+    mouse_cursor_mode,
+    mouse_cursor_hand,
+    mouse_cursor_horizontal_drag,
+    mouse_cursor_vertical_drag,
+    mouse_cursor_left_edge,
+    mouse_cursor_top_left_corner,
+    mouse_cursor_top_edge,
+    mouse_cursor_top_right_corner,
+    mouse_cursor_right_edge,
+    mouse_cursor_bottom_right_corner,
+    mouse_cursor_bottom_edge,
+    mouse_cursor_bottom_left_corner,
+    mouse_cursor_max
+  };
+
+struct mouse_cursor_types
+{
+  /* Printable name for error messages (optional).  */
+  const char *name;
+
+  /* Lisp variable controlling the cursor shape.  */
+  /* FIXME: A couple of these variables are defined in the C code but
+     are not actually accessible from Lisp.  They should probably be
+     made accessible or removed.  */
+  Lisp_Object *shape_var_ptr;
+
+  /* The default shape.  */
+  int default_shape;
+};
+
+/* This array must stay in sync with enum mouse_cursor above!  */
+
+static const struct mouse_cursor_types mouse_cursor_types[] =
+  {
+    {"text", &Vx_pointer_shape, ANDROID_XC_XTERM, },
+    {"nontext", &Vx_nontext_pointer_shape, ANDROID_XC_LEFT_PTR, },
+    {"hourglass", &Vx_hourglass_pointer_shape, ANDROID_XC_WATCH, },
+    {"modeline", &Vx_mode_pointer_shape, ANDROID_XC_XTERM, },
+    {NULL, &Vx_sensitive_text_pointer_shape, ANDROID_XC_HAND2, },
+    {NULL, &Vx_window_horizontal_drag_shape, ANDROID_XC_SB_H_DOUBLE_ARROW, },
+    {NULL, &Vx_window_vertical_drag_shape, ANDROID_XC_SB_V_DOUBLE_ARROW, },
+    {NULL, &Vx_window_left_edge_shape, ANDROID_XC_LEFT_SIDE, },
+    {NULL, &Vx_window_top_left_corner_shape, ANDROID_XC_TOP_LEFT_CORNER, },
+    {NULL, &Vx_window_top_edge_shape, ANDROID_XC_TOP_SIDE, },
+    {NULL, &Vx_window_top_right_corner_shape, ANDROID_XC_TOP_RIGHT_CORNER, },
+    {NULL, &Vx_window_right_edge_shape, ANDROID_XC_RIGHT_SIDE, },
+    {NULL, &Vx_window_bottom_right_corner_shape,
+     ANDROID_XC_BOTTOM_RIGHT_CORNER, },
+    {NULL, &Vx_window_bottom_edge_shape, ANDROID_XC_BOTTOM_SIDE, },
+    {NULL, &Vx_window_bottom_left_corner_shape,
+       ANDROID_XC_BOTTOM_LEFT_CORNER, },
+  };
+
+struct mouse_cursor_data
+{
+  /* Cursor numbers chosen.  */
+  unsigned int cursor_num[mouse_cursor_max];
+
+  /* Allocated Cursor values, or zero for failed attempts.  */
+  android_cursor cursor[mouse_cursor_max];
+};
+
+
+
+static void
+android_set_mouse_color (struct frame *f, Lisp_Object arg,
+                        Lisp_Object oldval)
+{
+  struct android_output *x = f->output_data.android;
+  struct mouse_cursor_data cursor_data = { -1, -1 };
+  unsigned long pixel = android_decode_color (f, arg, BLACK_PIX_DEFAULT (f));
+  unsigned long mask_color = FRAME_BACKGROUND_PIXEL (f);
+  int i;
+
+  /* Don't let pointers be invisible.  */
+  if (mask_color == pixel)
+    pixel = FRAME_FOREGROUND_PIXEL (f);
+
+  x->mouse_pixel = pixel;
+
+  for (i = 0; i < mouse_cursor_max; i++)
+    {
+      Lisp_Object shape_var = *mouse_cursor_types[i].shape_var_ptr;
+      cursor_data.cursor_num[i]
+       = (!NILP (shape_var)
+          ? check_uinteger_max (shape_var, UINT_MAX)
+          : mouse_cursor_types[i].default_shape);
+    }
+
+  block_input ();
+
+  for (i = 0; i < mouse_cursor_max; i++)
+    cursor_data.cursor[i]
+      = android_create_font_cursor (cursor_data.cursor_num[i]);
+
+  if (FRAME_ANDROID_WINDOW (f))
+    {
+      f->output_data.android->current_cursor
+       = cursor_data.cursor[mouse_cursor_text];
+      android_define_cursor (FRAME_ANDROID_WINDOW (f),
+                            f->output_data.android->current_cursor);
+    }
+
+#define INSTALL_CURSOR(FIELD, SHORT_INDEX)                             \
+   eassert (x->FIELD                                                   \
+           != cursor_data.cursor[mouse_cursor_ ## SHORT_INDEX]);       \
+   if (x->FIELD != 0)                                                  \
+     android_free_cursor (x->FIELD);                                   \
+   x->FIELD = cursor_data.cursor[mouse_cursor_ ## SHORT_INDEX];
+
+  INSTALL_CURSOR (text_cursor, text);
+  INSTALL_CURSOR (nontext_cursor, nontext);
+  INSTALL_CURSOR (hourglass_cursor, hourglass);
+  INSTALL_CURSOR (modeline_cursor, mode);
+  INSTALL_CURSOR (hand_cursor, hand);
+  INSTALL_CURSOR (horizontal_drag_cursor, horizontal_drag);
+  INSTALL_CURSOR (vertical_drag_cursor, vertical_drag);
+  INSTALL_CURSOR (left_edge_cursor, left_edge);
+  INSTALL_CURSOR (top_left_corner_cursor, top_left_corner);
+  INSTALL_CURSOR (top_edge_cursor, top_edge);
+  INSTALL_CURSOR (top_right_corner_cursor, top_right_corner);
+  INSTALL_CURSOR (right_edge_cursor, right_edge);
+  INSTALL_CURSOR (bottom_right_corner_cursor, bottom_right_corner);
+  INSTALL_CURSOR (bottom_edge_cursor, bottom_edge);
+  INSTALL_CURSOR (bottom_left_corner_cursor, bottom_left_corner);
+
+#undef INSTALL_CURSOR
+
+  unblock_input ();
+
+  update_face_from_frame_parameter (f, Qmouse_color, arg);
+}
+
+static void
+android_set_title (struct frame *f, Lisp_Object name,
+                  Lisp_Object old_name)
+{
+  /* Don't change the title if it's already NAME.  */
+  if (EQ (name, f->title))
+    return;
+
+  update_mode_lines = 38;
+
+  fset_title (f, name);
+
+  if (NILP (name))
+    name = f->name;
+  else
+    CHECK_STRING (name);
+}
+
+static void
+android_set_alpha (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
+{
+  double alpha = 1.0;
+  double newval[2];
+  int i;
+  Lisp_Object item;
+
+  /* N.B. that setting the window alpha is actually unsupported under
+     Android.  */
+
+  for (i = 0; i < 2; i++)
+    {
+      newval[i] = 1.0;
+      if (CONSP (arg))
+        {
+          item = CAR (arg);
+          arg  = CDR (arg);
+        }
+      else
+        item = arg;
+
+      if (NILP (item))
+       alpha = - 1.0;
+      else if (FLOATP (item))
+       {
+         alpha = XFLOAT_DATA (item);
+         if (! (0 <= alpha && alpha <= 1.0))
+           args_out_of_range (make_float (0.0), make_float (1.0));
+       }
+      else if (FIXNUMP (item))
+       {
+         EMACS_INT ialpha = XFIXNUM (item);
+         if (! (0 <= ialpha && ialpha <= 100))
+           args_out_of_range (make_fixnum (0), make_fixnum (100));
+         alpha = ialpha / 100.0;
+       }
+      else
+       wrong_type_argument (Qnumberp, item);
+      newval[i] = alpha;
+    }
+
+  for (i = 0; i < 2; i++)
+    f->alpha[i] = newval[i];
+
+  if (FRAME_TERMINAL (f)->set_frame_alpha_hook)
+    {
+      block_input ();
+      FRAME_TERMINAL (f)->set_frame_alpha_hook (f);
+      unblock_input ();
+    }
+}
+
+static void
+android_set_no_focus_on_map (struct frame *f, Lisp_Object new_value,
+                            Lisp_Object old_value)
+{
+  if (!EQ (new_value, old_value))
+    {
+      android_set_dont_focus_on_map (FRAME_ANDROID_WINDOW (f),
+                                    !NILP (new_value));
+      FRAME_NO_FOCUS_ON_MAP (f) = !NILP (new_value);
+    }
+}
+
+static void
+android_set_no_accept_focus (struct frame *f, Lisp_Object new_value,
+                            Lisp_Object old_value)
+{
+  if (!EQ (new_value, old_value))
+    {
+      android_set_dont_accept_focus (FRAME_ANDROID_WINDOW (f),
+                                    !NILP (new_value));
+      FRAME_NO_ACCEPT_FOCUS (f) = !NILP (new_value);
+    }
+}
+
+frame_parm_handler android_frame_parm_handlers[] =
+{
+  gui_set_autoraise,
+  gui_set_autolower,
+  android_set_background_color,
+  android_set_border_color,
+  gui_set_border_width,
+  android_set_cursor_color,
+  android_set_cursor_type,
+  gui_set_font,
+  android_set_foreground_color,
+  NULL,
+  NULL,
+  android_set_child_frame_border_width,
+  android_set_internal_border_width,
+  gui_set_right_divider_width,
+  gui_set_bottom_divider_width,
+  android_set_menu_bar_lines,
+  android_set_mouse_color,
+  android_explicitly_set_name,
+  gui_set_scroll_bar_width,
+  gui_set_scroll_bar_height,
+  android_set_title,
+  gui_set_unsplittable,
+  gui_set_vertical_scroll_bars,
+  gui_set_horizontal_scroll_bars,
+  gui_set_visibility,
+  android_set_tab_bar_lines,
+  android_set_tool_bar_lines,
+  NULL,
+  NULL,
+  gui_set_screen_gamma,
+  gui_set_line_spacing,
+  gui_set_left_fringe,
+  gui_set_right_fringe,
+  NULL,
+  gui_set_fullscreen,
+  gui_set_font_backend,
+  android_set_alpha,
+  NULL,
+  android_set_tool_bar_position,
+  NULL,
+  NULL,
+  android_set_parent_frame,
+  NULL,
+  android_set_no_focus_on_map,
+  android_set_no_accept_focus,
+  NULL,
+  NULL,
+  gui_set_no_special_glyphs,
+  NULL,
+  NULL,
+};
+
+
+
+/* Battery information support.  */
+
+DEFUN ("android-query-battery", Fandroid_query_battery,
+       Sandroid_query_battery, 0, 0, 0,
+       doc: /* Perform a query for battery information.
+Value is nil upon failure, or a list of the form:
+
+  (CAPACITY CHARGE-COUNTER CURRENT-AVERAGE CURRENT-NOW STATUS
+   REMAINING PLUGGED TEMP)
+
+where REMAINING, CURRENT-AVERAGE, and CURRENT-NOW are undefined prior
+to Android 5.0.
+
+See the documentation at
+
+  https://developer.android.com/reference/android/os/BatteryManager
+
+for more details about these values.  */)
+  (void)
+{
+  struct android_battery_state state;
+
+  /* Make sure the Android libraries have been initialized.  */
+
+  if (!android_init_gui)
+    return Qnil;
+
+  /* Perform the query.  */
+
+  if (android_query_battery (&state))
+    return Qnil;
+
+  return listn (8, make_int (state.capacity),
+               make_fixnum (state.charge_counter),
+               make_int (state.current_average),
+               make_int (state.current_now),
+               make_fixnum (state.status),
+               make_int (state.remaining),
+               make_fixnum (state.plugged),
+               make_fixnum (state.temperature));
+}
+
+
+
+/* Directory access requests.  */
+
+DEFUN ("android-request-directory-access", Fandroid_request_directory_access,
+       Sandroid_request_directory_access, 0, 0, "",
+       doc: /* Request access to a directory within external storage.
+On Android 5.0 and later, prompt for a directory within external or
+application storage, and grant access to it; some of these directories
+cannot be accessed through the regular `/sdcard' filesystem.
+
+If access to the directory is granted, it will eventually appear
+within the directory `/content/storage'.  */)
+  (void)
+{
+  if (android_get_current_api_level () < 21)
+    error ("Emacs can only access application storage on"
+          " Android 5.0 and later");
+
+  if (!android_init_gui)
+    return Qnil;
+
+  android_request_directory_access ();
+  return Qnil;
+}
+
+
+
+/* Miscellaneous input method related stuff.  */
+
+/* Report X, Y, by the phys cursor width and height as the cursor
+   anchor rectangle for W's frame.  */
+
+void
+android_set_preeditarea (struct window *w, int x, int y)
+{
+  struct frame *f;
+
+  f = WINDOW_XFRAME (w);
+
+  /* Convert the window coordinates to the frame's coordinate
+     space.  */
+  x = (WINDOW_TO_FRAME_PIXEL_X (w, x)
+       + WINDOW_LEFT_FRINGE_WIDTH (w)
+       + WINDOW_LEFT_MARGIN_WIDTH (w));
+  y = WINDOW_TO_FRAME_PIXEL_Y (w, y);
+
+  /* Note that calculating the baseline is too hard, so the bottom of
+     the cursor is used instead.  */
+  android_update_cursor_anchor_info (FRAME_ANDROID_WINDOW (f), x,
+                                    y, y + w->phys_cursor_height,
+                                    y + w->phys_cursor_height);
+}
+
+#endif /* !ANDROID_STUBIFY */
+
+
+
+void
+syms_of_androidfns (void)
+{
+  /* Miscellaneous symbols used by some functions here.  */
+  DEFSYM (Qtrue_color, "true-color");
+  DEFSYM (Qalways, "always");
+
+  DEFVAR_LISP ("x-pointer-shape", Vx_pointer_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_pointer_shape = Qnil;
+
+#if false /* This doesn't really do anything.  */
+  DEFVAR_LISP ("x-nontext-pointer-shape", Vx_nontext_pointer_shape,
+    doc: /* SKIP: real doc in xfns.c.  */);
+#endif
+  Vx_nontext_pointer_shape = Qnil;
+
+  DEFVAR_LISP ("x-hourglass-pointer-shape", Vx_hourglass_pointer_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_hourglass_pointer_shape = Qnil;
+
+  DEFVAR_LISP ("x-sensitive-text-pointer-shape",
+             Vx_sensitive_text_pointer_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_sensitive_text_pointer_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-horizontal-drag-cursor",
+             Vx_window_horizontal_drag_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_horizontal_drag_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-vertical-drag-cursor",
+             Vx_window_vertical_drag_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_vertical_drag_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-left-edge-cursor",
+              Vx_window_left_edge_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_left_edge_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-top-left-corner-cursor",
+              Vx_window_top_left_corner_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_top_left_corner_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-top-edge-cursor",
+              Vx_window_top_edge_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_top_edge_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-top-right-corner-cursor",
+              Vx_window_top_right_corner_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_top_right_corner_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-right-edge-cursor",
+              Vx_window_right_edge_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_right_edge_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-bottom-right-corner-cursor",
+              Vx_window_bottom_right_corner_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_bottom_right_corner_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-bottom-edge-cursor",
+              Vx_window_bottom_edge_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_bottom_edge_shape = Qnil;
+
+#if false /* This doesn't really do anything.  */
+  DEFVAR_LISP ("x-mode-pointer-shape", Vx_mode_pointer_shape,
+    doc: /* SKIP: real doc in xfns.c.  */);
+#endif
+  Vx_mode_pointer_shape = Qnil;
+
+  DEFVAR_LISP ("x-window-bottom-left-corner-cursor",
+              Vx_window_bottom_left_corner_shape,
+    doc: /* SKIP: real text in xfns.c.  */);
+  Vx_window_bottom_left_corner_shape = Qnil;
+
+  DEFVAR_LISP ("x-cursor-fore-pixel", Vx_cursor_fore_pixel,
+    doc: /* SKIP: real doc in xfns.c.  */);
+  Vx_cursor_fore_pixel = Qnil;
+
+  /* Used by Fx_show_tip.  */
+  DEFSYM (Qrun_at_time, "run-at-time");
+  DEFSYM (Qx_hide_tip, "x-hide-tip");
+  DEFSYM (Qcancel_timer, "cancel-timer");
+  DEFSYM (Qassq_delete_all, "assq-delete-all");
+  DEFSYM (Qcolor, "color");
+
+  DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size,
+    doc: /* SKIP: real doc in xfns.c.  */);
+  Vx_max_tooltip_size = Qnil;
+
+  DEFVAR_BOOL ("android-pass-multimedia-buttons-to-system",
+              android_pass_multimedia_buttons_to_system,
+    doc: /* Whether or not to pass volume control buttons to the system.
+Generally, the `volume-up', `volume-down' and `volume-mute' keys are
+processed by Emacs, but setting this to non-nil they are passed to the
+operating system instead of being intercepted by Emacs.
+
+Note that if you set this, you will no longer be able to quit Emacs
+using the volume down button.  */);
+  android_pass_multimedia_buttons_to_system = false;
+
+  DEFVAR_BOOL ("android-use-exec-loader", android_use_exec_loader,
+    doc: /* Whether or not to bypass system restrictions on program execution.
+
+Android 10 and later prevent programs from executing files installed
+in writable directories, such as the application data directory.
+
+When non-nil, Emacs will bypass this restriction by running such
+executables under system call tracing, and replacing the `execve'
+system call with a version which ignores the system's security
+restrictions.
+
+This option has no effect on Android 9 and earlier.  */);
+  android_use_exec_loader = true;
+
+  /* Functions defined.  */
+  defsubr (&Sx_create_frame);
+  defsubr (&Sxw_color_defined_p);
+  defsubr (&Sxw_color_values);
+  defsubr (&Sxw_display_color_p);
+  defsubr (&Sx_display_grayscale_p);
+  defsubr (&Sx_display_pixel_width);
+  defsubr (&Sx_display_pixel_height);
+  defsubr (&Sx_display_planes);
+  defsubr (&Sx_display_color_cells);
+  defsubr (&Sx_display_screens);
+  defsubr (&Sx_display_mm_width);
+  defsubr (&Sx_display_mm_height);
+  defsubr (&Sx_display_backing_store);
+  defsubr (&Sx_display_visual_class);
+  defsubr (&Sandroid_display_monitor_attributes_list);
+  defsubr (&Sandroid_frame_geometry);
+  defsubr (&Sandroid_frame_edges);
+  defsubr (&Sandroid_frame_list_z_order);
+  defsubr (&Sandroid_frame_restack);
+  defsubr (&Sandroid_mouse_absolute_pixel_position);
+  defsubr (&Sandroid_set_mouse_absolute_pixel_position);
+  defsubr (&Sandroid_get_connection);
+  defsubr (&Sx_display_list);
+  defsubr (&Sx_show_tip);
+  defsubr (&Sx_hide_tip);
+  defsubr (&Sandroid_detect_mouse);
+  defsubr (&Sandroid_toggle_on_screen_keyboard);
+  defsubr (&Sx_server_vendor);
+  defsubr (&Sx_server_version);
+#ifndef ANDROID_STUBIFY
+  defsubr (&Sandroid_query_battery);
+  defsubr (&Sandroid_request_directory_access);
+
+  tip_timer = Qnil;
+  staticpro (&tip_timer);
+  tip_frame = Qnil;
+  staticpro (&tip_frame);
+  tip_last_frame = Qnil;
+  staticpro (&tip_last_frame);
+  tip_last_string = Qnil;
+  staticpro (&tip_last_string);
+  tip_last_parms = Qnil;
+  staticpro (&tip_last_parms);
+  tip_dx = Qnil;
+  staticpro (&tip_dx);
+  tip_dy = Qnil;
+  staticpro (&tip_dy);
+#endif /* !ANDROID_STUBIFY */
+}
diff --git a/src/androidfont.c b/src/androidfont.c
new file mode 100644
index 00000000000..db2f94008f2
--- /dev/null
+++ b/src/androidfont.c
@@ -0,0 +1,1104 @@
+/* Android fallback font driver.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Due to the terrible nature of the Android Typeface subsystems, this
+   font driver is only used as a fallback when sfntfont-android.c
+   fails to enumerate any fonts at all.  */
+
+#include <config.h>
+
+#include "lisp.h"
+#include "dispextern.h"
+#include "composite.h"
+#include "blockinput.h"
+#include "charset.h"
+#include "frame.h"
+#include "window.h"
+#include "fontset.h"
+#include "androidterm.h"
+#include "character.h"
+#include "coding.h"
+#include "font.h"
+#include "termchar.h"
+#include "pdumper.h"
+#include "android.h"
+
+#ifndef ANDROID_STUBIFY
+
+#include <android/log.h>
+
+struct android_emacs_font_driver
+{
+  jclass class;
+  jmethodID list;
+  jmethodID match;
+  jmethodID list_families;
+  jmethodID open_font;
+  jmethodID has_char;
+  jmethodID text_extents;
+  jmethodID encode_char;
+  jmethodID draw;
+
+  /* Static methods.  */
+  jmethodID create_font_driver;
+};
+
+struct android_emacs_font_spec
+{
+  jclass class;
+  jfieldID foundry;
+  jfieldID family;
+  jfieldID adstyle;
+  jfieldID registry;
+  jfieldID width;
+  jfieldID weight;
+  jfieldID slant;
+  jfieldID size;
+  jfieldID spacing;
+  jfieldID avgwidth;
+  jfieldID dpi;
+};
+
+struct android_emacs_font_metrics
+{
+  jclass class;
+  jfieldID lbearing;
+  jfieldID rbearing;
+  jfieldID width;
+  jfieldID ascent;
+  jfieldID descent;
+};
+
+struct android_emacs_font_object
+{
+  jclass class;
+  jfieldID min_width;
+  jfieldID max_width;
+  jfieldID pixel_size;
+  jfieldID height;
+  jfieldID space_width;
+  jfieldID average_width;
+  jfieldID ascent;
+  jfieldID descent;
+  jfieldID underline_thickness;
+  jfieldID underline_position;
+  jfieldID baseline_offset;
+  jfieldID relative_compose;
+  jfieldID default_ascent;
+  jfieldID encoding_charset;
+  jfieldID repertory_charset;
+};
+
+struct android_integer
+{
+  jclass class;
+  jmethodID constructor;
+  jmethodID int_value;
+};
+
+struct androidfont_info
+{
+  /* The font pseudo-vector object.  */
+  struct font font;
+
+  /* The Java-side font.  */
+  jobject object;
+
+  /* Cached glyph metrics arranged in a two dimensional array.  */
+  struct font_metrics **metrics;
+};
+
+struct androidfont_entity
+{
+  /* The font entity pvec.  */
+  struct font_entity font;
+
+  /* The Java-side font entity.  */
+  jobject object;
+};
+
+/* Method and class identifiers associated with the EmacsFontDriver
+   class.  */
+
+struct android_emacs_font_driver font_driver_class;
+
+/* Field and class identifiers associated with the
+   EmacsFontDriver$FontSpec class.  */
+
+struct android_emacs_font_spec font_spec_class;
+
+/* Method and class identifiers associated with the Integer class.  */
+
+struct android_integer integer_class;
+
+/* Field and class identifiers associated with the
+   EmacsFontDriver$FontMetrics class.  */
+
+struct android_emacs_font_metrics font_metrics_class;
+
+/* Field and class identifiers associated with the
+   EmacsFontDriver$FontObject class.  */
+
+struct android_emacs_font_object font_object_class;
+
+/* The font cache.  */
+
+static Lisp_Object font_cache;
+
+/* The Java-side font driver.  */
+
+static jobject font_driver;
+
+
+
+/* Initialize the class and method identifiers for functions in the
+   EmacsFontDriver class, and place them in `font_driver_class'.  */
+
+static void
+android_init_font_driver (void)
+{
+  jclass old;
+
+  font_driver_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsFontDriver");
+  eassert (font_driver_class.class);
+
+  old = font_driver_class.class;
+  font_driver_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!font_driver_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                                   
\
+  font_driver_class.c_name                                                     
\
+    = (*android_java_env)->GetMethodID (android_java_env,                      
\
+                                       font_driver_class.class,                
\
+                                       name, signature);                       
\
+  eassert (font_driver_class.c_name);
+
+  FIND_METHOD (list, "list", "(Lorg/gnu/emacs/EmacsFontDriver$FontSpec;)"
+              "[Lorg/gnu/emacs/EmacsFontDriver$FontEntity;");
+  FIND_METHOD (match, "match", "(Lorg/gnu/emacs/EmacsFontDriver$FontSpec;)"
+              "Lorg/gnu/emacs/EmacsFontDriver$FontEntity;");
+  FIND_METHOD (list_families, "listFamilies", "()[Ljava/lang/String;");
+  FIND_METHOD (open_font, "openFont", "(Lorg/gnu/emacs/EmacsFontDriver$Font"
+              "Entity;I)Lorg/gnu/emacs/EmacsFontDriver$FontObject;");
+  FIND_METHOD (has_char, "hasChar", "(Lorg/gnu/emacs/EmacsFontDriver$Font"
+              "Spec;C)I");
+  FIND_METHOD (text_extents, "textExtents", "(Lorg/gnu/emacs/EmacsFontDriver"
+              "$FontObject;[ILorg/gnu/emacs/EmacsFontDriver$FontMetrics;)V");
+  FIND_METHOD (encode_char, "encodeChar", "(Lorg/gnu/emacs/EmacsFontDriver"
+              "$FontObject;C)I");
+  FIND_METHOD (draw, "draw", "(Lorg/gnu/emacs/EmacsFontDriver$FontObject;"
+              "Lorg/gnu/emacs/EmacsGC;Lorg/gnu/emacs/EmacsDrawable;[IIIIZ)I");
+
+  font_driver_class.create_font_driver
+    = (*android_java_env)->GetStaticMethodID (android_java_env,
+                                             font_driver_class.class,
+                                             "createFontDriver",
+                                             "()Lorg/gnu/emacs/"
+                                             "EmacsFontDriver;");
+  eassert (font_driver_class.create_font_driver);
+#undef FIND_METHOD
+}
+
+/* Initialize the class and field identifiers for functions in the
+   EmacsFontDriver$FontSpec class, and place them in
+   `font_spec_class'.  */
+
+static void
+android_init_font_spec (void)
+{
+  jclass old;
+
+  font_spec_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsFontDriver"
+                                     "$FontSpec");
+  eassert (font_spec_class.class);
+
+  old = font_spec_class.class;
+  font_spec_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!font_spec_class.class)
+    emacs_abort ();
+
+#define FIND_FIELD(c_name, name, signature)                                    
\
+  font_spec_class.c_name                                                       
\
+    = (*android_java_env)->GetFieldID (android_java_env,                       
\
+                                      font_spec_class.class,                   
\
+                                      name, signature);                        
\
+  eassert (font_spec_class.c_name);
+
+  FIND_FIELD (foundry, "foundry", "Ljava/lang/String;");
+  FIND_FIELD (family, "family", "Ljava/lang/String;");
+  FIND_FIELD (adstyle, "adstyle", "Ljava/lang/String;");
+  FIND_FIELD (registry, "registry", "Ljava/lang/String;");
+  FIND_FIELD (width, "width", "Ljava/lang/Integer;");
+  FIND_FIELD (weight, "weight", "Ljava/lang/Integer;");
+  FIND_FIELD (slant, "slant", "Ljava/lang/Integer;");
+  FIND_FIELD (size, "size", "Ljava/lang/Integer;");
+  FIND_FIELD (spacing, "spacing", "Ljava/lang/Integer;");
+  FIND_FIELD (avgwidth, "avgwidth", "Ljava/lang/Integer;");
+  FIND_FIELD (dpi, "dpi", "Ljava/lang/Integer;");
+#undef FIND_FIELD
+}
+
+static void
+android_init_font_metrics (void)
+{
+  jclass old;
+
+  font_metrics_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsFontDriver"
+                                     "$FontMetrics");
+  eassert (font_metrics_class.class);
+
+  old = font_metrics_class.class;
+  font_metrics_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!font_metrics_class.class)
+    emacs_abort ();
+
+#define FIND_FIELD(c_name, name, signature)                                    
\
+  font_metrics_class.c_name                                                    
\
+    = (*android_java_env)->GetFieldID (android_java_env,                       
\
+                                      font_metrics_class.class,                
\
+                                      name, signature);                        
\
+  eassert (font_metrics_class.c_name);
+
+  FIND_FIELD (lbearing, "lbearing", "S");
+  FIND_FIELD (rbearing, "rbearing", "S");
+  FIND_FIELD (width, "width", "S");
+  FIND_FIELD (ascent, "ascent", "S");
+  FIND_FIELD (descent, "descent", "S");
+#undef FIND_FIELD
+}
+
+static void
+android_init_integer (void)
+{
+  jclass old;
+
+  integer_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "java/lang/Integer");
+  eassert (integer_class.class);
+
+  old = integer_class.class;
+  integer_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!integer_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                                   
\
+  integer_class.c_name                                                         
\
+    = (*android_java_env)->GetMethodID (android_java_env,                      
\
+                                       integer_class.class,                    
\
+                                       name, signature);                       
\
+  eassert (integer_class.c_name);
+
+  FIND_METHOD (constructor, "<init>", "(I)V");
+  FIND_METHOD (int_value, "intValue", "()I");
+#undef FIND_METHOD
+}
+
+static void
+android_init_font_object (void)
+{
+  jclass old;
+
+  font_object_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsFontDriver"
+                                     "$FontObject");
+  eassert (font_object_class.class);
+
+  old = font_object_class.class;
+  font_object_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!font_object_class.class)
+    emacs_abort ();
+
+#define FIND_FIELD(c_name, name, signature)                                    
\
+  font_object_class.c_name                                                     
\
+    = (*android_java_env)->GetFieldID (android_java_env,                       
\
+                                      font_object_class.class,                 
\
+                                      name, signature);                        
\
+  eassert (font_object_class.c_name);
+
+  FIND_FIELD (min_width, "minWidth", "I");
+  FIND_FIELD (max_width, "maxWidth", "I");
+  FIND_FIELD (pixel_size, "pixelSize", "I");
+  FIND_FIELD (height, "height", "I");
+  FIND_FIELD (space_width, "spaceWidth", "I");
+  FIND_FIELD (average_width, "averageWidth", "I");
+  FIND_FIELD (ascent, "ascent", "I");
+  FIND_FIELD (descent, "descent", "I");
+  FIND_FIELD (underline_thickness, "underlineThickness", "I");
+  FIND_FIELD (underline_position, "underlinePosition", "I");
+  FIND_FIELD (baseline_offset, "baselineOffset", "I");
+  FIND_FIELD (relative_compose, "relativeCompose", "I");
+  FIND_FIELD (default_ascent, "defaultAscent", "I");
+  FIND_FIELD (encoding_charset, "encodingCharset", "I");
+  FIND_FIELD (repertory_charset, "repertoryCharset", "I");
+#undef FIND_FIELD
+}
+
+static Lisp_Object
+androidfont_get_cache (struct frame *frame)
+{
+  return font_cache;
+}
+
+/* Initialize the Java side of the font driver if it has not already
+   been initialized.  This is only done whenever necessary because the
+   font driver otherwise uses a lot of memory, as it has to keep every
+   typeface open.  */
+
+static void
+androidfont_check_init (void)
+{
+  jmethodID method;
+  jobject old;
+
+  if (font_driver)
+    return;
+
+  /* Log a loud message.  This font driver really should not be
+     used.  */
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "The Android font driver is being used."
+                      "  Please investigate why this is so.");
+
+  method = font_driver_class.create_font_driver;
+
+  /* Initialize the font driver on the Java side.  */
+  font_driver
+    = (*android_java_env)->CallStaticObjectMethod (android_java_env,
+                                                  font_driver_class.class,
+                                                  method);
+  android_exception_check ();
+
+  old = font_driver;
+  font_driver
+    = (*android_java_env)->NewGlobalRef (android_java_env, font_driver);
+  ANDROID_DELETE_LOCAL_REF (old);
+}
+
+/* Return a local reference to an instance of EmacsFontDriver$FontSpec
+   with the same values as FONT.  */
+
+static jobject
+androidfont_from_lisp (Lisp_Object font)
+{
+  jobject spec, integer;
+  jstring string;
+  Lisp_Object tem;
+
+  spec = (*android_java_env)->AllocObject (android_java_env,
+                                          font_spec_class.class);
+  android_exception_check ();
+
+#define DO_SYMBOL_FIELD(field, index)                                          
\
+  tem = AREF (font, index);                                                    
\
+  if (SYMBOLP (tem))                                                           
\
+    {                                                                          
\
+      /* Java seems to DTRT with the Emacs string encoding, so this does       
\
+        not matter at all.  */                                                 
\
+      string = (*android_java_env)->NewStringUTF (android_java_env,            
\
+                                                 SSDATA (SYMBOL_NAME (tem)));  
\
+      android_exception_check_1 (spec);                                        
        \
+                                                                               
\
+      (*android_java_env)->SetObjectField (android_java_env, spec,             
\
+                                          font_spec_class.field,               
\
+                                          string);                             
\
+      ANDROID_DELETE_LOCAL_REF (string);                                       
\
+    }                                                                          
\
+
+  DO_SYMBOL_FIELD (foundry, FONT_FOUNDRY_INDEX);
+  DO_SYMBOL_FIELD (family, FONT_FAMILY_INDEX);
+  DO_SYMBOL_FIELD (adstyle, FONT_ADSTYLE_INDEX);
+  DO_SYMBOL_FIELD (registry, FONT_REGISTRY_INDEX);
+
+#undef DO_SYMBOL_FIELD
+
+#define DO_CARDINAL_FIELD(field, value)                                        
        \
+  if (value != -1)                                                             
\
+    {                                                                          
\
+      integer = (*android_java_env)->NewObject (android_java_env,              
\
+                                               integer_class.class,            
\
+                                               integer_class.constructor,      
\
+                                               (jint) value);                  
\
+      android_exception_check_1 (spec);                                        
        \
+                                                                               
\
+      (*android_java_env)->SetObjectField (android_java_env, spec,             
\
+                                          font_spec_class.field,               
\
+                                          integer);                            
\
+      ANDROID_DELETE_LOCAL_REF (integer);                                      
\
+    }
+
+  DO_CARDINAL_FIELD (width, FONT_WIDTH_NUMERIC (font));
+  DO_CARDINAL_FIELD (weight, FONT_WEIGHT_NUMERIC (font));
+  DO_CARDINAL_FIELD (slant, FONT_SLANT_NUMERIC (font));
+  DO_CARDINAL_FIELD (size, (FIXNUMP (AREF (font, FONT_SIZE_INDEX))
+                           ? XFIXNUM (AREF (font, FONT_SIZE_INDEX))
+                           : -1));
+  DO_CARDINAL_FIELD (spacing, (FIXNUMP (AREF (font, FONT_SPACING_INDEX))
+                              ? XFIXNUM (AREF (font, FONT_SPACING_INDEX))
+                              : -1));
+  DO_CARDINAL_FIELD (avgwidth, (FIXNUMP (AREF (font, FONT_AVGWIDTH_INDEX))
+                               ? XFIXNUM (AREF (font, FONT_AVGWIDTH_INDEX))
+                               : -1));
+  DO_CARDINAL_FIELD (dpi, (FIXNUMP (AREF (font, FONT_DPI_INDEX))
+                          ? XFIXNUM (AREF (font, FONT_DPI_INDEX))
+                          : -1));
+
+#undef DO_CARDINAL_FIELD
+
+  return spec;
+}
+
+static void
+androidfont_from_java (jobject spec, Lisp_Object entity)
+{
+  jobject tem;
+  jint value;
+  const char *string;
+
+#define DO_SYMBOL_FIELD(field, index)                                          
\
+  tem = (*android_java_env)->GetObjectField (android_java_env,                 
\
+                                            spec,                              
\
+                                            font_spec_class.field);            
\
+  if (tem)                                                                     
\
+    {                                                                          
\
+      string = (*android_java_env)->GetStringUTFChars (android_java_env,       
\
+                                                      tem, NULL);              
\
+      if (!string)                                                             
\
+       memory_full (0);                                                        
\
+      ASET (entity, index, intern (string));                                   
\
+      (*android_java_env)->ReleaseStringUTFChars (android_java_env,            
\
+                                                 tem, string);                 
\
+      ANDROID_DELETE_LOCAL_REF (tem);                                          
\
+    }
+
+  DO_SYMBOL_FIELD (foundry, FONT_FOUNDRY_INDEX);
+  DO_SYMBOL_FIELD (family, FONT_FAMILY_INDEX);
+  DO_SYMBOL_FIELD (adstyle, FONT_ADSTYLE_INDEX);
+  DO_SYMBOL_FIELD (registry, FONT_REGISTRY_INDEX);
+
+#undef DO_SYMBOL_FIELD
+#define DO_CARDINAL_FIELD(field, index, is_style)                      \
+  tem = (*android_java_env)->GetObjectField (android_java_env,         \
+                                            spec,                      \
+                                            font_spec_class.field);    \
+  if (tem)                                                             \
+    {                                                                  \
+      value                                                            \
+       = (*android_java_env)->CallIntMethod (android_java_env,         \
+                                             tem,                      \
+                                             integer_class.int_value); \
+      if (!is_style)                                                   \
+       ASET (entity, index, make_fixnum (value));                      \
+      else                                                             \
+       FONT_SET_STYLE (entity, index, make_fixnum (value));            \
+      ANDROID_DELETE_LOCAL_REF (tem);                                  \
+    }
+
+  DO_CARDINAL_FIELD (width, FONT_WIDTH_INDEX, true);
+  DO_CARDINAL_FIELD (weight, FONT_WEIGHT_INDEX, true);
+  DO_CARDINAL_FIELD (slant, FONT_SLANT_INDEX, true);
+  DO_CARDINAL_FIELD (size, FONT_SIZE_INDEX, false);
+  DO_CARDINAL_FIELD (spacing, FONT_SPACING_INDEX, false);
+  DO_CARDINAL_FIELD (avgwidth, FONT_AVGWIDTH_INDEX, false);
+  DO_CARDINAL_FIELD (dpi, FONT_DPI_INDEX, false);
+
+#undef DO_CARDINAL_FIELD
+}
+
+/* Transfer the values from FONT, which must be some kind of font
+   entity, */
+
+static Lisp_Object
+androidfont_list (struct frame *f, Lisp_Object font_spec)
+{
+  jobject spec, array, tem;
+  jarray entities;
+  jsize i, size;
+  Lisp_Object value, entity;
+  struct androidfont_entity *info;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  spec = androidfont_from_lisp (font_spec);
+  array = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                font_driver,
+                                                font_driver_class.list,
+                                                spec);
+  android_exception_check_1 (spec);
+  ANDROID_DELETE_LOCAL_REF (spec);
+
+  entities = (jarray) array;
+  size = (*android_java_env)->GetArrayLength (android_java_env,
+                                             entities);
+  value = Qnil;
+
+  for (i = 0; i < size; ++i)
+    {
+      entity = font_make_entity_android (VECSIZE (struct androidfont_entity));
+      info = (struct androidfont_entity *) XFONT_ENTITY (entity);
+
+      /* The type must be set correctly, or font_open_entity won't be
+        able to find the right font driver.  */
+      ASET (entity, FONT_TYPE_INDEX, Qandroid);
+
+      /* Clear this now in case GC happens without it set, which can
+        happen if androidfont_from_java runs out of memory.  */
+      info->object = NULL;
+
+      tem = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                       entities, i);
+      androidfont_from_java (tem, entity);
+
+      /* Now, make a global reference to the Java font entity.  */
+      info->object = (*android_java_env)->NewGlobalRef (android_java_env,
+                                                       (jobject) tem);
+      android_exception_check_2 (tem, entities);
+      ANDROID_DELETE_LOCAL_REF (tem);
+
+      value = Fcons (entity, value);
+    }
+
+  ANDROID_DELETE_LOCAL_REF (entities);
+  return Fnreverse (value);
+}
+
+static Lisp_Object
+androidfont_match (struct frame *f, Lisp_Object font_spec)
+{
+  jobject spec, result;
+  Lisp_Object entity;
+  struct androidfont_entity *info;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  spec = androidfont_from_lisp (font_spec);
+  result = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 font_driver,
+                                                 font_driver_class.match,
+                                                 spec);
+  android_exception_check_1 (spec);
+  ANDROID_DELETE_LOCAL_REF (spec);
+
+  entity = font_make_entity_android (VECSIZE (struct androidfont_entity));
+  info = (struct androidfont_entity *) XFONT_ENTITY (entity);
+
+  /* The type must be set correctly, or font_open_entity won't be able
+     to find the right font driver.  */
+  ASET (entity, FONT_TYPE_INDEX, Qandroid);
+
+  info->object = NULL;
+  androidfont_from_java (result, entity);
+  info->object = (*android_java_env)->NewGlobalRef (android_java_env,
+                                                   (jobject) result);
+  android_exception_check_1 (result);
+  ANDROID_DELETE_LOCAL_REF (result);
+
+  return entity;
+}
+
+static int
+androidfont_draw (struct glyph_string *s, int from, int to,
+                 int x, int y, bool with_background)
+{
+  struct androidfont_info *info;
+  jarray chars;
+  int rc;
+  jobject gcontext, drawable;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  verify (sizeof (unsigned int) == sizeof (jint));
+  info = (struct androidfont_info *) s->font;
+
+  gcontext = android_resolve_handle (s->gc->gcontext,
+                                    ANDROID_HANDLE_GCONTEXT);
+  drawable = android_resolve_handle (FRAME_ANDROID_DRAWABLE (s->f),
+                                    ANDROID_HANDLE_WINDOW);
+  chars = (*android_java_env)->NewIntArray (android_java_env,
+                                           to - from);
+  android_exception_check ();
+
+  (*android_java_env)->SetIntArrayRegion (android_java_env, chars,
+                                         0, to - from,
+                                         (jint *) s->char2b + from);
+
+  info = (struct androidfont_info *) s->font;
+  prepare_face_for_display (s->f, s->face);
+
+  rc = (*android_java_env)->CallIntMethod (android_java_env,
+                                          font_driver,
+                                          font_driver_class.draw,
+                                          info->object,
+                                          gcontext, drawable,
+                                          chars, (jint) x, (jint) y,
+                                          (jint) s->width,
+                                          (jboolean) with_background);
+  android_exception_check_1 (chars);
+  ANDROID_DELETE_LOCAL_REF (chars);
+
+  return rc;
+}
+
+static Lisp_Object
+androidfont_open_font (struct frame *f, Lisp_Object font_entity,
+                      int pixel_size)
+{
+  struct androidfont_info *font_info;
+  struct androidfont_entity *entity;
+  struct font *font;
+  Lisp_Object font_object;
+  jobject old;
+  jint value;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  if (XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX)) != 0)
+    pixel_size = XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX));
+  else if (pixel_size == 0)
+    {
+      /* This bit was copied from xfont.c.  The values might need
+        adjustment.  */
+
+      if (FRAME_FONT (f))
+       pixel_size = FRAME_FONT (f)->pixel_size;
+      else
+       pixel_size = 12;
+    }
+
+  entity = (struct androidfont_entity *) XFONT_ENTITY (font_entity);
+
+  block_input ();
+  font_object = font_make_object (VECSIZE (struct androidfont_info),
+                                 font_entity, pixel_size);
+  ASET (font_object, FONT_TYPE_INDEX, Qandroid);
+  font_info = (struct androidfont_info *) XFONT_OBJECT (font_object);
+  font = &font_info->font;
+  font->driver = &androidfont_driver;
+
+  /* Clear font_info->object and font_info->metrics early in case GC
+     happens later on! */
+  font_info->object = NULL;
+  font_info->metrics = NULL;
+  unblock_input ();
+
+  font_info->object
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            font_driver,
+                                            font_driver_class.open_font,
+                                            entity->object,
+                                            (jint) pixel_size);
+  android_exception_check ();
+
+  old = font_info->object;
+  font_info->object
+    = (*android_java_env)->NewGlobalRef (android_java_env, old);
+  android_exception_check_1 (old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!font_info->object)
+    return Qnil;
+
+  /* Copy the font attributes from the Java object.  */
+  androidfont_from_java (font_info->object, font_object);
+
+  /* Copy font attributes inside EmacsFontDriver$FontObject.  */
+#define DO_CARDINAL_FIELD(field)                                       \
+  value                                                                        
\
+    = (*android_java_env)->GetIntField (android_java_env,              \
+                                       font_info->object,              \
+                                       font_object_class.field);       \
+  font->field = value;
+
+  DO_CARDINAL_FIELD (min_width);
+  DO_CARDINAL_FIELD (max_width);
+  DO_CARDINAL_FIELD (pixel_size);
+  DO_CARDINAL_FIELD (height);
+  DO_CARDINAL_FIELD (space_width);
+  DO_CARDINAL_FIELD (average_width);
+  DO_CARDINAL_FIELD (ascent);
+  DO_CARDINAL_FIELD (descent);
+  DO_CARDINAL_FIELD (underline_thickness);
+  DO_CARDINAL_FIELD (underline_position);
+  DO_CARDINAL_FIELD (baseline_offset);
+  DO_CARDINAL_FIELD (relative_compose);
+  DO_CARDINAL_FIELD (default_ascent);
+  DO_CARDINAL_FIELD (encoding_charset);
+  DO_CARDINAL_FIELD (repertory_charset);
+
+#undef DO_CARDINAL_FIELD
+
+  /* This should eventually become unnecessary.  */
+  font->props[FONT_NAME_INDEX] = Ffont_xlfd_name (font_object, Qnil);
+
+  return font_object;
+}
+
+static void
+androidfont_close_font (struct font *font)
+{
+  struct androidfont_info *info;
+  int i;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  info = (struct androidfont_info *) font;
+
+  /* Free the font metrics cache if it exists.  */
+
+  if (info->metrics)
+    {
+      for (i = 0; i < 256; ++i)
+       xfree (info->metrics[i]);
+      xfree (info->metrics);
+    }
+
+  info->metrics = NULL;
+
+  /* If info->object is NULL, then FONT was unsuccessfully created,
+     and there is no global reference that has to be deleted.
+
+     Alternatively, FONT may have been closed by font_close_object,
+     with this function called from GC.  */
+
+  if (!info->object)
+    return;
+
+  (*android_java_env)->DeleteGlobalRef (android_java_env,
+                                       info->object);
+  info->object = NULL;
+}
+
+static int
+androidfont_has_char (Lisp_Object font, int c)
+{
+  struct androidfont_info *info;
+  struct androidfont_entity *entity;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  if (FONT_ENTITY_P (font))
+    {
+      entity = (struct androidfont_entity *) XFONT_ENTITY (font);
+
+      return (*android_java_env)->CallIntMethod (android_java_env,
+                                                font_driver,
+                                                font_driver_class.has_char,
+                                                entity->object, (jint) c);
+    }
+  else
+    {
+      info = (struct androidfont_info *) XFONT_OBJECT (font);
+
+      return (*android_java_env)->CallIntMethod (android_java_env,
+                                                font_driver,
+                                                font_driver_class.has_char,
+                                                info->object, (jint) c);
+    }
+}
+
+static unsigned
+androidfont_encode_char (struct font *font, int c)
+{
+  struct androidfont_info *info;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  info = (struct androidfont_info *) font;
+
+  return (*android_java_env)->CallIntMethod (android_java_env,
+                                            font_driver,
+                                            font_driver_class.encode_char,
+                                            info->object, (jchar) c);
+}
+
+static void
+androidfont_cache_text_extents (struct androidfont_info *info,
+                               unsigned int glyph,
+                               struct font_metrics *metrics)
+{
+  int i;
+
+  /* Glyphs larger than 65535 can't be cached.  */
+  if (glyph >= 256 * 256)
+    return;
+
+  if (!info->metrics)
+    info->metrics = xzalloc (256 * sizeof *info->metrics);
+
+  if (!info->metrics[glyph / 256])
+    {
+      info->metrics[glyph / 256]
+       = xnmalloc (256, sizeof **info->metrics);
+
+      /* Now, all the metrics in that array as invalid by setting
+        lbearing to SHRT_MAX.  */
+      for (i = 0; i < 256; ++i)
+       info->metrics[glyph / 256][i].lbearing = SHRT_MAX;
+    }
+
+  /* Finally, cache the glyph.  */
+  info->metrics[glyph / 256][glyph % 256] = *metrics;
+}
+
+static bool
+androidfont_check_cached_extents (struct androidfont_info *info,
+                                 unsigned int glyph,
+                                 struct font_metrics *metrics)
+{
+  if (info->metrics && info->metrics[glyph / 256]
+      && info->metrics[glyph / 256][glyph % 256].lbearing != SHRT_MAX)
+    {
+      *metrics = info->metrics[glyph / 256][glyph % 256];
+      return true;
+    }
+
+  return false;
+}
+
+static void
+androidfont_text_extents (struct font *font, const unsigned int *code,
+                         int nglyphs, struct font_metrics *metrics)
+{
+  struct androidfont_info *info;
+  jarray codepoint_array;
+  jobject metrics_object;
+  short value;
+
+  /* Maybe initialize the font driver.  */
+  androidfont_check_init ();
+
+  info = (struct androidfont_info *) font;
+
+  if (nglyphs == 1
+      && androidfont_check_cached_extents (info, *code, metrics))
+    return;
+
+  /* Allocate the arrays of code points and font metrics.  */
+  codepoint_array
+    = (*android_java_env)->NewIntArray (android_java_env,
+                                       nglyphs);
+  if (!codepoint_array)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      memory_full (0);
+    }
+
+  verify (sizeof (unsigned int) == sizeof (jint));
+
+  /* Always true on every Android device.  */
+  (*android_java_env)->SetIntArrayRegion (android_java_env,
+                                         codepoint_array,
+                                         0, nglyphs,
+                                         (jint *) code);
+
+  metrics_object
+    = (*android_java_env)->AllocObject (android_java_env,
+                                       font_metrics_class.class);
+
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      font_driver,
+                                      font_driver_class.text_extents,
+                                      info->object, codepoint_array,
+                                      metrics_object);
+
+  if ((*android_java_env)->ExceptionCheck (android_java_env))
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (metrics_object);
+      ANDROID_DELETE_LOCAL_REF (codepoint_array);
+      memory_full (0);
+    }
+
+#define DO_CARDINAL_FIELD(field)                                       \
+  value                                                                        
\
+    = (*android_java_env)->GetShortField (android_java_env,            \
+                                         metrics_object,               \
+                                         font_metrics_class.field);    \
+  metrics->field = value;
+
+  DO_CARDINAL_FIELD (lbearing);
+  DO_CARDINAL_FIELD (rbearing);
+  DO_CARDINAL_FIELD (width);
+  DO_CARDINAL_FIELD (ascent);
+  DO_CARDINAL_FIELD (descent);
+
+#undef DO_CARDINAL_FIELD
+
+  ANDROID_DELETE_LOCAL_REF (metrics_object);
+  ANDROID_DELETE_LOCAL_REF (codepoint_array);
+
+  /* Emacs spends a lot of time in androidfont_text_extents, which
+     makes calling JNI too slow.  Cache the metrics for this single
+     glyph.  */
+
+  if (nglyphs == 1)
+    androidfont_cache_text_extents (info, *code, metrics);
+}
+
+static Lisp_Object
+androidfont_list_family (struct frame *f)
+{
+  Lisp_Object families;
+  jarray family_array;
+  jobject string;
+  jsize i, length;
+  const char *family;
+
+  /* Return if the Android font driver is not initialized.  Loading
+     every font under Android takes a non trivial amount of memory,
+     and is not something that should be done when the user tries to
+     list all of the font families.  */
+
+  if (!font_driver)
+    return Qnil;
+
+  family_array
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            font_driver,
+                                            font_driver_class.list_families);
+  android_exception_check ();
+
+  length = (*android_java_env)->GetArrayLength (android_java_env,
+                                               family_array);
+  families = Qnil;
+
+  for (i = 0; i < length; ++i)
+    {
+      string = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                          family_array, i);
+      family = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                      (jstring) string, NULL);
+
+      if (!family)
+       {
+         ANDROID_DELETE_LOCAL_REF (string);
+         ANDROID_DELETE_LOCAL_REF (family_array);
+       }
+
+      families = Fcons (build_string_from_utf8 (string), families);
+      (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                                 (jstring) string,
+                                                 family);
+      ANDROID_DELETE_LOCAL_REF (string);
+    }
+
+  ANDROID_DELETE_LOCAL_REF (family_array);
+  return Fnreverse (families);
+}
+
+struct font_driver androidfont_driver =
+  {
+    .type = LISPSYM_INITIALLY (Qandroid),
+    .case_sensitive = true,
+    .get_cache = androidfont_get_cache,
+    .list = androidfont_list,
+    .match = androidfont_match,
+    .draw = androidfont_draw,
+    .open_font = androidfont_open_font,
+    .close_font = androidfont_close_font,
+    .has_char = androidfont_has_char,
+    .encode_char = androidfont_encode_char,
+    .text_extents = androidfont_text_extents,
+    .list_family = androidfont_list_family,
+  };
+
+static void
+syms_of_androidfont_for_pdumper (void)
+{
+  register_font_driver (&androidfont_driver, NULL);
+}
+
+void
+syms_of_androidfont (void)
+{
+  DEFSYM (Qfontsize, "fontsize");
+
+  pdumper_do_now_and_after_load (syms_of_androidfont_for_pdumper);
+
+  font_cache = list (Qnil);
+  staticpro (&font_cache);
+}
+
+void
+init_androidfont (void)
+{
+  if (!android_init_gui)
+    return;
+
+  android_init_font_driver ();
+  android_init_font_spec ();
+  android_init_font_metrics ();
+  android_init_font_object ();
+  android_init_integer ();
+
+  /* The Java font driver is not initialized here because it uses a lot
+     of memory.  */
+}
+
+void
+android_finalize_font_entity (struct font_entity *entity)
+{
+  struct androidfont_entity *info;
+
+  info = (struct androidfont_entity *) entity;
+
+  if (info->object)
+    (*android_java_env)->DeleteGlobalRef (android_java_env,
+                                         info->object);
+
+  /* Not sure if this can be called twice.  */
+  info->object = NULL;
+}
+
+#endif
diff --git a/src/androidgui.h b/src/androidgui.h
new file mode 100644
index 00000000000..14225f7bf80
--- /dev/null
+++ b/src/androidgui.h
@@ -0,0 +1,754 @@
+/* Android window system support.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _ANDROID_GUI_H_
+#define _ANDROID_GUI_H_
+
+struct android_char_struct
+{
+  int rbearing;
+  int lbearing;
+  int width;
+  int ascent;
+  int descent;
+};
+
+typedef struct android_char_struct XCharStruct;
+
+typedef unsigned short android_handle;
+
+typedef android_handle android_pixmap, Emacs_Pixmap;
+typedef android_handle android_window, Emacs_Window;
+typedef android_handle android_gcontext, GContext;
+typedef android_handle android_drawable, Drawable;
+typedef android_handle android_cursor, Emacs_Cursor;
+
+typedef unsigned int android_time;
+
+struct android_rectangle
+{
+  int x, y;
+  unsigned width, height;
+};
+
+struct android_point
+{
+  int x, y;
+};
+
+/* Keep this in sync with EmacsGC.java! */
+
+enum android_gc_function
+  {
+    ANDROID_GC_COPY    = 0,
+    ANDROID_GC_XOR     = 1,
+  };
+
+enum android_gc_value_mask
+  {
+    ANDROID_GC_FOREGROUND        = (1 << 0),
+    ANDROID_GC_BACKGROUND        = (1 << 1),
+    ANDROID_GC_FUNCTION                  = (1 << 2),
+    ANDROID_GC_CLIP_X_ORIGIN     = (1 << 3),
+    ANDROID_GC_CLIP_Y_ORIGIN     = (1 << 4),
+    ANDROID_GC_CLIP_MASK         = (1 << 5),
+    ANDROID_GC_STIPPLE           = (1 << 6),
+    ANDROID_GC_FILL_STYLE        = (1 << 7),
+    ANDROID_GC_TILE_STIP_X_ORIGIN = (1 << 8),
+    ANDROID_GC_TILE_STIP_Y_ORIGIN = (1 << 9),
+  };
+
+enum android_fill_style
+  {
+    ANDROID_FILL_SOLID          = 0,
+    ANDROID_FILL_OPAQUE_STIPPLED = 1,
+  };
+
+enum android_window_value_mask
+  {
+    ANDROID_CW_BACK_PIXEL       = (1 << 1),
+    ANDROID_CW_OVERRIDE_REDIRECT = (1 << 2),
+  };
+
+struct android_set_window_attributes
+{
+  /* The background pixel.  */
+  unsigned long background_pixel;
+
+  /* Whether or not the window is override redirect.  This cannot be
+     set after creation on Android.  */
+  bool override_redirect;
+};
+
+struct android_gc_values
+{
+  /* The foreground and background.  */
+  unsigned long foreground, background;
+
+  /* The function.  */
+  enum android_gc_function function;
+
+  /* The fill style.  */
+  enum android_fill_style fill_style;
+
+  /* The clip X and Y origin.  */
+  int clip_x_origin, clip_y_origin;
+
+  /* The clip mask image and stipple.  */
+  android_pixmap clip_mask, stipple;
+
+  /* The tile-stipple X and Y origins.  */
+  int ts_x_origin, ts_y_origin;
+};
+
+/* X-like graphics context structure.  This is implemented in
+   EmacsGC.java, but a copy is kept here to avoid sending changes all
+   the time.  */
+
+struct android_gc
+{
+  /* Array of clip rectangles.  */
+  struct android_rectangle *clip_rects;
+
+  /* Number of clip rectangles.  When -1, it means clipping should not
+     be applied.  */
+  int num_clip_rects;
+
+  /* The Java-side handle.  */
+  android_gcontext gcontext;
+
+  /* Current foreground color.  */
+  unsigned long foreground;
+
+  /* Current background color.  */
+  unsigned long background;
+
+  /* The function.  */
+  enum android_gc_function function;
+
+  /* The fill style.  */
+  enum android_fill_style fill_style;
+
+  /* The clip X and Y origin.  */
+  int clip_x_origin, clip_y_origin;
+
+  /* The clip mask image and stipple.  */
+  android_pixmap clip_mask, stipple;
+
+  /* The tile-stipple X and Y origins.  */
+  int ts_x_origin, ts_y_origin;
+};
+
+enum android_swap_action
+  {
+    ANDROID_COPIED,
+  };
+
+enum android_shape
+  {
+    ANDROID_CONVEX,
+  };
+
+enum android_coord_mode
+  {
+    ANDROID_COORD_MODE_ORIGIN,
+  };
+
+struct android_swap_info
+{
+  /* The window to swap.  */
+  android_window swap_window;
+
+  /* Unused field present only for consistency with X.  */
+  enum android_swap_action swap_action;
+};
+
+#define NativeRectangle                        Emacs_Rectangle
+#define CONVERT_TO_NATIVE_RECT(xr, nr) ((xr) = (nr))
+#define CONVERT_FROM_EMACS_RECT(xr, nr) ((nr) = (xr))
+
+#define STORE_NATIVE_RECT(nr, rx, ry, rwidth, rheight) \
+  ((nr).x = (rx), (nr).y = (ry),                       \
+   (nr).width = (rwidth), (nr).height = (rheight))     \
+
+#define ForgetGravity          0
+#define NorthWestGravity       1
+#define NorthGravity           2
+#define NorthEastGravity       3
+#define WestGravity            4
+#define CenterGravity          5
+#define EastGravity            6
+#define SouthWestGravity       7
+#define SouthGravity           8
+#define SouthEastGravity       9
+#define StaticGravity          10
+
+#define NoValue                0x0000
+#define XValue         0x0001
+#define YValue         0x0002
+#define WidthValue     0x0004
+#define HeightValue    0x0008
+#define AllValues      0x000F
+#define XNegative      0x0010
+#define YNegative      0x0020
+
+#define USPosition     (1L << 0) /* user specified x, y */
+#define USSize         (1L << 1) /* user specified width, height */
+#define PPosition      (1L << 2) /* program specified position */
+#define PSize          (1L << 3) /* program specified size */
+#define PMinSize       (1L << 4) /* program specified minimum size */
+#define PMaxSize       (1L << 5) /* program specified maximum size */
+#define PResizeInc     (1L << 6) /* program specified resize increments */
+#define PAspect                (1L << 7) /* program specified min, max aspect 
ratios */
+#define PBaseSize      (1L << 8) /* program specified base for incrementing */
+#define PWinGravity    (1L << 9) /* program specified window gravity */
+
+#ifndef ANDROID_STUBIFY
+
+/* Universal NULL handle.  */
+static const int ANDROID_NONE, ANDROID_NO_SYMBOL;
+
+/* Keep these as conceptually close to X as possible: that makes
+   synchronizing code between the ports much easier.  */
+
+enum android_event_type
+  {
+    ANDROID_KEY_PRESS,
+    ANDROID_KEY_RELEASE,
+    ANDROID_CONFIGURE_NOTIFY,
+    ANDROID_FOCUS_IN,
+    ANDROID_FOCUS_OUT,
+    ANDROID_WINDOW_ACTION,
+    ANDROID_ENTER_NOTIFY,
+    ANDROID_LEAVE_NOTIFY,
+    ANDROID_MOTION_NOTIFY,
+    ANDROID_BUTTON_PRESS,
+    ANDROID_BUTTON_RELEASE,
+    ANDROID_TOUCH_DOWN,
+    ANDROID_TOUCH_UP,
+    ANDROID_TOUCH_MOVE,
+    ANDROID_WHEEL,
+    ANDROID_ICONIFIED,
+    ANDROID_DEICONIFIED,
+    ANDROID_CONTEXT_MENU,
+    ANDROID_EXPOSE,
+    ANDROID_INPUT_METHOD,
+  };
+
+struct android_any_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+};
+
+enum android_modifier_mask
+  {
+    ANDROID_SHIFT_MASK  = 193,
+    ANDROID_CONTROL_MASK = 4096,
+    ANDROID_ALT_MASK    = 2,
+    ANDROID_SUPER_MASK  = 4,
+    ANDROID_META_MASK   = 65536,
+  };
+
+struct android_key_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  android_time time;
+  unsigned int state;
+  unsigned int keycode;
+
+  /* If this field is -1, then android_lookup_string should be called
+     to retrieve the associated individual characters.  */
+  unsigned int unicode_char;
+
+  /* If this field is non-zero, a text conversion barrier should be
+     generated with its value as the counter.  */
+  unsigned long counter;
+};
+
+typedef struct android_key_event android_key_pressed_event;
+
+/* These hard coded values are Android modifier keycodes derived
+   through experimentation.  */
+
+#define ANDROID_IS_MODIFIER_KEY(key)                                   \
+  ((key) == 57 || (key) == 58 || (key) == 113 || (key) == 114          \
+   || (key) == 119 || (key) == 117 || (key) == 118 || (key) == 78      \
+   || (key) == 94 || (key) == 59 || (key) == 60 || (key) == 95         \
+   || (key) == 63 || (key) == 115)
+
+struct android_configure_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  android_time time;
+  int x, y;
+  int width, height;
+};
+
+struct android_focus_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  android_time time;
+};
+
+struct android_window_action_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+
+  /* The window handle.  This can be ANDROID_NONE.  */
+  android_window window;
+
+  /* Numerical identifier for this action.  If 0 and WINDOW is set,
+     then it means the frame associated with that window has been
+     destroyed.  Otherwise, it means Emacs should create a new
+     frame.  */
+  unsigned int action;
+};
+
+struct android_crossing_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  int x, y;
+  unsigned long time;
+};
+
+struct android_motion_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  int x, y;
+  unsigned long time;
+};
+
+struct android_button_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  int x, y;
+  unsigned long time;
+  unsigned int state;
+  unsigned int button;
+};
+
+struct android_expose_event
+{
+  enum android_event_type type;
+  unsigned long serial;
+  android_window window;
+  int x, y;
+  int width, height;
+};
+
+enum android_touch_event_flags
+  {
+    /* This touch sequence has been intercepted by the WM (probably
+       for back gesture navigation or some such.)  */
+    ANDROID_TOUCH_SEQUENCE_CANCELED = 1,
+  };
+
+struct android_touch_event
+{
+  /* Type of the event.  */
+  enum android_event_type type;
+
+  /* Serial identifying the event.  */
+  unsigned long serial;
+
+  /* Window associated with the event.  */
+  android_window window;
+
+  /* X and Y coordinates of the event.  */
+  int x, y;
+
+  /* Time of the event, and the pointer identifier.  */
+  unsigned long time;
+
+  /* Index of the pointer being tracked.  */
+  unsigned int pointer_id;
+
+  /* Flags associated with this event.  */
+  int flags;
+};
+
+struct android_wheel_event
+{
+  /* Type of the event.  */
+  enum android_event_type type;
+
+  /* Serial identifying the event.  */
+  unsigned long serial;
+
+  /* Window associated with the event.  */
+  android_window window;
+
+  /* X and Y coordinates of the event.  */
+  int x, y;
+
+  /* Time of the event, and the pointer identifier.  */
+  unsigned long time;
+
+  /* Modifier state at the time of the event.  */
+  int state;
+
+  /* Motion alongside the X and Y axes.  */
+  double x_delta, y_delta;
+};
+
+struct android_iconify_event
+{
+  /* Type of the event.  */
+  enum android_event_type type;
+
+  /* Serial identifying the event.  */
+  unsigned long serial;
+
+  /* Window associated with the event.  */
+  android_window window;
+};
+
+struct android_menu_event
+{
+  /* Type of the event.  */
+  enum android_event_type type;
+
+  /* Serial identifying the event.  */
+  unsigned long serial;
+
+  /* Window associated with the event.  Always None.  */
+  android_window window;
+
+  /* Menu event ID.  */
+  int menu_event_id;
+
+  /* Menu event serial; this counter identifies the context menu.  */
+  int menu_event_serial;
+};
+
+enum android_ime_operation
+  {
+    ANDROID_IME_COMMIT_TEXT,
+    ANDROID_IME_DELETE_SURROUNDING_TEXT,
+    ANDROID_IME_FINISH_COMPOSING_TEXT,
+    ANDROID_IME_SET_COMPOSING_TEXT,
+    ANDROID_IME_SET_COMPOSING_REGION,
+    ANDROID_IME_SET_POINT,
+    ANDROID_IME_START_BATCH_EDIT,
+    ANDROID_IME_END_BATCH_EDIT,
+    ANDROID_IME_REQUEST_SELECTION_UPDATE,
+    ANDROID_IME_REQUEST_CURSOR_UPDATES,
+  };
+
+enum
+  {
+    ANDROID_CURSOR_UPDATE_IMMEDIATE = 1,
+    ANDROID_CURSOR_UPDATE_MONITOR   = (1 << 1),
+  };
+
+struct android_ime_event
+{
+  /* Type of the event.  */
+  enum android_event_type type;
+
+  /* The event serial.  */
+  unsigned long serial;
+
+  /* The associated window.  */
+  android_window window;
+
+  /* What operation is being performed.  */
+  enum android_ime_operation operation;
+
+  /* The details of the operation.  START and END provide buffer
+     indices, and may actually mean ``left'' and ``right''.  */
+  ptrdiff_t start, end, position;
+
+  /* The number of characters in TEXT.
+
+     If OPERATION is ANDROID_IME_REQUEST_CURSOR_UPDATES, then this is
+     actually the cursor update mode associated with that
+     operation.  */
+  size_t length;
+
+  /* TEXT is either NULL, or a pointer to LENGTH bytes of malloced
+     UTF-16 encoded text that must be decoded by Emacs.
+
+     POSITION is where point should end up after the text is
+     committed, relative to TEXT.  If POSITION is less than 0, it is
+     relative to TEXT's start; otherwise, it is relative to its
+     end.  */
+  unsigned short *text;
+
+  /* Value to set the counter to after the operation completes.  */
+  unsigned long counter;
+};
+
+union android_event
+{
+  enum android_event_type type;
+  struct android_any_event xany;
+  struct android_key_event xkey;
+  struct android_configure_event xconfigure;
+  struct android_focus_event xfocus;
+  struct android_window_action_event xaction;
+  struct android_crossing_event xcrossing;
+  struct android_motion_event xmotion;
+  struct android_button_event xbutton;
+  struct android_expose_event xexpose;
+
+  /* This has no parallel in X, since the X model of having
+     monotonically increasing touch IDs can't work on Android.  */
+  struct android_touch_event touch;
+
+  /* This has no parallel in X outside the X Input Extension, and
+     emulating the input extension interface would be awfully
+     complicated.  */
+  struct android_wheel_event wheel;
+
+  /* This has no parallel in X because Android doesn't have window
+     properties.  */
+  struct android_iconify_event iconified;
+
+  /* This is only used to transmit selected menu items.  */
+  struct android_menu_event menu;
+
+  /* This is used to dispatch input method editing requests.  */
+  struct android_ime_event ime;
+};
+
+enum
+  {
+    ANDROID_CURRENT_TIME = 0L,
+  };
+
+enum android_lookup_status
+  {
+    ANDROID_BUFFER_OVERFLOW,
+    ANDROID_LOOKUP_NONE,
+    ANDROID_LOOKUP_CHARS,
+    ANDROID_LOOKUP_KEYSYM,
+    ANDROID_LOOKUP_BOTH,
+  };
+
+enum android_ic_mode
+  {
+    ANDROID_IC_MODE_NULL   = 0,
+    ANDROID_IC_MODE_ACTION = 1,
+    ANDROID_IC_MODE_TEXT   = 2,
+  };
+
+extern int android_pending (void);
+extern void android_next_event (union android_event *);
+extern bool android_check_if_event (union android_event *,
+                                   bool (*) (union android_event *,
+                                             void *),
+                                   void *);
+
+extern android_window android_create_window (android_window, int,
+                                            int, int, int,
+                                            enum android_window_value_mask,
+                                            struct
+                                            android_set_window_attributes *);
+extern void android_change_window_attributes (android_window,
+                                             enum android_window_value_mask,
+                                             struct
+                                             android_set_window_attributes *);
+extern void android_set_window_background (android_window, unsigned long);
+extern void android_destroy_window (android_window);
+extern void android_reparent_window (android_window, android_window,
+                                    int, int);
+extern void android_set_clip_rectangles (struct android_gc *,
+                                        int, int,
+                                        struct android_rectangle *,
+                                        int);
+extern void android_change_gc (struct android_gc *,
+                              enum android_gc_value_mask,
+                              struct android_gc_values *);
+
+extern void android_clear_window (android_window);
+extern void android_map_window (android_window);
+extern void android_unmap_window (android_window);
+extern void android_resize_window (android_window, unsigned int,
+                                  unsigned int);
+extern void android_move_window (android_window, int, int);
+extern void android_swap_buffers (struct android_swap_info *, int);
+extern void android_get_gc_values (struct android_gc *,
+                                  enum android_gc_value_mask,
+                                  struct android_gc_values *);
+extern void android_set_foreground (struct android_gc *,
+                                   unsigned long);
+extern void android_fill_rectangle (android_drawable, struct android_gc *,
+                                   int, int, unsigned int, unsigned int);
+extern android_pixmap android_create_pixmap_from_bitmap_data (char *,
+                                                             unsigned int,
+                                                             unsigned int,
+                                                             unsigned long,
+                                                             unsigned long,
+                                                             unsigned int);
+extern void android_set_clip_mask (struct android_gc *, android_pixmap);
+extern void android_set_fill_style (struct android_gc *,
+                                   enum android_fill_style);
+extern void android_copy_area (android_drawable, android_drawable,
+                              struct android_gc *, int, int,
+                              unsigned int, unsigned int, int, int);
+extern void android_free_pixmap (android_drawable);
+
+extern void android_set_background (struct android_gc *, unsigned long);
+extern void android_fill_polygon (android_drawable, struct android_gc *,
+                                 struct android_point *, int,
+                                 enum android_shape,
+                                 enum android_coord_mode);
+extern void android_draw_rectangle (android_drawable, struct android_gc *,
+                                   int, int, unsigned int, unsigned int);
+extern void android_draw_point (android_window, struct android_gc *,
+                               int, int);
+extern void android_draw_line (android_window, struct android_gc *,
+                              int, int, int, int);
+extern android_pixmap android_create_pixmap (unsigned int, unsigned int,
+                                            int);
+extern void android_set_ts_origin (struct android_gc *, int, int);
+extern void android_clear_area (android_window, int, int, unsigned int,
+                               unsigned int);
+extern android_pixmap android_create_bitmap_from_data (char *, unsigned int,
+                                                      unsigned int);
+
+extern void android_bell (void);
+extern void android_set_input_focus (android_window, unsigned long);
+extern void android_raise_window (android_window);
+extern void android_lower_window (android_window);
+extern int android_query_tree (android_window, android_window *,
+                              android_window *, android_window **,
+                              unsigned int *);
+extern void android_get_geometry (android_window, android_window *,
+                                 int *, int *, unsigned int *,
+                                 unsigned int *, unsigned int *);
+extern void android_move_resize_window (android_window, int, int,
+                                       unsigned int, unsigned int);
+extern void android_map_raised (android_window);
+extern void android_translate_coordinates (android_window, int,
+                                          int, int *, int *);
+extern int android_wc_lookup_string (android_key_pressed_event *,
+                                    wchar_t *, int, int *,
+                                    enum android_lookup_status *);
+extern void android_update_ic (android_window, ptrdiff_t, ptrdiff_t,
+                              ptrdiff_t, ptrdiff_t);
+extern void android_reset_ic (android_window, enum android_ic_mode);
+extern void android_update_extracted_text (android_window, void *,
+                                          int);
+extern void android_update_cursor_anchor_info (android_window, float,
+                                              float, float, float);
+extern int android_set_fullscreen (android_window, bool);
+
+enum android_cursor_shape
+  {
+    ANDROID_XC_XTERM = 1008,
+    ANDROID_XC_LEFT_PTR = 1000,
+    ANDROID_XC_WATCH = 1004,
+    ANDROID_XC_HAND2 = 1002,
+    ANDROID_XC_SB_H_DOUBLE_ARROW = 1014,
+    ANDROID_XC_SB_V_DOUBLE_ARROW = 1015,
+    ANDROID_XC_LEFT_SIDE = 1020,
+    ANDROID_XC_TOP_LEFT_CORNER = 1020,
+    ANDROID_XC_TOP_SIDE = 1020,
+    ANDROID_XC_TOP_RIGHT_CORNER = 1020,
+    ANDROID_XC_RIGHT_SIDE = 1020,
+    ANDROID_XC_BOTTOM_RIGHT_CORNER = 1020,
+    ANDROID_XC_BOTTOM_SIDE = 1020,
+    ANDROID_XC_BOTTOM_LEFT_CORNER = 1020,
+    ANDROID_XC_NULL = 0,
+  };
+
+extern android_cursor android_create_font_cursor (enum android_cursor_shape);
+extern void android_define_cursor (android_window, android_cursor);
+extern void android_free_cursor (android_cursor);
+
+#endif
+
+
+
+/* Image support.  Keep the API as similar to XImage as possible.  To
+   avoid leaving a huge mess of "#ifndef ANDROID_STUBIFY" in image.c,
+   stubs should be defined for all functions.  */
+
+enum android_image_format
+  {
+    ANDROID_Z_PIXMAP,
+  };
+
+struct android_image
+{
+  int width, height;
+  enum android_image_format format;
+  char *data;
+  int depth;
+  int bytes_per_line;
+  int bits_per_pixel;
+};
+
+extern struct android_image *android_create_image (unsigned int,
+                                                  enum android_image_format,
+                                                  char *, unsigned int,
+                                                  unsigned int);
+extern void android_destroy_image (struct android_image *);
+
+extern void android_put_pixel (struct android_image *, int, int,
+                              unsigned long);
+extern unsigned long android_get_pixel (struct android_image *, int, int);
+extern struct android_image *android_get_image (android_drawable,
+                                               enum android_image_format);
+extern void android_put_image (android_pixmap, struct android_image *);
+
+
+/* Native image transforms.  */
+
+/* 3x2 matrix describing a projective transform.  See
+   android_transform_coordinates for details.  */
+
+struct android_transform
+{
+  float m1, m2, m3;
+  float m4, m5, m6;
+};
+
+extern void android_project_image_bilinear (struct android_image *,
+                                           struct android_image *,
+                                           struct android_transform *);
+extern void android_project_image_nearest (struct android_image *,
+                                          struct android_image *,
+                                          struct android_transform *);
+
+
+
+/* X emulation stuff also needed while building stubs.  */
+
+extern struct android_gc *android_create_gc (enum android_gc_value_mask,
+                                            struct android_gc_values *);
+extern void android_free_gc (struct android_gc *);
+
+#endif /* _ANDROID_GUI_H_ */
diff --git a/src/androidmenu.c b/src/androidmenu.c
new file mode 100644
index 00000000000..94e3f646b44
--- /dev/null
+++ b/src/androidmenu.c
@@ -0,0 +1,844 @@
+/* Communication module for Android terminals.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "lisp.h"
+#include "androidterm.h"
+#include "android.h"
+#include "blockinput.h"
+#include "keyboard.h"
+#include "menu.h"
+
+#ifndef ANDROID_STUBIFY
+
+#include <android/log.h>
+
+/* Flag indicating whether or not a popup menu has been posted and not
+   yet popped down.  */
+
+static int popup_activated_flag;
+
+/* Serial number used to identify which context menu events are
+   associated with the context menu currently being displayed.  */
+
+unsigned int current_menu_serial;
+
+int
+popup_activated (void)
+{
+  return popup_activated_flag;
+}
+
+
+
+/* Toolkit menu implementation.  */
+
+/* Structure describing the EmacsContextMenu class.  */
+
+struct android_emacs_context_menu
+{
+  jclass class;
+  jmethodID create_context_menu;
+  jmethodID add_item;
+  jmethodID add_submenu;
+  jmethodID add_pane;
+  jmethodID parent;
+  jmethodID display;
+  jmethodID dismiss;
+};
+
+/* Identifiers associated with the EmacsContextMenu class.  */
+static struct android_emacs_context_menu menu_class;
+
+static void
+android_init_emacs_context_menu (void)
+{
+  jclass old;
+
+  menu_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsContextMenu");
+  eassert (menu_class.class);
+
+  old = menu_class.class;
+  menu_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!menu_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  menu_class.c_name                                            \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       menu_class.class,       \
+                                       name, signature);       \
+  eassert (menu_class.c_name);
+
+#define FIND_METHOD_STATIC(c_name, name, signature)            \
+  menu_class.c_name                                            \
+    = (*android_java_env)->GetStaticMethodID (android_java_env,        \
+                                             menu_class.class, \
+                                             name, signature); \
+  eassert (menu_class.c_name);
+
+  FIND_METHOD_STATIC (create_context_menu, "createContextMenu",
+                     "(Ljava/lang/String;)"
+                     "Lorg/gnu/emacs/EmacsContextMenu;");
+
+  FIND_METHOD (add_item, "addItem", "(ILjava/lang/String;ZZZ"
+              "Ljava/lang/String;Z)V");
+  FIND_METHOD (add_submenu, "addSubmenu", "(Ljava/lang/String;"
+              "Ljava/lang/String;)"
+              "Lorg/gnu/emacs/EmacsContextMenu;");
+  FIND_METHOD (add_pane, "addPane", "(Ljava/lang/String;)V");
+  FIND_METHOD (parent, "parent", "()Lorg/gnu/emacs/EmacsContextMenu;");
+  FIND_METHOD (display, "display", "(Lorg/gnu/emacs/EmacsWindow;III)Z");
+  FIND_METHOD (dismiss, "dismiss", "(Lorg/gnu/emacs/EmacsWindow;)V");
+
+#undef FIND_METHOD
+#undef FIND_METHOD_STATIC
+}
+
+static void
+android_unwind_local_frame (void)
+{
+  (*android_java_env)->PopLocalFrame (android_java_env, NULL);
+}
+
+/* Push a local reference frame to the JVM stack and record it on the
+   specpdl.  Release local references created within that frame when
+   the specpdl is unwound past where it is after returning.  */
+
+static void
+android_push_local_frame (void)
+{
+  int rc;
+
+  rc = (*android_java_env)->PushLocalFrame (android_java_env, 30);
+
+  /* This means the JVM ran out of memory.  */
+  if (rc < 1)
+    android_exception_check ();
+
+  record_unwind_protect_void (android_unwind_local_frame);
+}
+
+/* Data for android_dismiss_menu.  */
+
+struct android_dismiss_menu_data
+{
+  /* The menu object.  */
+  jobject menu;
+
+  /* The window object.  */
+  jobject window;
+};
+
+/* Cancel the context menu passed in POINTER.  Also, clear
+   popup_activated_flag.  */
+
+static void
+android_dismiss_menu (void *pointer)
+{
+  struct android_dismiss_menu_data *data;
+
+  data = pointer;
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      data->menu,
+                                      menu_class.dismiss,
+                                      data->window);
+  popup_activated_flag = 0;
+}
+
+/* Recursively process events until a ANDROID_CONTEXT_MENU event
+   arrives.  Then, return the item ID specified in the event in
+   *ID.  */
+
+static void
+android_process_events_for_menu (int *id)
+{
+  int blocked;
+
+  /* Set menu_event_id to -1; handle_one_android_event will set it to
+     the event ID upon receiving a context menu event.  This can cause
+     a non-local exit.  */
+  x_display_list->menu_event_id = -1;
+
+  /* Unblock input completely.  */
+  blocked = interrupt_input_blocked;
+  totally_unblock_input ();
+
+  /* Now wait for the menu event ID to change.  */
+  while (x_display_list->menu_event_id == -1)
+    {
+      /* Wait for events to become available.  */
+      android_wait_event ();
+
+      /* Process pending signals.  */
+      process_pending_signals ();
+
+      /* Maybe quit.  This is important because the framework (on
+        Android 4.0.3) can sometimes fail to deliver context menu
+        closed events if a submenu was opened, and the user still
+        needs to be able to quit.  */
+      maybe_quit ();
+    }
+
+  /* Restore the input block.  */
+  interrupt_input_blocked = blocked;
+
+  /* Return the ID.  */
+  *id = x_display_list->menu_event_id;
+}
+
+/* Structure describing a ``subprefix'' in the menu.  */
+
+struct android_menu_subprefix
+{
+  /* The subprefix above.  */
+  struct android_menu_subprefix *last;
+
+  /* The subprefix itself.  */
+  Lisp_Object subprefix;
+};
+
+/* Free the subprefixes starting from *DATA.  */
+
+static void
+android_free_subprefixes (void *data)
+{
+  struct android_menu_subprefix **head, *subprefix;
+
+  head = data;
+
+  while (*head)
+    {
+      subprefix = *head;
+      *head = subprefix->last;
+
+      xfree (subprefix);
+    }
+}
+
+Lisp_Object
+android_menu_show (struct frame *f, int x, int y, int menuflags,
+                  Lisp_Object title, const char **error_name)
+{
+  jobject context_menu, current_context_menu;
+  jobject title_string, help_string, temp;
+  size_t i;
+  Lisp_Object pane_name, prefix;
+  const char *pane_string;
+  specpdl_ref count, count1;
+  Lisp_Object item_name, enable, def, tem, entry, type, selected;
+  Lisp_Object help;
+  jmethodID method;
+  jobject store;
+  bool rc;
+  jobject window;
+  int id, item_id, submenu_depth;
+  struct android_dismiss_menu_data data;
+  struct android_menu_subprefix *subprefix, *temp_subprefix;
+  struct android_menu_subprefix *subprefix_1;
+  bool checkmark;
+  unsigned int serial;
+
+  count = SPECPDL_INDEX ();
+  serial = ++current_menu_serial;
+
+  block_input ();
+
+  /* Push the first local frame.  */
+  android_push_local_frame ();
+
+  /* Set title_string to a Java string containing TITLE if non-nil.
+     If the menu consists of more than one pane, replace the title
+     with the pane header item so that the menu looks consistent.  */
+
+  title_string = NULL;
+  if (STRINGP (title) && menu_items_n_panes < 2)
+    title_string = android_build_string (title);
+
+  /* Push the first local frame for the context menu.  */
+  method = menu_class.create_context_menu;
+  current_context_menu = context_menu
+    = (*android_java_env)->CallStaticObjectMethod (android_java_env,
+                                                  menu_class.class,
+                                                  method,
+                                                  title_string);
+
+  /* Delete the unused title reference.  */
+
+  if (title_string)
+    ANDROID_DELETE_LOCAL_REF (title_string);
+
+  /* Push the second local frame for temporaries.  */
+  count1 = SPECPDL_INDEX ();
+  android_push_local_frame ();
+
+  /* Iterate over the menu.  */
+  i = 0, submenu_depth = 0;
+
+  while (i < menu_items_used)
+    {
+      if (NILP (AREF (menu_items, i)))
+       {
+         /* This is the start of a new submenu.  However, it can be
+            ignored here.  */
+         i += 1;
+         submenu_depth += 1;
+       }
+      else if (EQ (AREF (menu_items, i), Qlambda))
+       {
+         /* This is the end of a submenu.  Go back to the previous
+            context menu.  */
+         store = current_context_menu;
+         current_context_menu
+           = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                    current_context_menu,
+                                                    menu_class.parent);
+         android_exception_check ();
+
+         if (store != context_menu)
+           ANDROID_DELETE_LOCAL_REF (store);
+         i += 1;
+         submenu_depth -= 1;
+
+         if (!current_context_menu || submenu_depth < 0)
+           {
+             __android_log_print (ANDROID_LOG_FATAL, __func__,
+                                  "unbalanced submenu pop in menu_items");
+             emacs_abort ();
+           }
+       }
+      else if (EQ (AREF (menu_items, i), Qt)
+              && submenu_depth != 0)
+       i += MENU_ITEMS_PANE_LENGTH;
+      else if (EQ (AREF (menu_items, i), Qquote))
+       i += 1;
+      else if (EQ (AREF (menu_items, i), Qt))
+       {
+         /* If the menu contains a single pane, then the pane is
+            actually TITLE.  Don't duplicate the text within the
+            context menu title.  */
+
+         if (menu_items_n_panes < 2)
+           goto next_item;
+
+         /* This is a new pane.  Switch back to the topmost context
+            menu.  */
+         if (current_context_menu != context_menu)
+           ANDROID_DELETE_LOCAL_REF (current_context_menu);
+         current_context_menu = context_menu;
+
+         /* Now figure out the title of this pane.  */
+         pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
+         prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+         pane_string = (NILP (pane_name)
+                        ? "" : SSDATA (pane_name));
+         if ((menuflags & MENU_KEYMAPS) && !NILP (prefix))
+           pane_string++;
+
+         /* Add the pane.  */
+         temp = (*android_java_env)->NewStringUTF (android_java_env,
+                                                   pane_string);
+         android_exception_check ();
+
+         (*android_java_env)->CallVoidMethod (android_java_env,
+                                              current_context_menu,
+                                              menu_class.add_pane,
+                                              temp);
+         android_exception_check ();
+         ANDROID_DELETE_LOCAL_REF (temp);
+
+       next_item:
+         i += MENU_ITEMS_PANE_LENGTH;
+       }
+      else
+       {
+         item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+         enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+         def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
+         type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
+         selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
+         help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
+
+         /* This is an actual menu item (or submenu).  Add it to the
+            menu.  */
+
+         if (i + MENU_ITEMS_ITEM_LENGTH < menu_items_used
+             && NILP (AREF (menu_items, i + MENU_ITEMS_ITEM_LENGTH)))
+           {
+             /* This is a submenu.  Add it.  */
+             title_string = (!NILP (item_name)
+                             ? android_build_string (item_name)
+                             : NULL);
+             help_string = NULL;
+
+             /* Menu items can have tool tips on Android 26 and
+                later.  In this case, set it to the help string.  */
+
+             if (android_get_current_api_level () >= 26
+                 && STRINGP (help))
+               help_string = android_build_string (help);
+
+             store = current_context_menu;
+             current_context_menu
+               = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                        current_context_menu,
+                                                        menu_class.add_submenu,
+                                                        title_string,
+                                                        help_string);
+             android_exception_check ();
+
+             if (store != context_menu)
+               ANDROID_DELETE_LOCAL_REF (store);
+
+             if (title_string)
+               ANDROID_DELETE_LOCAL_REF (title_string);
+
+             if (help_string)
+               ANDROID_DELETE_LOCAL_REF (help_string);
+           }
+         else if (NILP (def) && menu_separator_name_p (SSDATA (item_name)))
+           /* Ignore this separator item.  */
+           ;
+         else
+           {
+             /* Compute the item ID.  This is the index of value.
+                Make sure it doesn't overflow.  */
+
+             if (!INT_ADD_OK (0, i + MENU_ITEMS_ITEM_VALUE, &item_id))
+               memory_full (i + MENU_ITEMS_ITEM_VALUE * sizeof (Lisp_Object));
+
+             /* Add this menu item with the appropriate state.  */
+
+             title_string = (!NILP (item_name)
+                             ? android_build_string (item_name)
+                             : NULL);
+             help_string = NULL;
+
+             /* Menu items can have tool tips on Android 26 and
+                later.  In this case, set it to the help string.  */
+
+             if (android_get_current_api_level () >= 26
+                 && STRINGP (help))
+               help_string = android_build_string (help);
+
+             /* Determine whether or not to display a check box.  */
+
+             checkmark = (EQ (type, QCtoggle)
+                          || EQ (type, QCradio));
+
+             (*android_java_env)->CallVoidMethod (android_java_env,
+                                                  current_context_menu,
+                                                  menu_class.add_item,
+                                                  (jint) item_id,
+                                                  title_string,
+                                                  (jboolean) !NILP (enable),
+                                                  (jboolean) checkmark,
+                                                  (jboolean) !NILP (selected),
+                                                  help_string,
+                                                  (jboolean) (EQ (type,
+                                                                  QCradio)));
+             android_exception_check ();
+
+             if (title_string)
+               ANDROID_DELETE_LOCAL_REF (title_string);
+
+             if (help_string)
+               ANDROID_DELETE_LOCAL_REF (help_string);
+           }
+
+         i += MENU_ITEMS_ITEM_LENGTH;
+       }
+    }
+
+  /* The menu has now been built.  Pop the second local frame.  */
+  unbind_to (count1, Qnil);
+
+  /* Now, display the context menu.  */
+  window = android_resolve_handle (FRAME_ANDROID_WINDOW (f),
+                                  ANDROID_HANDLE_WINDOW);
+  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
+                                              context_menu,
+                                              menu_class.display,
+                                              window, (jint) x,
+                                              (jint) y,
+                                              (jint) serial);
+  android_exception_check ();
+
+  if (!rc)
+    /* This means displaying the menu failed.  */
+    goto finish;
+
+  /* Make sure the context menu is always dismissed.  */
+  data.menu = context_menu;
+  data.window = window;
+  record_unwind_protect_ptr (android_dismiss_menu, &data);
+
+  /* Next, process events waiting for something to be selected.  */
+  popup_activated_flag = 1;
+  android_process_events_for_menu (&id);
+
+  if (!id)
+    /* This means no menu item was selected.  */
+    goto finish;
+
+  /* This means the id is invalid.  */
+  if (id >= ASIZE (menu_items))
+    goto finish;
+
+  /* Now return the menu item at that location.  */
+  tem = Qnil;
+  subprefix = NULL;
+  record_unwind_protect_ptr (android_free_subprefixes, &subprefix);
+
+  /* Find the selected item, and its pane, to return
+     the proper value.  */
+
+  prefix = entry = Qnil;
+  i = 0;
+  while (i < menu_items_used)
+    {
+      if (NILP (AREF (menu_items, i)))
+       {
+         temp_subprefix = xmalloc (sizeof *temp_subprefix);
+         temp_subprefix->last = subprefix;
+         subprefix = temp_subprefix;
+         subprefix->subprefix = prefix;
+
+         prefix = entry;
+         i++;
+       }
+      else if (EQ (AREF (menu_items, i), Qlambda))
+       {
+         prefix = subprefix->subprefix;
+         temp_subprefix = subprefix->last;
+         xfree (subprefix);
+         subprefix = temp_subprefix;
+
+         i++;
+       }
+      else if (EQ (AREF (menu_items, i), Qt))
+       {
+         prefix
+           = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+         i += MENU_ITEMS_PANE_LENGTH;
+       }
+      /* Ignore a nil in the item list.
+        It's meaningful only for dialog boxes.  */
+      else if (EQ (AREF (menu_items, i), Qquote))
+       i += 1;
+      else
+       {
+         entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+
+         if (i + MENU_ITEMS_ITEM_VALUE == id)
+           {
+             if (menuflags & MENU_KEYMAPS)
+               {
+                 entry = list1 (entry);
+
+                 if (!NILP (prefix))
+                   entry = Fcons (prefix, entry);
+
+                 for (subprefix_1 = subprefix; subprefix_1;
+                      subprefix_1 = subprefix_1->last)
+                   if (!NILP (subprefix_1->subprefix))
+                     entry = Fcons (subprefix_1->subprefix, entry);
+               }
+
+             tem = entry;
+           }
+         i += MENU_ITEMS_ITEM_LENGTH;
+       }
+    }
+
+  unblock_input ();
+  return unbind_to (count, tem);
+
+ finish:
+  unblock_input ();
+  return unbind_to (count, Qnil);
+}
+
+
+
+/* Toolkit dialog implementation.  */
+
+/* Structure describing the EmacsDialog class.  */
+
+struct android_emacs_dialog
+{
+  jclass class;
+  jmethodID create_dialog;
+  jmethodID add_button;
+  jmethodID display;
+};
+
+/* Identifiers associated with the EmacsDialog class.  */
+static struct android_emacs_dialog dialog_class;
+
+static void
+android_init_emacs_dialog (void)
+{
+  jclass old;
+
+  dialog_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsDialog");
+  eassert (dialog_class.class);
+
+  old = dialog_class.class;
+  dialog_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 (jobject) old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!dialog_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  dialog_class.c_name                                          \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       dialog_class.class,     \
+                                       name, signature);       \
+  eassert (dialog_class.c_name);
+
+#define FIND_METHOD_STATIC(c_name, name, signature)                    \
+  dialog_class.c_name                                                  \
+    = (*android_java_env)->GetStaticMethodID (android_java_env,                
\
+                                             dialog_class.class,       \
+                                             name, signature);         \
+
+  FIND_METHOD_STATIC (create_dialog, "createDialog", "(Ljava/lang/String;"
+                     "Ljava/lang/String;I)Lorg/gnu/emacs/EmacsDialog;");
+  FIND_METHOD (add_button, "addButton", "(Ljava/lang/String;IZ)V");
+  FIND_METHOD (display, "display", "()Z");
+
+#undef FIND_METHOD
+#undef FIND_METHOD_STATIC
+}
+
+static Lisp_Object
+android_dialog_show (struct frame *f, Lisp_Object title,
+                    Lisp_Object header, const char **error_name)
+{
+  specpdl_ref count;
+  jobject dialog, java_header, java_title, temp;
+  size_t i;
+  Lisp_Object item_name, enable, entry;
+  bool rc;
+  int id;
+  jmethodID method;
+  unsigned int serial;
+
+  /* Generate a unique ID for events from this dialog box.  */
+  serial = ++current_menu_serial;
+
+  if (menu_items_n_panes > 1)
+    {
+      *error_name = "Multiple panes in dialog box";
+      return Qnil;
+    }
+
+  /* Do the initial setup.  */
+  count = SPECPDL_INDEX ();
+  *error_name = NULL;
+
+  android_push_local_frame ();
+
+  /* Figure out what header to use.  */
+  java_header = (!NILP (header)
+                ? android_build_jstring ("Information")
+                : android_build_jstring ("Question"));
+
+  /* And the title.  */
+  java_title = android_build_string (title);
+
+  /* Now create the dialog.  */
+  method = dialog_class.create_dialog;
+  dialog = (*android_java_env)->CallStaticObjectMethod (android_java_env,
+                                                       dialog_class.class,
+                                                       method, java_header,
+                                                       java_title,
+                                                       (jint) serial);
+  android_exception_check ();
+
+  /* Delete now unused local references.  */
+  if (java_header)
+    ANDROID_DELETE_LOCAL_REF (java_header);
+  ANDROID_DELETE_LOCAL_REF (java_title);
+
+  /* Create the buttons.  */
+  i = MENU_ITEMS_PANE_LENGTH;
+  while (i < menu_items_used)
+    {
+      item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
+      enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
+
+      /* Verify that there is no submenu here.  */
+
+      if (NILP (item_name))
+       {
+         *error_name = "Submenu in dialog items";
+         return unbind_to (count, Qnil);
+       }
+
+      /* Skip past boundaries between buttons on different sides.  The
+        Android toolkit is too silly to understand this
+        distinction.  */
+
+      if (EQ (item_name, Qquote))
+       ++i;
+      else
+       {
+         /* Make sure i is within bounds.  */
+         if (i > TYPE_MAXIMUM (jint))
+           {
+             *error_name = "Dialog box too big";
+             return unbind_to (count, Qnil);
+           }
+
+         /* Add the button.  */
+         temp = android_build_string (item_name);
+         (*android_java_env)->CallVoidMethod (android_java_env,
+                                              dialog,
+                                              dialog_class.add_button,
+                                              temp, (jint) i,
+                                              (jboolean) NILP (enable));
+         android_exception_check ();
+         ANDROID_DELETE_LOCAL_REF (temp);
+         i += MENU_ITEMS_ITEM_LENGTH;
+       }
+    }
+
+  /* The dialog is now built.  Run it.  */
+  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
+                                              dialog,
+                                              dialog_class.display);
+  android_exception_check ();
+
+  if (!rc)
+    quit ();
+
+  /* Wait for the menu ID to arrive.  */
+  android_process_events_for_menu (&id);
+
+  if (!id)
+    quit ();
+
+  /* Find the selected item, and its pane, to return
+     the proper value.  */
+  i = 0;
+  while (i < menu_items_used)
+    {
+      if (EQ (AREF (menu_items, i), Qt))
+       i += MENU_ITEMS_PANE_LENGTH;
+      else if (EQ (AREF (menu_items, i), Qquote))
+       /* This is the boundary between left-side elts and right-side
+          elts.  */
+       ++i;
+      else
+       {
+         entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+
+         if (id == i)
+           return entry;
+
+         i += MENU_ITEMS_ITEM_LENGTH;
+       }
+    }
+
+  return Qnil;
+}
+
+Lisp_Object
+android_popup_dialog (struct frame *f, Lisp_Object header,
+                     Lisp_Object contents)
+{
+  Lisp_Object title;
+  const char *error_name;
+  Lisp_Object selection;
+  specpdl_ref specpdl_count = SPECPDL_INDEX ();
+
+  check_window_system (f);
+
+  /* Decode the dialog items from what was specified.  */
+  title = Fcar (contents);
+  CHECK_STRING (title);
+  record_unwind_protect_void (unuse_menu_items);
+
+  if (NILP (Fcar (Fcdr (contents))))
+    /* No buttons specified, add an "Ok" button so users can pop down
+       the dialog.  */
+    contents = list2 (title, Fcons (build_string ("Ok"), Qt));
+
+  list_of_panes (list1 (contents));
+
+  /* Display them in a dialog box.  */
+  block_input ();
+  selection = android_dialog_show (f, title, header, &error_name);
+  unblock_input ();
+
+  unbind_to (specpdl_count, Qnil);
+  discard_menu_items ();
+
+  if (error_name)
+    error ("%s", error_name);
+
+  return selection;
+}
+
+#else
+
+int
+popup_activated (void)
+{
+  return 0;
+}
+
+#endif
+
+DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p,
+       Smenu_or_popup_active_p, 0, 0, 0,
+       doc: /* SKIP: real doc in xfns.c.  */)
+  (void)
+{
+  return (popup_activated ()) ? Qt : Qnil;
+}
+
+void
+init_androidmenu (void)
+{
+#ifndef ANDROID_STUBIFY
+  android_init_emacs_context_menu ();
+  android_init_emacs_dialog ();
+#endif
+}
+
+void
+syms_of_androidmenu (void)
+{
+  defsubr (&Smenu_or_popup_active_p);
+}
diff --git a/src/androidselect.c b/src/androidselect.c
new file mode 100644
index 00000000000..5735eda2dd5
--- /dev/null
+++ b/src/androidselect.c
@@ -0,0 +1,803 @@
+/* Communication module for Android terminals.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <minmax.h>
+#include <unistd.h>
+
+#include <boot-time.h>
+#include <sys/types.h>
+
+#include "lisp.h"
+#include "blockinput.h"
+#include "coding.h"
+#include "android.h"
+#include "androidterm.h"
+
+/* Selection support on Android is confined to copying and pasting of
+   plain text and MIME data from the clipboard.  There is no primary
+   selection.
+
+   While newer versions of Android are supposed to have the necessary
+   interfaces for transferring other kinds of selection data, doing so
+   is too complicated, and involves registering ``content providers''
+   and all kinds of other stuff; for this reason, Emacs does not
+   support setting the clipboard contents to anything other than plain
+   text.  */
+
+
+
+/* Structure describing the EmacsClipboard class.  */
+
+struct android_emacs_clipboard
+{
+  jclass class;
+  jmethodID set_clipboard;
+  jmethodID owns_clipboard;
+  jmethodID clipboard_exists;
+  jmethodID get_clipboard;
+  jmethodID make_clipboard;
+  jmethodID get_clipboard_targets;
+  jmethodID get_clipboard_data;
+};
+
+/* Methods associated with the EmacsClipboard class.  */
+static struct android_emacs_clipboard clipboard_class;
+
+/* Reference to the EmacsClipboard object.  */
+static jobject clipboard;
+
+
+
+static void
+android_init_emacs_clipboard (void)
+{
+  jclass old;
+
+  clipboard_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsClipboard");
+  eassert (clipboard_class.class);
+
+  old = clipboard_class.class;
+  clipboard_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!clipboard_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                   \
+  clipboard_class.c_name                                       \
+    = (*android_java_env)->GetMethodID (android_java_env,      \
+                                       clipboard_class.class,  \
+                                       name, signature);       \
+  assert (clipboard_class.c_name);
+
+  FIND_METHOD (set_clipboard, "setClipboard", "([B)V");
+  FIND_METHOD (owns_clipboard, "ownsClipboard", "()I");
+  FIND_METHOD (clipboard_exists, "clipboardExists", "()Z");
+  FIND_METHOD (get_clipboard, "getClipboard", "()[B");
+  FIND_METHOD (get_clipboard_targets, "getClipboardTargets",
+              "()[[B");
+  FIND_METHOD (get_clipboard_data, "getClipboardData",
+              "([B)[J");
+
+  clipboard_class.make_clipboard
+    = (*android_java_env)->GetStaticMethodID (android_java_env,
+                                             clipboard_class.class,
+                                             "makeClipboard",
+                                             "()Lorg/gnu/emacs/"
+                                             "EmacsClipboard;");
+  assert (clipboard_class.make_clipboard);
+
+#undef FIND_METHOD
+}
+
+
+
+
+DEFUN ("android-clipboard-owner-p", Fandroid_clipboard_owner_p,
+       Sandroid_clipboard_owner_p, 0, 0, 0,
+       doc: /* Return whether or not Emacs owns the clipboard.
+Alternatively, return the symbol `lambda' if that could not be
+determined.  */)
+  (void)
+{
+  jint rc;
+
+  if (!android_init_gui)
+    error ("Accessing clipboard without display connection");
+
+  block_input ();
+  rc = (*android_java_env)->CallIntMethod (android_java_env,
+                                          clipboard,
+                                          clipboard_class.owns_clipboard);
+  android_exception_check ();
+  unblock_input ();
+
+  /* If rc is 0 or 1, then Emacs knows whether or not it owns the
+     clipboard.  If rc is -1, then Emacs does not.  */
+
+  if (rc < 0)
+    return Qlambda;
+
+  return rc ? Qt : Qnil;
+}
+
+DEFUN ("android-set-clipboard", Fandroid_set_clipboard,
+       Sandroid_set_clipboard, 1, 1, 0,
+       doc: /* Set the clipboard text to STRING.  */)
+  (Lisp_Object string)
+{
+  jarray bytes;
+
+  if (!android_init_gui)
+    error ("Accessing clipboard without display connection");
+
+  CHECK_STRING (string);
+  string = ENCODE_UTF_8 (string);
+
+  bytes = (*android_java_env)->NewByteArray (android_java_env,
+                                            SBYTES (string));
+  android_exception_check ();
+
+  (*android_java_env)->SetByteArrayRegion (android_java_env, bytes,
+                                          0, SBYTES (string),
+                                          (jbyte *) SDATA (string));
+  (*android_java_env)->CallVoidMethod (android_java_env,
+                                      clipboard,
+                                      clipboard_class.set_clipboard,
+                                      bytes);
+  android_exception_check_1 (bytes);
+
+  ANDROID_DELETE_LOCAL_REF (bytes);
+  return Qnil;
+}
+
+DEFUN ("android-get-clipboard", Fandroid_get_clipboard,
+       Sandroid_get_clipboard, 0, 0, 0,
+       doc: /* Return the current contents of the clipboard.
+Value is a multibyte string containing decoded clipboard
+text.
+Alternatively, return nil if the clipboard is empty.  */)
+  (void)
+{
+  Lisp_Object string;
+  jarray bytes;
+  jmethodID method;
+  size_t length;
+  jbyte *data;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  method = clipboard_class.get_clipboard;
+  bytes
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            clipboard,
+                                            method);
+  android_exception_check ();
+
+  if (!bytes)
+    return Qnil;
+
+  length = (*android_java_env)->GetArrayLength (android_java_env,
+                                               bytes);
+  data = (*android_java_env)->GetByteArrayElements (android_java_env,
+                                                   bytes, NULL);
+  android_exception_check_nonnull (data, bytes);
+
+  string = make_unibyte_string ((char *) data, length);
+
+  (*android_java_env)->ReleaseByteArrayElements (android_java_env,
+                                                bytes, data,
+                                                JNI_ABORT);
+  ANDROID_DELETE_LOCAL_REF (bytes);
+
+  /* Now decode the resulting string.  */
+  return code_convert_string_norecord (string, Qutf_8, false);
+}
+
+DEFUN ("android-clipboard-exists-p", Fandroid_clipboard_exists_p,
+       Sandroid_clipboard_exists_p, 0, 0, 0,
+       doc: /* Return whether or not clipboard contents exist.  */)
+  (void)
+{
+  jboolean rc;
+  jmethodID method;
+
+  if (!android_init_gui)
+    error ("No Android display connection");
+
+  method = clipboard_class.clipboard_exists;
+  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
+                                              clipboard,
+                                              method);
+  android_exception_check ();
+
+  return rc ? Qt : Qnil;
+}
+
+DEFUN ("android-browse-url", Fandroid_browse_url,
+       Sandroid_browse_url, 1, 2, 0,
+       doc: /* Open URL in an external application.  URL should be a
+URL-encoded URL with a scheme specified unless SEND is non-nil.
+Signal an error upon failure.
+
+If SEND is nil, start a program that is able to display the URL, such
+as a web browser.  Otherwise, try to share URL using programs such as
+email clients.  */)
+  (Lisp_Object url, Lisp_Object send)
+{
+  Lisp_Object value;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  CHECK_STRING (url);
+  value = android_browse_url (url, send);
+
+  /* Signal an error upon failure.  */
+  if (!NILP (value))
+    signal_error ("Error browsing URL", value);
+
+  return Qnil;
+}
+
+
+
+/* MIME clipboard support.  This provides support for reading MIME
+   data (but not text) from the clipboard.  */
+
+DEFUN ("android-get-clipboard-targets", Fandroid_get_clipboard_targets,
+       Sandroid_get_clipboard_targets, 0, 0, 0,
+       doc: /* Return a list of data types in the clipboard.
+Value is a list of MIME types as strings, each defining a single extra
+data type available from the clipboard.  */)
+  (void)
+{
+  jarray bytes_array;
+  jbyteArray bytes;
+  jmethodID method;
+  size_t length, length1, i;
+  jbyte *data;
+  Lisp_Object targets, tem;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  targets = Qnil;
+  block_input ();
+  method = clipboard_class.get_clipboard_targets;
+  bytes_array = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                      clipboard, method);
+  android_exception_check ();
+
+  if (!bytes_array)
+    goto fail;
+
+  length = (*android_java_env)->GetArrayLength (android_java_env,
+                                               bytes_array);
+  for (i = 0; i < length; ++i)
+    {
+      /* Retireve the MIME type.  */
+      bytes
+       = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                     bytes_array, i);
+      android_exception_check_nonnull (bytes, bytes_array);
+
+      /* Cons it onto the list of targets.  */
+      length1 = (*android_java_env)->GetArrayLength (android_java_env,
+                                                    bytes);
+      data = (*android_java_env)->GetByteArrayElements (android_java_env,
+                                                       bytes, NULL);
+      android_exception_check_nonnull_1 (data, bytes, bytes_array);
+
+      /* Decode the string.  */
+      tem = make_unibyte_string ((char *) data, length1);
+      tem = code_convert_string_norecord (tem, Qutf_8, false);
+      targets = Fcons (tem, targets);
+
+      /* Delete the retrieved data.  */
+      (*android_java_env)->ReleaseByteArrayElements (android_java_env,
+                                                    bytes, data,
+                                                    JNI_ABORT);
+      ANDROID_DELETE_LOCAL_REF (bytes);
+    }
+  unblock_input ();
+
+  ANDROID_DELETE_LOCAL_REF (bytes_array);
+  return Fnreverse (targets);
+
+ fail:
+  unblock_input ();
+  return Qnil;
+}
+
+/* Free the memory inside PTR, a pointer to a char pointer.  */
+
+static void
+android_xfree_inside (void *ptr)
+{
+  xfree (*(char **) ptr);
+}
+
+DEFUN ("android-get-clipboard-data", Fandroid_get_clipboard_data,
+       Sandroid_get_clipboard_data, 1, 1, 0,
+       doc: /* Return the clipboard data of the given MIME TYPE.
+Value is a unibyte string containing the entire contents of the
+clipboard, after its owner has converted the data to the given
+MIME type.  Value is nil if the conversion fails, or if the data
+is not present.
+
+Value is also nil if the clipboard data consists of a single URL which
+does not have any corresponding data.  In that case, use
+`android-get-clipboard' instead.  */)
+  (Lisp_Object type)
+{
+  jlongArray array;
+  jbyteArray bytes;
+  jmethodID method;
+  int fd;
+  ptrdiff_t rc;
+  jlong offset, length, *longs;
+  specpdl_ref ref;
+  char *buffer, *start;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  /* Encode the string as UTF-8.  */
+  CHECK_STRING (type);
+  type = ENCODE_UTF_8 (type);
+
+  /* Then give it to the selection code.  */
+  block_input ();
+  bytes = (*android_java_env)->NewByteArray (android_java_env,
+                                            SBYTES (type));
+  (*android_java_env)->SetByteArrayRegion (android_java_env, bytes,
+                                          0, SBYTES (type),
+                                          (jbyte *) SDATA (type));
+  android_exception_check ();
+
+  method = clipboard_class.get_clipboard_data;
+  array = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                clipboard, method,
+                                                bytes);
+  android_exception_check_1 (bytes);
+  ANDROID_DELETE_LOCAL_REF (bytes);
+
+  if (!array)
+    goto fail;
+
+  longs = (*android_java_env)->GetLongArrayElements (android_java_env,
+                                                    array, NULL);
+  android_exception_check_nonnull (longs, array);
+
+  /* longs[0] is the file descriptor.
+     longs[1] is an offset to apply to the file.
+     longs[2] is either -1, or the number of bytes to read from the
+     file.  */
+  fd = longs[0];
+  offset = longs[1];
+  length = longs[2];
+
+  (*android_java_env)->ReleaseLongArrayElements (android_java_env,
+                                                array, longs,
+                                                JNI_ABORT);
+  ANDROID_DELETE_LOCAL_REF (array);
+  unblock_input ();
+
+  /* Now begin reading from longs[0].  */
+  ref = SPECPDL_INDEX ();
+  record_unwind_protect_int (close_file_unwind, fd);
+
+  if (length != -1)
+    {
+      buffer = xmalloc (MIN (length, PTRDIFF_MAX));
+      record_unwind_protect_ptr (xfree, buffer);
+
+      rc = emacs_read_quit (fd, buffer,
+                           MIN (length, PTRDIFF_MAX));
+
+      /* Return nil upon an IO problem.  */
+      if (rc < 0)
+       return unbind_to (ref, Qnil);
+
+      /* Return the data as a unibyte string.  */
+      return unbind_to (ref, make_unibyte_string (buffer, rc));
+    }
+
+  /* Otherwise, read BUFSIZ bytes at a time.  */
+  buffer = xmalloc (BUFSIZ);
+  length = 0;
+  start = buffer;
+
+  record_unwind_protect_ptr (android_xfree_inside, &buffer);
+
+  /* Seek to the start of the data.  */
+
+  if (offset)
+    {
+      if (lseek (fd, offset, SEEK_SET) < 0)
+       return unbind_to (ref, Qnil);
+    }
+
+  while (true)
+    {
+      rc = emacs_read_quit (fd, start, BUFSIZ);
+
+      if (!INT_ADD_OK (rc, length, &length)
+         || PTRDIFF_MAX - length < BUFSIZ)
+       memory_full (PTRDIFF_MAX);
+
+      if (rc < 0)
+       return unbind_to (ref, Qnil);
+
+      if (rc < BUFSIZ)
+       break;
+
+      buffer = xrealloc (buffer, length + BUFSIZ);
+      start = buffer + length;
+    }
+
+  return unbind_to (ref, make_unibyte_string (buffer, rc));
+
+ fail:
+  unblock_input ();
+  return Qnil;
+}
+
+
+
+/* Desktop notifications.  `android-desktop-notify' implements a
+   facsimile of `notifications-notify'.  */
+
+/* Structure describing the EmacsDesktopNotification class.  */
+
+struct android_emacs_desktop_notification
+{
+  jclass class;
+  jmethodID init;
+  jmethodID display;
+};
+
+/* Methods provided by the EmacsDesktopNotification class.  */
+static struct android_emacs_desktop_notification notification_class;
+
+/* Initialize virtual function IDs and class pointers tied to the
+   EmacsDesktopNotification class.  */
+
+static void
+android_init_emacs_desktop_notification (void)
+{
+  jclass old;
+
+  notification_class.class
+    = (*android_java_env)->FindClass (android_java_env,
+                                     "org/gnu/emacs/EmacsDesktopNotification");
+  eassert (notification_class.class);
+
+  old = notification_class.class;
+  notification_class.class
+    = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
+                                                 old);
+  ANDROID_DELETE_LOCAL_REF (old);
+
+  if (!notification_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)                           \
+  notification_class.c_name                                            \
+    = (*android_java_env)->GetMethodID (android_java_env,              \
+                                       notification_class.class,       \
+                                       name, signature);               \
+  assert (notification_class.c_name);
+
+  FIND_METHOD (init, "<init>", "(Ljava/lang/String;"
+              "Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;II)V");
+  FIND_METHOD (display, "display", "()V");
+#undef FIND_METHOD
+}
+
+/* Return the numeric resource ID designating the icon within the
+   ``android.R.drawable'' package by the supplied NAME.
+
+   If no icon is found, return that of
+   ``android.R.drawable.ic_dialog_alert''.  */
+
+static jint
+android_locate_icon (const char *name)
+{
+  jclass drawable;
+  jfieldID field;
+  jint rc;
+
+  if (android_verify_jni_string (name))
+    /* If NAME isn't valid, return the default value.  */
+    return 17301543; /* android.R.drawable.ic_dialog_alert.  */
+
+  drawable = (*android_java_env)->FindClass (android_java_env,
+                                            "android/R$drawable");
+  android_exception_check ();
+
+  field = (*android_java_env)->GetStaticFieldID (android_java_env,
+                                                drawable, name, "I");
+  (*android_java_env)->ExceptionClear (android_java_env);
+
+  if (!field)
+    rc = 17301543; /* android.R.drawable.ic_dialog_alert.  */
+  else
+    rc = (*android_java_env)->GetStaticIntField (android_java_env,
+                                                drawable, field);
+
+  ANDROID_DELETE_LOCAL_REF (drawable);
+  return rc;
+}
+
+/* Display a desktop notification with the provided TITLE, BODY,
+   REPLACES_ID, GROUP, ICON, and URGENCY.  Return an identifier for
+   the resulting notification.  */
+
+static intmax_t
+android_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
+                               Lisp_Object replaces_id,
+                               Lisp_Object group, Lisp_Object icon,
+                               Lisp_Object urgency)
+{
+  static intmax_t counter;
+  intmax_t id;
+  jstring title1, body1, group1, identifier1;
+  jint type, icon1;
+  jobject notification;
+  char identifier[INT_STRLEN_BOUND (int)
+                 + INT_STRLEN_BOUND (long int)
+                 + INT_STRLEN_BOUND (intmax_t)
+                 + sizeof "..."];
+  struct timespec boot_time;
+
+  if (EQ (urgency, Qlow))
+    type = 2; /* IMPORTANCE_LOW */
+  else if (EQ (urgency, Qnormal))
+    type = 3; /* IMPORTANCE_DEFAULT */
+  else if (EQ (urgency, Qcritical))
+    type = 4; /* IMPORTANCE_HIGH */
+  else
+    signal_error ("Invalid notification importance given", urgency);
+
+  if (NILP (replaces_id))
+    {
+      /* Generate a new identifier.  */
+      INT_ADD_WRAPV (counter, 1, &counter);
+      id = counter;
+    }
+  else
+    {
+      CHECK_INTEGER (replaces_id);
+      if (!integer_to_intmax (replaces_id, &id))
+       id = -1; /* Overflow.  */
+    }
+
+  /* Locate the integer ID linked to ICON.  */
+  icon1 = android_locate_icon (SSDATA (icon));
+
+  /* Generate a unique identifier for this notification.  Because
+     Android persists notifications past system shutdown, also include
+     the boot time within IDENTIFIER.  Scale it down to avoid being
+     perturbed by minor instabilities in the returned boot time,
+     however.  */
+
+  boot_time.tv_sec = 0;
+  get_boot_time (&boot_time);
+  sprintf (identifier, "%d.%ld.%jd", (int) getpid (),
+          (long int) (boot_time.tv_sec / 2), id);
+
+  /* Encode all strings into their Java counterparts.  */
+  title1 = android_build_string (title);
+  body1  = android_build_string (body);
+  group1 = android_build_string (group);
+  identifier1 = android_build_jstring (identifier);
+
+  /* Create the notification.  */
+  notification
+    = (*android_java_env)->NewObject (android_java_env,
+                                     notification_class.class,
+                                     notification_class.init,
+                                     title1, body1, group1,
+                                     identifier1, icon1, type);
+  android_exception_check_4 (title1, body1, group1, identifier1);
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (title1);
+  ANDROID_DELETE_LOCAL_REF (body1);
+  ANDROID_DELETE_LOCAL_REF (group1);
+  ANDROID_DELETE_LOCAL_REF (identifier1);
+
+  /* Display the notification.  */
+  (*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
+                                                notification,
+                                                notification_class.class,
+                                                notification_class.display);
+  android_exception_check_1 (notification);
+  ANDROID_DELETE_LOCAL_REF (notification);
+
+  /* Return the ID.  */
+  return id;
+}
+
+DEFUN ("android-notifications-notify", Fandroid_notifications_notify,
+       Sandroid_notifications_notify, 0, MANY, 0, doc:
+       /* Display a desktop notification.
+ARGS must contain keywords followed by values.  Each of the following
+keywords is understood:
+
+  :title        The notification title.
+  :body         The notification body.
+  :replaces-id  The ID of a previous notification to supersede.
+  :group        The notification group, or nil.
+  :urgency      One of the symbols `low', `normal' or `critical',
+                defining the importance of the notification group.
+  :icon         The name of a drawable resource to display as the
+                notification's icon.
+
+The notification group and urgency are ignored on Android 7.1 and
+earlier versions of Android.  Outside such older systems, it
+identifies a category that will be displayed in the system Settings
+menu.  The urgency provided always extends to affect all notifications
+displayed within that category.  If the group is not provided, it
+defaults to the string "Desktop Notifications".
+
+The provided icon should be the name of a "drawable resource" present
+within the "android.R.drawable" class designating an icon with a
+transparent background.  If no icon is provided (or the icon is absent
+from this system), it defaults to "ic_dialog_alert".
+
+When the system is running Android 13 or later, notifications sent
+will be silently disregarded unless permission to display
+notifications is expressly granted from the "App Info" settings panel
+corresponding to Emacs.
+
+A title and body must be supplied.  Value is an integer (fixnum or
+bignum) uniquely designating the notification displayed, which may
+subsequently be specified as the `:replaces-id' of another call to
+this function.
+
+usage: (android-notifications-notify &rest ARGS) */)
+  (ptrdiff_t nargs, Lisp_Object *args)
+{
+  Lisp_Object title, body, replaces_id, group, urgency;
+  Lisp_Object icon;
+  Lisp_Object key, value;
+  ptrdiff_t i;
+
+  if (!android_init_gui)
+    error ("No Android display connection!");
+
+  /* Clear each variable above.  */
+  title = body = replaces_id = group = icon = urgency = Qnil;
+
+  /* If NARGS is odd, error.  */
+
+  if (nargs & 1)
+    error ("Odd number of arguments in call to 
`android-notifications-notify'");
+
+  /* Next, iterate through ARGS, searching for arguments.  */
+
+  for (i = 0; i < nargs; i += 2)
+    {
+      key = args[i];
+      value = args[i + 1];
+
+      if (EQ (key, QCtitle))
+       title = value;
+      else if (EQ (key, QCbody))
+       body = value;
+      else if (EQ (key, QCreplaces_id))
+       replaces_id = value;
+      else if (EQ (key, QCgroup))
+       group = value;
+      else if (EQ (key, QCurgency))
+       urgency = value;
+      else if (EQ (key, QCicon))
+       icon = value;
+    }
+
+  /* Demand at least TITLE and BODY be present.  */
+
+  if (NILP (title) || NILP (body))
+    error ("Title or body not provided");
+
+  /* Now check the type and possibly expand each non-nil argument.  */
+
+  CHECK_STRING (title);
+  CHECK_STRING (body);
+
+  if (NILP (urgency))
+    urgency = Qlow;
+
+  if (NILP (group))
+    group = build_string ("Desktop Notifications");
+
+  if (NILP (icon))
+    icon = build_string ("ic_dialog_alert");
+  else
+    CHECK_STRING (icon);
+
+  return make_int (android_notifications_notify_1 (title, body, replaces_id,
+                                                  group, icon, urgency));
+}
+
+
+
+void
+init_androidselect (void)
+{
+  jobject tem;
+  jmethodID make_clipboard;
+
+  if (!android_init_gui)
+    return;
+
+  android_init_emacs_clipboard ();
+  android_init_emacs_desktop_notification ();
+
+  make_clipboard = clipboard_class.make_clipboard;
+  tem
+    = (*android_java_env)->CallStaticObjectMethod (android_java_env,
+                                                  clipboard_class.class,
+                                                  make_clipboard);
+  if (!tem)
+    emacs_abort ();
+
+  clipboard = (*android_java_env)->NewGlobalRef (android_java_env, tem);
+
+  if (!clipboard)
+    emacs_abort ();
+
+  ANDROID_DELETE_LOCAL_REF (tem);
+}
+
+void
+syms_of_androidselect (void)
+{
+  DEFSYM (QCtitle, ":title");
+  DEFSYM (QCbody, ":body");
+  DEFSYM (QCreplaces_id, ":replaces-id");
+  DEFSYM (QCgroup, ":group");
+  DEFSYM (QCurgency, ":urgency");
+  DEFSYM (QCicon, ":icon");
+
+  DEFSYM (Qlow, "low");
+  DEFSYM (Qnormal, "normal");
+  DEFSYM (Qcritical, "critical");
+
+  defsubr (&Sandroid_clipboard_owner_p);
+  defsubr (&Sandroid_set_clipboard);
+  defsubr (&Sandroid_get_clipboard);
+  defsubr (&Sandroid_clipboard_exists_p);
+  defsubr (&Sandroid_browse_url);
+  defsubr (&Sandroid_get_clipboard_targets);
+  defsubr (&Sandroid_get_clipboard_data);
+
+  defsubr (&Sandroid_notifications_notify);
+}
diff --git a/src/androidterm.c b/src/androidterm.c
new file mode 100644
index 00000000000..a60dd50e5db
--- /dev/null
+++ b/src/androidterm.c
@@ -0,0 +1,6526 @@
+/* Communication module for Android terminals.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <semaphore.h>
+
+#include "lisp.h"
+#include "androidterm.h"
+#include "keyboard.h"
+#include "blockinput.h"
+#include "android.h"
+#include "buffer.h"
+#include "window.h"
+#include "textconv.h"
+#include "coding.h"
+#include "pdumper.h"
+
+/* This is a chain of structures for all the X displays currently in
+   use.  */
+
+struct android_display_info *x_display_list;
+
+
+
+/* Android terminal interface functions.  */
+
+#ifndef ANDROID_STUBIFY
+
+#include <android/log.h>
+
+/* Non-zero means that a HELP_EVENT has been generated since Emacs
+   start.  */
+
+static bool any_help_event_p;
+
+/* Counters for tallying up scroll wheel events if
+   mwheel_coalesce_scroll_events is true.  */
+
+static double wheel_event_x, wheel_event_y;
+
+enum
+  {
+    ANDROID_EVENT_NORMAL,
+    ANDROID_EVENT_GOTO_OUT,
+    ANDROID_EVENT_DROP,
+  };
+
+/* Find the frame whose window has the identifier WDESC.
+
+   This is like x_window_to_frame in xterm.c, except that DPYINFO may
+   be NULL, as there is only at most one Android display, and is only
+   specified in order to stay consistent with X.  */
+
+static struct frame *
+android_window_to_frame (struct android_display_info *dpyinfo,
+                        android_window wdesc)
+{
+  Lisp_Object tail, frame;
+  struct frame *f;
+
+  if (wdesc == ANDROID_NONE)
+    return NULL;
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+
+      if (!FRAME_ANDROID_P (f))
+       continue;
+
+      if (FRAME_ANDROID_WINDOW (f) == wdesc)
+        return f;
+    }
+
+  return NULL;
+}
+
+static void
+android_clear_frame (struct frame *f)
+{
+  /* Clearing the frame will erase any cursor, so mark them all as no
+     longer visible.  */
+  mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f)));
+  android_clear_window (FRAME_ANDROID_DRAWABLE (f));
+}
+
+static void
+android_show_hourglass (struct frame *f)
+{
+  struct android_output *x;
+
+  /* This isn't implemented like X because a window brings alongside
+     too many unneeded resources.  */
+
+  x = FRAME_ANDROID_OUTPUT (f);
+
+  /* If the hourglass window is mapped inside a popup menu, input
+     could be lost if the menu is popped down and the grab is
+     relinquished, but the hourglass window is still up.  Just
+     avoid displaying the hourglass at all while popups are
+     active.  */
+
+  if (popup_activated ())
+    return;
+
+  x->hourglass = true;
+
+  if (!f->pointer_invisible)
+    android_define_cursor (FRAME_ANDROID_WINDOW (f),
+                          x->hourglass_cursor);
+}
+
+static void
+android_hide_hourglass (struct frame *f)
+{
+  struct android_output *x;
+
+  x = FRAME_ANDROID_OUTPUT (f);
+  x->hourglass = false;
+
+  if (!f->pointer_invisible)
+    android_define_cursor (FRAME_ANDROID_WINDOW (f),
+                          x->current_cursor);
+}
+
+static void
+android_flash (struct frame *f)
+{
+  struct android_gc *gc;
+  struct android_gc_values values;
+  int rc;
+  fd_set fds;
+
+  block_input ();
+
+  values.function = ANDROID_GC_XOR;
+  values.foreground = (FRAME_FOREGROUND_PIXEL (f)
+                      ^ FRAME_BACKGROUND_PIXEL (f));
+
+  gc = android_create_gc ((ANDROID_GC_FUNCTION
+                          | ANDROID_GC_FOREGROUND),
+                         &values);
+
+  /* Get the height not including a menu bar widget.  */
+  int height = FRAME_PIXEL_HEIGHT (f);
+  /* Height of each line to flash.  */
+  int flash_height = FRAME_LINE_HEIGHT (f);
+  /* These will be the left and right margins of the rectangles.  */
+  int flash_left = FRAME_INTERNAL_BORDER_WIDTH (f);
+  int flash_right = FRAME_PIXEL_WIDTH (f) - FRAME_INTERNAL_BORDER_WIDTH (f);
+  int width = flash_right - flash_left;
+
+  /* If window is tall, flash top and bottom line.  */
+  if (height > 3 * FRAME_LINE_HEIGHT (f))
+    {
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                             flash_left,
+                             (FRAME_INTERNAL_BORDER_WIDTH (f)
+                              + FRAME_TOP_MARGIN_HEIGHT (f)),
+                             width, flash_height);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                             flash_left,
+                             (height - flash_height
+                              - FRAME_INTERNAL_BORDER_WIDTH (f)
+                              - FRAME_BOTTOM_MARGIN_HEIGHT (f)),
+                             width, flash_height);
+
+    }
+  else
+    /* If it is short, flash it all.  */
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                           flash_left, FRAME_INTERNAL_BORDER_WIDTH (f),
+                           width, (height - 2
+                                   * FRAME_INTERNAL_BORDER_WIDTH (f)));
+
+  flush_frame (f);
+
+  struct timespec delay = make_timespec (0, 150 * 1000 * 1000);
+  struct timespec wakeup = timespec_add (current_timespec (), delay);
+
+  /* Keep waiting until past the time wakeup or any input gets
+     available.  */
+  while (! detect_input_pending ())
+    {
+      struct timespec current = current_timespec ();
+      struct timespec timeout;
+
+      /* Break if result would not be positive.  */
+      if (timespec_cmp (wakeup, current) <= 0)
+       break;
+
+      /* How long `select' should wait.  */
+      timeout = make_timespec (0, 10 * 1000 * 1000);
+
+      /* Wait for some input to become available on the X
+        connection.  */
+      FD_ZERO (&fds);
+
+      /* Try to wait that long--but we might wake up sooner.  */
+      rc = pselect (0, &fds, NULL, NULL, &timeout, NULL);
+
+      /* Some input is available, exit the visible bell.  */
+      if (rc >= 0)
+       break;
+    }
+
+  /* If window is tall, flash top and bottom line.  */
+  if (height > 3 * FRAME_LINE_HEIGHT (f))
+    {
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                             flash_left,
+                             (FRAME_INTERNAL_BORDER_WIDTH (f)
+                              + FRAME_TOP_MARGIN_HEIGHT (f)),
+                             width, flash_height);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                             flash_left,
+                             (height - flash_height
+                              - FRAME_INTERNAL_BORDER_WIDTH (f)
+                              - FRAME_BOTTOM_MARGIN_HEIGHT (f)),
+                             width, flash_height);
+    }
+  else
+    /* If it is short, flash it all.  */
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                           flash_left, FRAME_INTERNAL_BORDER_WIDTH (f),
+                           width, (height - 2
+                                   * FRAME_INTERNAL_BORDER_WIDTH (f)));
+
+  android_free_gc (gc);
+  flush_frame (f);
+
+  unblock_input ();
+}
+
+static void
+android_ring_bell (struct frame *f)
+{
+  if (visible_bell)
+    android_flash (f);
+  else
+    {
+      block_input ();
+      android_bell ();
+      unblock_input ();
+    }
+}
+
+static android_cursor
+make_invisible_cursor (struct android_display_info *dpyinfo)
+{
+  return android_create_font_cursor (ANDROID_XC_NULL);
+}
+
+static void
+android_toggle_visible_pointer (struct frame *f, bool invisible)
+{
+  struct android_display_info *dpyinfo;
+
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+
+  if (!dpyinfo->invisible_cursor)
+    dpyinfo->invisible_cursor = make_invisible_cursor (dpyinfo);
+
+  if (invisible)
+    android_define_cursor (FRAME_ANDROID_WINDOW (f),
+                          dpyinfo->invisible_cursor);
+  else
+    android_define_cursor (FRAME_ANDROID_WINDOW (f),
+                          (FRAME_ANDROID_OUTPUT (f)->hourglass
+                           ? f->output_data.android->hourglass_cursor
+                           : f->output_data.android->current_cursor));
+
+  f->pointer_invisible = invisible;
+}
+
+static void
+android_toggle_invisible_pointer (struct frame *f, bool invisible)
+{
+  block_input ();
+  android_toggle_visible_pointer (f, invisible);
+  unblock_input ();
+}
+
+/* Start an update of frame F.  This function is installed as a hook
+   for update_begin, i.e. it is called when update_begin is called.
+   This function is called prior to calls to gui_update_window_begin
+   for each window being updated.  Currently, there is nothing to do
+   here because all interesting stuff is done on a window basis.  */
+
+static void
+android_update_begin (struct frame *f)
+{
+  /* The frame is no longer complete, as it is in the midst of an
+     update.  */
+  FRAME_ANDROID_COMPLETE_P (f) = false;
+}
+
+/* End update of frame F.  This function is installed as a hook in
+   update_end.  */
+
+static void
+android_update_end (struct frame *f)
+{
+  /* Mouse highlight may be displayed again.  */
+  MOUSE_HL_INFO (f)->mouse_face_defer = false;
+}
+
+static void
+show_back_buffer (struct frame *f)
+{
+  struct android_swap_info swap_info;
+
+  memset (&swap_info, 0, sizeof (swap_info));
+  swap_info.swap_window = FRAME_ANDROID_WINDOW (f);
+  swap_info.swap_action = ANDROID_COPIED;
+  android_swap_buffers (&swap_info, 1);
+
+  /* Now the back buffer no longer needs to be flipped.  */
+  FRAME_ANDROID_NEED_BUFFER_FLIP (f) = false;
+}
+
+/* Flip back buffers on F if it has undrawn content.  */
+
+static void
+android_flush_dirty_back_buffer_on (struct frame *f)
+{
+  if (FRAME_GARBAGED_P (f)
+      || buffer_flipping_blocked_p ()
+      /* If the frame is not already up to date, do not flush buffers
+        on input, as that will result in flicker.  */
+      || !FRAME_ANDROID_COMPLETE_P (f)
+      || !FRAME_ANDROID_NEED_BUFFER_FLIP (f))
+    return;
+
+  show_back_buffer (f);
+}
+
+/* Convert between the modifier bits Android uses and the modifier
+   bits Emacs uses.  */
+
+static int
+android_android_to_emacs_modifiers (struct android_display_info *dpyinfo,
+                                   int state)
+{
+  return (((state & ANDROID_CONTROL_MASK) ? ctrl_modifier  : 0)
+         | ((state & ANDROID_SHIFT_MASK) ? shift_modifier : 0)
+         | ((state & ANDROID_ALT_MASK)   ? meta_modifier  : 0)
+         | ((state & ANDROID_SUPER_MASK) ? super_modifier : 0)
+         | ((state & ANDROID_META_MASK)  ? alt_modifier   : 0));
+}
+
+static int
+android_emacs_to_android_modifiers (struct android_display_info *dpyinfo,
+                                   intmax_t state)
+{
+  return (((state & ctrl_modifier)    ? ANDROID_CONTROL_MASK : 0)
+         | ((state & shift_modifier) ? ANDROID_SHIFT_MASK   : 0)
+         | ((state & meta_modifier)  ? ANDROID_ALT_MASK     : 0)
+         | ((state & super_modifier) ? ANDROID_SUPER_MASK   : 0)
+         | ((state & alt_modifier)   ? ANDROID_META_MASK    : 0));
+}
+
+static void android_frame_rehighlight (struct android_display_info *);
+
+static void
+android_lower_frame (struct frame *f)
+{
+  android_lower_window (FRAME_ANDROID_WINDOW (f));
+}
+
+static void
+android_raise_frame (struct frame *f)
+{
+  android_raise_window (FRAME_ANDROID_WINDOW (f));
+}
+
+static void
+android_new_focus_frame (struct android_display_info *dpyinfo,
+                        struct frame *frame)
+{
+  struct frame *old_focus;
+
+  old_focus = dpyinfo->focus_frame;
+
+  if (frame != dpyinfo->focus_frame)
+    {
+      /* Set this before calling other routines, so that they see
+        the correct value of x_focus_frame.  */
+      dpyinfo->focus_frame = frame;
+
+      if (old_focus && old_focus->auto_lower)
+       android_lower_frame (old_focus);
+
+      if (dpyinfo->focus_frame && dpyinfo->focus_frame->auto_raise)
+       dpyinfo->pending_autoraise_frame = dpyinfo->focus_frame;
+      else
+       dpyinfo->pending_autoraise_frame = NULL;
+    }
+
+  android_frame_rehighlight (dpyinfo);
+}
+
+static void
+android_focus_changed (int type, int state,
+                      struct android_display_info *dpyinfo,
+                      struct frame *frame, struct input_event *bufp)
+{
+  if (type == ANDROID_FOCUS_IN)
+    {
+      if (dpyinfo->x_focus_event_frame != frame)
+        {
+          android_new_focus_frame (dpyinfo, frame);
+          dpyinfo->x_focus_event_frame = frame;
+          bufp->kind = FOCUS_IN_EVENT;
+          XSETFRAME (bufp->frame_or_window, frame);
+        }
+
+      frame->output_data.android->focus_state |= state;
+    }
+  else if (type == ANDROID_FOCUS_OUT)
+    {
+      frame->output_data.android->focus_state &= ~state;
+
+      if (dpyinfo->x_focus_event_frame == frame)
+        {
+          dpyinfo->x_focus_event_frame = 0;
+          android_new_focus_frame (dpyinfo, 0);
+
+          bufp->kind = FOCUS_OUT_EVENT;
+          XSETFRAME (bufp->frame_or_window, frame);
+        }
+
+      if (frame->pointer_invisible)
+        android_toggle_invisible_pointer (frame, false);
+    }
+}
+
+static void
+android_detect_focus_change (struct android_display_info *dpyinfo,
+                            struct frame *frame,
+                            union android_event *event,
+                            struct input_event *bufp)
+{
+  if (!frame)
+    return;
+
+  switch (event->type)
+    {
+    case ANDROID_FOCUS_IN:
+    case ANDROID_FOCUS_OUT:
+      android_focus_changed (event->type, FOCUS_EXPLICIT,
+                            dpyinfo, frame, bufp);
+      break;
+
+    default:
+      break;
+    }
+}
+
+static bool
+android_note_mouse_movement (struct frame *frame,
+                            struct android_motion_event *event)
+{
+  struct android_display_info *dpyinfo;
+  Emacs_Rectangle *r;
+
+  if (!FRAME_ANDROID_OUTPUT (frame))
+    return false;
+
+  dpyinfo = FRAME_DISPLAY_INFO (frame);
+  dpyinfo->last_mouse_motion_frame = frame;
+  dpyinfo->last_mouse_motion_x = event->x;
+  dpyinfo->last_mouse_motion_y = event->y;
+  dpyinfo->last_mouse_movement_time = event->time;
+
+  /* Has the mouse moved off the glyph it was on at the last sighting?  */
+  r = &dpyinfo->last_mouse_glyph;
+  if (frame != dpyinfo->last_mouse_glyph_frame
+      || event->x < r->x || event->x >= r->x + r->width
+      || event->y < r->y || event->y >= r->y + r->height)
+    {
+      frame->mouse_moved = true;
+      note_mouse_highlight (frame, event->x, event->y);
+      /* Remember which glyph we're now on.  */
+      remember_mouse_glyph (frame, event->x, event->y, r);
+      dpyinfo->last_mouse_glyph_frame = frame;
+      return true;
+    }
+
+  return false;
+}
+
+static struct frame *
+mouse_or_wdesc_frame (struct android_display_info *dpyinfo, int wdesc)
+{
+  struct frame *lm_f = (gui_mouse_grabbed (dpyinfo)
+                       ? dpyinfo->last_mouse_frame
+                       : NULL);
+
+  if (lm_f && !EQ (track_mouse, Qdropping)
+      && !EQ (track_mouse, Qdrag_source))
+    return lm_f;
+  else
+    {
+      struct frame *w_f = android_window_to_frame (dpyinfo, wdesc);
+
+      /* Do not return a tooltip frame.  */
+      if (!w_f || FRAME_TOOLTIP_P (w_f))
+       return EQ (track_mouse, Qdropping) ? lm_f : NULL;
+      else
+       /* When dropping it would be probably nice to raise w_f
+          here.  */
+       return w_f;
+    }
+}
+
+static Lisp_Object
+android_construct_mouse_click (struct input_event *result,
+                              struct android_button_event *event,
+                              struct frame *f)
+{
+  struct android_display_info *dpyinfo;
+  int x, y;
+
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+  x = event->x;
+  y = event->y;
+
+  /* Make the event type NO_EVENT; we'll change that when we decide
+     otherwise.  */
+  result->kind = MOUSE_CLICK_EVENT;
+  result->code = event->button - 1;
+  result->timestamp = event->time;
+  result->modifiers = (android_android_to_emacs_modifiers (dpyinfo,
+                                                          event->state)
+                      | (event->type == ANDROID_BUTTON_RELEASE
+                         ? up_modifier : down_modifier));
+
+  XSETINT (result->x, x);
+  XSETINT (result->y, y);
+  XSETFRAME (result->frame_or_window, f);
+  result->arg = Qnil;
+  return Qnil;
+}
+
+/* Generate a TOUCHSCREEN_UPDATE_EVENT for all pressed tools in FRAME.
+   Return the event in IE.  Do not set IE->timestamp, as that is left
+   to the caller.  */
+
+static void
+android_update_tools (struct frame *f, struct input_event *ie)
+{
+  struct android_touch_point *touchpoint;
+
+  ie->kind = TOUCHSCREEN_UPDATE_EVENT;
+  XSETFRAME (ie->frame_or_window, f);
+  ie->arg = Qnil;
+
+  /* Build the list of active touches.  */
+  for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points;
+       touchpoint; touchpoint = touchpoint->next)
+    {
+      /* Skip touch points which originated on the tool bar.  */
+
+      if (touchpoint->tool_bar_p)
+       continue;
+
+      ie->arg = Fcons (list3i (touchpoint->x,
+                              touchpoint->y,
+                              touchpoint->tool_id),
+                      ie->arg);
+    }
+}
+
+/* Find and return an existing tool pressed against FRAME, identified
+   by POINTER_ID.  Return NULL if no tool by that ID was found.  */
+
+static struct android_touch_point *
+android_find_tool (struct frame *f, int pointer_id)
+{
+  struct android_touch_point *touchpoint;
+
+  for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points;
+       touchpoint; touchpoint = touchpoint->next)
+    {
+      if (touchpoint->tool_id == pointer_id)
+       return touchpoint;
+    }
+
+  return NULL;
+}
+
+/* Decode STRING, an array of N little endian UTF-16 characters, into
+   a Lisp string.  Return Qnil if the string is too large, and the
+   encoded string otherwise.  */
+
+static Lisp_Object
+android_decode_utf16 (unsigned short *utf16, size_t n)
+{
+  struct coding_system coding;
+  ptrdiff_t size;
+
+  if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
+    return Qnil;
+
+  /* Set up the coding system.  Decoding a UTF-16 string (with no BOM)
+     should not signal.  */
+
+  memset (&coding, 0, sizeof coding);
+
+  setup_coding_system (Qutf_16le, &coding);
+  coding.source = (const unsigned char *) utf16;
+  decode_coding_object (&coding, Qnil, 0, 0, size,
+                       size, Qt);
+
+  return coding.dst_object;
+}
+
+/* Handle a cursor update request for F from the input method.
+   MODE specifies whether or not an update should be sent immediately,
+   and whether or not they are needed in the future.
+
+   If MODE & ANDROID_CURSOR_UPDATE_IMMEDIATE, report the position of
+   F's old selected window's phys cursor now.
+
+   If MODE & ANDROID_CURSOR_UPDATE_MONITOR, set
+   `need_cursor_updates'.  */
+
+static void
+android_request_cursor_updates (struct frame *f, int mode)
+{
+  struct window *w;
+
+  if (mode & ANDROID_CURSOR_UPDATE_IMMEDIATE
+      && WINDOWP (WINDOW_LIVE_P (f->old_selected_window)
+                 ? f->old_selected_window
+                 : f->selected_window))
+    {
+      /* Prefer the old selected window, as its selection is what was
+        reported to the IME previously.  */
+
+      w = XWINDOW (WINDOW_LIVE_P (f->old_selected_window)
+                  ? f->old_selected_window
+                  : f->selected_window);
+      android_set_preeditarea (w, w->cursor.x, w->cursor.y);
+    }
+
+  /* Now say whether or not updates are needed in the future.  */
+  FRAME_OUTPUT_DATA (f)->need_cursor_updates
+    = (mode & ANDROID_CURSOR_UPDATE_MONITOR);
+}
+
+/* Handle a single input method event EVENT, delivered to the frame
+   F.
+
+   Perform the text conversion action specified inside.  */
+
+static void
+android_handle_ime_event (union android_event *event, struct frame *f)
+{
+  Lisp_Object text UNINIT;
+  struct android_output *output;
+
+  /* First, decode the text if necessary.  */
+
+  switch (event->ime.operation)
+    {
+    case ANDROID_IME_COMMIT_TEXT:
+    case ANDROID_IME_SET_COMPOSING_TEXT:
+      text = android_decode_utf16 (event->ime.text,
+                                  event->ime.length);
+      xfree (event->ime.text);
+      break;
+
+    default:
+      break;
+    }
+
+  /* Finally, perform the appropriate conversion action.  */
+
+  switch (event->ime.operation)
+    {
+    case ANDROID_IME_COMMIT_TEXT:
+      commit_text (f, text, event->ime.position,
+                  event->ime.counter);
+      break;
+
+    case ANDROID_IME_DELETE_SURROUNDING_TEXT:
+      delete_surrounding_text (f, event->ime.start,
+                              event->ime.end,
+                              event->ime.counter);
+      break;
+
+    case ANDROID_IME_FINISH_COMPOSING_TEXT:
+
+      if (event->ime.length == 2)
+       {
+         output = FRAME_ANDROID_OUTPUT (f);
+
+         /* A new input method has connected to Emacs.  Stop
+            reporting changes that the previous input method has
+            asked to monitor.  */
+
+         output->extracted_text_flags = 0;
+         output->extracted_text_token = 0;
+         output->extracted_text_hint = 0;
+         output->need_cursor_updates = false;
+       }
+
+      finish_composing_text (f, event->ime.counter,
+                            event->ime.length == 1);
+
+      if (event->ime.length == 2)
+       {
+         /* Now cancel outstanding batch edits if a new input method
+            has connected.  */
+
+         f->conversion.batch_edit_flags = 0;
+         f->conversion.batch_edit_count = 0;
+       }
+
+      break;
+
+    case ANDROID_IME_SET_COMPOSING_TEXT:
+      set_composing_text (f, text, event->ime.position,
+                         event->ime.counter);
+      break;
+
+    case ANDROID_IME_SET_COMPOSING_REGION:
+      set_composing_region (f, event->ime.start,
+                           event->ime.end,
+                           event->ime.counter);
+      break;
+
+    case ANDROID_IME_SET_POINT:
+      textconv_set_point_and_mark (f, event->ime.start,
+                                  event->ime.end,
+                                  event->ime.counter);
+      break;
+
+    case ANDROID_IME_START_BATCH_EDIT:
+      start_batch_edit (f, event->ime.counter);
+      break;
+
+    case ANDROID_IME_END_BATCH_EDIT:
+      end_batch_edit (f, event->ime.counter);
+      break;
+
+    case ANDROID_IME_REQUEST_SELECTION_UPDATE:
+      request_point_update (f, event->ime.counter);
+      break;
+
+    case ANDROID_IME_REQUEST_CURSOR_UPDATES:
+      android_request_cursor_updates (f, event->ime.length);
+      break;
+    }
+}
+
+
+
+/* Forward declaration.  */
+static void android_notify_conversion (unsigned long);
+
+static int
+handle_one_android_event (struct android_display_info *dpyinfo,
+                         union android_event *event, int *finish,
+                         struct input_event *hold_quit)
+{
+  union android_event configureEvent;
+  struct frame *f, *any, *mouse_frame;
+  Mouse_HLInfo *hlinfo;
+  union buffered_input_event inev;
+  int modifiers, count, do_help;
+  struct android_touch_point *touchpoint, **last;
+  Lisp_Object window;
+  int scroll_height;
+  double scroll_unit;
+  int keysym;
+  ptrdiff_t nchars, i;
+  struct window *w;
+
+  /* It is okay for this to not resemble handle_one_xevent so much.
+     Differences in event handling code are much less nasty than
+     stuble differences in the graphics code.  */
+
+  do_help = count = 0;
+  hlinfo = &dpyinfo->mouse_highlight;
+  *finish = ANDROID_EVENT_NORMAL;
+  any = android_window_to_frame (dpyinfo, event->xany.window);
+  nchars = 0;
+
+  if (any && any->wait_event_type == event->type)
+    any->wait_event_type = 0; /* Indicates we got it.  */
+
+  EVENT_INIT (inev.ie);
+
+  switch (event->type)
+    {
+    case ANDROID_CONFIGURE_NOTIFY:
+      configureEvent = *event;
+
+      f = android_window_to_frame (dpyinfo,
+                                  configureEvent.xconfigure.window);
+
+      if (!f)
+       goto OTHER;
+
+      if (FRAME_TOOLTIP_P (f))
+       {
+         if (FRAME_PIXEL_HEIGHT (f) != configureEvent.xconfigure.height
+             || FRAME_PIXEL_WIDTH (f) != configureEvent.xconfigure.width)
+           SET_FRAME_GARBAGED (f);
+
+         FRAME_PIXEL_HEIGHT (f) = configureEvent.xconfigure.height;
+         FRAME_PIXEL_WIDTH (f) = configureEvent.xconfigure.width;
+       }
+
+      int width = configureEvent.xconfigure.width;
+      int height = configureEvent.xconfigure.height;
+
+      if (CONSP (frame_size_history))
+       frame_size_history_extra (f, build_string ("ConfigureNotify"),
+                                 FRAME_PIXEL_WIDTH (f),
+                                 FRAME_PIXEL_HEIGHT (f),
+                                 width, height, f->new_width,
+                                 f->new_height);
+
+      /* Even if the number of character rows and columns has
+        not changed, the font size may have changed, so we need
+        to check the pixel dimensions as well.  */
+
+      if (width != FRAME_PIXEL_WIDTH (f)
+         || height != FRAME_PIXEL_HEIGHT (f)
+         || (f->new_size_p
+             && ((f->new_width >= 0 && width != f->new_width)
+                 || (f->new_height >= 0 && height != f->new_height))))
+       {
+         change_frame_size (f, width, height, false, true, false);
+         android_clear_under_internal_border (f);
+         SET_FRAME_GARBAGED (f);
+         cancel_mouse_face (f);
+       }
+
+      /* Now change the left and top position of this window.  */
+
+      {
+       int old_left = f->left_pos;
+       int old_top = f->top_pos;
+       Lisp_Object frame;
+
+       XSETFRAME (frame, f);
+
+       {
+         android_window root;
+         unsigned int dummy_uint;
+
+         android_get_geometry (FRAME_ANDROID_WINDOW (f),
+                               &root, &f->left_pos, &f->top_pos,
+                               &dummy_uint, &dummy_uint,
+                               &dummy_uint);
+       }
+
+       if (!FRAME_TOOLTIP_P (f)
+           && (old_left != f->left_pos || old_top != f->top_pos))
+         {
+           inev.ie.kind = MOVE_FRAME_EVENT;
+           XSETFRAME (inev.ie.frame_or_window, f);
+         }
+
+      if (f && FRAME_OUTPUT_DATA (f)->need_cursor_updates)
+       {
+         w = XWINDOW (f->selected_window);
+         android_set_preeditarea (w, w->cursor.x, w->cursor.y);
+       }
+      }
+
+      goto OTHER;
+
+    case ANDROID_KEY_PRESS:
+
+      /* Set f to any.  There are no ``outer windows'' on Android.  */
+      f = any;
+
+      /* If mouse-highlight is an integer, input clears out
+        mouse highlighting.  */
+      if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
+         && (any == 0
+             || !EQ (any->tool_bar_window, hlinfo->mouse_face_window)
+             || !EQ (any->tab_bar_window, hlinfo->mouse_face_window)))
+        {
+         mouse_frame = hlinfo->mouse_face_mouse_frame;
+
+         clear_mouse_face (hlinfo);
+         hlinfo->mouse_face_hidden = true;
+
+         if (mouse_frame)
+           android_flush_dirty_back_buffer_on (mouse_frame);
+       }
+
+      if (!f)
+       goto OTHER;
+
+      if (event->xkey.counter)
+       /* This event was generated by `performEditorAction'.  Make
+          sure it is processed before any subsequent edits.  */
+       textconv_barrier (f, event->xkey.counter);
+
+      wchar_t copy_buffer[129];
+      wchar_t *copy_bufptr = copy_buffer;
+      int copy_bufsiz = 128 * sizeof (wchar_t);
+
+      event->xkey.state
+       |= android_emacs_to_android_modifiers (dpyinfo,
+                                              extra_keyboard_modifiers);
+      modifiers = event->xkey.state;
+
+      /* Common for all keysym input events.  */
+      XSETFRAME (inev.ie.frame_or_window, any);
+      inev.ie.modifiers
+       = android_android_to_emacs_modifiers (dpyinfo, modifiers);
+      inev.ie.timestamp = event->xkey.time;
+
+      keysym = event->xkey.keycode;
+
+      {
+       enum android_lookup_status status_return;
+
+       nchars = android_wc_lookup_string (&event->xkey, copy_bufptr,
+                                          copy_bufsiz, &keysym,
+                                          &status_return);
+
+       /* android_lookup_string can't be called twice, so there's no
+          way to recover from buffer overflow.  */
+       if (status_return == ANDROID_BUFFER_OVERFLOW)
+         goto done_keysym;
+       else if (status_return == ANDROID_LOOKUP_NONE)
+         {
+           /* Don't skip preedit text events.  */
+           if (event->xkey.keycode != (uint32_t) -1)
+             goto done_keysym;
+         }
+       else if (status_return == ANDROID_LOOKUP_CHARS)
+         keysym = ANDROID_NO_SYMBOL;
+       else if (status_return != ANDROID_LOOKUP_KEYSYM
+                && status_return != ANDROID_LOOKUP_BOTH)
+         emacs_abort ();
+
+       /* Deal with pre-edit text events.  On Android, these are
+          simply encoded as events with associated strings and a
+          keycode set to ``-1''.  */
+
+       if (event->xkey.keycode == (uint32_t) -1)
+         {
+           inev.ie.kind = PREEDIT_TEXT_EVENT;
+           inev.ie.arg = Qnil;
+
+           /* If text was looked up, decode it and make it the
+              preedit text.  */
+
+           if (status_return == ANDROID_LOOKUP_CHARS && nchars)
+             {
+               copy_bufptr[nchars] = 0;
+               inev.ie.arg = from_unicode_buffer (copy_bufptr);
+             }
+
+           goto done_keysym;
+         }
+      }
+
+      if (nchars == 1 && copy_bufptr[0] >= 32)
+       {
+         /* Deal with characters.  */
+
+         if (copy_bufptr[0] < 128)
+           inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+         else
+           inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+
+         inev.ie.code = copy_bufptr[0];
+       }
+      else if (nchars < 2 && keysym)
+       {
+         /* If the key is a modifier key, just return.  */
+         if (ANDROID_IS_MODIFIER_KEY (keysym))
+           goto done_keysym;
+
+         /* Next, deal with special ``characters'' by giving the
+            keycode to keyboard.c.  */
+         inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+         inev.ie.code = keysym;
+       }
+      else
+       {
+         /* Finally, deal with strings.  */
+
+         for (i = 0; i < nchars; ++i)
+           {
+             inev.ie.kind = (SINGLE_BYTE_CHAR_P (copy_bufptr[i])
+                             ? ASCII_KEYSTROKE_EVENT
+                             : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+             inev.ie.code = copy_bufptr[i];
+
+             /* If the character is actually '\n', then change this
+                to RET.  */
+
+             if (copy_bufptr[i] == '\n')
+               {
+                 inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+                 inev.ie.code = 66;
+               }
+
+             kbd_buffer_store_buffered_event (&inev, hold_quit);
+           }
+
+         count += nchars;
+         inev.ie.kind = NO_EVENT;  /* Already stored above.  */
+       }
+
+      goto done_keysym;
+
+    done_keysym:
+
+      /* Now proceed to tell the input method the current position of
+        the cursor, if required.  */
+
+      if (f && FRAME_OUTPUT_DATA (f)->need_cursor_updates)
+       {
+         w = XWINDOW (f->selected_window);
+         android_set_preeditarea (w, w->cursor.x, w->cursor.y);
+       }
+
+      goto OTHER;
+
+    case ANDROID_FOCUS_IN:
+    case ANDROID_FOCUS_OUT:
+      android_detect_focus_change (dpyinfo, any, event, &inev.ie);
+      goto OTHER;
+
+    case ANDROID_WINDOW_ACTION:
+
+      /* This is a special event sent by android_run_in_emacs_thread
+        used to make Android run stuff.  */
+
+      if (!event->xaction.window && !event->xaction.action)
+       /* Don't run queries here, as it may run inside editor
+          commands, which can expose an inconsistent view of buffer
+          contents to the input method during command execution.
+
+          Instead, wait for Emacs to return to `android_select'.  */
+       goto OTHER;
+
+      f = any;
+
+      if (event->xaction.action == 0)
+       {
+         /* Action 0 either means that a window has been destroyed
+            and its associated frame should be as well.  */
+
+         if (event->xaction.window)
+           {
+             if (!f)
+               goto OTHER;
+
+             inev.ie.kind = DELETE_WINDOW_EVENT;
+             XSETFRAME (inev.ie.frame_or_window, f);
+           }
+       }
+
+    case ANDROID_ENTER_NOTIFY:
+      f = any;
+
+      if (f)
+       android_note_mouse_movement (f, &event->xmotion);
+      goto OTHER;
+
+    case ANDROID_MOTION_NOTIFY:
+
+      previous_help_echo_string = help_echo_string;
+      help_echo_string = Qnil;
+
+      if (hlinfo->mouse_face_hidden)
+       {
+         hlinfo->mouse_face_hidden = false;
+         clear_mouse_face (hlinfo);
+       }
+
+      f = any;
+
+      if (f)
+       {
+         /* Maybe generate a SELECT_WINDOW_EVENT for
+            `mouse-autoselect-window' but don't let popup menus
+            interfere with this (Bug#1261).  */
+         if (!NILP (Vmouse_autoselect_window)
+             && !popup_activated ()
+             /* Don't switch if we're currently in the minibuffer.
+                This tries to work around problems where the
+                minibuffer gets unselected unexpectedly, and where
+                you then have to move your mouse all the way down to
+                the minibuffer to select it.  */
+             && !MINI_WINDOW_P (XWINDOW (selected_window))
+             /* With `focus-follows-mouse' non-nil create an event
+                also when the target window is on another frame.  */
+             && (f == XFRAME (selected_frame)
+                 || !NILP (focus_follows_mouse)))
+           {
+             static Lisp_Object last_mouse_window;
+             Lisp_Object window
+               = window_from_coordinates (f, event->xmotion.x,
+                                          event->xmotion.y, 0,
+                                          false, false);
+
+             /* A window will be autoselected only when it is not
+                selected now and the last mouse movement event was
+                not in it.  The remainder of the code is a bit vague
+                wrt what a "window" is.  For immediate autoselection,
+                the window is usually the entire window but for GTK
+                where the scroll bars don't count.  For delayed
+                autoselection the window is usually the window's text
+                area including the margins.  */
+             if (WINDOWP (window)
+                 && !EQ (window, last_mouse_window)
+                 && !EQ (window, selected_window))
+               {
+                 inev.ie.kind = SELECT_WINDOW_EVENT;
+                 inev.ie.frame_or_window = window;
+               }
+
+             /* Remember the last window where we saw the mouse.  */
+             last_mouse_window = window;
+           }
+
+         if (!android_note_mouse_movement (f, &event->xmotion))
+           help_echo_string = previous_help_echo_string;
+       }
+
+      /* If the contents of the global variable help_echo_string
+        has changed, generate a HELP_EVENT.  */
+      if (!NILP (help_echo_string)
+         || !NILP (previous_help_echo_string))
+       do_help = 1;
+
+      if (f)
+       android_flush_dirty_back_buffer_on (f);
+
+      goto OTHER;
+
+    case ANDROID_LEAVE_NOTIFY:
+      f = any;
+
+      if (f)
+        {
+         /* Now clear dpyinfo->last_mouse_motion_frame, or
+            gui_redo_mouse_highlight will end up highlighting the
+            last known position of the mouse if a tooltip frame is
+            later unmapped.  */
+
+         if (f == dpyinfo->last_mouse_motion_frame)
+           dpyinfo->last_mouse_motion_frame = NULL;
+
+         /* Something similar applies to
+            dpyinfo->last_mouse_glyph_frame.  */
+         if (f == dpyinfo->last_mouse_glyph_frame)
+           dpyinfo->last_mouse_glyph_frame = NULL;
+
+          if (f == hlinfo->mouse_face_mouse_frame)
+            {
+              /* If we move outside the frame, then we're
+                 certainly no longer on any text in the frame.  */
+              clear_mouse_face (hlinfo);
+              hlinfo->mouse_face_mouse_frame = 0;
+             android_flush_dirty_back_buffer_on (f);
+            }
+
+          /* Generate a nil HELP_EVENT to cancel a help-echo.
+             Do it only if there's something to cancel.
+             Otherwise, the startup message is cleared when
+             the mouse leaves the frame.  */
+          if (any_help_event_p
+             /* But never if `mouse-drag-and-drop-region' is in
+                progress, since that results in the tooltip being
+                dismissed when the mouse moves on top.  */
+             && !((EQ (track_mouse, Qdrag_source)
+                   || EQ (track_mouse, Qdropping))
+                  && gui_mouse_grabbed (dpyinfo)))
+           do_help = -1;
+        }
+
+      goto OTHER;
+
+    case ANDROID_EXPOSE:
+
+      f = any;
+
+      if (f)
+        {
+          if (!FRAME_VISIBLE_P (f))
+            {
+              f->output_data.android->has_been_visible = true;
+              SET_FRAME_GARBAGED (f);
+            }
+
+          if (!FRAME_GARBAGED_P (f))
+            {
+              expose_frame (f, event->xexpose.x, event->xexpose.y,
+                           event->xexpose.width, event->xexpose.height);
+             show_back_buffer (f);
+           }
+        }
+
+      goto OTHER;
+
+    case ANDROID_BUTTON_PRESS:
+    case ANDROID_BUTTON_RELEASE:
+      /* If we decide we want to generate an event to be seen
+        by the rest of Emacs, we put it here.  */
+
+      f = any;
+
+      Lisp_Object tab_bar_arg = Qnil;
+      bool tab_bar_p = false;
+      bool tool_bar_p = false;
+
+      dpyinfo->last_mouse_glyph_frame = NULL;
+
+      f = mouse_or_wdesc_frame (dpyinfo, event->xbutton.window);
+
+      if (f && event->xbutton.type == ANDROID_BUTTON_PRESS
+         && !popup_activated ()
+         /* && !x_window_to_scroll_bar (event->xbutton.display, */
+         /*                          event->xbutton.window, 2) */
+         && !FRAME_NO_ACCEPT_FOCUS (f))
+       {
+         /* When clicking into a child frame or when clicking
+            into a parent frame with the child frame selected and
+            `no-accept-focus' is not set, select the clicked
+            frame.  */
+         struct frame *hf = dpyinfo->highlight_frame;
+
+         if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf)))
+           {
+             android_set_input_focus (FRAME_ANDROID_WINDOW (f),
+                                      event->xbutton.time);
+
+             if (FRAME_PARENT_FRAME (f))
+               android_raise_window (FRAME_ANDROID_WINDOW (f));
+           }
+       }
+
+      if (f)
+       {
+         /* Is this in the tab-bar?  */
+         if (WINDOWP (f->tab_bar_window)
+             && WINDOW_TOTAL_LINES (XWINDOW (f->tab_bar_window)))
+           {
+             Lisp_Object window;
+             int x = event->xbutton.x;
+             int y = event->xbutton.y;
+
+             window = window_from_coordinates (f, x, y, 0, true, true);
+             tab_bar_p = EQ (window, f->tab_bar_window);
+
+             if (tab_bar_p)
+               {
+                 tab_bar_arg = handle_tab_bar_click
+                   (f, x, y, (event->xbutton.type
+                              == ANDROID_BUTTON_PRESS),
+                    android_android_to_emacs_modifiers (dpyinfo,
+                                                        event->xbutton.state));
+                 android_flush_dirty_back_buffer_on (f);
+               }
+           }
+
+         /* Is this in the tool-bar?  */
+         if (WINDOWP (f->tool_bar_window)
+             && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window)))
+           {
+             Lisp_Object window;
+             int x = event->xbutton.x;
+             int y = event->xbutton.y;
+
+             window = window_from_coordinates (f, x, y, 0, true, true);
+             tool_bar_p = (EQ (window, f->tool_bar_window)
+                           && ((event->xbutton.type
+                                != ANDROID_BUTTON_RELEASE)
+                               || f->last_tool_bar_item != -1));
+
+             if (tool_bar_p && event->xbutton.button < 4)
+               {
+                 handle_tool_bar_click
+                   (f, x, y, (event->xbutton.type
+                              == ANDROID_BUTTON_PRESS),
+                    android_android_to_emacs_modifiers (dpyinfo,
+                                                        event->xbutton.state));
+                 android_flush_dirty_back_buffer_on (f);
+               }
+           }
+
+         if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p)
+           if (! popup_activated ())
+             {
+               android_construct_mouse_click (&inev.ie, &event->xbutton, f);
+
+               if (!NILP (tab_bar_arg))
+                 inev.ie.arg = tab_bar_arg;
+             }
+       }
+
+      if (event->type == ANDROID_BUTTON_PRESS)
+       {
+         dpyinfo->grabbed |= (1 << event->xbutton.button);
+         dpyinfo->last_mouse_frame = f;
+         if (f && !tab_bar_p)
+           f->last_tab_bar_item = -1;
+         if (f && !tool_bar_p)
+           f->last_tool_bar_item = -1;
+       }
+      else
+       dpyinfo->grabbed &= ~(1 << event->xbutton.button);
+
+      /* Ignore any mouse motion that happened before this event;
+        any subsequent mouse-movement Emacs events should reflect
+        only motion after the ButtonPress/Release.  */
+      if (f != 0)
+       f->mouse_moved = false;
+
+      goto OTHER;
+
+      /* Touch events.  The events here don't parallel X so much.  */
+    case ANDROID_TOUCH_DOWN:
+
+      if (!any)
+       goto OTHER;
+
+      /* This event is sent when a tool is put on the screen.  X and Y
+        are the location of the finger, and pointer_id identifies the
+        tool for as long as it is still held down.  First, see if the
+        touch point already exists and can be reused (this shouldn't
+        happen, but be safe.)  */
+
+      touchpoint = android_find_tool (any, event->touch.pointer_id);
+
+      if (touchpoint)
+       {
+         /* Simply update the tool position and send an update.  */
+         touchpoint->x = event->touch.x;
+         touchpoint->y = event->touch.x;
+         android_update_tools (any, &inev.ie);
+         inev.ie.timestamp = event->touch.time;
+
+         goto OTHER;
+       }
+
+      /* Otherwise, link a new touchpoint onto the output's list of
+        pressed tools.  */
+
+      touchpoint = xmalloc (sizeof *touchpoint);
+      touchpoint->tool_id = event->touch.pointer_id;
+      touchpoint->x = event->touch.x;
+      touchpoint->y = event->touch.x;
+      touchpoint->next = FRAME_OUTPUT_DATA (any)->touch_points;
+      touchpoint->tool_bar_p = false;
+      FRAME_OUTPUT_DATA (any)->touch_points = touchpoint;
+
+      /* Figure out whether or not the tool was pressed on the tool
+        bar.  Note that the code which runs when it was is more or
+        less an abuse of the mouse highlight machinery, but it works
+        well enough in practice.  */
+
+      if (WINDOWP (any->tool_bar_window)
+         && WINDOW_TOTAL_LINES (XWINDOW (any->tool_bar_window)))
+       {
+         Lisp_Object window;
+         int x = event->touch.x;
+         int y = event->touch.y;
+
+         window = window_from_coordinates (any, x, y, 0, true,
+                                           true);
+
+         /* If this touch has started in the tool bar, do not
+            send it to Lisp.  Instead, simulate a tool bar
+            click, releasing it once it goes away.  */
+
+         if (EQ (window, any->tool_bar_window))
+           {
+             /* Call note_mouse_highlight on the tool bar
+                item.  Otherwise, get_tool_bar_item will
+                return 1.
+
+                This is not necessary when mouse-highlight is
+                nil.  */
+
+             if (!NILP (Vmouse_highlight))
+               {
+                 /* Clear the pointer invisible flag to always make
+                    note_mouse_highlight do its thing.  */
+                 any->pointer_invisible = false;
+                 note_mouse_highlight (any, x, y);
+
+                 /* Always allow future mouse motion to
+                    update the mouse highlight, no matter
+                    where it is.  */
+                 memset (&dpyinfo->last_mouse_glyph, 0,
+                         sizeof dpyinfo->last_mouse_glyph);
+                 dpyinfo->last_mouse_glyph_frame = any;
+               }
+
+             handle_tool_bar_click (any, x, y, true, 0);
+
+             /* Flush any changes made by that to the front
+                buffer.  */
+             android_flush_dirty_back_buffer_on (any);
+
+             /* Mark the touch point as being grabbed by the tool
+                bar.  */
+             touchpoint->tool_bar_p = true;
+             goto OTHER;
+           }
+       }
+
+      /* Now generate the Emacs event.  */
+      inev.ie.kind = TOUCHSCREEN_BEGIN_EVENT;
+      inev.ie.timestamp = event->touch.time;
+      XSETFRAME (inev.ie.frame_or_window, any);
+      XSETINT (inev.ie.x, event->touch.x);
+      XSETINT (inev.ie.y, event->touch.y);
+      XSETINT (inev.ie.arg, event->touch.pointer_id);
+
+      goto OTHER;
+
+    case ANDROID_TOUCH_MOVE:
+
+      if (!any)
+       goto OTHER;
+
+      /* Look for the tool that moved.  */
+
+      touchpoint = android_find_tool (any, event->touch.pointer_id);
+
+      /* If it doesn't exist or has been grabbed by the tool bar, skip
+        processing this event.  */
+
+      if (!touchpoint || touchpoint->tool_bar_p)
+       goto OTHER;
+
+      /* Otherwise, update the position and send the update event.  */
+
+      touchpoint->x = event->touch.x;
+      touchpoint->y = event->touch.y;
+      android_update_tools (any, &inev.ie);
+      inev.ie.timestamp = event->touch.time;
+
+      goto OTHER;
+
+    case ANDROID_TOUCH_UP:
+
+      if (!any)
+       goto OTHER;
+
+      /* Now find and unlink the tool in question.  */
+
+      last = &FRAME_OUTPUT_DATA (any)->touch_points;
+      while ((touchpoint = *last))
+       {
+         if (touchpoint->tool_id == event->touch.pointer_id)
+           {
+             *last = touchpoint->next;
+
+             if (touchpoint->tool_bar_p)
+               {
+                 xfree (touchpoint);
+
+                 /* Do what is necessary to release the tool bar and
+                    possibly trigger a click.  */
+
+                 if (any->last_tool_bar_item != -1)
+                   handle_tool_bar_click (any, event->touch.x,
+                                          event->touch.y, false,
+                                          0);
+
+                 /* Cancel any outstanding mouse highlight.  */
+                 note_mouse_highlight (any, -1, -1);
+                 android_flush_dirty_back_buffer_on (any);
+
+                 goto OTHER;
+               }
+
+             /* The tool was unlinked.  Free it and generate the
+                appropriate Emacs event (assuming that it was not
+                grabbed by the tool bar).  */
+             xfree (touchpoint);
+
+             inev.ie.kind = TOUCHSCREEN_END_EVENT;
+             inev.ie.timestamp = event->touch.time;
+
+             /* Report whether the sequence has been canceled.  */
+
+             if (event->touch.flags & ANDROID_TOUCH_SEQUENCE_CANCELED)
+               inev.ie.modifiers = 1;
+
+             XSETFRAME (inev.ie.frame_or_window, any);
+             XSETINT (inev.ie.x, event->touch.x);
+             XSETINT (inev.ie.y, event->touch.y);
+             XSETINT (inev.ie.arg, event->touch.pointer_id);
+
+             /* Break out of the loop.  */
+             goto OTHER;
+           }
+         else
+           last = &touchpoint->next;
+       }
+
+      /* No touch point was found.  This shouldn't happen.  */
+      goto OTHER;
+
+      /* Wheel motion.  The events here don't parallel X because
+        Android doesn't have scroll valuators.  */
+
+    case ANDROID_WHEEL:
+
+      if (!any)
+       goto OTHER;
+
+      if (fabs (event->wheel.x_delta) > 0
+         || fabs (event->wheel.y_delta) > 0)
+       {
+         if (mwheel_coalesce_scroll_events)
+           {
+             if (signbit (event->wheel.x_delta)
+                 != signbit (wheel_event_x))
+               wheel_event_x = 0.0;
+
+             if (signbit (event->wheel.y_delta)
+                 != signbit (wheel_event_y))
+               wheel_event_y = 0.0;
+
+             /* Tally up deltas until one of them exceeds 1.0.  */
+             wheel_event_x += event->wheel.x_delta;
+             wheel_event_y += event->wheel.y_delta;
+
+             if (fabs (wheel_event_x) < 1.0
+                 && fabs (wheel_event_y) < 1.0)
+               goto OTHER;
+           }
+         else
+           {
+             /* Use the deltas in the event.  */
+             wheel_event_x = event->wheel.x_delta;
+             wheel_event_y = event->wheel.y_delta;
+           }
+
+         /* Determine what kind of event to send.  */
+         inev.ie.kind = ((fabs (wheel_event_y)
+                          >= fabs (wheel_event_x))
+                         ? WHEEL_EVENT : HORIZ_WHEEL_EVENT);
+         inev.ie.timestamp = event->wheel.time;
+
+         /* Set the event coordinates.  */
+         XSETINT (inev.ie.x, event->wheel.x);
+         XSETINT (inev.ie.y, event->wheel.y);
+
+         /* Set the frame.  */
+         XSETFRAME (inev.ie.frame_or_window, any);
+
+         /* Figure out the scroll direction.  */
+         inev.ie.modifiers = (signbit ((fabs (wheel_event_x)
+                                        >= fabs (wheel_event_y))
+                                       ? wheel_event_x
+                                       : wheel_event_y)
+                              ? down_modifier : up_modifier);
+
+         /* Figure out how much to scale the deltas by.  */
+         window = window_from_coordinates (any, event->wheel.x,
+                                           event->wheel.y, NULL,
+                                           false, false);
+
+         if (WINDOWP (window))
+           scroll_height = XWINDOW (window)->pixel_height;
+         else
+           /* EVENT_X and EVENT_Y can be outside the
+              frame if F holds the input grab, so fall
+              back to the height of the frame instead.  */
+           scroll_height = FRAME_PIXEL_HEIGHT (any);
+
+         scroll_unit = pow (scroll_height, 2.0 / 3.0);
+
+         /* Add the keyboard modifiers.  */
+         inev.ie.modifiers
+           |= android_android_to_emacs_modifiers (dpyinfo,
+                                                  event->wheel.state);
+
+         /* Finally include the scroll deltas.  */
+         inev.ie.arg = list3 (Qnil,
+                              make_float (wheel_event_x
+                                          * scroll_unit),
+                              make_float (wheel_event_y
+                                          * scroll_unit));
+
+         wheel_event_x = 0.0;
+         wheel_event_y = 0.0;
+       }
+
+      goto OTHER;
+
+      /* Iconification.  This is vastly simpler than on X.  */
+    case ANDROID_ICONIFIED:
+
+      if (!any)
+       goto OTHER;
+
+      if (FRAME_ICONIFIED_P (any))
+       goto OTHER;
+
+      SET_FRAME_VISIBLE (any, false);
+      SET_FRAME_ICONIFIED (any, true);
+
+      inev.ie.kind = ICONIFY_EVENT;
+      XSETFRAME (inev.ie.frame_or_window, any);
+      goto OTHER;
+
+    case ANDROID_DEICONIFIED:
+
+      if (!any)
+       goto OTHER;
+
+      if (!FRAME_ICONIFIED_P (any))
+       goto OTHER;
+
+      SET_FRAME_VISIBLE (any, true);
+      SET_FRAME_ICONIFIED (any, false);
+
+      inev.ie.kind = DEICONIFY_EVENT;
+      XSETFRAME (inev.ie.frame_or_window, any);
+      goto OTHER;
+
+      /* Context menu handling.  */
+    case ANDROID_CONTEXT_MENU:
+
+      if (dpyinfo->menu_event_id == -1
+         /* Previously displayed popup menus might generate events
+            after dismissal, which might interfere.
+            `current_menu_serial' is always set to an identifier
+            identifying the last context menu to be displayed.  */
+         && event->menu.menu_event_serial == current_menu_serial)
+       dpyinfo->menu_event_id = event->menu.menu_event_id;
+
+      goto OTHER;
+
+      /* Input method events.  textconv.c functions are called here to
+        queue events, which are then executed in a safe context
+        inside keyboard.c.  */
+    case ANDROID_INPUT_METHOD:
+
+      if (!any)
+       {
+         /* Free any text allocated for this event.  */
+         xfree (event->ime.text);
+
+         /* If edits associated with this event haven't been
+            processed yet, signal their completion to avoid delays
+            the next time a call to `android_sync_edit' is made.
+
+            If events for a deleted frame are interleaved with events
+            for another frame, the edit counter may be prematurely
+            incremented before edits associated with the other frames
+            are processed.  This is not a problem in practice.  */
+
+         android_notify_conversion (event->ime.counter);
+       }
+      else
+       android_handle_ime_event (event, any);
+
+      goto OTHER;
+
+    default:
+      goto OTHER;
+    }
+
+ OTHER:
+  if (inev.ie.kind != NO_EVENT)
+    {
+      kbd_buffer_store_buffered_event (&inev, hold_quit);
+      count++;
+    }
+
+  if (do_help
+      && !(hold_quit && hold_quit->kind != NO_EVENT))
+    {
+      Lisp_Object frame;
+
+      if (f)
+       XSETFRAME (frame, f);
+      else
+       frame = Qnil;
+
+      if (do_help > 0)
+       {
+         any_help_event_p = true;
+         gen_help_event (help_echo_string, frame, help_echo_window,
+                         help_echo_object, help_echo_pos);
+       }
+      else
+       {
+         help_echo_string = Qnil;
+         gen_help_event (Qnil, frame, Qnil, Qnil, 0);
+       }
+      count++;
+    }
+
+  return count;
+}
+
+static int
+android_read_socket (struct terminal *terminal,
+                    struct input_event *hold_quit)
+{
+  int count = 0;
+  struct android_display_info *dpyinfo;
+
+  dpyinfo = terminal->display_info.android;
+
+  block_input ();
+  while (android_pending ())
+    {
+      int finish;
+      union android_event event;
+
+      android_next_event (&event);
+      count += handle_one_android_event (dpyinfo, &event, &finish,
+                                        hold_quit);
+
+      if (finish == ANDROID_EVENT_GOTO_OUT)
+       break;
+    }
+  unblock_input ();
+
+  /* If the focus was just given to an auto-raising frame, raise it
+     now.  */
+  if (dpyinfo->pending_autoraise_frame)
+    {
+      android_raise_frame (dpyinfo->pending_autoraise_frame);
+      dpyinfo->pending_autoraise_frame = NULL;
+    }
+
+  return count;
+}
+
+static void
+android_frame_up_to_date (struct frame *f)
+{
+  eassert (FRAME_ANDROID_P (f));
+  block_input ();
+  FRAME_MOUSE_UPDATE (f);
+
+  if (!buffer_flipping_blocked_p ()
+      && FRAME_ANDROID_NEED_BUFFER_FLIP (f))
+    show_back_buffer (f);
+
+  /* The frame is now complete, as its contents have been drawn.  */
+  FRAME_ANDROID_COMPLETE_P (f) = true;
+
+  /* Shrink the scanline buffer used by the font backend.  */
+  sfntfont_android_shrink_scanline_buffer ();
+  unblock_input ();
+}
+
+static void
+android_buffer_flipping_unblocked_hook (struct frame *f)
+{
+  block_input ();
+
+  if (FRAME_ANDROID_NEED_BUFFER_FLIP (f))
+    show_back_buffer (f);
+
+  unblock_input ();
+}
+
+static void
+android_query_frame_background_color (struct frame *f, Emacs_Color *bgcolor)
+{
+  unsigned long background;
+
+  background = FRAME_BACKGROUND_PIXEL (f);
+  bgcolor->pixel = background;
+
+  android_query_colors (f, bgcolor, 1);
+}
+
+int
+android_parse_color (struct frame *f, const char *color_name,
+                    Emacs_Color *color)
+{
+  unsigned short r, g, b;
+  Lisp_Object tem, tem1;
+  unsigned long lisp_color;
+
+  if (parse_color_spec (color_name, &r, &g, &b))
+    {
+      color->red = r;
+      color->green = g;
+      color->blue = b;
+
+      return 1;
+    }
+
+  tem = x_display_list->color_map;
+  for (; CONSP (tem); tem = XCDR (tem))
+    {
+      tem1 = XCAR (tem);
+
+      if (CONSP (tem1)
+         && !xstrcasecmp (SSDATA (XCAR (tem1)), color_name))
+       {
+         lisp_color = XFIXNUM (XCDR (tem1));
+         color->red = RED_FROM_ULONG (lisp_color) * 257;
+         color->green = GREEN_FROM_ULONG (lisp_color) * 257;
+         color->blue = BLUE_FROM_ULONG (lisp_color) * 257;
+         return 1;
+       }
+    }
+
+  return 0;
+}
+
+bool
+android_alloc_nearest_color (struct frame *f, Emacs_Color *color)
+{
+  gamma_correct (f, color);
+  color->pixel = RGB_TO_ULONG (color->red / 256,
+                              color->green / 256,
+                              color->blue / 256);
+
+  return true;
+}
+
+void
+android_query_colors (struct frame *f, Emacs_Color *colors, int ncolors)
+{
+  int i;
+
+  for (i = 0; i < ncolors; ++i)
+    {
+      colors[i].red = RED_FROM_ULONG (colors[i].pixel) * 257;
+      colors[i].green = RED_FROM_ULONG (colors[i].pixel) * 257;
+      colors[i].blue = RED_FROM_ULONG (colors[i].pixel) * 257;
+    }
+}
+
+static void
+android_mouse_position (struct frame **fp, int insist,
+                       Lisp_Object *bar_window,
+                       enum scroll_bar_part *part, Lisp_Object *x,
+                       Lisp_Object *y, Time *timestamp)
+{
+  Lisp_Object tail, frame;
+  struct android_display_info *dpyinfo;
+
+  dpyinfo = FRAME_DISPLAY_INFO (*fp);
+
+  /* This is the best implementation possible on Android, where the
+     system doesn't let Emacs obtain any information about the mouse
+     pointer at all.  */
+
+  if (dpyinfo->last_mouse_motion_frame)
+    {
+      *fp = dpyinfo->last_mouse_motion_frame;
+      *timestamp = dpyinfo->last_mouse_movement_time;
+      *x = make_fixnum (dpyinfo->last_mouse_motion_x);
+      *y = make_fixnum (dpyinfo->last_mouse_motion_y);
+      *bar_window = Qnil;
+      *part = scroll_bar_nowhere;
+
+      FOR_EACH_FRAME (tail, frame)
+       {
+         if (FRAME_ANDROID_P (XFRAME (frame)))
+           XFRAME (frame)->mouse_moved = false;
+       }
+
+      dpyinfo->last_mouse_motion_frame->mouse_moved = false;
+    }
+}
+
+static Lisp_Object
+android_get_focus_frame (struct frame *f)
+{
+  Lisp_Object lisp_focus;
+  struct frame *focus;
+
+  focus = FRAME_DISPLAY_INFO (f)->focus_frame;
+
+  if (!focus)
+    return Qnil;
+
+  XSETFRAME (lisp_focus, focus);
+  return lisp_focus;
+}
+
+static void
+android_focus_frame (struct frame *f, bool noactivate)
+{
+  /* Set the input focus to the frame's window.  The system only lets
+     this work on child frames.  */
+  android_set_input_focus (FRAME_ANDROID_WINDOW (f),
+                          ANDROID_CURRENT_TIME);
+}
+
+/* The two procedures below only have to update the cursor on Android,
+   as there are no window borders there.  */
+
+static void
+android_frame_highlight (struct frame *f)
+{
+  gui_update_cursor (f, true);
+}
+
+static void
+android_frame_unhighlight (struct frame *f)
+{
+  gui_update_cursor (f, true);
+}
+
+static void
+android_frame_rehighlight (struct android_display_info *dpyinfo)
+{
+  struct frame *old_highlight;
+
+  old_highlight = dpyinfo->highlight_frame;
+
+  if (dpyinfo->focus_frame)
+    {
+      dpyinfo->highlight_frame
+       = ((FRAMEP (FRAME_FOCUS_FRAME (dpyinfo->focus_frame)))
+          ? XFRAME (FRAME_FOCUS_FRAME (dpyinfo->focus_frame))
+          : dpyinfo->focus_frame);
+      if (!FRAME_LIVE_P (dpyinfo->highlight_frame))
+       {
+         fset_focus_frame (dpyinfo->focus_frame, Qnil);
+         dpyinfo->highlight_frame = dpyinfo->focus_frame;
+       }
+    }
+  else
+    dpyinfo->highlight_frame = 0;
+
+  if (dpyinfo->highlight_frame != old_highlight)
+    {
+      /* This is not yet required on Android.  */
+      if (old_highlight)
+       android_frame_unhighlight (old_highlight);
+      if (dpyinfo->highlight_frame)
+       android_frame_highlight (dpyinfo->highlight_frame);
+    }
+}
+
+static void
+android_frame_rehighlight_hook (struct frame *f)
+{
+  android_frame_rehighlight (FRAME_DISPLAY_INFO (f));
+}
+
+static void
+android_frame_raise_lower (struct frame *f, bool raise_flag)
+{
+  if (raise_flag)
+    android_raise_frame (f);
+  else
+    android_lower_frame (f);
+}
+
+void
+android_make_frame_visible (struct frame *f)
+{
+  android_map_window (FRAME_ANDROID_WINDOW (f));
+
+  SET_FRAME_VISIBLE (f, true);
+  SET_FRAME_ICONIFIED (f, false);
+}
+
+void
+android_make_frame_invisible (struct frame *f)
+{
+  /* Don't keep the highlight on an invisible frame.  */
+  if (FRAME_DISPLAY_INFO (f)->highlight_frame == f)
+    FRAME_DISPLAY_INFO (f)->highlight_frame = 0;
+
+  android_unmap_window (FRAME_ANDROID_WINDOW (f));
+
+  SET_FRAME_VISIBLE (f, false);
+  SET_FRAME_ICONIFIED (f, false);
+}
+
+static void
+android_make_frame_visible_invisible (struct frame *f, bool visible)
+{
+  if (visible)
+    android_make_frame_visible (f);
+  else
+    android_make_frame_invisible (f);
+}
+
+static void
+android_fullscreen_hook (struct frame *f)
+{
+  Lisp_Object wanted;
+
+  if (!FRAME_PARENT_FRAME (f))
+    {
+      /* Explicitly setting fullscreen is not supported on older
+        Android versions.  */
+
+      wanted = (f->want_fullscreen == FULLSCREEN_BOTH
+               ? Qfullscreen : Qmaximized);
+
+      if (android_set_fullscreen (FRAME_ANDROID_WINDOW (f),
+                                 EQ (wanted, Qfullscreen)))
+       store_frame_param (f, Qfullscreen, Qmaximized);
+      else
+        store_frame_param (f, Qfullscreen, wanted);
+    }
+  else
+    {
+      store_frame_param (f, Qfullscreen, Qnil);
+
+      /* If this is a child frame, don't keep it fullscreen
+        anymore.  */
+      android_set_fullscreen (FRAME_ANDROID_WINDOW (f), false);
+    }
+}
+
+void
+android_iconify_frame (struct frame *f)
+{
+  /* This really doesn't work on Android.  */
+  error ("Can't notify window manager of iconification");
+}
+
+static void
+android_wait_for_event (struct frame *f, int eventtype)
+{
+  if (!FLOATP (Vandroid_wait_for_event_timeout))
+    return;
+
+  int level = interrupt_input_blocked;
+  struct timespec tmo, tmo_at, time_now;
+
+  f->wait_event_type = eventtype;
+
+  /* Default timeout is 0.1 second.  Hopefully not noticeable.  */
+  double timeout = XFLOAT_DATA (Vandroid_wait_for_event_timeout);
+  time_t timeout_seconds = (time_t) timeout;
+  tmo = make_timespec (timeout_seconds,
+                      (long int) ((timeout - timeout_seconds)
+                                  * 1000 * 1000 * 1000));
+  tmo_at = timespec_add (current_timespec (), tmo);
+
+  while (f->wait_event_type)
+    {
+      pending_signals = true;
+      totally_unblock_input ();
+      /* XTread_socket is called after unblock.  */
+      block_input ();
+      interrupt_input_blocked = level;
+
+      time_now = current_timespec ();
+      if (timespec_cmp (tmo_at, time_now) < 0)
+       break;
+
+      tmo = timespec_sub (tmo_at, time_now);
+      if (android_select (0, NULL, NULL, NULL, &tmo) == 0)
+        break; /* Timeout */
+    }
+
+  f->wait_event_type = 0;
+}
+
+static void
+android_set_window_size_1 (struct frame *f, bool change_gravity,
+                          int width, int height)
+{
+  if (change_gravity)
+    f->win_gravity = NorthWestGravity;
+
+  android_resize_window (FRAME_ANDROID_WINDOW (f), width,
+                        height);
+
+  SET_FRAME_GARBAGED (f);
+
+  if (FRAME_VISIBLE_P (f))
+    {
+      android_wait_for_event (f, ANDROID_CONFIGURE_NOTIFY);
+
+      if (CONSP (frame_size_history))
+       frame_size_history_extra (f, build_string ("set_window_size_1 visible"),
+                                 FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f),
+                                 width, height, f->new_width, f->new_height);
+    }
+  else
+    {
+      if (CONSP (frame_size_history))
+       frame_size_history_extra (f, build_string ("set_window_size_1 "
+                                                  "invisible"),
+                                 FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f),
+                                 width, height, f->new_width, f->new_height);
+
+      adjust_frame_size (f, FRAME_PIXEL_TO_TEXT_WIDTH (f, width),
+                        FRAME_PIXEL_TO_TEXT_HEIGHT (f, height),
+                        5, 0, Qx_set_window_size_1);
+    }
+}
+
+void
+android_set_window_size (struct frame *f, bool change_gravity,
+                        int width, int height)
+{
+  block_input ();
+
+  android_set_window_size_1 (f, change_gravity, width, height);
+  android_clear_under_internal_border (f);
+
+  /* If cursor was outside the new size, mark it as off.  */
+  mark_window_cursors_off (XWINDOW (f->root_window));
+
+  /* Clear out any recollection of where the mouse highlighting was,
+     since it might be in a place that's outside the new frame size.
+     Actually checking whether it is outside is a pain in the neck,
+     so don't try--just let the highlighting be done afresh with new size.  */
+  cancel_mouse_face (f);
+
+  unblock_input ();
+
+  do_pending_window_change (false);
+}
+
+static void
+android_set_offset (struct frame *f, int xoff, int yoff,
+                   int change_gravity)
+{
+  if (change_gravity > 0)
+    {
+      f->top_pos = yoff;
+      f->left_pos = xoff;
+      f->size_hint_flags &= ~ (XNegative | YNegative);
+      if (xoff < 0)
+       f->size_hint_flags |= XNegative;
+      if (yoff < 0)
+       f->size_hint_flags |= YNegative;
+      f->win_gravity = NorthWestGravity;
+    }
+
+  android_move_window (FRAME_ANDROID_WINDOW (f), xoff, yoff);
+}
+
+static void
+android_set_alpha (struct frame *f)
+{
+  /* Not supported on Android.  */
+}
+
+static Lisp_Object
+android_new_font (struct frame *f, Lisp_Object font_object, int fontset)
+{
+  struct font *font = XFONT_OBJECT (font_object);
+  int unit, font_ascent, font_descent;
+
+  if (fontset < 0)
+    fontset = fontset_from_font (font_object);
+  FRAME_FONTSET (f) = fontset;
+  if (FRAME_FONT (f) == font)
+    /* This font is already set in frame F.  There's nothing more to
+       do.  */
+    return font_object;
+
+  FRAME_FONT (f) = font;
+  FRAME_BASELINE_OFFSET (f) = font->baseline_offset;
+  FRAME_COLUMN_WIDTH (f) = font->average_width;
+  get_font_ascent_descent (font, &font_ascent, &font_descent);
+  FRAME_LINE_HEIGHT (f) = font_ascent + font_descent;
+
+  /* We could use a more elaborate calculation here.  */
+  FRAME_TAB_BAR_HEIGHT (f) = FRAME_TAB_BAR_LINES (f) * FRAME_LINE_HEIGHT (f);
+
+  /* Compute character columns occupied by scrollbar.
+
+     Don't do things differently for non-toolkit scrollbars
+     (Bug#17163).  */
+  unit = FRAME_COLUMN_WIDTH (f);
+  if (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0)
+    FRAME_CONFIG_SCROLL_BAR_COLS (f)
+      = (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) + unit - 1) / unit;
+  else
+    FRAME_CONFIG_SCROLL_BAR_COLS (f) = (14 + unit - 1) / unit;
+
+
+  /* Don't change the size of a tip frame; there's no point in doing it
+     because it's done in Fx_show_tip, and it leads to problems because
+     the tip frame has no widget.  */
+  if (FRAME_ANDROID_WINDOW (f) != 0 && !FRAME_TOOLTIP_P (f))
+    adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f),
+                      FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), 3,
+                      false, Qfont);
+
+  return font_object;
+}
+
+static bool
+android_bitmap_icon (struct frame *f, Lisp_Object file)
+{
+  return false;
+}
+
+static void
+android_free_pixmap_hook (struct frame *f, Emacs_Pixmap pixmap)
+{
+  android_free_pixmap (pixmap);
+}
+
+void
+android_free_frame_resources (struct frame *f)
+{
+  struct android_display_info *dpyinfo;
+  Mouse_HLInfo *hlinfo;
+  struct android_touch_point *last, *next;
+
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+  hlinfo = &dpyinfo->mouse_highlight;
+
+  block_input ();
+  free_frame_faces (f);
+
+  /* FRAME_ANDROID_WINDOW can be 0 if frame creation failed.  */
+  if (FRAME_ANDROID_WINDOW (f))
+    android_destroy_window (FRAME_ANDROID_WINDOW (f));
+
+  android_free_gcs (f);
+
+  /* Free cursors.  */
+  if (f->output_data.android->text_cursor)
+    android_free_cursor (f->output_data.android->text_cursor);
+  if (f->output_data.android->nontext_cursor)
+    android_free_cursor (f->output_data.android->nontext_cursor);
+  if (f->output_data.android->modeline_cursor)
+    android_free_cursor (f->output_data.android->modeline_cursor);
+  if (f->output_data.android->hand_cursor)
+    android_free_cursor (f->output_data.android->hand_cursor);
+  if (f->output_data.android->hourglass_cursor)
+    android_free_cursor (f->output_data.android->hourglass_cursor);
+  if (f->output_data.android->horizontal_drag_cursor)
+    android_free_cursor (f->output_data.android->horizontal_drag_cursor);
+  if (f->output_data.android->vertical_drag_cursor)
+    android_free_cursor (f->output_data.android->vertical_drag_cursor);
+  if (f->output_data.android->left_edge_cursor)
+    android_free_cursor (f->output_data.android->left_edge_cursor);
+  if (f->output_data.android->top_left_corner_cursor)
+    android_free_cursor (f->output_data.android->top_left_corner_cursor);
+  if (f->output_data.android->top_edge_cursor)
+    android_free_cursor (f->output_data.android->top_edge_cursor);
+  if (f->output_data.android->top_right_corner_cursor)
+    android_free_cursor (f->output_data.android->top_right_corner_cursor);
+  if (f->output_data.android->right_edge_cursor)
+    android_free_cursor (f->output_data.android->right_edge_cursor);
+  if (f->output_data.android->bottom_right_corner_cursor)
+    android_free_cursor (f->output_data.android->bottom_right_corner_cursor);
+  if (f->output_data.android->bottom_edge_cursor)
+    android_free_cursor (f->output_data.android->bottom_edge_cursor);
+  if (f->output_data.android->bottom_left_corner_cursor)
+    android_free_cursor (f->output_data.android->bottom_left_corner_cursor);
+
+  /* Free extra GCs allocated by android_setup_relief_colors.  */
+  if (f->output_data.android->white_relief.gc)
+    {
+      android_free_gc (f->output_data.android->white_relief.gc);
+      f->output_data.android->white_relief.gc = 0;
+    }
+  if (f->output_data.android->black_relief.gc)
+    {
+      android_free_gc (f->output_data.android->black_relief.gc);
+      f->output_data.android->black_relief.gc = 0;
+    }
+
+  if (f == dpyinfo->focus_frame)
+    dpyinfo->focus_frame = 0;
+  if (f == dpyinfo->x_focus_event_frame)
+    dpyinfo->x_focus_event_frame = 0;
+  if (f == dpyinfo->highlight_frame)
+    dpyinfo->highlight_frame = 0;
+  if (f == hlinfo->mouse_face_mouse_frame)
+    reset_mouse_highlight (hlinfo);
+
+  /* These two need to be freed now that they are used to compute the
+     mouse position, I think.  */
+  if (f == dpyinfo->last_mouse_motion_frame)
+    dpyinfo->last_mouse_motion_frame = NULL;
+  if (f == dpyinfo->last_mouse_frame)
+    dpyinfo->last_mouse_frame = NULL;
+
+  /* Free all tool presses currently active on this frame.  */
+  next = FRAME_OUTPUT_DATA (f)->touch_points;
+  while (next)
+    {
+      last = next;
+      next = next->next;
+      xfree (last);
+    }
+
+  /* Clear this in case unblock_input reads events.  */
+  FRAME_OUTPUT_DATA (f)->touch_points = NULL;
+
+  unblock_input ();
+}
+
+static void
+android_delete_frame (struct frame *f)
+{
+  android_free_frame_resources (f);
+  xfree (f->output_data.android);
+  f->output_data.android = NULL;
+}
+
+static void
+android_delete_terminal (struct terminal *terminal)
+{
+  error ("Cannot terminate connection to Android display server");
+}
+
+
+
+/* RIF functions.  */
+
+static void
+android_scroll_run (struct window *w, struct run *run)
+{
+  struct frame *f = XFRAME (w->frame);
+  int x, y, width, height, from_y, to_y, bottom_y;
+
+  /* Get frame-relative bounding box of the text display area of W,
+     without mode lines.  Include in this box the left and right
+     fringe of W.  */
+  window_box (w, ANY_AREA, &x, &y, &width, &height);
+
+  from_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->current_y);
+  to_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->desired_y);
+  bottom_y = y + height;
+
+  if (to_y < from_y)
+    {
+      /* Scrolling up.  Make sure we don't copy part of the mode
+        line at the bottom.  */
+      if (from_y + run->height > bottom_y)
+       height = bottom_y - from_y;
+      else
+       height = run->height;
+    }
+  else
+    {
+      /* Scrolling down.  Make sure we don't copy over the mode line.
+        at the bottom.  */
+      if (to_y + run->height > bottom_y)
+       height = bottom_y - to_y;
+      else
+       height = run->height;
+    }
+
+  block_input ();
+
+  /* Cursor off.  Will be switched on again in gui_update_window_end.  */
+  gui_clear_cursor (w);
+
+  /* To avoid sequence point problems, make sure to only call
+     FRAME_ANDROID_DRAWABLE once.  */
+  android_copy_area (FRAME_ANDROID_DRAWABLE (f),
+                    FRAME_ANDROID_WINDOW (f),
+                    f->output_data.android->normal_gc,
+                    x, from_y, width, height, x, to_y);
+
+  unblock_input ();
+}
+
+static void
+android_after_update_window_line (struct window *w, struct glyph_row 
*desired_row)
+{
+  eassert (w);
+
+  if (!desired_row->mode_line_p && !w->pseudo_window_p)
+    desired_row->redraw_fringe_bitmaps_p = true;
+}
+
+static void
+android_flip_and_flush (struct frame *f)
+{
+  block_input ();
+
+  if (FRAME_ANDROID_NEED_BUFFER_FLIP (f))
+    show_back_buffer (f);
+
+  /* The frame is complete again as its contents were just
+     flushed.  */
+  FRAME_ANDROID_COMPLETE_P (f) = true;
+  unblock_input ();
+}
+
+static void
+android_clear_rectangle (struct frame *f, struct android_gc *gc, int x,
+                        int y, int width, int height)
+{
+  struct android_gc_values xgcv;
+
+  android_get_gc_values (gc, (ANDROID_GC_BACKGROUND
+                             | ANDROID_GC_FOREGROUND),
+                        &xgcv);
+  android_set_foreground (gc, xgcv.background);
+  android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                         x, y, width, height);
+  android_set_foreground (gc, xgcv.foreground);
+}
+
+static void
+android_reset_clip_rectangles (struct frame *f, struct android_gc *gc)
+{
+  android_set_clip_mask (gc, ANDROID_NONE);
+}
+
+static void
+android_clip_to_row (struct window *w, struct glyph_row *row,
+                    enum glyph_row_area area, struct android_gc *gc)
+{
+  struct android_rectangle clip_rect;
+  int window_x, window_y, window_width;
+
+  window_box (w, area, &window_x, &window_y, &window_width, 0);
+
+  clip_rect.x = window_x;
+  clip_rect.y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, row->y));
+  clip_rect.y = max (clip_rect.y, window_y);
+  clip_rect.width = window_width;
+  clip_rect.height = row->visible_height;
+
+  android_set_clip_rectangles (gc, 0, 0, &clip_rect, 1);
+}
+
+static void
+android_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
+                           struct draw_fringe_bitmap_params *p)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct android_gc *gc = f->output_data.android->normal_gc;
+  struct face *face = p->face;
+
+  /* Must clip because of partially visible lines.  */
+  android_clip_to_row (w, row, ANY_AREA, gc);
+
+  if (p->bx >= 0 && !p->overlay_p)
+    {
+      /* In case the same realized face is used for fringes and for
+        something displayed in the text (e.g. face `region' on
+        mono-displays, the fill style may have been changed to
+        ANDROID_FILL_SOLID in
+        android_draw_glyph_string_background.  */
+      if (face->stipple)
+       {
+         android_set_fill_style (face->gc, ANDROID_FILL_OPAQUE_STIPPLED);
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), face->gc,
+                                 p->bx, p->by, p->nx, p->ny);
+         android_set_fill_style (face->gc, ANDROID_FILL_SOLID);
+
+         row->stipple_p = true;
+       }
+      else
+       {
+         android_set_background (face->gc, face->background);
+         android_clear_rectangle (f, face->gc, p->bx, p->by, p->nx, p->ny);
+         android_set_foreground (face->gc, face->foreground);
+       }
+    }
+
+  if (p->which)
+    {
+      android_drawable drawable;
+      char *bits;
+      android_pixmap pixmap, clipmask;
+      struct android_gc_values gcv;
+      unsigned long background, cursor_pixel;
+      int depth;
+
+      drawable = FRAME_ANDROID_DRAWABLE (f);
+      clipmask = ANDROID_NONE;
+      background = face->background;
+      cursor_pixel = f->output_data.android->cursor_pixel;
+      depth = FRAME_DISPLAY_INFO (f)->n_planes;
+
+      if (p->wd > 8)
+       bits = (char *) (p->bits + p->dh);
+      else
+       bits = (char *) p->bits + p->dh;
+
+      pixmap = android_create_pixmap_from_bitmap_data (bits, p->wd, p->h,
+                                                      (p->cursor_p
+                                                       ? (p->overlay_p
+                                                          ? face->background
+                                                          : cursor_pixel)
+                                                       : face->foreground),
+                                                      background, depth);
+
+      if (p->overlay_p)
+       {
+         clipmask = android_create_pixmap_from_bitmap_data (bits, p->wd, p->h,
+                                                            1, 0, 1);
+
+         gcv.clip_mask = clipmask;
+         gcv.clip_x_origin = p->x;
+         gcv.clip_y_origin = p->y;
+         android_change_gc (gc, (ANDROID_GC_CLIP_MASK
+                                 | ANDROID_GC_CLIP_X_ORIGIN
+                                 | ANDROID_GC_CLIP_Y_ORIGIN),
+                            &gcv);
+       }
+
+      android_copy_area (pixmap, drawable, gc, 0, 0, p->wd, p->h,
+                        p->x, p->y);
+      android_free_pixmap (pixmap);
+
+      if (p->overlay_p)
+       {
+         gcv.clip_mask = ANDROID_NONE;
+         android_change_gc (gc, ANDROID_GC_CLIP_MASK, &gcv);
+         android_free_pixmap (clipmask);
+       }
+    }
+
+  android_reset_clip_rectangles (f, gc);
+}
+
+/* Set S->gc to a suitable GC for drawing glyph string S in cursor
+   face.  */
+
+static void
+android_set_cursor_gc (struct glyph_string *s)
+{
+  if (s->font == FRAME_FONT (s->f)
+      && s->face->background == FRAME_BACKGROUND_PIXEL (s->f)
+      && s->face->foreground == FRAME_FOREGROUND_PIXEL (s->f)
+      && !s->cmp)
+    s->gc = s->f->output_data.android->cursor_gc;
+  else
+    {
+      /* Cursor on non-default face: must merge.  */
+      struct android_gc_values xgcv;
+      unsigned long mask;
+
+      xgcv.background = s->f->output_data.android->cursor_pixel;
+      xgcv.foreground = s->face->background;
+
+      /* If the glyph would be invisible, try a different foreground.  */
+      if (xgcv.foreground == xgcv.background)
+       xgcv.foreground = s->face->foreground;
+      if (xgcv.foreground == xgcv.background)
+       xgcv.foreground = s->f->output_data.android->cursor_foreground_pixel;
+      if (xgcv.foreground == xgcv.background)
+       xgcv.foreground = s->face->foreground;
+
+      /* Make sure the cursor is distinct from text in this face.  */
+      if (xgcv.background == s->face->background
+         && xgcv.foreground == s->face->foreground)
+       {
+         xgcv.background = s->face->foreground;
+         xgcv.foreground = s->face->background;
+       }
+
+      mask = (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND);
+
+      if (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc)
+       android_change_gc (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc,
+                          mask, &xgcv);
+      else
+       FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc
+          = android_create_gc (mask, &xgcv);
+
+      s->gc = FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc;
+    }
+}
+
+
+/* Set up S->gc of glyph string S for drawing text in mouse face.  */
+
+static void
+android_set_mouse_face_gc (struct glyph_string *s)
+{
+  if (s->font == s->face->font)
+    s->gc = s->face->gc;
+  else
+    {
+      /* Otherwise construct scratch_cursor_gc with values from FACE
+        except for FONT.  */
+      struct android_gc_values xgcv;
+      unsigned long mask;
+
+      xgcv.background = s->face->background;
+      xgcv.foreground = s->face->foreground;
+
+      mask = (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND);
+
+      if (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc)
+       android_change_gc (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc,
+                          mask, &xgcv);
+      else
+       FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc
+          = android_create_gc (mask, &xgcv);
+
+      s->gc = FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc;
+    }
+
+  eassert (s->gc != 0);
+}
+
+
+/* Set S->gc of glyph string S to a GC suitable for drawing a mode line.
+   Faces to use in the mode line have already been computed when the
+   matrix was built, so there isn't much to do, here.  */
+
+static void
+android_set_mode_line_face_gc (struct glyph_string *s)
+{
+  s->gc = s->face->gc;
+}
+
+/* Set S->gc of glyph string S for drawing that glyph string.  Set
+   S->stippled_p to a non-zero value if the face of S has a stipple
+   pattern.  */
+
+static void
+android_set_glyph_string_gc (struct glyph_string *s)
+{
+  prepare_face_for_display (s->f, s->face);
+
+  if (s->hl == DRAW_NORMAL_TEXT)
+    {
+      s->gc = s->face->gc;
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else if (s->hl == DRAW_INVERSE_VIDEO)
+    {
+      android_set_mode_line_face_gc (s);
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else if (s->hl == DRAW_CURSOR)
+    {
+      android_set_cursor_gc (s);
+      s->stippled_p = false;
+    }
+  else if (s->hl == DRAW_MOUSE_FACE)
+    {
+      android_set_mouse_face_gc (s);
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else if (s->hl == DRAW_IMAGE_RAISED
+          || s->hl == DRAW_IMAGE_SUNKEN)
+    {
+      s->gc = s->face->gc;
+      s->stippled_p = s->face->stipple != 0;
+    }
+  else
+    emacs_abort ();
+
+  /* GC must have been set.  */
+  eassert (s->gc != 0);
+}
+
+
+/* Set clipping for output of glyph string S.  S may be part of a mode
+   line or menu if we don't have X toolkit support.  */
+
+static void
+android_set_glyph_string_clipping (struct glyph_string *s)
+{
+  struct android_rectangle *r = s->clip;
+  int n = get_glyph_string_clip_rects (s, r, 2);
+
+  if (n > 0)
+    android_set_clip_rectangles (s->gc, 0, 0, r, n);
+  s->num_clips = n;
+}
+
+
+/* Set SRC's clipping for output of glyph string DST.  This is called
+   when we are drawing DST's left_overhang or right_overhang only in
+   the area of SRC.  */
+
+static void
+android_set_glyph_string_clipping_exactly (struct glyph_string *src,
+                                          struct glyph_string *dst)
+{
+  struct android_rectangle r;
+
+  r.x = src->x;
+  r.width = src->width;
+  r.y = src->y;
+  r.height = src->height;
+  dst->clip[0] = r;
+  dst->num_clips = 1;
+  android_set_clip_rectangles (dst->gc, 0, 0, &r, 1);
+}
+
+static void
+android_compute_glyph_string_overhangs (struct glyph_string *s)
+{
+  if (s->cmp == NULL
+      && (s->first_glyph->type == CHAR_GLYPH
+         || s->first_glyph->type == COMPOSITE_GLYPH))
+    {
+      struct font_metrics metrics;
+
+      if (s->first_glyph->type == CHAR_GLYPH)
+       {
+         struct font *font = s->font;
+         font->driver->text_extents (font, s->char2b, s->nchars, &metrics);
+       }
+      else
+       {
+         Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
+
+         composition_gstring_width (gstring, s->cmp_from, s->cmp_to, &metrics);
+       }
+      s->right_overhang = (metrics.rbearing > metrics.width
+                          ? metrics.rbearing - metrics.width : 0);
+      s->left_overhang = metrics.lbearing < 0 ? - metrics.lbearing : 0;
+    }
+  else if (s->cmp)
+    {
+      s->right_overhang = s->cmp->rbearing - s->cmp->pixel_width;
+      s->left_overhang = - s->cmp->lbearing;
+    }
+}
+
+static void
+android_clear_glyph_string_rect (struct glyph_string *s, int x, int y,
+                                int w, int h)
+{
+  android_clear_rectangle (s->f, s->gc, x, y, w, h);
+}
+
+static void
+android_draw_glyph_string_background (struct glyph_string *s, bool force_p)
+{
+  /* Nothing to do if background has already been drawn or if it
+     shouldn't be drawn in the first place.  */
+  if (!s->background_filled_p)
+    {
+      int box_line_width = max (s->face->box_horizontal_line_width, 0);
+
+      if (s->stippled_p)
+       {
+         /* Fill background with a stipple pattern.  */
+         android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED);
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                                 s->x, s->y + box_line_width,
+                                 s->background_width,
+                                 s->height - 2 * box_line_width);
+         android_set_fill_style (s->gc, ANDROID_FILL_SOLID);
+         s->background_filled_p = true;
+       }
+      else if (FONT_HEIGHT (s->font) < s->height - 2 * box_line_width
+              /* When xdisp.c ignores FONT_HEIGHT, we cannot trust
+                 font dimensions, since the actual glyphs might be
+                 much smaller.  So in that case we always clear the
+                 rectangle with background color.  */
+              || FONT_TOO_HIGH (s->font)
+              || s->font_not_found_p
+              || s->extends_to_end_of_line_p
+              || force_p)
+       {
+         android_clear_glyph_string_rect (s, s->x, s->y + box_line_width,
+                                          s->background_width,
+                                          s->height - 2 * box_line_width);
+         s->background_filled_p = true;
+       }
+    }
+}
+
+static void
+android_fill_triangle (struct frame *f, struct android_gc *gc,
+                      struct android_point point1,
+                      struct android_point point2,
+                      struct android_point point3)
+{
+  struct android_point abc[3];
+
+  abc[0] = point1;
+  abc[1] = point2;
+  abc[2] = point3;
+
+  android_fill_polygon (FRAME_ANDROID_DRAWABLE (f),
+                       gc, abc, 3, ANDROID_CONVEX,
+                       ANDROID_COORD_MODE_ORIGIN);
+}
+
+static struct android_point
+android_make_point (int x, int y)
+{
+  struct android_point pt;
+
+  pt.x = x;
+  pt.y = y;
+
+  return pt;
+}
+
+static bool
+android_inside_rect_p (struct android_rectangle *rects, int nrects, int x,
+                      int y)
+{
+  int i;
+
+  for (i = 0; i < nrects; ++i)
+    {
+      if (x >= rects[i].x && y >= rects[i].y
+         && x < rects[i].x + rects[i].width
+         && y < rects[i].y + rects[i].height)
+       return true;
+    }
+
+  return false;
+}
+
+static void
+android_clear_point (struct frame *f, struct android_gc *gc,
+                    int x, int y)
+{
+  struct android_gc_values xgcv;
+
+  android_get_gc_values (gc, ANDROID_GC_BACKGROUND | ANDROID_GC_FOREGROUND,
+                        &xgcv);
+  android_set_foreground (gc, xgcv.background);
+  android_draw_point (FRAME_ANDROID_DRAWABLE (f), gc, x, y);
+  android_set_foreground (gc, xgcv.foreground);
+}
+
+static void
+android_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x,
+                         int bottom_y, int hwidth, int vwidth, bool raised_p,
+                         bool top_p, bool bot_p, bool left_p, bool right_p,
+                         struct android_rectangle *clip_rect)
+{
+  struct android_gc *gc, *white_gc, *black_gc, *normal_gc;
+  android_drawable drawable;
+
+  /* This code is more complicated than it has to be, because of two
+     minor hacks to make the boxes look nicer: (i) if width > 1, draw
+     the outermost line using the black relief.  (ii) Omit the four
+     corner pixels.  */
+
+  white_gc = f->output_data.android->white_relief.gc;
+  black_gc = f->output_data.android->black_relief.gc;
+  normal_gc = f->output_data.android->normal_gc;
+
+  drawable = FRAME_ANDROID_DRAWABLE (f);
+
+  android_set_clip_rectangles (white_gc, 0, 0, clip_rect, 1);
+  android_set_clip_rectangles (black_gc, 0, 0, clip_rect, 1);
+
+  if (raised_p)
+    gc = white_gc;
+  else
+    gc = black_gc;
+
+  /* Draw lines.  */
+
+  if (top_p)
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, top_y,
+                           right_x - left_x + 1, hwidth);
+
+  if (left_p)
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, top_y,
+                           vwidth, bottom_y - top_y + 1);
+
+  if (raised_p)
+    gc = black_gc;
+  else
+    gc = white_gc;
+
+  if (bot_p)
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x,
+                           bottom_y - hwidth + 1,
+                           right_x - left_x + 1, hwidth);
+
+  if (right_p)
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
+                           right_x - vwidth + 1,
+                           top_y, vwidth, bottom_y - top_y + 1);
+
+  /* Draw corners.  */
+
+  if (bot_p && left_p)
+    android_fill_triangle (f, raised_p ? white_gc : black_gc,
+                          android_make_point (left_x, bottom_y - hwidth),
+                          android_make_point (left_x + vwidth,
+                                              bottom_y - hwidth),
+                          android_make_point (left_x, bottom_y));
+
+  if (top_p && right_p)
+    android_fill_triangle (f, raised_p ? white_gc : black_gc,
+                          android_make_point (right_x - vwidth, top_y),
+                          android_make_point (right_x, top_y),
+                          android_make_point (right_x - vwidth,
+                                              top_y + hwidth));
+
+  /* Draw outer line.  */
+
+  if (top_p && left_p && bot_p && right_p
+      && hwidth > 1 && vwidth > 1)
+    android_draw_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                           black_gc, left_x, top_y,
+                           right_x - left_x, bottom_y - top_y);
+  else
+    {
+      if (top_p && hwidth > 1)
+       android_draw_line (drawable, black_gc, left_x, top_y,
+                          right_x + 1, top_y);
+
+      if (bot_p && hwidth > 1)
+       android_draw_line (drawable, black_gc, left_x, bottom_y,
+                          right_x + 1, bottom_y);
+
+      if (left_p && vwidth > 1)
+       android_draw_line (drawable, black_gc, left_x, top_y,
+                          left_x, bottom_y + 1);
+
+      if (right_p && vwidth > 1)
+       android_draw_line (drawable, black_gc, right_x, top_y,
+                          right_x, bottom_y + 1);
+    }
+
+  /* Erase corners.  */
+
+  if (hwidth > 1 && vwidth > 1)
+    {
+      if (left_p && top_p && android_inside_rect_p (clip_rect, 1,
+                                                   left_x, top_y))
+       android_clear_point (f, normal_gc, left_x, top_y);
+
+      if (left_p && bot_p && android_inside_rect_p (clip_rect, 1,
+                                                   left_x, bottom_y))
+       android_clear_point (f, normal_gc, left_x, bottom_y);
+
+      if (right_p && top_p && android_inside_rect_p (clip_rect, 1,
+                                                    right_x, top_y))
+       android_clear_point (f, normal_gc, right_x, top_y);
+
+      if (right_p && bot_p && android_inside_rect_p (clip_rect, 1,
+                                                    right_x, bottom_y))
+       android_clear_point (f, normal_gc, right_x, bottom_y);
+    }
+
+  android_reset_clip_rectangles (f, white_gc);
+  android_reset_clip_rectangles (f, black_gc);
+}
+
+static void
+android_draw_box_rect (struct glyph_string *s,
+                      int left_x, int top_y, int right_x, int bottom_y,
+                      int hwidth, int vwidth, bool left_p, bool right_p,
+                      struct android_rectangle *clip_rect)
+{
+  struct android_gc_values xgcv;
+
+  android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
+  android_set_foreground (s->gc, s->face->box_color);
+  android_set_clip_rectangles (s->gc, 0, 0, clip_rect, 1);
+
+  /* Top.  */
+  android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
+                         top_y, right_x - left_x + 1, hwidth);
+
+  /* Left.  */
+  if (left_p)
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
+                           top_y, vwidth, bottom_y - top_y + 1);
+
+  /* Bottom.  */
+  android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
+                         bottom_y - hwidth + 1, right_x - left_x + 1,
+                         hwidth);
+
+  /* Right.  */
+  if (right_p)
+    android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                           right_x - vwidth + 1, top_y, vwidth,
+                           bottom_y - top_y + 1);
+
+  android_set_foreground (s->gc, xgcv.foreground);
+  android_reset_clip_rectangles (s->f, s->gc);
+}
+
+#define HIGHLIGHT_COLOR_DARK_BOOST_LIMIT 48000
+
+static bool
+android_alloc_lighter_color (struct frame *f, unsigned long *pixel,
+                            double factor, int delta)
+{
+  Emacs_Color color, new;
+  long bright;
+  bool success_p;
+
+  /* Get RGB color values.  */
+  color.pixel = *pixel;
+  android_query_colors (f, &color, 1);
+
+  /* Change RGB values by specified FACTOR.  Avoid overflow!  */
+  eassert (factor >= 0);
+  new.red = min (0xffff, factor * color.red);
+  new.green = min (0xffff, factor * color.green);
+  new.blue = min (0xffff, factor * color.blue);
+
+  /* Calculate brightness of COLOR.  */
+  bright = (2 * color.red + 3 * color.green + color.blue) / 6;
+
+  /* We only boost colors that are darker than
+     HIGHLIGHT_COLOR_DARK_BOOST_LIMIT.  */
+  if (bright < HIGHLIGHT_COLOR_DARK_BOOST_LIMIT)
+    /* Make an additive adjustment to NEW, because it's dark enough so
+       that scaling by FACTOR alone isn't enough.  */
+    {
+      /* How far below the limit this color is (0 - 1, 1 being darker).  */
+      double dimness = 1 - (double) bright / HIGHLIGHT_COLOR_DARK_BOOST_LIMIT;
+      /* The additive adjustment.  */
+      int min_delta = delta * dimness * factor / 2;
+
+      if (factor < 1)
+       {
+         new.red =   max (0, new.red -   min_delta);
+         new.green = max (0, new.green - min_delta);
+         new.blue =  max (0, new.blue -  min_delta);
+       }
+      else
+       {
+         new.red =   min (0xffff, min_delta + new.red);
+         new.green = min (0xffff, min_delta + new.green);
+         new.blue =  min (0xffff, min_delta + new.blue);
+       }
+    }
+
+  /* Try to allocate the color.  */
+  success_p = android_alloc_nearest_color (f, &new);
+
+  if (success_p)
+    {
+      if (new.pixel == *pixel)
+       {
+         /* If we end up with the same color as before, try adding
+            delta to the RGB values.  */
+         new.red = min (0xffff, delta + color.red);
+         new.green = min (0xffff, delta + color.green);
+         new.blue = min (0xffff, delta + color.blue);
+         success_p = android_alloc_nearest_color (f, &new);
+       }
+      else
+       success_p = true;
+
+      *pixel = new.pixel;
+    }
+
+  return success_p;
+}
+
+/* Set up the foreground color for drawing relief lines of glyph
+   string S.  RELIEF is a pointer to a struct relief containing the GC
+   with which lines will be drawn.  Use a color that is FACTOR or
+   DELTA lighter or darker than the relief's background which is found
+   in S->f->output_data.android->relief_background.  If such a color
+   cannot be allocated, use DEFAULT_PIXEL, instead.  */
+
+static void
+android_setup_relief_color (struct frame *f, struct relief *relief,
+                           double factor, int delta,
+                           unsigned long default_pixel)
+{
+  struct android_gc_values xgcv;
+  struct android_output *di = f->output_data.android;
+  unsigned long mask = ANDROID_GC_FOREGROUND;
+  unsigned long pixel;
+  unsigned long background = di->relief_background;
+  struct android_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+
+  if (relief->gc && relief->pixel != -1)
+    relief->pixel = -1;
+
+  /* Allocate new color.  */
+  xgcv.foreground = default_pixel;
+  pixel = background;
+
+  if (dpyinfo->n_planes != 1
+      && android_alloc_lighter_color (f, &pixel, factor, delta))
+    xgcv.foreground = relief->pixel = pixel;
+
+  if (relief->gc == 0)
+    relief->gc = android_create_gc (mask, &xgcv);
+  else
+    android_change_gc (relief->gc, mask, &xgcv);
+}
+
+/* Set up colors for the relief lines around glyph string S.  */
+
+static void
+android_setup_relief_colors (struct glyph_string *s)
+{
+  struct android_output *di;
+  unsigned long color;
+
+  di = s->f->output_data.android;
+
+  if (s->face->use_box_color_for_shadows_p)
+    color = s->face->box_color;
+  else if (s->first_glyph->type == IMAGE_GLYPH
+          && s->img->pixmap
+          && !IMAGE_BACKGROUND_TRANSPARENT (s->img, s->f, 0))
+    color = IMAGE_BACKGROUND (s->img, s->f, 0);
+  else
+    {
+      struct android_gc_values xgcv;
+
+      /* Get the background color of the face.  */
+      android_get_gc_values (s->gc, ANDROID_GC_BACKGROUND, &xgcv);
+      color = xgcv.background;
+    }
+
+  if (di->white_relief.gc == 0
+      || color != di->relief_background)
+    {
+      di->relief_background = color;
+      android_setup_relief_color (s->f, &di->white_relief, 1.2, 0x8000,
+                                 WHITE_PIX_DEFAULT (s->f));
+      android_setup_relief_color (s->f, &di->black_relief, 0.6, 0x4000,
+                                 BLACK_PIX_DEFAULT (s->f));
+    }
+}
+
+static void
+android_draw_glyph_string_box (struct glyph_string *s)
+{
+  int hwidth, vwidth, left_x, right_x, top_y, bottom_y, last_x;
+  bool raised_p, left_p, right_p;
+  struct glyph *last_glyph;
+  struct android_rectangle clip_rect;
+
+  last_x = ((s->row->full_width_p && !s->w->pseudo_window_p)
+           ? WINDOW_RIGHT_EDGE_X (s->w)
+           : window_box_right (s->w, s->area));
+
+  /* The glyph that may have a right box line.  For static
+     compositions and images, the right-box flag is on the first glyph
+     of the glyph string; for other types it's on the last glyph.  */
+  if (s->cmp || s->img)
+    last_glyph = s->first_glyph;
+  else if (s->first_glyph->type == COMPOSITE_GLYPH
+          && s->first_glyph->u.cmp.automatic)
+    {
+      /* For automatic compositions, we need to look up the last glyph
+        in the composition.  */
+        struct glyph *end = s->row->glyphs[s->area] + s->row->used[s->area];
+       struct glyph *g = s->first_glyph;
+       for (last_glyph = g++;
+            g < end && g->u.cmp.automatic && g->u.cmp.id == s->cmp_id
+              && g->slice.cmp.to < s->cmp_to;
+            last_glyph = g++)
+         ;
+    }
+  else
+    last_glyph = s->first_glyph + s->nchars - 1;
+
+  vwidth = eabs (s->face->box_vertical_line_width);
+  hwidth = eabs (s->face->box_horizontal_line_width);
+  raised_p = s->face->box == FACE_RAISED_BOX;
+  left_x = s->x;
+  right_x = (s->row->full_width_p && s->extends_to_end_of_line_p
+            ? last_x - 1
+            : min (last_x, s->x + s->background_width) - 1);
+  top_y = s->y;
+  bottom_y = top_y + s->height - 1;
+
+  left_p = (s->first_glyph->left_box_line_p
+           || (s->hl == DRAW_MOUSE_FACE
+               && (s->prev == NULL
+                   || s->prev->hl != s->hl)));
+  right_p = (last_glyph->right_box_line_p
+            || (s->hl == DRAW_MOUSE_FACE
+                && (s->next == NULL
+                    || s->next->hl != s->hl)));
+
+  get_glyph_string_clip_rect (s, &clip_rect);
+
+  if (s->face->box == FACE_SIMPLE_BOX)
+    android_draw_box_rect (s, left_x, top_y, right_x, bottom_y, hwidth,
+                          vwidth, left_p, right_p, &clip_rect);
+  else
+    {
+      android_setup_relief_colors (s);
+      android_draw_relief_rect (s->f, left_x, top_y, right_x, bottom_y, hwidth,
+                               vwidth, raised_p, true, true, left_p, right_p,
+                               &clip_rect);
+    }
+}
+
+static void
+android_draw_glyph_string_bg_rect (struct glyph_string *s, int x, int y,
+                                  int w, int h)
+{
+  if (s->stippled_p)
+    {
+      /* Fill background with a stipple pattern.  */
+      android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x,
+                             y, w, h);
+      android_set_fill_style (s->gc, ANDROID_FILL_SOLID);
+    }
+  else
+    android_clear_glyph_string_rect (s, x, y, w, h);
+}
+
+static void
+android_draw_image_relief (struct glyph_string *s)
+{
+  int x1, y1, thick;
+  bool raised_p, top_p, bot_p, left_p, right_p;
+  int extra_x, extra_y;
+  struct android_rectangle r;
+  int x = s->x;
+  int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
+
+  /* If first glyph of S has a left box line, start drawing it to the
+     right of that line.  */
+  if (s->face->box != FACE_NO_BOX
+      && s->first_glyph->left_box_line_p
+      && s->slice.x == 0)
+    x += max (s->face->box_vertical_line_width, 0);
+
+  /* If there is a margin around the image, adjust x- and y-position
+     by that margin.  */
+  if (s->slice.x == 0)
+    x += s->img->hmargin;
+  if (s->slice.y == 0)
+    y += s->img->vmargin;
+
+  if (s->hl == DRAW_IMAGE_SUNKEN
+      || s->hl == DRAW_IMAGE_RAISED)
+    {
+      if (s->face->id == TAB_BAR_FACE_ID)
+       thick = (tab_bar_button_relief < 0
+                ? DEFAULT_TAB_BAR_BUTTON_RELIEF
+                : min (tab_bar_button_relief, 1000000));
+      else
+       thick = (tool_bar_button_relief < 0
+                ? DEFAULT_TOOL_BAR_BUTTON_RELIEF
+                : min (tool_bar_button_relief, 1000000));
+      raised_p = s->hl == DRAW_IMAGE_RAISED;
+    }
+  else
+    {
+      thick = eabs (s->img->relief);
+      raised_p = s->img->relief > 0;
+    }
+
+  x1 = x + s->slice.width - 1;
+  y1 = y + s->slice.height - 1;
+
+  extra_x = extra_y = 0;
+  if (s->face->id == TAB_BAR_FACE_ID)
+    {
+      if (CONSP (Vtab_bar_button_margin)
+         && FIXNUMP (XCAR (Vtab_bar_button_margin))
+         && FIXNUMP (XCDR (Vtab_bar_button_margin)))
+       {
+         extra_x = XFIXNUM (XCAR (Vtab_bar_button_margin)) - thick;
+         extra_y = XFIXNUM (XCDR (Vtab_bar_button_margin)) - thick;
+       }
+      else if (FIXNUMP (Vtab_bar_button_margin))
+       extra_x = extra_y = XFIXNUM (Vtab_bar_button_margin) - thick;
+    }
+
+  if (s->face->id == TOOL_BAR_FACE_ID)
+    {
+      if (CONSP (Vtool_bar_button_margin)
+         && FIXNUMP (XCAR (Vtool_bar_button_margin))
+         && FIXNUMP (XCDR (Vtool_bar_button_margin)))
+       {
+         extra_x = XFIXNUM (XCAR (Vtool_bar_button_margin));
+         extra_y = XFIXNUM (XCDR (Vtool_bar_button_margin));
+       }
+      else if (FIXNUMP (Vtool_bar_button_margin))
+       extra_x = extra_y = XFIXNUM (Vtool_bar_button_margin);
+    }
+
+  top_p = bot_p = left_p = right_p = false;
+
+  if (s->slice.x == 0)
+    x -= thick + extra_x, left_p = true;
+  if (s->slice.y == 0)
+    y -= thick + extra_y, top_p = true;
+  if (s->slice.x + s->slice.width == s->img->width)
+    x1 += thick + extra_x, right_p = true;
+  if (s->slice.y + s->slice.height == s->img->height)
+    y1 += thick + extra_y, bot_p = true;
+
+  android_setup_relief_colors (s);
+  get_glyph_string_clip_rect (s, &r);
+  android_draw_relief_rect (s->f, x, y, x1, y1, thick, thick, raised_p,
+                           top_p, bot_p, left_p, right_p, &r);
+}
+
+static void
+android_draw_image_foreground (struct glyph_string *s)
+{
+  int x = s->x;
+  int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
+
+  /* If first glyph of S has a left box line, start drawing it to the
+     right of that line.  */
+  if (s->face->box != FACE_NO_BOX
+      && s->first_glyph->left_box_line_p
+      && s->slice.x == 0)
+    x += max (s->face->box_vertical_line_width, 0);
+
+  /* If there is a margin around the image, adjust x- and y-position
+     by that margin.  */
+  if (s->slice.x == 0)
+    x += s->img->hmargin;
+  if (s->slice.y == 0)
+    y += s->img->vmargin;
+
+  if (s->img->pixmap)
+    {
+      unsigned long mask = (ANDROID_GC_CLIP_MASK
+                           | ANDROID_GC_CLIP_X_ORIGIN
+                           | ANDROID_GC_CLIP_Y_ORIGIN
+                           | ANDROID_GC_FUNCTION);
+      struct android_gc_values xgcv;
+      struct android_rectangle clip_rect, image_rect, r;
+
+      xgcv.clip_mask = s->img->mask;
+      xgcv.clip_x_origin = x - s->slice.x;
+      xgcv.clip_y_origin = y - s->slice.y;
+      xgcv.function = ANDROID_GC_COPY;
+      android_change_gc (s->gc, mask, &xgcv);
+
+      get_glyph_string_clip_rect (s, &clip_rect);
+      image_rect.x = x;
+      image_rect.y = y;
+      image_rect.width = s->slice.width;
+      image_rect.height = s->slice.height;
+
+      if (gui_intersect_rectangles (&clip_rect, &image_rect, &r))
+       android_copy_area (s->img->pixmap,
+                          FRAME_ANDROID_DRAWABLE (s->f),
+                          s->gc, s->slice.x + r.x - x,
+                          s->slice.y + r.y - y,
+                          r.width, r.height, r.x, r.y);
+
+      /* When the image has a mask, we can expect that at least part
+        of a mouse highlight or a block cursor will be visible.  If
+        the image doesn't have a mask, make a block cursor visible by
+        drawing a rectangle around the image.  I believe it's looking
+        better if we do nothing here for mouse-face.  */
+      if (s->hl == DRAW_CURSOR && !s->img->mask)
+       {
+         int relief = eabs (s->img->relief);
+         android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                                 x - relief, y - relief,
+                                 s->slice.width + relief*2 - 1,
+                                 s->slice.height + relief*2 - 1);
+       }
+
+      android_set_clip_mask (s->gc, ANDROID_NONE);
+    }
+  else
+    /* Draw a rectangle if image could not be loaded.  */
+    android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, y,
+                           s->slice.width - 1, s->slice.height - 1);
+}
+
+static void
+android_draw_image_glyph_string (struct glyph_string *s)
+{
+  int box_line_hwidth = max (s->face->box_vertical_line_width, 0);
+  int box_line_vwidth = max (s->face->box_horizontal_line_width, 0);
+  int height;
+
+  height = s->height;
+  if (s->slice.y == 0)
+    height -= box_line_vwidth;
+  if (s->slice.y + s->slice.height >= s->img->height)
+    height -= box_line_vwidth;
+
+  /* Fill background with face under the image.  Do it only if row is
+     taller than image or if image has a clip mask to reduce
+     flickering.  */
+  s->stippled_p = s->face->stipple != 0;
+  if (height > s->slice.height
+      || s->img->hmargin
+      || s->img->vmargin
+      || s->img->mask
+      || s->img->pixmap == 0
+      || s->width != s->background_width)
+    {
+      if (s->stippled_p)
+       s->row->stipple_p = true;
+
+      int x = s->x;
+      int y = s->y;
+      int width = s->background_width;
+
+      if (s->first_glyph->left_box_line_p
+         && s->slice.x == 0)
+       {
+         x += box_line_hwidth;
+         width -= box_line_hwidth;
+       }
+
+      if (s->slice.y == 0)
+       y += box_line_vwidth;
+
+      android_draw_glyph_string_bg_rect (s, x, y, width, height);
+
+      s->background_filled_p = true;
+    }
+
+  /* Draw the foreground.  */
+  android_draw_image_foreground (s);
+  android_set_glyph_string_clipping (s);
+
+  /* If we must draw a relief around the image, do it.  */
+  if (s->img->relief
+      || s->hl == DRAW_IMAGE_RAISED
+      || s->hl == DRAW_IMAGE_SUNKEN)
+    android_draw_image_relief (s);
+}
+
+static void
+android_draw_stretch_glyph_string (struct glyph_string *s)
+{
+  eassert (s->first_glyph->type == STRETCH_GLYPH);
+
+  if (s->hl == DRAW_CURSOR && !x_stretch_cursor_p)
+    {
+      /* If `x-stretch-cursor' is nil, don't draw a block cursor as
+        wide as the stretch glyph.  */
+      int width, background_width = s->background_width;
+      int x = s->x;
+
+      if (!s->row->reversed_p)
+       {
+         int left_x = window_box_left_offset (s->w, TEXT_AREA);
+
+         if (x < left_x)
+           {
+             background_width -= left_x - x;
+             x = left_x;
+           }
+       }
+      else
+       {
+         /* In R2L rows, draw the cursor on the right edge of the
+            stretch glyph.  */
+         int right_x = window_box_right (s->w, TEXT_AREA);
+
+         if (x + background_width > right_x)
+           background_width -= x - right_x;
+         x += background_width;
+       }
+      width = min (FRAME_COLUMN_WIDTH (s->f), background_width);
+      if (s->row->reversed_p)
+       x -= width;
+
+      /* Draw cursor.  */
+      android_draw_glyph_string_bg_rect (s, x, s->y, width, s->height);
+
+      /* Clear rest using the GC of the original non-cursor face.  */
+      if (width < background_width)
+       {
+         int y = s->y;
+         int w = background_width - width, h = s->height;
+         struct android_rectangle r;
+         struct android_gc *gc;
+
+         if (!s->row->reversed_p)
+           x += width;
+         else
+           x = s->x;
+         if (s->row->mouse_face_p
+             && cursor_in_mouse_face_p (s->w))
+           {
+             android_set_mouse_face_gc (s);
+             gc = s->gc;
+           }
+         else
+           gc = s->face->gc;
+
+         get_glyph_string_clip_rect (s, &r);
+         android_set_clip_rectangles (gc, 0, 0, &r, 1);
+
+         if (s->face->stipple)
+           {
+             /* Fill background with a stipple pattern.  */
+             android_set_fill_style (gc, ANDROID_FILL_OPAQUE_STIPPLED);
+             android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
+                                     gc, x, y, w, h);
+             android_set_fill_style (gc, ANDROID_FILL_SOLID);
+
+             s->row->stipple_p = true;
+           }
+         else
+           {
+             struct android_gc_values xgcv;
+             android_get_gc_values (gc, (ANDROID_GC_FOREGROUND
+                                         | ANDROID_GC_BACKGROUND),
+                                    &xgcv);
+             android_set_foreground (gc, xgcv.background);
+             android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
+                                     gc, x, y, w, h);
+             android_set_foreground (gc, xgcv.foreground);
+           }
+
+         android_reset_clip_rectangles (s->f, gc);
+       }
+    }
+  else if (!s->background_filled_p)
+    {
+      int background_width = s->background_width;
+      int x = s->x, text_left_x = window_box_left (s->w, TEXT_AREA);
+
+      /* Don't draw into left fringe or scrollbar area except for
+         header line and mode line.  */
+      if (s->area == TEXT_AREA
+         && x < text_left_x && !s->row->mode_line_p)
+       {
+         background_width -= text_left_x - x;
+         x = text_left_x;
+       }
+
+      if (!s->row->stipple_p)
+       s->row->stipple_p = s->stippled_p;
+
+      if (background_width > 0)
+       android_draw_glyph_string_bg_rect (s, x, s->y,
+                                          background_width,
+                                          s->height);
+    }
+
+  s->background_filled_p = true;
+}
+
+static void
+android_get_scale_factor (int *scale_x, int *scale_y)
+{
+  /* This is 96 everywhere else, but 160 on Android.  */
+  const int base_res = 160;
+  struct android_display_info *dpyinfo;
+
+  dpyinfo = x_display_list;
+  *scale_x = *scale_y = 1;
+
+  if (dpyinfo)
+    {
+      if (dpyinfo->resx > base_res)
+       *scale_x = floor (dpyinfo->resx / base_res);
+      if (dpyinfo->resy > base_res)
+       *scale_y = floor (dpyinfo->resy / base_res);
+    }
+}
+
+static void
+android_draw_underwave (struct glyph_string *s, int decoration_width)
+{
+  int scale_x, scale_y;
+
+  android_get_scale_factor (&scale_x, &scale_y);
+
+  int wave_height = 3 * scale_y, wave_length = 2 * scale_x;
+
+  int dx, dy, x0, y0, width, x1, y1, x2, y2, xmax;
+  bool odd;
+  struct android_rectangle wave_clip, string_clip, final_clip;
+
+  dx = wave_length;
+  dy = wave_height - 1;
+  x0 = s->x;
+  y0 = s->ybase + wave_height / 2;
+  width = decoration_width;
+  xmax = x0 + width;
+
+  /* Find and set clipping rectangle */
+
+  wave_clip.x = x0;
+  wave_clip.y = y0;
+  wave_clip.width = width;
+  wave_clip.height = wave_height;
+  get_glyph_string_clip_rect (s, &string_clip);
+
+  if (!gui_intersect_rectangles (&wave_clip, &string_clip, &final_clip))
+    return;
+
+  android_set_clip_rectangles (s->gc, 0, 0, &final_clip, 1);
+
+  /* Draw the waves */
+
+  x1 = x0 - (x0 % dx);
+  x2 = x1 + dx;
+  odd = (x1 / dx) & 1;
+  y1 = y2 = y0;
+
+  if (odd)
+    y1 += dy;
+  else
+    y2 += dy;
+
+  if (INT_MAX - dx < xmax)
+    emacs_abort ();
+
+  while (x1 <= xmax)
+    {
+      android_draw_line (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                        x1, y1, x2, y2);
+      x1  = x2, y1 = y2;
+      x2 += dx, y2 = y0 + odd*dy;
+      odd = !odd;
+    }
+
+  /* Restore previous clipping rectangle(s) */
+  android_set_clip_rectangles (s->gc, 0, 0, s->clip, s->num_clips);
+}
+
+static void
+android_draw_glyph_string_foreground (struct glyph_string *s)
+{
+  int i, x;
+
+  /* If first glyph of S has a left box line, start drawing the text
+     of S to the right of that box line.  */
+  if (s->face->box != FACE_NO_BOX
+      && s->first_glyph->left_box_line_p)
+    x = s->x + max (s->face->box_vertical_line_width, 0);
+  else
+    x = s->x;
+
+  /* Draw characters of S as rectangles if S's font could not be
+     loaded.  */
+  if (s->font_not_found_p)
+    {
+      for (i = 0; i < s->nchars; ++i)
+       {
+         struct glyph *g = s->first_glyph + i;
+         android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
+                                 s->gc, x, s->y,
+                                 g->pixel_width - 1,
+                                 s->height - 1);
+         x += g->pixel_width;
+       }
+    }
+  else
+    {
+      struct font *font = s->font;
+      int boff = font->baseline_offset;
+      int y;
+
+      if (font->vertical_centering)
+       boff = VCENTER_BASELINE_OFFSET (font, s->f) - boff;
+
+      y = s->ybase - boff;
+      if (s->for_overlaps
+         || (s->background_filled_p && s->hl != DRAW_CURSOR))
+       font->driver->draw (s, 0, s->nchars, x, y, false);
+      else
+       font->driver->draw (s, 0, s->nchars, x, y, true);
+      if (s->face->overstrike)
+       font->driver->draw (s, 0, s->nchars, x + 1, y, false);
+    }
+}
+
+static void
+android_draw_composite_glyph_string_foreground (struct glyph_string *s)
+{
+  int i, j, x;
+  struct font *font = s->font;
+
+  /* If first glyph of S has a left box line, start drawing the text
+     of S to the right of that box line.  */
+  if (s->face && s->face->box != FACE_NO_BOX
+      && s->first_glyph->left_box_line_p)
+    x = s->x + max (s->face->box_vertical_line_width, 0);
+  else
+    x = s->x;
+
+  /* S is a glyph string for a composition.  S->cmp_from is the index
+     of the first character drawn for glyphs of this composition.
+     S->cmp_from == 0 means we are drawing the very first character of
+     this composition.  */
+
+  /* Draw a rectangle for the composition if the font for the very
+     first character of the composition could not be loaded.  */
+  if (s->font_not_found_p)
+    {
+      if (s->cmp_from == 0)
+       android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
+                               s->gc, x, s->y,
+                               s->width - 1, s->height - 1);
+    }
+  else if (! s->first_glyph->u.cmp.automatic)
+    {
+      int y = s->ybase;
+
+      for (i = 0, j = s->cmp_from; i < s->nchars; i++, j++)
+       /* TAB in a composition means display glyphs with
+          padding space on the left or right.  */
+       if (COMPOSITION_GLYPH (s->cmp, j) != '\t')
+         {
+           int xx = x + s->cmp->offsets[j * 2];
+           int yy = y - s->cmp->offsets[j * 2 + 1];
+
+           font->driver->draw (s, j, j + 1, xx, yy, false);
+           if (s->face->overstrike)
+             font->driver->draw (s, j, j + 1, xx + 1, yy, false);
+         }
+    }
+  else
+    {
+      Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
+      Lisp_Object glyph;
+      int y = s->ybase;
+      int width = 0;
+
+      for (i = j = s->cmp_from; i < s->cmp_to; i++)
+       {
+         glyph = LGSTRING_GLYPH (gstring, i);
+         if (NILP (LGLYPH_ADJUSTMENT (glyph)))
+           width += LGLYPH_WIDTH (glyph);
+         else
+           {
+             int xoff, yoff, wadjust;
+
+             if (j < i)
+               {
+                 font->driver->draw (s, j, i, x, y, false);
+                 if (s->face->overstrike)
+                   font->driver->draw (s, j, i, x + 1, y, false);
+                 x += width;
+               }
+             xoff = LGLYPH_XOFF (glyph);
+             yoff = LGLYPH_YOFF (glyph);
+             wadjust = LGLYPH_WADJUST (glyph);
+             font->driver->draw (s, i, i + 1, x + xoff, y + yoff, false);
+             if (s->face->overstrike)
+               font->driver->draw (s, i, i + 1, x + xoff + 1, y + yoff,
+                                   false);
+             x += wadjust;
+             j = i + 1;
+             width = 0;
+           }
+       }
+      if (j < i)
+       {
+         font->driver->draw (s, j, i, x, y, false);
+         if (s->face->overstrike)
+           font->driver->draw (s, j, i, x + 1, y, false);
+       }
+    }
+}
+
+static void
+android_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
+{
+  struct glyph *glyph = s->first_glyph;
+  unsigned char2b[8];
+  int x, i, j;
+
+  /* If first glyph of S has a left box line, start drawing the text
+     of S to the right of that box line.  */
+  if (s->face && s->face->box != FACE_NO_BOX
+      && s->first_glyph->left_box_line_p)
+    x = s->x + max (s->face->box_vertical_line_width, 0);
+  else
+    x = s->x;
+
+  s->char2b = char2b;
+
+  for (i = 0; i < s->nchars; i++, glyph++)
+    {
+#ifdef GCC_LINT
+      enum { PACIFY_GCC_BUG_81401 = 1 };
+#else
+      enum { PACIFY_GCC_BUG_81401 = 0 };
+#endif
+      char buf[7 + PACIFY_GCC_BUG_81401];
+      char *str = NULL;
+      int len = glyph->u.glyphless.len;
+
+      if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_ACRONYM)
+       {
+         if (len > 0
+             && CHAR_TABLE_P (Vglyphless_char_display)
+             && (CHAR_TABLE_EXTRA_SLOTS (XCHAR_TABLE (Vglyphless_char_display))
+                 >= 1))
+           {
+             Lisp_Object acronym
+               = (! glyph->u.glyphless.for_no_font
+                  ? CHAR_TABLE_REF (Vglyphless_char_display,
+                                    glyph->u.glyphless.ch)
+                  : XCHAR_TABLE (Vglyphless_char_display)->extras[0]);
+             if (CONSP (acronym))
+               acronym = XCAR (acronym);
+             if (STRINGP (acronym))
+               str = SSDATA (acronym);
+           }
+       }
+      else if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_HEX_CODE)
+       {
+         unsigned int ch = glyph->u.glyphless.ch;
+         eassume (ch <= MAX_CHAR);
+         sprintf (buf, "%0*X", ch < 0x10000 ? 4 : 6, ch);
+         str = buf;
+       }
+
+      if (str)
+       {
+         int upper_len = (len + 1) / 2;
+
+         /* It is assured that all LEN characters in STR is ASCII.  */
+         for (j = 0; j < len; j++)
+            char2b[j] = s->font->driver->encode_char (s->font, str[j]) & 
0xFFFF;
+         s->font->driver->draw (s, 0, upper_len,
+                                x + glyph->slice.glyphless.upper_xoff,
+                                s->ybase + glyph->slice.glyphless.upper_yoff,
+                                false);
+         s->font->driver->draw (s, upper_len, len,
+                                x + glyph->slice.glyphless.lower_xoff,
+                                s->ybase + glyph->slice.glyphless.lower_yoff,
+                                false);
+       }
+      if (glyph->u.glyphless.method != GLYPHLESS_DISPLAY_THIN_SPACE)
+       android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                               x, s->ybase - glyph->ascent,
+                               glyph->pixel_width - 1,
+                               glyph->ascent + glyph->descent - 1);
+      x += glyph->pixel_width;
+   }
+
+  /* Defend against hypothetical bad code elsewhere that uses
+     s->char2b after this function returns.  */
+  s->char2b = NULL;
+}
+
+static void
+android_draw_glyph_string (struct glyph_string *s)
+{
+  bool relief_drawn_p = false;
+
+  /* If S draws into the background of its successors, draw the
+     background of the successors first so that S can draw into it.
+     This makes S->next use XDrawString instead of XDrawImageString.  */
+  if (s->next && s->right_overhang && !s->for_overlaps)
+    {
+      int width;
+      struct glyph_string *next;
+
+      for (width = 0, next = s->next;
+          next && width < s->right_overhang;
+          width += next->width, next = next->next)
+       if (next->first_glyph->type != IMAGE_GLYPH)
+         {
+           android_set_glyph_string_gc (next);
+           android_set_glyph_string_clipping (next);
+           if (next->first_glyph->type == STRETCH_GLYPH)
+             android_draw_stretch_glyph_string (next);
+           else
+             android_draw_glyph_string_background (next, true);
+           next->num_clips = 0;
+         }
+    }
+
+  /* Set up S->gc, set clipping and draw S.  */
+  android_set_glyph_string_gc (s);
+
+  /* Draw relief (if any) in advance for char/composition so that the
+     glyph string can be drawn over it.  */
+  if (!s->for_overlaps
+      && s->face->box != FACE_NO_BOX
+      && (s->first_glyph->type == CHAR_GLYPH
+         || s->first_glyph->type == COMPOSITE_GLYPH))
+
+    {
+      android_set_glyph_string_clipping (s);
+      android_draw_glyph_string_background (s, true);
+      android_draw_glyph_string_box (s);
+      android_set_glyph_string_clipping (s);
+      relief_drawn_p = true;
+    }
+  else if (!s->clip_head /* draw_glyphs didn't specify a clip mask. */
+          && !s->clip_tail
+          && ((s->prev && s->prev->hl != s->hl && s->left_overhang)
+              || (s->next && s->next->hl != s->hl && s->right_overhang)))
+    /* We must clip just this glyph.  left_overhang part has already
+       drawn when s->prev was drawn, and right_overhang part will be
+       drawn later when s->next is drawn. */
+    android_set_glyph_string_clipping_exactly (s, s);
+  else
+    android_set_glyph_string_clipping (s);
+
+  switch (s->first_glyph->type)
+    {
+    case IMAGE_GLYPH:
+      android_draw_image_glyph_string (s);
+      break;
+
+    case XWIDGET_GLYPH:
+      emacs_abort ();
+      break;
+
+    case STRETCH_GLYPH:
+      android_draw_stretch_glyph_string (s);
+      break;
+
+    case CHAR_GLYPH:
+      if (s->for_overlaps)
+       s->background_filled_p = true;
+      else
+       android_draw_glyph_string_background (s, false);
+      android_draw_glyph_string_foreground (s);
+      break;
+
+    case COMPOSITE_GLYPH:
+      if (s->for_overlaps || (s->cmp_from > 0
+                             && ! s->first_glyph->u.cmp.automatic))
+       s->background_filled_p = true;
+      else
+       android_draw_glyph_string_background (s, true);
+      android_draw_composite_glyph_string_foreground (s);
+      break;
+
+    case GLYPHLESS_GLYPH:
+      if (s->for_overlaps)
+       s->background_filled_p = true;
+      else
+       android_draw_glyph_string_background (s, true);
+      android_draw_glyphless_glyph_string_foreground (s);
+      break;
+
+    default:
+      emacs_abort ();
+    }
+
+  if (!s->for_overlaps)
+    {
+      int area_x, area_y, area_width, area_height;
+      int area_max_x, decoration_width;
+
+      /* Prevent the underline from overwriting surrounding areas
+        and the fringe.  */
+      window_box (s->w, s->area, &area_x, &area_y,
+                 &area_width, &area_height);
+      area_max_x = area_x + area_width - 1;
+
+      decoration_width = s->width;
+      if (!s->row->mode_line_p
+         && !s->row->tab_line_p
+         && area_max_x < (s->x + decoration_width - 1))
+       decoration_width -= (s->x + decoration_width - 1) - area_max_x;
+
+      /* Draw relief if not yet drawn.  */
+      if (!relief_drawn_p && s->face->box != FACE_NO_BOX)
+       android_draw_glyph_string_box (s);
+
+      /* Draw underline.  */
+      if (s->face->underline)
+        {
+          if (s->face->underline == FACE_UNDER_WAVE)
+            {
+              if (s->face->underline_defaulted_p)
+                android_draw_underwave (s, decoration_width);
+              else
+                {
+                  struct android_gc_values xgcv;
+                  android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
+                  android_set_foreground (s->gc, s->face->underline_color);
+                  android_draw_underwave (s, decoration_width);
+                  android_set_foreground (s->gc, xgcv.foreground);
+                }
+            }
+          else if (s->face->underline == FACE_UNDER_LINE)
+            {
+              unsigned long thickness, position;
+              int y;
+
+              if (s->prev
+                 && s->prev->face->underline == FACE_UNDER_LINE
+                 && (s->prev->face->underline_at_descent_line_p
+                     == s->face->underline_at_descent_line_p)
+                 && (s->prev->face->underline_pixels_above_descent_line
+                     == s->face->underline_pixels_above_descent_line))
+                {
+                  /* We use the same underline style as the previous one.  */
+                  thickness = s->prev->underline_thickness;
+                  position = s->prev->underline_position;
+                }
+              else
+                {
+                 struct font *font = font_for_underline_metrics (s);
+                 unsigned long minimum_offset;
+                 bool underline_at_descent_line;
+                 bool use_underline_position_properties;
+                 Lisp_Object val = (WINDOW_BUFFER_LOCAL_VALUE
+                                    (Qunderline_minimum_offset, s->w));
+
+                 if (FIXNUMP (val))
+                   minimum_offset = max (0, XFIXNUM (val));
+                 else
+                   minimum_offset = 1;
+
+                 val = (WINDOW_BUFFER_LOCAL_VALUE
+                        (Qx_underline_at_descent_line, s->w));
+                 underline_at_descent_line
+                   = (!(NILP (val) || BASE_EQ (val, Qunbound))
+                      || s->face->underline_at_descent_line_p);
+
+                 val = (WINDOW_BUFFER_LOCAL_VALUE
+                        (Qx_use_underline_position_properties, s->w));
+                 use_underline_position_properties
+                   = !(NILP (val) || BASE_EQ (val, Qunbound));
+
+                  /* Get the underline thickness.  Default is 1 pixel.  */
+                  if (font && font->underline_thickness > 0)
+                    thickness = font->underline_thickness;
+                  else
+                    thickness = 1;
+                  if (underline_at_descent_line)
+                   position = ((s->height - thickness)
+                               - (s->ybase - s->y)
+                               - s->face->underline_pixels_above_descent_line);
+                  else
+                    {
+                      /* Get the underline position.  This is the
+                         recommended vertical offset in pixels from
+                         the baseline to the top of the underline.
+                         This is a signed value according to the
+                         specs, and its default is
+
+                         ROUND ((maximum descent) / 2), with
+                         ROUND(x) = floor (x + 0.5)  */
+
+                      if (use_underline_position_properties
+                          && font && font->underline_position >= 0)
+                        position = font->underline_position;
+                      else if (font)
+                        position = (font->descent + 1) / 2;
+                      else
+                        position = minimum_offset;
+                    }
+
+                 /* Ignore minimum_offset if the amount of pixels was
+                    explicitly specified.  */
+                 if (!s->face->underline_pixels_above_descent_line)
+                   position = max (position, minimum_offset);
+                }
+              /* Check the sanity of thickness and position.  We should
+                 avoid drawing underline out of the current line area.  */
+             if (s->y + s->height <= s->ybase + position)
+               position = (s->height - 1) - (s->ybase - s->y);
+              if (s->y + s->height < s->ybase + position + thickness)
+                thickness = (s->y + s->height) - (s->ybase + position);
+              s->underline_thickness = thickness;
+              s->underline_position = position;
+              y = s->ybase + position;
+              if (s->face->underline_defaulted_p)
+                android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                                       s->x, y, decoration_width, thickness);
+              else
+                {
+                  struct android_gc_values xgcv;
+                  android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
+                  android_set_foreground (s->gc, s->face->underline_color);
+                  android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                                         s->x, y, decoration_width, thickness);
+                  android_set_foreground (s->gc, xgcv.foreground);
+                }
+            }
+        }
+      /* Draw overline.  */
+      if (s->face->overline_p)
+       {
+         unsigned long dy = 0, h = 1;
+
+         if (s->face->overline_color_defaulted_p)
+           android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
+                                   s->gc, s->x, s->y + dy,
+                                   decoration_width, h);
+         else
+           {
+             struct android_gc_values xgcv;
+             android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
+             android_set_foreground (s->gc, s->face->overline_color);
+             android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                                     s->x, s->y + dy, decoration_width, h);
+             android_set_foreground (s->gc, xgcv.foreground);
+           }
+       }
+
+      /* Draw strike-through.  */
+      if (s->face->strike_through_p)
+       {
+         /* Y-coordinate and height of the glyph string's first
+            glyph.  We cannot use s->y and s->height because those
+            could be larger if there are taller display elements
+            (e.g., characters displayed with a larger font) in the
+            same glyph row.  */
+         int glyph_y = s->ybase - s->first_glyph->ascent;
+         int glyph_height = s->first_glyph->ascent + s->first_glyph->descent;
+         /* Strike-through width and offset from the glyph string's
+            top edge.  */
+          unsigned long h = 1;
+          unsigned long dy = (glyph_height - h) / 2;
+
+         if (s->face->strike_through_color_defaulted_p)
+           android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f),
+                                   s->gc, s->x, glyph_y + dy,
+                                   s->width, h);
+         else
+           {
+             struct android_gc_values xgcv;
+             android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
+             android_set_foreground (s->gc, s->face->strike_through_color);
+             android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
+                                     s->x, glyph_y + dy, decoration_width,
+                                     h);
+             android_set_foreground (s->gc, xgcv.foreground);
+           }
+       }
+
+      if (s->prev)
+       {
+         struct glyph_string *prev;
+
+         for (prev = s->prev; prev; prev = prev->prev)
+           if (prev->hl != s->hl
+               && prev->x + prev->width + prev->right_overhang > s->x)
+             {
+               /* As prev was drawn while clipped to its own area, we
+                  must draw the right_overhang part using s->hl now.  */
+               enum draw_glyphs_face save = prev->hl;
+
+               prev->hl = s->hl;
+               android_set_glyph_string_gc (prev);
+               android_set_glyph_string_clipping_exactly (s, prev);
+               if (prev->first_glyph->type == CHAR_GLYPH)
+                 android_draw_glyph_string_foreground (prev);
+               else
+                 android_draw_composite_glyph_string_foreground (prev);
+               android_reset_clip_rectangles (prev->f, prev->gc);
+               prev->hl = save;
+               prev->num_clips = 0;
+             }
+       }
+
+      if (s->next)
+       {
+         struct glyph_string *next;
+
+         for (next = s->next; next; next = next->next)
+           if (next->hl != s->hl
+               && next->x - next->left_overhang < s->x + s->width)
+             {
+               /* As next will be drawn while clipped to its own area,
+                  we must draw the left_overhang part using s->hl now.  */
+               enum draw_glyphs_face save = next->hl;
+
+               next->hl = s->hl;
+               android_set_glyph_string_gc (next);
+               android_set_glyph_string_clipping_exactly (s, next);
+               if (next->first_glyph->type == CHAR_GLYPH)
+                 android_draw_glyph_string_foreground (next);
+               else
+                 android_draw_composite_glyph_string_foreground (next);
+               android_reset_clip_rectangles (next->f, next->gc);
+               next->hl = save;
+               next->num_clips = 0;
+               next->clip_head = s->next;
+             }
+       }
+    }
+
+  /* Reset clipping.  */
+  android_reset_clip_rectangles (s->f, s->gc);
+  s->num_clips = 0;
+
+  /* Set the stippled flag that tells redisplay whether or not a
+     stipple was actually draw.  */
+
+  if (s->first_glyph->type != STRETCH_GLYPH
+      && s->first_glyph->type != IMAGE_GLYPH
+      && !s->row->stipple_p)
+    s->row->stipple_p = s->stippled_p;
+}
+
+static void
+android_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
+{
+  if (!f->pointer_invisible
+      && !FRAME_ANDROID_OUTPUT (f)->hourglass
+      && f->output_data.android->current_cursor != cursor)
+    android_define_cursor (FRAME_ANDROID_WINDOW (f), cursor);
+
+  f->output_data.android->current_cursor = cursor;
+}
+
+static void
+android_clear_frame_area (struct frame *f, int x, int y,
+                         int width, int height)
+{
+  android_clear_area (FRAME_ANDROID_DRAWABLE (f),
+                     x, y, width, height);
+}
+
+void
+android_clear_under_internal_border (struct frame *f)
+{
+  if (FRAME_INTERNAL_BORDER_WIDTH (f) > 0)
+    {
+      int border = FRAME_INTERNAL_BORDER_WIDTH (f);
+      int width = FRAME_PIXEL_WIDTH (f);
+      int height = FRAME_PIXEL_HEIGHT (f);
+      int margin = FRAME_TOP_MARGIN_HEIGHT (f);
+      int bottom_margin = FRAME_BOTTOM_MARGIN_HEIGHT (f);
+      int face_id = (FRAME_PARENT_FRAME (f)
+                    ? (!NILP (Vface_remapping_alist)
+                       ? lookup_basic_face (NULL, f,
+                                            CHILD_FRAME_BORDER_FACE_ID)
+                       : CHILD_FRAME_BORDER_FACE_ID)
+                    : (!NILP (Vface_remapping_alist)
+                       ? lookup_basic_face (NULL, f,
+                                            INTERNAL_BORDER_FACE_ID)
+                       : INTERNAL_BORDER_FACE_ID));
+      struct face *face = FACE_FROM_ID_OR_NULL (f, face_id);
+
+      if (face)
+       {
+         unsigned long color = face->background;
+         struct android_gc *gc = f->output_data.android->normal_gc;
+
+         android_set_foreground (gc, color);
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, margin,
+                                 width, border);
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, 0,
+                                 border, height);
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, width - 
border,
+                                 0, border, height);
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0,
+                                 height - bottom_margin - border,
+                                 width, border);
+         android_set_foreground (gc, FRAME_FOREGROUND_PIXEL (f));
+       }
+      else
+       {
+         android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, 0,
+                             border, height);
+         android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0,
+                             margin, width, border);
+         android_clear_area (FRAME_ANDROID_DRAWABLE (f), width - border,
+                             0, border, height);
+         android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0,
+                             height - bottom_margin - border,
+                             width, border);
+       }
+    }
+}
+
+static void
+android_draw_hollow_cursor (struct window *w, struct glyph_row *row)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct android_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  int x, y, wd, h;
+  struct android_gc_values xgcv;
+  struct glyph *cursor_glyph;
+  struct android_gc *gc;
+
+  /* Get the glyph the cursor is on.  If we can't tell because
+     the current matrix is invalid or such, give up.  */
+  cursor_glyph = get_phys_cursor_glyph (w);
+  if (cursor_glyph == NULL)
+    return;
+
+  /* Compute frame-relative coordinates for phys cursor.  */
+  get_phys_cursor_geometry (w, row, cursor_glyph, &x, &y, &h);
+  wd = w->phys_cursor_width - 1;
+
+  /* The foreground of cursor_gc is typically the same as the normal
+     background color, which can cause the cursor box to be invisible.  */
+  xgcv.foreground = f->output_data.android->cursor_pixel;
+  if (dpyinfo->scratch_cursor_gc)
+    android_change_gc (dpyinfo->scratch_cursor_gc,
+                      ANDROID_GC_FOREGROUND, &xgcv);
+  else
+    dpyinfo->scratch_cursor_gc
+      =  android_create_gc (ANDROID_GC_FOREGROUND, &xgcv);
+  gc = dpyinfo->scratch_cursor_gc;
+
+  /* When on R2L character, show cursor at the right edge of the
+     glyph, unless the cursor box is as wide as the glyph or wider
+     (the latter happens when x-stretch-cursor is non-nil).  */
+  if ((cursor_glyph->resolved_level & 1) != 0
+      && cursor_glyph->pixel_width > wd)
+    {
+      x += cursor_glyph->pixel_width - wd;
+      if (wd > 0)
+       wd -= 1;
+    }
+  /* Set clipping, draw the rectangle, and reset clipping again.  */
+  android_clip_to_row (w, row, TEXT_AREA, gc);
+  android_draw_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x, y, wd, h - 1);
+  android_reset_clip_rectangles (f, gc);
+}
+
+static void
+android_draw_bar_cursor (struct window *w, struct glyph_row *row, int width,
+                        enum text_cursor_kinds kind)
+{
+  struct frame *f = XFRAME (w->frame);
+  struct glyph *cursor_glyph;
+  int cursor_start_y;
+
+  /* If cursor is out of bounds, don't draw garbage.  This can happen
+     in mini-buffer windows when switching between echo area glyphs
+     and mini-buffer.  */
+  cursor_glyph = get_phys_cursor_glyph (w);
+  if (cursor_glyph == NULL)
+    return;
+
+  /* Experimental avoidance of cursor on xwidget.  */
+  if (cursor_glyph->type == XWIDGET_GLYPH)
+    return;
+
+  /* If on an image, draw like a normal cursor.  That's usually better
+     visible than drawing a bar, esp. if the image is large so that
+     the bar might not be in the window.  */
+  if (cursor_glyph->type == IMAGE_GLYPH)
+    {
+      struct glyph_row *r;
+      r = MATRIX_ROW (w->current_matrix, w->phys_cursor.vpos);
+      draw_phys_cursor_glyph (w, r, DRAW_CURSOR);
+    }
+  else
+    {
+      struct android_gc *gc = FRAME_DISPLAY_INFO (f)->scratch_cursor_gc;
+      unsigned long mask = ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND;
+      struct face *face = FACE_FROM_ID (f, cursor_glyph->face_id);
+      struct android_gc_values xgcv;
+
+      /* If the glyph's background equals the color we normally draw
+        the bars cursor in, the bar cursor in its normal color is
+        invisible.  Use the glyph's foreground color instead in this
+        case, on the assumption that the glyph's colors are chosen so
+        that the glyph is legible.  */
+      if (face->background == f->output_data.android->cursor_pixel)
+       xgcv.background = xgcv.foreground = face->foreground;
+      else
+       xgcv.background = xgcv.foreground = 
f->output_data.android->cursor_pixel;
+
+      if (gc)
+       android_change_gc (gc, mask, &xgcv);
+      else
+       {
+          gc = android_create_gc (mask, &xgcv);
+         FRAME_DISPLAY_INFO (f)->scratch_cursor_gc = gc;
+       }
+
+      android_clip_to_row (w, row, TEXT_AREA, gc);
+
+      if (kind == BAR_CURSOR)
+       {
+         int x = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
+
+         if (width < 0)
+           width = FRAME_CURSOR_WIDTH (f);
+         width = min (cursor_glyph->pixel_width, width);
+
+         w->phys_cursor_width = width;
+
+         /* If the character under cursor is R2L, draw the bar cursor
+            on the right of its glyph, rather than on the left.  */
+         if ((cursor_glyph->resolved_level & 1) != 0)
+           x += cursor_glyph->pixel_width - width;
+
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x,
+                                 WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y),
+                                 width, row->height);
+       }
+      else /* HBAR_CURSOR */
+       {
+         int dummy_x, dummy_y, dummy_h;
+         int x = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
+
+         if (width < 0)
+           width = row->height;
+
+         width = min (row->height, width);
+
+         get_phys_cursor_geometry (w, row, cursor_glyph, &dummy_x,
+                                   &dummy_y, &dummy_h);
+
+         cursor_start_y = WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y
+                                                   + row->height - width);
+
+         if ((cursor_glyph->resolved_level & 1) != 0
+             && cursor_glyph->pixel_width > w->phys_cursor_width - 1)
+           x += cursor_glyph->pixel_width - w->phys_cursor_width + 1;
+         android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x,
+                                 cursor_start_y,
+                                 w->phys_cursor_width - 1, width);
+       }
+
+      android_reset_clip_rectangles (f, gc);
+    }
+}
+
+static void
+android_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
+                           int x, int y, enum text_cursor_kinds cursor_type,
+                           int cursor_width, bool on_p, bool active_p)
+{
+  struct frame *f;
+
+  f = WINDOW_XFRAME (w);
+
+  if (on_p)
+    {
+      w->phys_cursor_type = cursor_type;
+      w->phys_cursor_on_p = true;
+
+      if (glyph_row->exact_window_width_line_p
+         && (glyph_row->reversed_p
+             ? (w->phys_cursor.hpos < 0)
+             : (w->phys_cursor.hpos >= glyph_row->used[TEXT_AREA])))
+       {
+         glyph_row->cursor_in_fringe_p = true;
+         draw_fringe_bitmap (w, glyph_row, glyph_row->reversed_p);
+       }
+      else
+       {
+         switch (cursor_type)
+           {
+           case HOLLOW_BOX_CURSOR:
+             android_draw_hollow_cursor (w, glyph_row);
+             break;
+
+           case FILLED_BOX_CURSOR:
+             draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
+             break;
+
+           case BAR_CURSOR:
+             android_draw_bar_cursor (w, glyph_row, cursor_width, BAR_CURSOR);
+             break;
+
+           case HBAR_CURSOR:
+             android_draw_bar_cursor (w, glyph_row, cursor_width, HBAR_CURSOR);
+             break;
+
+           case NO_CURSOR:
+             w->phys_cursor_width = 0;
+             break;
+
+           default:
+             emacs_abort ();
+           }
+       }
+
+      /* Now proceed to tell the input method the current position of
+        the cursor, if required.  */
+
+      if (FRAME_OUTPUT_DATA (f)->need_cursor_updates
+         && w == XWINDOW (f->selected_window))
+       android_set_preeditarea (w, x, y);
+    }
+}
+
+static void
+android_draw_vertical_window_border (struct window *w, int x, int y0, int y1)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct face *face;
+
+  face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);
+  if (face)
+    android_set_foreground (f->output_data.android->normal_gc,
+                           face->foreground);
+
+  android_draw_line (FRAME_ANDROID_DRAWABLE (f),
+                    f->output_data.android->normal_gc,
+                    x, y0, x, y1);
+}
+
+static void
+android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
+{
+  struct frame *f = XFRAME (WINDOW_FRAME (w));
+  struct face *face = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FACE_ID);
+  struct face *face_first
+    = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FIRST_PIXEL_FACE_ID);
+  struct face *face_last
+    = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_LAST_PIXEL_FACE_ID);
+  unsigned long color = face ? face->foreground : FRAME_FOREGROUND_PIXEL (f);
+  unsigned long color_first = (face_first
+                              ? face_first->foreground
+                              : FRAME_FOREGROUND_PIXEL (f));
+  unsigned long color_last = (face_last
+                             ? face_last->foreground
+                             : FRAME_FOREGROUND_PIXEL (f));
+
+  if ((y1 - y0 > x1 - x0) && (x1 - x0 >= 3))
+    /* A vertical divider, at least three pixels wide: Draw first and
+       last pixels differently.  */
+    {
+      android_set_foreground (f->output_data.android->normal_gc,
+                             color_first);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x0, y0, 1, y1 - y0);
+      android_set_foreground (f->output_data.android->normal_gc,
+                             color);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x0 + 1, y0, x1 - x0 - 2, y1 - y0);
+      android_set_foreground (f->output_data.android->normal_gc,
+                             color_last);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x1 - 1, y0, 1, y1 - y0);
+    }
+  else if ((x1 - x0 > y1 - y0) && (y1 - y0 >= 3))
+    /* A horizontal divider, at least three pixels high: Draw first
+       and last pixels differently.  */
+    {
+      android_set_foreground (f->output_data.android->normal_gc,
+                             color_first);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x0, y0, x1 - x0, 1);
+      android_set_foreground (f->output_data.android->normal_gc, color);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x0, y0 + 1, x1 - x0, y1 - y0 - 2);
+      android_set_foreground (f->output_data.android->normal_gc,
+                             color_last);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x0, y1 - 1, x1 - x0, 1);
+    }
+  else
+    {
+      /* In any other case do not draw the first and last pixels
+        differently.  */
+      android_set_foreground (f->output_data.android->normal_gc, color);
+      android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
+                             f->output_data.android->normal_gc,
+                             x0, y0, x1 - x0, y1 - y0);
+    }
+}
+
+
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#else
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif
+
+/* Input method related functions.  Some of these are called from Java
+   within the UI thread.  */
+
+/* A counter used to decide when an editing request completes.  */
+static unsigned long edit_counter;
+
+/* The last counter known to have completed.  */
+static unsigned long last_edit_counter;
+
+/* Semaphore posted every time the counter increases.  */
+static sem_t edit_sem;
+
+/* Try to synchronize with the UI thread, waiting a certain amount of
+   time for outstanding editing requests to complete.
+
+   Every time one of the text retrieval functions is called and an
+   editing request is made, Emacs gives the main thread approximately
+   100 ms to process it, in order to mostly keep the input method in
+   sync with the buffer contents.  */
+
+static void
+android_sync_edit (void)
+{
+  struct timespec start, end, rem;
+  unsigned long counter;
+
+  counter = __atomic_load_n (&last_edit_counter,
+                            __ATOMIC_SEQ_CST);
+
+  if (counter == edit_counter)
+    return;
+
+  start = current_timespec ();
+  end = timespec_add (start, make_timespec (0, 100000000));
+
+  while (true)
+    {
+      rem = timespec_sub (end, current_timespec ());
+
+      /* Timeout.  */
+      if (timespec_sign (rem) < 0)
+       break;
+
+      if (__atomic_load_n (&last_edit_counter,
+                          __ATOMIC_SEQ_CST)
+         == edit_counter)
+       break;
+
+      sem_timedwait (&edit_sem, &end);
+    }
+}
+
+/* Return a copy of the specified Java string and its length in
+   *LENGTH.  Use the JNI environment ENV.  Value is NULL if copying
+   *the string fails.  */
+
+static unsigned short *
+android_copy_java_string (JNIEnv *env, jstring string, size_t *length)
+{
+  jsize size, i;
+  const jchar *java;
+  unsigned short *buffer;
+
+  size = (*env)->GetStringLength (env, string);
+  buffer = malloc (size * sizeof *buffer);
+
+  if (!buffer)
+    return NULL;
+
+  java = (*env)->GetStringChars (env, string, NULL);
+
+  if (!java)
+    {
+      free (buffer);
+      return NULL;
+    }
+
+  for (i = 0; i < size; ++i)
+    buffer[i] = java[i];
+
+  *length = size;
+  (*env)->ReleaseStringChars (env, string, java);
+  return buffer;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (beginBatchEdit) (JNIEnv *env, jobject object, jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_START_BATCH_EDIT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (endBatchEdit) (JNIEnv *env, jobject object, jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_END_BATCH_EDIT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (commitCompletion) (JNIEnv *env, jobject object, jshort window,
+                               jstring completion_text, jint position)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+  unsigned short *text;
+  size_t length;
+
+  /* First, obtain a copy of the Java string.  */
+  text = android_copy_java_string (env, completion_text, &length);
+
+  if (!text)
+    return;
+
+  /* Next, populate the event.  Events will always eventually be
+     delivered on Android, so handle_one_android_event can be relied
+     on to free text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_COMMIT_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = min (length, PTRDIFF_MAX);
+  event.ime.position = position;
+  event.ime.text = text;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (commitText) (JNIEnv *env, jobject object, jshort window,
+                         jstring commit_text, jint position)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+  unsigned short *text;
+  size_t length;
+
+  /* First, obtain a copy of the Java string.  */
+  text = android_copy_java_string (env, commit_text, &length);
+
+  if (!text)
+    return;
+
+  /* Next, populate the event.  Events will always eventually be
+     delivered on Android, so handle_one_android_event can be relied
+     on to free text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_COMMIT_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = min (length, PTRDIFF_MAX);
+  event.ime.position = position;
+  event.ime.text = text;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (deleteSurroundingText) (JNIEnv *env, jobject object,
+                                    jshort window, jint left_length,
+                                    jint right_length)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_DELETE_SURROUNDING_TEXT;
+  event.ime.start = left_length;
+  event.ime.end = right_length;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (finishComposingText) (JNIEnv *env, jobject object,
+                                  jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+/* Structure describing the context used for a text query.  */
+
+struct android_conversion_query_context
+{
+  /* The conversion request.  */
+  struct textconv_callback_struct query;
+
+  /* The window the request is being made on.  */
+  android_window window;
+
+  /* Whether or not the request was successful.  */
+  bool success;
+};
+
+/* Obtain the text from the frame whose window is that specified in
+   DATA using the text conversion query specified there.
+
+   Set ((struct android_conversion_query_context *) DATA)->success on
+   success.  */
+
+static void
+android_perform_conversion_query (void *data)
+{
+  struct android_conversion_query_context *context;
+  struct frame *f;
+
+  context = data;
+
+  /* Find the frame associated with the window.  */
+  f = android_window_to_frame (NULL, context->window);
+
+  if (!f)
+    return;
+
+  textconv_query (f, &context->query, 0);
+
+  /* context->query.text will have been set even if textconv_query
+     returns 1.  */
+
+  context->success = true;
+}
+
+/* Convert a string in BUFFER, containing N characters in Emacs's
+   internal multibyte encoding, to a Java string utilizing the
+   specified JNI environment ENV.
+
+   If N is equal to BYTES, then BUFFER holds unibyte or plain-ASCII
+   characters.  Otherwise, BUFFER holds multibyte characters.
+
+   Make sure N and BYTES are absolutely correct, or you are asking for
+   trouble.
+
+   Value is a jstring upon success, NULL otherwise.  Any exceptions
+   generated are not cleared.  */
+
+static jstring
+android_text_to_string (JNIEnv *env, char *buffer, ptrdiff_t n,
+                       ptrdiff_t bytes)
+{
+  jchar *utf16;
+  size_t size, index;
+  jstring string;
+  int encoded;
+
+  if (n == bytes)
+    {
+      /* This buffer holds no multibyte characters.  */
+
+      if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
+       return NULL;
+
+      utf16 = malloc (size);
+      index = 0;
+
+      if (!utf16)
+       return NULL;
+
+      while (n--)
+       {
+         utf16[index] = buffer[index];
+         index++;
+       }
+
+      string = (*env)->NewString (env, utf16, bytes);
+      free (utf16);
+
+      return string;
+    }
+
+  /* Allocate enough to hold N characters.  */
+
+  if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
+    return NULL;
+
+  utf16 = malloc (size);
+  index = 0;
+
+  if (!utf16)
+    return NULL;
+
+  while (n--)
+    {
+      eassert (CHAR_HEAD_P (*buffer));
+      encoded = STRING_CHAR ((unsigned char *) buffer);
+
+      /* Now establish how to save ENCODED into the string.
+         Emacs operates on multibyte characters, not UTF-16 characters
+         with surrogate pairs as Android does.
+
+         However, character positions in Java are represented as
+         character (rather than codepoint) indices into UTF-16
+         strings, meaning that text positions reported to Android can
+         become decoupled from their actual values if the text
+         returned incorporates characters that must be encoded as
+         surrogate pairs.
+
+         The hack used by Emacs is to simply replace each multibyte
+         character that doesn't fit in a jchar with the NULL
+         character.  */
+
+      if (encoded >= 65536)
+       encoded = 0;
+
+      utf16[index++] = encoded;
+      buffer += BYTES_BY_CHAR_HEAD (*buffer);
+    }
+
+  /* Create the string.  */
+  string = (*env)->NewString (env, utf16, index);
+  free (utf16);
+  return string;
+}
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getTextAfterCursor) (JNIEnv *env, jobject object, jshort window,
+                                 jint length, jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  struct android_conversion_query_context context;
+  jstring string;
+
+  /* First, set up the conversion query.  */
+  context.query.position = EMACS_INT_MAX;
+  context.query.direction = TEXTCONV_FORWARD_CHAR;
+  context.query.factor = min (length, 65535);
+  context.query.operation = TEXTCONV_RETRIEVAL;
+
+  /* Next, set the rest of the context.  */
+  context.window = window;
+  context.success = false;
+
+  /* Now try to perform the query.  */
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_perform_conversion_query,
+                                  &context))
+    return NULL;
+
+  if (!context.success)
+    return NULL;
+
+  /* context->query.text now contains the text in Emacs's internal
+     UTF-8 based encoding.
+
+     Convert it to Java's UTF-16 encoding, which is the same as
+     UTF-16, except that NULL bytes are encoded as surrogate pairs.
+
+     This assumes that `free' can free data allocated with xmalloc.  */
+
+  string = android_text_to_string (env, context.query.text.text,
+                                  context.query.text.length,
+                                  context.query.text.bytes);
+  free (context.query.text.text);
+
+  return string;
+}
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getTextBeforeCursor) (JNIEnv *env, jobject object, jshort window,
+                                  jint length, jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  struct android_conversion_query_context context;
+  jstring string;
+
+  /* First, set up the conversion query.  */
+  context.query.position = TYPE_MINIMUM (EMACS_INT);
+  context.query.direction = TEXTCONV_BACKWARD_CHAR;
+  context.query.factor = min (length, 65535);
+  context.query.operation = TEXTCONV_RETRIEVAL;
+
+  /* Next, set the rest of the context.  */
+  context.window = window;
+  context.success = false;
+
+  /* Now try to perform the query.  */
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_perform_conversion_query,
+                                  &context))
+    return NULL;
+
+  if (!context.success)
+    return NULL;
+
+  /* context->query.text now contains the text in Emacs's internal
+     UTF-8 based encoding.
+
+     Convert it to Java's UTF-16 encoding, which is the same as
+     UTF-16, except that NULL bytes are encoded as surrogate pairs.
+
+     This assumes that `free' can free data allocated with xmalloc.  */
+
+  string = android_text_to_string (env, context.query.text.text,
+                                  context.query.text.length,
+                                  context.query.text.bytes);
+  free (context.query.text.text);
+
+  return string;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setComposingText) (JNIEnv *env, jobject object, jshort window,
+                               jstring composing_text,
+                               jint new_cursor_position)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+  unsigned short *text;
+  size_t length;
+
+  /* First, obtain a copy of the Java string.  */
+  text = android_copy_java_string (env, composing_text, &length);
+
+  if (!text)
+    return;
+
+  /* Next, populate the event.  Events will always eventually be
+     delivered on Android, so handle_one_android_event can be relied
+     on to free text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_SET_COMPOSING_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = min (length, PTRDIFF_MAX);
+  event.ime.position = new_cursor_position;
+  event.ime.text = text;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setComposingRegion) (JNIEnv *env, jobject object, jshort window,
+                                 jint start, jint end)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_SET_COMPOSING_REGION;
+  event.ime.start = start + 1;
+  event.ime.end = end + 1;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (setSelection) (JNIEnv *env, jobject object, jshort window,
+                           jint start, jint end)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  /* While IMEs want access to the entire selection, Emacs only
+     supports setting the point.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_SET_POINT;
+  event.ime.start = start + 1;
+  event.ime.end = end + 1;
+  event.ime.length = 0;
+  event.ime.position = start;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+/* Structure describing the context for `getSelection'.  */
+
+struct android_get_selection_context
+{
+  /* The window in question.  */
+  android_window window;
+
+  /* The position of the window's point when it was last
+     redisplayed, and its last mark if active.  */
+  ptrdiff_t point, mark;
+};
+
+/* Function run on the main thread by `getSelection'.
+   Place the character position of point in PT.  */
+
+static void
+android_get_selection (void *data)
+{
+  struct android_get_selection_context *context;
+  struct frame *f;
+  struct window *w;
+  struct buffer *b;
+
+  context = data;
+
+  /* Look up the associated frame and its selected window.  */
+  f = android_window_to_frame (NULL, context->window);
+
+  if (!f)
+    context->point = -1;
+  else
+    {
+      w = XWINDOW (f->selected_window);
+
+      /* Return W's point as it is now.  Then, set
+        W->ephemeral_last_point to match the current point.  */
+      context->point = window_point (w);
+      w->ephemeral_last_point = context->point;
+
+      /* Default context->mark to w->last_point too.  */
+      context->mark = context->point;
+
+      /* If the mark is active, then set it properly.  Also, adjust
+        w->last_mark to match.  */
+      b = XBUFFER (w->contents);
+      if (!NILP (BVAR (b, mark_active)))
+       {
+         context->mark = marker_position (BVAR (b, mark));
+         w->last_mark = context->mark;
+       }
+    }
+}
+
+JNIEXPORT jintArray JNICALL
+NATIVE_NAME (getSelection) (JNIEnv *env, jobject object, jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  struct android_get_selection_context context;
+  jintArray array;
+  jint contents[2];
+
+  context.window = window;
+
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_get_selection,
+                                  &context))
+    return NULL;
+
+  if (context.point == -1)
+    return NULL;
+
+  /* Wraparound actually makes more sense than truncation; at least
+     editing will sort of work.  Convert the positions to start from
+     index 0, as that is what Android expects.  */
+  contents[0] = (unsigned int) min (context.point,
+                                   context.mark) - 1;
+  contents[1] = (unsigned int) max (context.point,
+                                   context.mark) - 1;
+
+  /* Now create the array.  */
+  array = (*env)->NewIntArray (env, 2);
+
+  if (!array)
+    return NULL;
+
+  /* Set its contents.  */
+  (*env)->SetIntArrayRegion (env, array, 0, 2, contents);
+  return array;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object,
+                                  jshort window, int action)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  /* It's a good idea to call `android_sync_edit' before sending the
+     key event.  Otherwise, if RET causes the current window to be
+     changed, any text previously committed might end up in the newly
+     selected window.  */
+
+  android_sync_edit ();
+
+  /* Undocumented behavior: performEditorAction is apparently expected
+     to finish composing any text.  */
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+
+  /* This value of `length' means that the input method should receive
+     an update containing the new conversion region.  */
+
+  event.ime.length = 1;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+
+  /* Finally, send the return key press.  `counter' is set; this means
+     that a text conversion barrier will be generated once the event
+     is read, which will cause subsequent edits to wait until the
+     edits associated with this key press complete.  */
+
+  event.xkey.type = ANDROID_KEY_PRESS;
+  event.xkey.serial = ++event_serial;
+  event.xkey.window = window;
+  event.xkey.time = 0;
+  event.xkey.state = 0;
+  event.xkey.keycode = 66;
+  event.xkey.unicode_char = 0;
+  event.xkey.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (performContextMenuAction) (JNIEnv *env, jobject object,
+                                       jshort window, int action)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+  int key;
+
+  /* Note that ACTION is determined in EmacsInputConnection, and as
+     such they are not actual resource IDs.  */
+
+  switch (action)
+    {
+    case 0: /* android.R.id.selectAll */
+    case 1: /* android.R.id.startSelectingText */
+    case 2: /* android.R.id.stopSelectingText */
+    default:
+      /* These actions are not implemented.  */
+      return;
+
+    case 3: /* android.R.id.cut */
+      key = 277;
+      break;
+
+    case 4: /* android.R.id.copy */
+      key = 278;
+      break;
+
+    case 5: /* android.R.id.paste */
+      key = 279;
+      break;
+    }
+
+  event.xkey.type = ANDROID_KEY_PRESS;
+  event.xkey.serial = ++event_serial;
+  event.xkey.window = window;
+  event.xkey.time = 0;
+  event.xkey.state = 0;
+  event.xkey.keycode = key;
+  event.xkey.unicode_char = 0;
+  event.xkey.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+
+
+/* Text extraction.  */
+
+struct android_get_extracted_text_context
+{
+  /* The parameters of the request.  */
+  int hint_max_chars;
+
+  /* Token for the request.  */
+  int token;
+
+  /* Flags associated with the request.  */
+  int flags;
+
+  /* The returned text, or NULL.  */
+  char *text;
+
+  /* The size of that text in characters and bytes.  */
+  ptrdiff_t length, bytes;
+
+  /* Offsets into that text.  */
+  ptrdiff_t start, start_offset, end_offset;
+
+  /* The window.  */
+  android_window window;
+
+  /* Whether or not the mark is active.  */
+  bool mark_active;
+};
+
+/* Return the extracted text in the extracted text context specified
+   by DATA.  Save its flags and token into its frame's state.  */
+
+static void
+android_get_extracted_text (void *data)
+{
+  struct android_get_extracted_text_context *request;
+  struct frame *f;
+
+  request = data;
+
+  /* Find the frame associated with the window.  */
+  f = android_window_to_frame (NULL, request->window);
+
+  if (!f)
+    return;
+
+  /* Now get the extracted text.  */
+  request->text
+    = get_extracted_text (f, min (request->hint_max_chars, 600),
+                         &request->start, &request->start_offset,
+                         &request->end_offset, &request->length,
+                         &request->bytes, &request->mark_active);
+
+  /* See if request->flags & GET_EXTRACTED_TEXT_MONITOR.  If so, then
+     the input method has asked to monitor changes to the extracted
+     text until the next IM context reset.  */
+
+  FRAME_ANDROID_OUTPUT (f)->extracted_text_flags = request->flags;
+  FRAME_ANDROID_OUTPUT (f)->extracted_text_token = request->token;
+  FRAME_ANDROID_OUTPUT (f)->extracted_text_hint = request->hint_max_chars;
+}
+
+/* Structure describing the `ExtractedTextRequest' class.
+   Valid only on the UI thread.  */
+
+struct android_extracted_text_request_class
+{
+  bool initialized;
+  jfieldID hint_max_chars;
+  jfieldID token;
+};
+
+/* Structure describing the `ExtractedText' class.
+   Valid only on the UI thread.  */
+
+struct android_extracted_text_class
+{
+  jclass class;
+  jmethodID constructor;
+  jfieldID flags;
+  jfieldID partial_start_offset;
+  jfieldID partial_end_offset;
+  jfieldID selection_start;
+  jfieldID selection_end;
+  jfieldID start_offset;
+  jfieldID text;
+};
+
+/* Fields and methods associated with the `ExtractedTextRequest'
+   class.  */
+struct android_extracted_text_request_class request_class;
+
+/* Fields and methods associated with the `ExtractedText' class.  */
+struct android_extracted_text_class text_class;
+
+/* Return an ExtractedText object corresponding to the extracted text
+   TEXT.  START is a character position describing the offset of the
+   first character in TEXT.  START_OFFSET is the offset of the lesser
+   of point or mark relative to START, and END_OFFSET is that of the
+   greater of point or mark relative to START.  MARK_ACTIVE specifies
+   whether or not the mark is currently active.
+
+   Assume that request_class and text_class have already been
+   initialized.
+
+   Value is NULL if an error occurs; the exception is not cleared,
+   else a local reference to the ExtractedText object.  */
+
+static jobject
+android_build_extracted_text (jstring text, ptrdiff_t start,
+                             ptrdiff_t start_offset,
+                             ptrdiff_t end_offset, bool mark_active)
+{
+  JNIEnv *env;
+  jobject object;
+
+  env = android_java_env;
+
+  /* Return NULL if the class has not yet been obtained.  */
+  if (!text_class.class)
+    return NULL;
+
+  /* Create an ExtractedText object containing this information.  */
+  object = (*env)->NewObject (env, text_class.class,
+                             text_class.constructor);
+  if (!object)
+    return NULL;
+
+  (*env)->SetIntField (env, object, text_class.flags,
+                      /* ExtractedText.FLAG_SELECTING */
+                      mark_active ? 2 : 0);
+  (*env)->SetIntField (env, object, text_class.partial_start_offset, -1);
+  (*env)->SetIntField (env, object, text_class.partial_end_offset, -1);
+  (*env)->SetIntField (env, object, text_class.selection_start,
+                      min (start_offset, TYPE_MAXIMUM (jint)));
+  (*env)->SetIntField (env, object, text_class.selection_end,
+                      min (end_offset, TYPE_MAXIMUM (jint)));
+
+  /* Subtract 1 from start: point indices in Emacs start from 1, but
+     Android expects 0.  */
+  (*env)->SetIntField (env, object, text_class.start_offset,
+                      min (start - 1, TYPE_MAXIMUM (jint)));
+  (*env)->SetObjectField (env, object, text_class.text, text);
+  return object;
+}
+
+JNIEXPORT jobject JNICALL
+NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object,
+                               jshort window, jobject request,
+                               jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  struct android_get_extracted_text_context context;
+  jstring string;
+  jclass class;
+  jobject object;
+
+  /* Initialize both classes if necessary.  */
+
+  if (!request_class.initialized)
+    {
+      class
+       = (*env)->FindClass (env, ("android/view/inputmethod"
+                                  "/ExtractedTextRequest"));
+      assert (class);
+
+      request_class.hint_max_chars
+       = (*env)->GetFieldID (env, class, "hintMaxChars", "I");
+      assert (request_class.hint_max_chars);
+
+      request_class.token
+       = (*env)->GetFieldID (env, class, "token", "I");
+      assert (request_class.token);
+
+      request_class.initialized = true;
+    }
+
+  if (!text_class.class)
+    {
+      text_class.class
+       = (*env)->FindClass (env, ("android/view/inputmethod"
+                                  "/ExtractedText"));
+      assert (text_class.class);
+
+      class
+       = text_class.class
+       = (*env)->NewGlobalRef (env, text_class.class);
+      assert (text_class.class);
+
+      text_class.flags
+       = (*env)->GetFieldID (env, class, "flags", "I");
+      text_class.partial_start_offset
+       = (*env)->GetFieldID (env, class, "partialStartOffset", "I");
+      text_class.partial_end_offset
+       = (*env)->GetFieldID (env, class, "partialEndOffset", "I");
+      text_class.selection_start
+       = (*env)->GetFieldID (env, class, "selectionStart", "I");
+      text_class.selection_end
+       = (*env)->GetFieldID (env, class, "selectionEnd", "I");
+      text_class.start_offset
+       = (*env)->GetFieldID (env, class, "startOffset", "I");
+      text_class.text
+       = (*env)->GetFieldID (env, class, "text", "Ljava/lang/CharSequence;");
+      text_class.constructor
+       = (*env)->GetMethodID (env, class, "<init>", "()V");
+    }
+
+  context.hint_max_chars
+    = (*env)->GetIntField (env, request, request_class.hint_max_chars);
+  context.token
+    = (*env)->GetIntField (env, request, request_class.token);
+  context.flags = flags;
+  context.text = NULL;
+  context.window = window;
+
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_get_extracted_text,
+                                  &context))
+    return NULL;
+
+  if (!context.text)
+    return NULL;
+
+  /* Encode the returned text.  */
+  string = android_text_to_string (env, context.text, context.length,
+                                  context.bytes);
+  free (context.text);
+
+  if (!string)
+    return NULL;
+
+  /* Create an ExtractedText object containing this information.  */
+  object = (*env)->NewObject (env, text_class.class,
+                             text_class.constructor);
+  if (!object)
+    return NULL;
+
+  (*env)->SetIntField (env, object, text_class.flags,
+                      /* ExtractedText.FLAG_SELECTING */
+                      context.mark_active ? 2 : 0);
+  (*env)->SetIntField (env, object, text_class.partial_start_offset, -1);
+  (*env)->SetIntField (env, object, text_class.partial_end_offset, -1);
+  (*env)->SetIntField (env, object, text_class.selection_start,
+                      min (context.start_offset, TYPE_MAXIMUM (jint)));
+  (*env)->SetIntField (env, object, text_class.selection_end,
+                      min (context.end_offset, TYPE_MAXIMUM (jint)));
+
+  /* Subtract 1 from start: point indices in Emacs start from 1, but
+     Android expects 0.  */
+  (*env)->SetIntField (env, object, text_class.start_offset,
+                      min (context.start - 1, TYPE_MAXIMUM (jint)));
+  (*env)->SetObjectField (env, object, text_class.text, string);
+  return object;
+}
+
+
+
+JNIEXPORT jstring JNICALL
+NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object,
+                              jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  struct android_get_extracted_text_context context;
+  jstring string;
+
+  context.hint_max_chars = -1;
+  context.token = 0;
+  context.text = NULL;
+  context.window = window;
+
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_get_extracted_text,
+                                  &context))
+    return NULL;
+
+  if (!context.text)
+    return NULL;
+
+  /* Encode the returned text.  */
+  string = android_text_to_string (env, context.text, context.length,
+                                  context.bytes);
+  free (context.text);
+
+  return string;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (requestSelectionUpdate) (JNIEnv *env, jobject object,
+                                     jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_REQUEST_SELECTION_UPDATE;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = 0;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (requestCursorUpdates) (JNIEnv *env, jobject object,
+                                   jshort window, jint mode)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_REQUEST_CURSOR_UPDATES;
+  event.ime.start = 0;
+  event.ime.end = 0;
+  event.ime.length = mode;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+
+  /* Since this does not affect the state of the buffer text, there is
+     no need to apply synchronization to this event.  */
+  event.ime.counter = 0;
+
+  android_write_event (&event);
+}
+
+/* Notice that a new input method connection has been initialized and
+   clear cursor update requests, extracted text requests, and the
+   composing region.  */
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (clearInputFlags) (JNIEnv *env, jobject object,
+                              jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  union android_event event;
+
+  event.ime.type = ANDROID_INPUT_METHOD;
+  event.ime.serial = ++event_serial;
+  event.ime.window = window;
+  event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
+  event.ime.start = 0;
+  event.ime.end = 0;
+
+  /* This value of `length' means that updates to the cursor position
+     and extracted text should not be reported anymore.  */
+
+  event.ime.length = 2;
+  event.ime.position = 0;
+  event.ime.text = NULL;
+  event.ime.counter = ++edit_counter;
+
+  android_write_event (&event);
+}
+
+
+
+/* Context for a call to `getSurroundingText'.  */
+
+struct android_get_surrounding_text_context
+{
+  /* Number of characters before the region to return.  */
+  int before_length;
+
+  /* Number of characters after the region to return.  */
+  int after_length;
+
+  /* The returned text, or NULL.  */
+  char *text;
+
+  /* The size of that text in characters and bytes.  */
+  ptrdiff_t length, bytes;
+
+  /* Offsets into that text.  */
+  ptrdiff_t offset, start, end;
+
+  /* The start and end indices of the conversion region.
+     -1 if it does not exist.  */
+  ptrdiff_t conversion_start, conversion_end;
+
+  /* The window.  */
+  android_window window;
+};
+
+/* Return the surrounding text in the surrounding text context
+   specified by DATA.  */
+
+static void
+android_get_surrounding_text (void *data)
+{
+  struct android_get_surrounding_text_context *request;
+  struct frame *f;
+  ptrdiff_t temp;
+
+  request = data;
+
+  /* Find the frame associated with the window.  */
+  f = android_window_to_frame (NULL, request->window);
+
+  if (!f)
+    return;
+
+  /* Now get the surrounding text.  */
+  request->text
+    = get_surrounding_text (f, request->before_length,
+                           request->after_length, &request->length,
+                           &request->bytes, &request->offset,
+                           &request->start, &request->end);
+
+  /* Sort request->start and request->end for compatibility with some
+     bad input methods.  */
+
+  if (request->end < request->start)
+    {
+      temp = request->start;
+      request->start = request->end;
+      request->end = temp;
+    }
+
+  /* Retrieve the conversion region.  */
+
+  request->conversion_start = -1;
+  request->conversion_end = -1;
+
+  if (MARKERP (f->conversion.compose_region_start))
+    {
+      request->conversion_start
+       = marker_position (f->conversion.compose_region_start) - 1;
+      request->conversion_end
+       = marker_position (f->conversion.compose_region_end) - 1;
+    }
+}
+
+/* Return a local reference to a `SurroundingText' object describing
+   WINDOW's surrounding text.  ENV should be a valid JNI environment
+   for the current thread.
+
+   BEFORE_LENGTH and AFTER_LENGTH specify the number of characters
+   around point and mark to return.
+
+   Return the conversion region (or -1) in *CONVERSION_START and
+   *CONVERSION_END if non-NULL.
+
+   Value is the object upon success, else NULL.  */
+
+static jobject
+android_get_surrounding_text_internal (JNIEnv *env, jshort window,
+                                      jint before_length,
+                                      jint after_length,
+                                      ptrdiff_t *conversion_start,
+                                      ptrdiff_t *conversion_end)
+{
+  struct android_get_surrounding_text_context context;
+  jstring string;
+  jobject object;
+
+  static jclass class;
+  static jmethodID constructor;
+
+  /* Initialize CLASS if it has not yet been initialized.  */
+
+  if (!class)
+    {
+      class
+       = (*env)->FindClass (env, ("android/view/inputmethod"
+                                  "/SurroundingText"));
+
+#if __ANDROID_API__ < 31
+      /* If CLASS cannot be found, the version of Android currently
+        running is too old.  */
+
+      if (!class)
+       {
+         (*env)->ExceptionClear (env);
+         return NULL;
+       }
+#else /* __ANDROID_API__ >= 31 */
+      assert (class);
+#endif /* __ANDROID_API__ < 31 */
+
+      class = (*env)->NewGlobalRef (env, class);
+      if (!class)
+       /* Clear class to prevent a local reference from remaining in
+          `class'.  */
+       return (class = NULL);
+
+      /* Now look for its constructor.  */
+      constructor = (*env)->GetMethodID (env, class, "<init>",
+                                        "(Ljava/lang/CharSequence;III)V");
+      assert (constructor);
+    }
+
+  context.before_length = before_length;
+  context.after_length = after_length;
+  context.window = window;
+  context.text = NULL;
+
+  android_sync_edit ();
+  if (android_run_in_emacs_thread (android_get_surrounding_text,
+                                  &context))
+    return NULL;
+
+  if (!context.text)
+    return NULL;
+
+  /* Encode the returned text.  */
+  string = android_text_to_string (env, context.text, context.length,
+                                  context.bytes);
+  free (context.text);
+
+  if (!string)
+    return NULL;
+
+  /* Create an SurroundingText object containing this information.  */
+  object = (*env)->NewObject (env, class, constructor, string,
+                             (jint) min (context.start,
+                                         TYPE_MAXIMUM (jint)),
+                             (jint) min (context.end,
+                                         TYPE_MAXIMUM (jint)),
+                             /* Adjust point offsets to fit into
+                                Android's 0-based indexing. */
+                             (jint) min (context.offset - 1,
+                                         TYPE_MAXIMUM (jint)));
+  if (!object)
+    return NULL;
+
+  /* Now return the conversion region if that was requested.  */
+
+  if (conversion_start)
+    {
+      *conversion_start = context.conversion_start;
+      *conversion_end = context.conversion_start;
+    }
+
+  return object;
+}
+
+JNIEXPORT jobject JNICALL
+NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject object,
+                                 jshort window, jint before_length,
+                                 jint after_length, jint flags)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  return android_get_surrounding_text_internal (env, window, before_length,
+                                               after_length, NULL, NULL);
+}
+
+JNIEXPORT jobject JNICALL
+NATIVE_NAME (takeSnapshot) (JNIEnv *env, jobject object, jshort window)
+{
+  JNI_STACK_ALIGNMENT_PROLOGUE;
+
+  jobject text;
+  ptrdiff_t start, end;
+
+  static jclass class;
+  static jmethodID constructor;
+
+  /* First, obtain the surrounding text and conversion region.  */
+  text = android_get_surrounding_text_internal (env, window, 600, 600,
+                                               &start, &end);
+
+  /* If that fails, return NULL.  */
+
+  if (!text)
+    return NULL;
+
+  /* Next, initialize the TextSnapshot class.  */
+
+  if (!class)
+    {
+      class
+       = (*env)->FindClass (env, ("android/view/inputmethod"
+                                  "/TextSnapshot"));
+#if __ANDROID_API__ < 33
+      /* If CLASS cannot be found, the version of Android currently
+        running is too old.  */
+
+      if (!class)
+       {
+         (*env)->ExceptionClear (env);
+         return NULL;
+       }
+#else /* __ANDROID_API__ >= 33 */
+      assert (class);
+#endif /* __ANDROID_API__ < 33 */
+
+      class = (*env)->NewGlobalRef (env, class);
+      if (!class)
+       /* Clear class to prevent a local reference from remaining in
+          `class'.  */
+       return (class = NULL);
+
+      constructor = (*env)->GetMethodID (env, class, "<init>",
+                                        "(Landroid/view/inputmethod"
+                                        "/SurroundingText;III)V");
+      assert (constructor);
+    }
+
+  /* Try to create a TextSnapshot object.  */
+  eassert (start <= end);
+  object = (*env)->NewObject (env, class, constructor, text,
+                             (jint) min (start, TYPE_MAXIMUM (jint)),
+                             (jint) min (end, TYPE_MAXIMUM (jint)),
+                             (jint) 0);
+  return object;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#else /* GCC */
+#pragma GCC diagnostic pop
+#endif /* __clang__ */
+
+
+
+/* Tell the input method where the composing region and selection of
+   F's selected window is located.  W should be F's selected window;
+   if it is NULL, then F->selected_window is used in its place.  */
+
+static void
+android_update_selection (struct frame *f, struct window *w)
+{
+  ptrdiff_t start, end, point, mark, start_offset, end_offset;
+  ptrdiff_t length, bytes;
+  struct buffer *b;
+  int hint, token;
+  char *text;
+  jobject extracted;
+  jstring string;
+  bool mark_active;
+
+  if (MARKERP (f->conversion.compose_region_start))
+    {
+      eassert (MARKERP (f->conversion.compose_region_end));
+
+      /* Indexing in android starts from 0 instead of 1.  */
+      start = marker_position (f->conversion.compose_region_start) - 1;
+      end = marker_position (f->conversion.compose_region_end) - 1;
+    }
+  else
+    start = -1, end = -1;
+
+  /* Now constrain START and END to the maximium size of a Java
+     integer.  */
+  start = min (start, TYPE_MAXIMUM (jint));
+  end = min (end, TYPE_MAXIMUM (jint));
+
+  if (!w)
+    w = XWINDOW (f->selected_window);
+
+  /* Figure out where the point and mark are.  If the mark is not
+     active, then point is set to equal mark.  */
+  b = XBUFFER (w->contents);
+  point = min (w->ephemeral_last_point,
+              TYPE_MAXIMUM (jint));
+  mark = ((!NILP (BVAR (b, mark_active))
+          && w->last_mark != -1)
+         ? min (w->last_mark, TYPE_MAXIMUM (jint))
+         : point);
+
+  /* Send the update.  Android doesn't employ a concept of ``point''
+     and ``mark''; instead, it only has a selection, where the start
+     of the selection is less than or equal to the end, and the region
+     is ``active'' when those two values differ.  Also, convert the
+     indices from 1-based Emacs indices to 0-based Android ones.  */
+  android_update_ic (FRAME_ANDROID_WINDOW (f), min (point, mark) - 1,
+                    max (point, mark) - 1, start, end);
+
+  /* Update the extracted text as well, if the input method has asked
+     for updates.  1 is
+     InputConnection.GET_EXTRACTED_TEXT_MONITOR.  */
+
+  if (FRAME_ANDROID_OUTPUT (f)->extracted_text_flags & 1)
+    {
+      hint = FRAME_ANDROID_OUTPUT (f)->extracted_text_hint;
+      token = FRAME_ANDROID_OUTPUT (f)->extracted_text_token;
+      text = get_extracted_text (f, min (hint, 600), &start,
+                                &start_offset, &end_offset,
+                                &length, &bytes, &mark_active);
+
+      if (text)
+       {
+         /* Make a string out of the extracted text.  */
+         string = android_text_to_string (android_java_env,
+                                          text, length, bytes);
+         xfree (text);
+         android_exception_check ();
+
+         /* Make extracted text out of that string.  */
+         extracted = android_build_extracted_text (string, start,
+                                                   start_offset,
+                                                   end_offset,
+                                                   mark_active);
+         android_exception_check_1 (string);
+         ANDROID_DELETE_LOCAL_REF (string);
+
+         if (extracted)
+           {
+             /* extracted is now an associated ExtractedText object.
+                Perform the update.  */
+             android_update_extracted_text (FRAME_ANDROID_WINDOW (f),
+                                            extracted, token);
+             ANDROID_DELETE_LOCAL_REF (extracted);
+           }
+       }
+    }
+}
+
+/* Return whether or not EVENT is an input method event destined for
+   the frame (struct frame *) ARG.  */
+
+static bool
+android_event_is_for_frame (union android_event *event, void *arg)
+{
+  struct frame *f;
+
+  f = arg;
+  return (event->type == ANDROID_INPUT_METHOD
+         && event->ime.window == FRAME_ANDROID_WINDOW (f));
+}
+
+/* Notice that the input method connection to F should be reset as a
+   result of a change to its contents.  */
+
+static void
+android_reset_conversion (struct frame *f)
+{
+  enum android_ic_mode mode;
+  struct window *w;
+  struct buffer *buffer;
+  Lisp_Object style;
+  union android_event event;
+
+  /* Reset the input method.
+
+     Select an appropriate ``input mode'' based on whether or not the
+     minibuffer window is selected, which in turn affects if ``RET''
+     inserts a newline or sends an editor action Emacs transforms into
+     a key event (refer to `performEditorAction'.)  */
+
+  w = XWINDOW (f->selected_window);
+  buffer = XBUFFER (WINDOW_BUFFER (w));
+
+  style = (EQ (find_symbol_value (Qoverriding_text_conversion_style),
+              Qlambda)
+          ? BVAR (buffer, text_conversion_style)
+          : find_symbol_value (Qoverriding_text_conversion_style));
+
+  if (NILP (style) || conversion_disabled_p ())
+    mode = ANDROID_IC_MODE_NULL;
+  else if (EQ (style, Qaction) || EQ (f->selected_window,
+                                     f->minibuffer_window))
+    mode = ANDROID_IC_MODE_ACTION;
+  else
+    mode = ANDROID_IC_MODE_TEXT;
+
+  /* Remove any existing input method events that apply to FRAME from
+     the event queue.
+
+     There's a small window between this and the call to
+     android_reset_ic between which more events can be generated.  */
+
+  while (android_check_if_event (&event, android_event_is_for_frame, f))
+    {
+      switch (event.ime.operation)
+       {
+       case ANDROID_IME_COMMIT_TEXT:
+       case ANDROID_IME_FINISH_COMPOSING_TEXT:
+       case ANDROID_IME_SET_COMPOSING_TEXT:
+         xfree (event.ime.text);
+         break;
+
+       default:
+         break;
+       }
+    }
+
+  android_reset_ic (FRAME_ANDROID_WINDOW (f), mode);
+
+  /* Clear extracted text flags.  Since the IM has been reinitialised,
+     it should no longer be displaying extracted text.  */
+  FRAME_ANDROID_OUTPUT (f)->extracted_text_flags = 0;
+
+  /* Move its selection to the specified position.  */
+  android_update_selection (f, NULL);
+}
+
+/* Notice that point has moved in the F's selected window's selected
+   buffer.  W is the window, and BUFFER is that buffer.  */
+
+static void
+android_set_point (struct frame *f, struct window *w,
+                  struct buffer *buffer)
+{
+  android_update_selection (f, w);
+}
+
+/* Notice that the composition region on F's old selected window has
+   changed.  */
+
+static void
+android_compose_region_changed (struct frame *f)
+{
+  android_update_selection (f, XWINDOW (f->old_selected_window));
+}
+
+/* Notice that the text conversion has completed.  */
+
+static void
+android_notify_conversion (unsigned long counter)
+{
+  int sval;
+
+  if (last_edit_counter < counter)
+    __atomic_store_n (&last_edit_counter, counter,
+                     __ATOMIC_SEQ_CST);
+
+  sem_getvalue (&edit_sem, &sval);
+
+  if (sval <= 0)
+    sem_post (&edit_sem);
+}
+
+/* Android text conversion interface.  */
+
+static struct textconv_interface text_conversion_interface =
+  {
+    android_reset_conversion,
+    android_set_point,
+    android_compose_region_changed,
+    android_notify_conversion,
+  };
+
+
+
+extern frame_parm_handler android_frame_parm_handlers[];
+
+#endif /* !ANDROID_STUBIFY */
+
+static struct redisplay_interface android_redisplay_interface =
+  {
+#ifndef ANDROID_STUBIFY
+    android_frame_parm_handlers,
+    gui_produce_glyphs,
+    gui_write_glyphs,
+    gui_insert_glyphs,
+    gui_clear_end_of_line,
+    android_scroll_run,
+    android_after_update_window_line,
+    NULL, /* update_window_begin */
+    NULL, /* update_window_end   */
+    android_flip_and_flush,
+    gui_clear_window_mouse_face,
+    gui_get_glyph_overhangs,
+    gui_fix_overlapping_area,
+    android_draw_fringe_bitmap,
+    NULL, /* define_fringe_bitmap */
+    NULL, /* destroy_fringe_bitmap */
+    android_compute_glyph_string_overhangs,
+    android_draw_glyph_string,
+    android_define_frame_cursor,
+    android_clear_frame_area,
+    android_clear_under_internal_border,
+    android_draw_window_cursor,
+    android_draw_vertical_window_border,
+    android_draw_window_divider,
+    NULL,
+    android_show_hourglass,
+    android_hide_hourglass,
+    android_default_font_parameter,
+#endif
+  };
+
+
+
+void
+frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
+{
+  /* This cannot be implemented on Android, and as such is left
+     blank.  */
+}
+
+char *
+get_keysym_name (int keysym)
+{
+  static char buffer[64];
+
+#ifndef ANDROID_STUBIFY
+  android_get_keysym_name (keysym, buffer, 64);
+#else
+  emacs_abort ();
+#endif
+  return buffer;
+}
+
+
+
+/* Create a struct terminal, initialize it with the Android specific
+   functions and make DISPLAY->TERMINAL point to it.  */
+
+static struct terminal *
+android_create_terminal (struct android_display_info *dpyinfo)
+{
+  struct terminal *terminal;
+
+  terminal = create_terminal (output_android,
+                             &android_redisplay_interface);
+  terminal->display_info.android = dpyinfo;
+  dpyinfo->terminal = terminal;
+
+  /* kboard is initialized in android_term_init.  */
+
+#ifndef ANDROID_STUBIFY
+
+  terminal->clear_frame_hook = android_clear_frame;
+  terminal->ring_bell_hook = android_ring_bell;
+  terminal->toggle_invisible_pointer_hook
+    = android_toggle_invisible_pointer;
+  terminal->update_begin_hook = android_update_begin;
+  terminal->update_end_hook = android_update_end;
+  terminal->read_socket_hook = android_read_socket;
+  terminal->frame_up_to_date_hook = android_frame_up_to_date;
+  terminal->buffer_flipping_unblocked_hook
+    = android_buffer_flipping_unblocked_hook;
+  terminal->defined_color_hook = android_defined_color;
+  terminal->query_frame_background_color
+    = android_query_frame_background_color;
+  terminal->query_colors = android_query_colors;
+  terminal->mouse_position_hook = android_mouse_position;
+  terminal->get_focus_frame = android_get_focus_frame;
+  terminal->focus_frame_hook = android_focus_frame;
+  terminal->frame_rehighlight_hook = android_frame_rehighlight_hook;
+  terminal->frame_raise_lower_hook = android_frame_raise_lower;
+  terminal->frame_visible_invisible_hook
+    = android_make_frame_visible_invisible;
+  terminal->fullscreen_hook = android_fullscreen_hook;
+  terminal->iconify_frame_hook = android_iconify_frame;
+  terminal->set_window_size_hook = android_set_window_size;
+  terminal->set_frame_offset_hook = android_set_offset;
+  terminal->set_frame_alpha_hook = android_set_alpha;
+  terminal->set_new_font_hook = android_new_font;
+  terminal->set_bitmap_icon_hook = android_bitmap_icon;
+  terminal->implicit_set_name_hook = android_implicitly_set_name;
+  terminal->menu_show_hook = android_menu_show;
+  terminal->popup_dialog_hook = android_popup_dialog;
+  terminal->change_tab_bar_height_hook = android_change_tab_bar_height;
+  terminal->change_tool_bar_height_hook = android_change_tool_bar_height;
+  terminal->set_scroll_bar_default_width_hook
+    = android_set_scroll_bar_default_width;
+  terminal->set_scroll_bar_default_height_hook
+    = android_set_scroll_bar_default_height;
+  terminal->free_pixmap = android_free_pixmap_hook;
+  terminal->delete_frame_hook = android_delete_frame;
+  terminal->delete_terminal_hook = android_delete_terminal;
+
+#else
+  emacs_abort ();
+#endif
+
+  return terminal;
+}
+
+/* Initialize the Android terminal interface.  The display connection
+   has already been set up by the system at this point.  */
+
+void
+android_term_init (void)
+{
+  struct terminal *terminal;
+  struct android_display_info *dpyinfo;
+  Lisp_Object color_file, color_map;
+
+  dpyinfo = xzalloc (sizeof *dpyinfo);
+  terminal = android_create_terminal (dpyinfo);
+  terminal->kboard = allocate_kboard (Qandroid);
+  terminal->kboard->reference_count++;
+
+  dpyinfo->n_planes = 24;
+
+  /* This function should only be called once at startup.  */
+  eassert (!x_display_list);
+  x_display_list = dpyinfo;
+
+  dpyinfo->name_list_element
+    = Fcons (build_pure_c_string ("android"), Qnil);
+
+  color_file = Fexpand_file_name (build_string ("rgb.txt"),
+                                 Vdata_directory);
+  color_map = Fx_load_color_file (color_file);
+
+  if (NILP (color_map))
+    fatal ("Could not read %s.\n", SDATA (color_file));
+
+  dpyinfo->color_map = color_map;
+
+#ifndef ANDROID_STUBIFY
+  dpyinfo->resx = android_pixel_density_x;
+  dpyinfo->resy = android_pixel_density_y;
+  dpyinfo->font_resolution = android_scaled_pixel_density;
+#endif /* ANDROID_STUBIFY */
+
+  /* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html  */
+  dpyinfo->smallest_font_height = 1;
+  dpyinfo->smallest_char_width = 1;
+
+  terminal->name = xstrdup ("android");
+
+  /* The display "connection" is now set up, and it must never go
+     away.  */
+  terminal->reference_count = 30000;
+
+  /* Set the baud rate to the same value it gets set to on X.  */
+  baud_rate = 19200;
+
+#ifndef ANDROID_STUBIFY
+  sem_init (&edit_sem, false, 0);
+  register_textconv_interface (&text_conversion_interface);
+#endif
+}
+
+
+
+/* Set Vandroid_build_fingerprint to a reasonable value, and also
+   Vandroid_build_manufacturer.  */
+
+static void
+android_set_build_fingerprint (void)
+{
+#ifdef ANDROID_STUBIFY
+  Vandroid_build_fingerprint = Qnil;
+#else /* !ANDROID_STUBIFY */
+  jclass class;
+  jfieldID field;
+  jobject string;
+  const char *data;
+
+  /* Set class to NULL so freeing an uninitialized local ref can be
+     avoided.  */
+  class = NULL;
+
+  /* Likewise for string.  */
+  string = NULL;
+
+  if (!android_init_gui)
+    goto fail;
+  else
+    {
+      /* Obtain Build.FINGERPRINT.  Clear exceptions after each query;
+        JNI can't find Build.FINGERPRINT on some systems.  */
+
+      class = (*android_java_env)->FindClass (android_java_env,
+                                             "android/os/Build");
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!class)
+       goto fail;
+
+      field = (*android_java_env)->GetStaticFieldID (android_java_env,
+                                                    class,
+                                                    "FINGERPRINT",
+                                                    "Ljava/lang/String;");
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!field)
+       goto fail;
+
+      string
+       = (*android_java_env)->GetStaticObjectField (android_java_env,
+                                                    class, field);
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!string)
+       goto fail;
+
+      data = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                    string, NULL);
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!data)
+       goto fail;
+
+      Vandroid_build_fingerprint = build_string_from_utf8 (data);
+      (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                                 string, data);
+
+      /* Now retrieve Build.MANUFACTURER.  */
+
+      ANDROID_DELETE_LOCAL_REF (string);
+      string = NULL;
+
+      field = (*android_java_env)->GetStaticFieldID (android_java_env,
+                                                    class,
+                                                    "MANUFACTURER",
+                                                    "Ljava/lang/String;");
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!field)
+       goto fail;
+
+      string
+       = (*android_java_env)->GetStaticObjectField (android_java_env,
+                                                    class, field);
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!string)
+       goto fail;
+
+      data = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                    string, NULL);
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      if (!data)
+       goto fail;
+
+      Vandroid_build_manufacturer = build_string_from_utf8 (data);
+      (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                                 string, data);
+    }
+
+  if (string)
+    ANDROID_DELETE_LOCAL_REF (string);
+
+  ANDROID_DELETE_LOCAL_REF (class);
+
+  return;
+
+ fail:
+  if (class)
+    ANDROID_DELETE_LOCAL_REF (class);
+
+  Vandroid_build_fingerprint = Qnil;
+  Vandroid_build_manufacturer = Qnil;
+#endif /* ANDROID_STUBIFY */
+}
+
+void
+syms_of_androidterm (void)
+{
+  Fprovide (Qandroid, Qnil);
+
+  DEFVAR_LISP ("android-wait-for-event-timeout",
+              Vandroid_wait_for_event_timeout,
+    doc: /* How long to wait for Android events.
+
+Emacs will wait up to this many seconds to receive events after
+making changes which affect the state of the graphical interface.
+Under some situations this can take an indefinite amount of time,
+so it is important to limit the wait.
+
+If set to a non-float value, there will be no wait at all.  */);
+  Vandroid_wait_for_event_timeout = make_float (0.1);
+
+  DEFVAR_BOOL ("x-use-underline-position-properties",
+              x_use_underline_position_properties,
+     doc: /* SKIP: real doc in xterm.c.  */);
+  x_use_underline_position_properties = true;
+  DEFSYM (Qx_use_underline_position_properties,
+         "x-use-underline-position-properties");
+
+  DEFVAR_BOOL ("x-underline-at-descent-line",
+              x_underline_at_descent_line,
+     doc: /* SKIP: real doc in xterm.c.  */);
+  x_underline_at_descent_line = false;
+
+  DEFVAR_LISP ("android-build-fingerprint", Vandroid_build_fingerprint,
+    doc: /* String identifying the device's OS version.
+This is a string that uniquely identifies the version of Android
+Emacs is running on.  */);
+  Vandroid_build_fingerprint = Qnil;
+
+  DEFVAR_LISP ("android-build-manufacturer", Vandroid_build_manufacturer,
+    doc: /* Name of the developer of the running version of Android.  */);
+  Vandroid_build_manufacturer = Qnil;
+
+  /* Only defined so loadup.el loads scroll-bar.el.  */
+  DEFVAR_LISP ("x-toolkit-scroll-bars", Vx_toolkit_scroll_bars,
+    doc: /* SKIP: real doc in xterm.c.  */);
+  Vx_toolkit_scroll_bars = Qnil;
+
+  /* Avoid dumping Vandroid_build_fingerprint.  */
+  pdumper_do_now_and_after_load (android_set_build_fingerprint);
+
+  DEFSYM (Qx_underline_at_descent_line, "x-underline-at-descent-line");
+}
+
+void
+mark_androidterm (void)
+{
+  if (x_display_list)
+    mark_object (x_display_list->color_map);
+}
diff --git a/src/androidterm.h b/src/androidterm.h
new file mode 100644
index 00000000000..e75d46b1dfb
--- /dev/null
+++ b/src/androidterm.h
@@ -0,0 +1,481 @@
+/* Communication module for Android terminals.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _ANDROID_TERM_H_
+#define _ANDROID_TERM_H_
+
+#include "androidgui.h"
+#include "frame.h"
+#include "character.h"
+#include "dispextern.h"
+#include "font.h"
+
+struct android_bitmap_record
+{
+  /* The image backing the bitmap and its mask.  */
+  android_pixmap pixmap, mask;
+
+  /* The file from which it comes.  */
+  char *file;
+
+  /* The number of references to it.  */
+  int refcount;
+
+  /* The height and width and the depth.  */
+  int height, width, depth;
+
+  /* Whether or not there is a mask.  */
+  bool have_mask;
+};
+
+struct android_display_info
+{
+  /* Chain of all struct android_display_info structures.  */
+  struct android_display_info *next;
+
+  /* The terminal.  */
+  struct terminal *terminal;
+
+  /* The root window.  This field is unused.  */
+  Emacs_Window root_window;
+
+  /* List possibly used only for the font cache but probably used for
+     something else too.  */
+  Lisp_Object name_list_element;
+
+  /* List of predefined X colors.  */
+  Lisp_Object color_map;
+
+  /* DPI of the display.  */
+  double resx, resy;
+
+  /* DPI used to convert font point sizes into pixel dimensions.
+     This is resy adjusted by a fixed scaling factor specified by
+     the user.  */
+  double font_resolution;
+
+  /* Scratch GC for drawing a cursor in a non-default face. */
+  struct android_gc *scratch_cursor_gc;
+
+  /* Mouse highlight information.  */
+  Mouse_HLInfo mouse_highlight;
+
+  /* Number of planes on this screen.  Always 24.  */
+  int n_planes;
+
+  /* Mask of things causing the mouse to be grabbed.  */
+  int grabbed;
+
+  /* Minimum width over all characters in all fonts in font_table.  */
+  int smallest_char_width;
+
+  /* Minimum font height over all fonts in font_table.  */
+  int smallest_font_height;
+
+  /* The number of fonts opened for this display.  */
+  int n_fonts;
+
+  /* Pointer to bitmap records.  */
+  struct android_bitmap_record *bitmaps;
+
+  /* Allocated size of bitmaps field.  */
+  ptrdiff_t bitmaps_size;
+
+  /* Last used bitmap index.  */
+  ptrdiff_t bitmaps_last;
+
+  /* The frame currently with the input focus.  */
+  struct frame *focus_frame;
+
+  /* The last frame mentioned in a focus event.  */
+  struct frame *x_focus_event_frame;
+
+  /* The frame which currently has the visual highlight, and should
+     get keyboard input.  It points to the focus frame's selected
+     window's frame, but can differ.  */
+  struct frame *highlight_frame;
+
+  /* The frame waiting to be auto-raised in android_read_socket.  */
+  struct frame *pending_autoraise_frame;
+
+  /* The frame where the mouse was the last time a button event
+     happened.  */
+  struct frame *last_mouse_frame;
+
+  /* The frame where the mouse was the last time the mouse glyph
+     changed.  */
+  struct frame *last_mouse_glyph_frame;
+
+  /* The frame where the mouse was the last time mouse motion
+     happened.  */
+  struct frame *last_mouse_motion_frame;
+
+  /* Position where the mouse was last time we reported a motion.
+     This is a position on last_mouse_motion_frame.  It is used in to
+     report the mouse position as well: see
+     android_mouse_position.  */
+  int last_mouse_motion_x, last_mouse_motion_y;
+
+  /* Where the mouse was the last time the mouse moved.  */
+  Emacs_Rectangle last_mouse_glyph;
+
+  /* The time of the last mouse movement.  */
+  Time last_mouse_movement_time;
+
+  /* ID of the last menu event received.  -1 means Emacs is waiting
+     for a context menu event.  */
+  int menu_event_id;
+
+  /* The invisible cursor used for pointer blanking.  */
+  android_cursor invisible_cursor;
+};
+
+/* Structure representing a single tool (finger or stylus) pressed
+   onto a frame.  */
+
+struct android_touch_point
+{
+  /* The next tool on this list.  */
+  struct android_touch_point *next;
+
+  /* The tool ID and the last known X and Y positions.  */
+  int tool_id, x, y;
+
+  /* Whether or not the tool is pressed on the tool bar.  */
+  bool tool_bar_p;
+};
+
+struct android_output
+{
+  /* Graphics contexts for the default font.  */
+  struct android_gc *normal_gc, *reverse_gc, *cursor_gc;
+
+  /* The window used for this frame.  */
+  Emacs_Window window;
+
+  /* Unused field.  */
+  Emacs_Window parent_desc;
+
+  /* Default ASCII font of this frame.  */
+  struct font *font;
+
+  /* The baseline offset of the default ASCII font.  */
+  int baseline_offset;
+
+  /* If a fontset is specified for this frame instead of font, this
+     value contains an ID of the fontset, else -1.  */
+  int fontset;
+
+  /* Various colors.  */
+  unsigned long cursor_pixel;
+  unsigned long mouse_pixel;
+  unsigned long cursor_foreground_pixel;
+
+  /* Foreground color for scroll bars.  A value of -1 means use the
+     default (black for non-toolkit scroll bars).  */
+  unsigned long scroll_bar_foreground_pixel;
+
+  /* Background color for scroll bars.  A value of -1 means use the
+     default (background color of the frame for non-toolkit scroll
+     bars).  */
+  unsigned long scroll_bar_background_pixel;
+
+  /* Cursors associated with this frame.  */
+  Emacs_Cursor text_cursor;
+  Emacs_Cursor nontext_cursor;
+  Emacs_Cursor modeline_cursor;
+  Emacs_Cursor hand_cursor;
+  Emacs_Cursor hourglass_cursor;
+  Emacs_Cursor horizontal_drag_cursor;
+  Emacs_Cursor vertical_drag_cursor;
+  Emacs_Cursor current_cursor;
+  Emacs_Cursor left_edge_cursor;
+  Emacs_Cursor top_left_corner_cursor;
+  Emacs_Cursor top_edge_cursor;
+  Emacs_Cursor top_right_corner_cursor;
+  Emacs_Cursor right_edge_cursor;
+  Emacs_Cursor bottom_right_corner_cursor;
+  Emacs_Cursor bottom_edge_cursor;
+  Emacs_Cursor bottom_left_corner_cursor;
+
+  /* Whether or not the hourglass cursor is being displayed.  */
+  bool hourglass;
+
+  /* This is the Emacs structure for the display this frame is on.  */
+  struct android_display_info *display_info;
+
+  /* True if this frame was ever previously visible.  */
+  bool_bf has_been_visible : 1;
+
+  /* True if this frame's alpha value is the same for both the active
+     and inactive states.  */
+  bool_bf alpha_identical_p : 1;
+
+  /* Flag that indicates whether or not the frame contents are
+     complete and can be safely flushed while handling async
+     input.  */
+  bool_bf complete : 1;
+
+  /* True that indicates whether or not a buffer flip is required
+     because the frame contents have been dirtied.  */
+  bool_bf need_buffer_flip : 1;
+
+  /* Whether or not the input method should be notified every time the
+     position of this frame's selected window changes.  */
+  bool_bf need_cursor_updates : 1;
+
+  /* Relief GCs, colors etc.  */
+  struct relief {
+    struct android_gc *gc;
+    unsigned long pixel;
+  } black_relief, white_relief;
+
+  /* The background for which the above relief GCs were set up.
+     They are changed only when a different background is involved.  */
+  unsigned long relief_background;
+
+  /* Focus state.  Only present for consistency with X; it is actually
+     a boolean.  */
+  int focus_state;
+
+  /* List of all tools (either styluses or fingers) pressed onto the
+     frame.  */
+  struct android_touch_point *touch_points;
+
+  /* Flags associated with the last request to obtain ``extracted
+     text''.  */
+  int extracted_text_flags;
+
+  /* Token asssociated with that request.  */
+  int extracted_text_token;
+
+  /* The number of characters of extracted text wanted by the IM.  */
+  int extracted_text_hint;
+};
+
+enum
+  {
+    /* Values for focus_state, used as bit mask.  EXPLICIT means we
+       received a FocusIn for the frame and know it has the focus.
+       IMPLICIT means we received an EnterNotify and the frame may
+       have the focus if no window manager is running.  FocusOut and
+       LeaveNotify clears EXPLICIT/IMPLICIT. */
+    FOCUS_NONE     = 0,
+    FOCUS_IMPLICIT = 1,
+    FOCUS_EXPLICIT = 2
+  };
+
+/* Return the Android output data for frame F.  */
+#define FRAME_ANDROID_OUTPUT(f)        ((f)->output_data.android)
+#define FRAME_OUTPUT_DATA(f)   ((f)->output_data.android)
+
+/* Return the Android window used for displaying data in frame F.  */
+#define FRAME_ANDROID_WINDOW(f)        ((f)->output_data.android->window)
+#define FRAME_NATIVE_WINDOW(f) ((f)->output_data.android->window)
+
+/* Return the need-buffer-flip flag for frame F.  */
+#define FRAME_ANDROID_NEED_BUFFER_FLIP(f)      \
+  ((f)->output_data.android->need_buffer_flip)
+
+/* Return the drawable used for rendering to frame F and mark the
+   frame as needing a buffer flip later.  There's no easy way to run
+   code after any drawing command, but code can be run whenever
+   someone asks for the handle necessary to draw.  */
+#define FRAME_ANDROID_DRAWABLE(f)                      \
+  (((f))->output_data.android->need_buffer_flip = true, \
+   FRAME_ANDROID_WINDOW ((f)))
+
+/* Return whether or not the frame F has been completely drawn.  Used
+   while handling async input.  */
+#define FRAME_ANDROID_COMPLETE_P(f)            \
+  ((f)->output_data.android->complete)
+
+#define FRAME_FONT(f)          ((f)->output_data.android->font)
+#define FRAME_FONTSET(f)       ((f)->output_data.android->fontset)
+
+#define FRAME_BASELINE_OFFSET(f)               \
+  ((f)->output_data.android->baseline_offset)
+
+/* This gives the android_display_info structure for the display F is
+   on.  */
+#define FRAME_DISPLAY_INFO(f) ((f)->output_data.android->display_info)
+
+/* Some things for X compatibility.  */
+#define BLACK_PIX_DEFAULT(f) 0
+#define WHITE_PIX_DEFAULT(f) 0xffffffff
+
+/* Android-specific scroll bar stuff.  */
+
+/* We represent scroll bars as lisp vectors.  This allows us to place
+   references to them in windows without worrying about whether we'll
+   end up with windows referring to dead scroll bars; the garbage
+   collector will free it when its time comes.
+
+   We use struct scroll_bar as a template for accessing fields of the
+   vector.  */
+
+struct scroll_bar
+{
+  /* These fields are shared by all vectors.  */
+  union vectorlike_header header;
+
+  /* The window we're a scroll bar for.  */
+  Lisp_Object window;
+
+  /* The next and previous in the chain of scroll bars in this frame.  */
+  Lisp_Object next, prev;
+
+  /* Fields after 'prev' are not traced by the GC.  */
+
+  /* The X window representing this scroll bar.  */
+  Emacs_Window x_window;
+
+  /* The position and size of the scroll bar in pixels, relative to the
+     frame.  */
+  int top, left, width, height;
+
+  /* The starting and ending positions of the handle, relative to the
+     handle area (i.e. zero is the top position, not
+     SCROLL_BAR_TOP_BORDER).  If they're equal, that means the handle
+     hasn't been drawn yet.
+
+     These are not actually the locations where the beginning and end
+     are drawn; in order to keep handles from becoming invisible when
+     editing large files, we establish a minimum height by always
+     drawing handle bottoms VERTICAL_SCROLL_BAR_MIN_HANDLE pixels below
+     where they would be normally; the bottom and top are in a
+     different coordinate system.  */
+  int start, end;
+
+  /* If the scroll bar handle is currently being dragged by the user,
+     this is the number of pixels from the top of the handle to the
+     place where the user grabbed it.  If the handle isn't currently
+     being dragged, this is -1.  */
+  int dragging;
+
+  /* True if the scroll bar is horizontal.  */
+  bool horizontal;
+};
+
+/* Turning a lisp vector value into a pointer to a struct scroll_bar.  */
+#define XSCROLL_BAR(vec) ((struct scroll_bar *) XVECTOR (vec))
+
+
+
+/* This is a chain of structures for all the Android displays
+   currently in use.  There is only ever one, but the rest of Emacs is
+   written with systems on which there can be many in mind.  */
+extern struct android_display_info *x_display_list;
+
+
+
+/* Start of function definitions.  These should be a neat subset of
+   the same ones in xterm.h, and come in the same order.  */
+
+/* From androidfns.c.  */
+
+extern void android_free_gcs (struct frame *);
+extern void android_default_font_parameter (struct frame *, Lisp_Object);
+extern void android_set_preeditarea (struct window *, int, int);
+
+/* Defined in androidterm.c.  */
+
+extern void android_term_init (void);
+extern void android_set_window_size (struct frame *, bool, int, int);
+extern void android_iconify_frame (struct frame *);
+extern void android_make_frame_visible (struct frame *);
+extern void android_make_frame_invisible (struct frame *);
+extern void android_free_frame_resources (struct frame *);
+
+extern int android_parse_color (struct frame *, const char *,
+                               Emacs_Color *);
+extern bool android_alloc_nearest_color (struct frame *, Emacs_Color *);
+extern void android_query_colors (struct frame *, Emacs_Color *, int);
+extern void android_clear_under_internal_border (struct frame *);
+
+extern void syms_of_androidterm (void);
+extern void mark_androidterm (void);
+
+/* Defined in androidfns.c.  */
+
+extern void android_change_tab_bar_height (struct frame *, int);
+extern void android_change_tool_bar_height (struct frame *, int);
+extern void android_set_scroll_bar_default_width (struct frame *);
+extern void android_set_scroll_bar_default_height (struct frame *);
+extern bool android_defined_color (struct frame *, const char *,
+                                  Emacs_Color *, bool, bool);
+extern void android_implicitly_set_name (struct frame *, Lisp_Object,
+                                        Lisp_Object);
+extern void android_explicitly_set_name (struct frame *, Lisp_Object,
+                                        Lisp_Object);
+
+extern void syms_of_androidfns (void);
+
+/* Defined in androidfont.c.  */
+
+extern struct font_driver androidfont_driver;
+
+extern void init_androidfont (void);
+extern void syms_of_androidfont (void);
+
+extern void android_finalize_font_entity (struct font_entity *);
+
+/* Defined in androidmenu.c.  */
+
+#ifndef ANDROID_STUBIFY
+
+extern unsigned int current_menu_serial;
+
+#endif
+
+extern Lisp_Object android_menu_show (struct frame *, int, int, int,
+                                     Lisp_Object, const char **);
+extern Lisp_Object android_popup_dialog (struct frame *, Lisp_Object,
+                                        Lisp_Object);
+
+extern void init_androidmenu (void);
+extern void syms_of_androidmenu (void);
+
+/* Defined in sfntfont-android.c.  */
+
+extern const struct font_driver android_sfntfont_driver;
+
+extern void sfntfont_android_shrink_scanline_buffer (void);
+extern void init_sfntfont_android (void);
+extern void syms_of_sfntfont_android (void);
+
+/* Defined in androidselect.c  */
+
+#ifndef ANDROID_STUBIFY
+
+extern void init_androidselect (void);
+extern void syms_of_androidselect (void);
+
+#endif
+
+
+
+#define RGB_TO_ULONG(r, g, b)   (((r) << 16) | ((g) << 8) | (b))
+#define RED_FROM_ULONG(color)  (((color) >> 16) & 0xff)
+#define GREEN_FROM_ULONG(color)        (((color) >> 8) & 0xff)
+#define BLUE_FROM_ULONG(color) ((color) & 0xff)
+
+
+
+#endif /* _ANDROID_TERM_H_ */
diff --git a/src/androidvfs.c b/src/androidvfs.c
new file mode 100644
index 00000000000..d6b832d6caf
--- /dev/null
+++ b/src/androidvfs.c
@@ -0,0 +1,7399 @@
+/* Android virtual file-system support for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <dlfcn.h>
+#include <dirent.h>
+#include <errno.h>
+#include <minmax.h>
+#include <string.h>
+#include <semaphore.h>
+
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <stat-time.h>
+
+#include <linux/ashmem.h>
+
+#include "android.h"
+#include "systime.h"
+#include "blockinput.h"
+
+#if __ANDROID_API__ >= 9
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#else /* __ANDROID_API__ < 9 */
+#include "android-asset.h"
+#endif /* __ANDROID_API__ >= 9 */
+
+#include <android/log.h>
+
+/* This file implements support for the various special-purpose
+   directories found on Android systems through a series of functions
+   that substitute for Unix system call wrappers.  Such directories
+   are not mounted in the Unix virtual file-system, but instead
+   require the use of special system APIs to access; Emacs pretends
+   they are mounted at specific folders within the root directory.
+
+   There are presently two directories: /assets, granting access to
+   asset files stored within the APK, and /content, providing direct
+   access to content URIs (in Android 4.4 and later) and content
+   directory trees (in Android 5.0 and later.)
+
+   Substitutes for the C library `open', `fstat', `close', `fclose',
+   `unlink', `symlink', `rmdir', `rename', `stat' system call wrappers
+   are implemented, which delegate their actions to function tables
+   contained inside ``VFS nodes''.
+
+   The functions of a VFS node are to provide the implementations of
+   the Unix file system operations that can be carried out on the file
+   designated by its name and to connect useful information (such as
+   internal file handles or identifiers) with those file names.  To
+   those ends, there exist several different types of vnodes, each
+   with a different set of functions and supplementary attributes.
+
+   The key to locating the correct vnode for any given file name is an
+   additional file system operation, defined by each node, which
+   ``names'' children.  This operation takes a relative file name and
+   returns a second node designating a constituent sub-file.
+
+   When a file system function is called, it invokes the `name'
+   operation of a special root vnode conceptually located at the top
+   of the Unix file system hierarchy, handing it the complete file
+   name given to it.  This vnode's name operation examines the first
+   component of the relative file name it receives and creates either
+   an asset, content, or Unix vnode, and calls the new vnode's `name'
+   operation with the remainder of the file name.
+
+   The vnode(s) created by each `name' operation may in turn create
+   different vnodes based on the components of the names they have
+   been provided that are used to repeat this process until no
+   components remain.  The vnode created for the last component of the
+   file name will provide its file system operations or be passed as
+   an argument to other file system operations to which the file has
+   been passed as an argument.
+
+   The substitute functions defined have two caveats, which however
+   don't prove problematic in an Emacs context: the first is that the
+   treatment of `..' is inconsistent with Unix, and has not really
+   been tested, while the second is that errno values do not always
+   conform to what the corresponding Unix system calls may return.
+   These caveats are described in more detail inside the last few
+   pages of this file.  */
+
+/* Structure describing an array of VFS operations.  */
+
+struct android_vnode;
+
+struct android_vdir
+{
+  /* Return a `struct dirent' describing the next file in this
+     directory stream, or NULL if the stream has reached its end.  */
+  struct dirent *(*readdir) (struct android_vdir *);
+
+  /* Close and release all resources allocated for this directory
+     stream.  */
+  void (*closedir) (struct android_vdir *);
+
+  /* Return a ``file descriptor'' tied to this directory stream.  */
+  int (*dirfd) (struct android_vdir *);
+};
+
+struct android_vops
+{
+  /* Name a child of the given VFS node, which should be a
+     directory.
+
+     LENGTH should be the length of NAME, excluding that of any
+     trailing NULL byte.
+
+     NAME should be a normalized and NULL-terminated relative file
+     name; it may contain a leading separator characters, but no
+     consecutive ones.
+
+     If NAME is empty, create another VFS node designating the same
+     file instead.
+
+     NAME should also be located within writable storage; it may be
+     overwritten as the vnode sees fit.
+
+     Value is a VFS node corresponding to the child, or NULL upon
+     failure.
+
+     A VFS node may be returned even if NAME does not exist, the
+     expectation being that either a later filesystem operation will
+     fail, or will create the file.  */
+  struct android_vnode *(*name) (struct android_vnode *, char *, size_t);
+
+  /* Open the specified VNODE, returning either a file descriptor or
+     an asset file descriptor.
+
+     FLAGS and MODE mean the same as they do to the Unix `open' system
+     call.
+
+     ASSET_P stipulates if an asset file descriptor may be returned;
+     if true, *ASSET may be set to an asset file descriptor.
+
+     If an asset file descriptor is unavailable or ASSET_P is false,
+     *FD will be set to a file descriptor.
+
+     If the vnode cannot be opened, value is -1 with errno set
+     accordingly.  Otherwise, value is 0 if a file descriptor was
+     returned, and 1 if an asset file descriptor was returned.  */
+  int (*open) (struct android_vnode *, int, mode_t, bool,
+              int *, AAsset **);
+
+  /* Close the specified VNODE, releasing all of its resources.
+     Save errno before making system calls that may set it, and
+     restore it to its original value before returning.
+
+     This is unrelated to `android_close', which primarily releases on
+     stat buffers linked to file or asset file descriptors.  */
+  void (*close) (struct android_vnode *);
+
+  /* Unlink the file and the specified VNODE.  Value and errno are the
+     same as Unix `unlink'.  */
+  int (*unlink) (struct android_vnode *);
+
+  /* Create a symlink from the specified VNODE to the target TARGET.
+     Value and errno are the same as `symlink' on Linux (which notably
+     means that errno is set to EPERM if VNODE doesn't support
+     symlinks.)  */
+  int (*symlink) (const char *, struct android_vnode *);
+
+  /* Remove VNODE from its parent directory.  VNODE must be an empty
+     directory.  Value and errno are the same as Unix `rmdir'.  */
+  int (*rmdir) (struct android_vnode *);
+
+  /* Move the file designated by SRC to DST, overwriting DST if
+     KEEP_EXISTING is false.
+
+     If KEEP_EXISTING is true and DST already exists, value is -1 with
+     errno set to EEXIST.
+
+     If VNODE does not natively support checking for a preexisting DST
+     and KEEP_EXISTING is true, value is -1 with errno set to ENOSYS.
+
+     Value is otherwise the same as `rename'.  */
+  int (*rename) (struct android_vnode *, struct android_vnode *, bool);
+
+  /* Return statistics for the specified VNODE.
+     Value and errno are the same as with Unix `stat'.  */
+  int (*stat) (struct android_vnode *, struct stat *);
+
+  /* Return whether or not VNODE is accessible.
+     Value, errno and MODE are the same as with Unix `access'.  */
+  int (*access) (struct android_vnode *, int);
+
+  /* Make a directory designated by VNODE, like Unix `mkdir'.  */
+  int (*mkdir) (struct android_vnode *, mode_t);
+
+  /* Change the access mode of the provided VNODE to MODE.  Value is
+     the same as with `chmod'.  FLAGS is passed verbatim from the call
+     to the delegating at-func, and is probably
+     AT_SYMLINK_NOFOLLOW.  */
+  int (*chmod) (struct android_vnode *, mode_t, int);
+
+  /* Return the target of VNODE if it is a symbolic link, or -1.
+     Value and errno are the same as with `readlink'.  */
+  ssize_t (*readlink) (struct android_vnode *, char *, size_t);
+
+  /* Open the specified VNODE as a directory.
+     Value is a ``directory handle'', or NULL upon failure.  */
+  struct android_vdir *(*opendir) (struct android_vnode *);
+};
+
+struct android_vnode
+{
+  /* Operations associated with this vnode.  */
+  struct android_vops *ops;
+
+  /* Type of this vnode and its flags.  */
+  short type, flags;
+};
+
+/* Structure describing a special named vnode relative to the root
+   vnode, or another directory vnode.  */
+
+struct android_special_vnode
+{
+  /* The name of the special file.  */
+  const char *name;
+
+  /* The length of that name.  */
+  size_t length;
+
+  /* Function called to create the initial vnode from the rest of the
+     component.  */
+  struct android_vnode *(*initial) (char *, size_t);
+};
+
+enum android_vnode_type
+  {
+    ANDROID_VNODE_UNIX,
+    ANDROID_VNODE_AFS,
+    ANDROID_VNODE_CONTENT,
+    ANDROID_VNODE_CONTENT_AUTHORITY,
+    ANDROID_VNODE_SAF_ROOT,
+    ANDROID_VNODE_SAF_TREE,
+    ANDROID_VNODE_SAF_FILE,
+    ANDROID_VNODE_SAF_NEW,
+  };
+
+
+
+/* Structure describing the android.database.Cursor class.  */
+
+struct android_cursor_class
+{
+  jclass class;
+  jmethodID close;
+};
+
+/* Structure describing the EmacsDirectoryEntry class.  */
+
+struct emacs_directory_entry_class
+{
+  jclass class;
+  jfieldID d_type;
+  jfieldID d_name;
+};
+
+/* Structure describing the android.os.ParcelFileDescriptor class used
+   to wrap file descriptors sent over IPC.  */
+
+struct android_parcel_file_descriptor_class
+{
+  jclass class;
+  jmethodID close;
+  jmethodID get_fd;
+  jmethodID detach_fd;
+};
+
+/* The java.lang.String class.  */
+static jclass java_string_class;
+
+/* Fields and methods associated with the Cursor class.  */
+static struct android_cursor_class cursor_class;
+
+/* Fields and methods associated with the EmacsDirectoryEntry
+   class.  */
+static struct emacs_directory_entry_class entry_class;
+
+/* Fields and methods associated with the ParcelFileDescriptor
+   class.  */
+static struct android_parcel_file_descriptor_class fd_class;
+
+/* Global references to several exception classes.  */
+static jclass file_not_found_exception, security_exception;
+static jclass operation_canceled_exception;
+static jclass unsupported_operation_exception, out_of_memory_error;
+
+/* Initialize `cursor_class' using the given JNI environment ENV.
+   Calling this function is not necessary on Android 4.4 and
+   earlier.  */
+
+static void
+android_init_cursor_class (JNIEnv *env)
+{
+  jclass old;
+
+  cursor_class.class
+    = (*env)->FindClass (env, "android/database/Cursor");
+  eassert (cursor_class.class);
+
+  old = cursor_class.class;
+  cursor_class.class
+    = (jclass) (*env)->NewGlobalRef (env, (jobject) old);
+  (*env)->DeleteLocalRef (env, old);
+
+  if (!cursor_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)           \
+  cursor_class.c_name                                  \
+    = (*env)->GetMethodID (env, cursor_class.class,    \
+                          name, signature);            \
+  assert (cursor_class.c_name);
+  FIND_METHOD (close, "close", "()V");
+#undef FIND_METHOD
+}
+
+/* Initialize `entry_class' using the given JNI environment ENV.
+   Calling this function is not necessary on Android 4.4 and
+   earlier.  */
+
+static void
+android_init_entry_class (JNIEnv *env)
+{
+  jclass old;
+
+  entry_class.class
+    = (*env)->FindClass (env, "org/gnu/emacs/EmacsDirectoryEntry");
+  eassert (entry_class.class);
+
+  old = entry_class.class;
+  entry_class.class
+    = (jclass) (*env)->NewGlobalRef (env, (jobject) old);
+  (*env)->DeleteLocalRef (env, old);
+
+  if (!entry_class.class)
+    emacs_abort ();
+
+  entry_class.d_type = (*env)->GetFieldID (env, entry_class.class,
+                                          "d_type", "I");
+  entry_class.d_name = (*env)->GetFieldID (env, entry_class.class,
+                                          "d_name",
+                                          "Ljava/lang/String;");
+  assert (entry_class.d_type && entry_class.d_name);
+}
+
+
+/* Initialize `fd_class' using the given JNI environment ENV.  Calling
+   this function is not necessary on Android 4.4 and earlier.  */
+
+static void
+android_init_fd_class (JNIEnv *env)
+{
+  jclass old;
+
+  fd_class.class
+    = (*env)->FindClass (env, "android/os/ParcelFileDescriptor");
+  eassert (fd_class.class);
+
+  old = fd_class.class;
+  fd_class.class
+    = (jclass) (*env)->NewGlobalRef (env, (jobject) old);
+  (*env)->DeleteLocalRef (env, old);
+
+  if (!fd_class.class)
+    emacs_abort ();
+
+#define FIND_METHOD(c_name, name, signature)           \
+  fd_class.c_name                                      \
+    = (*env)->GetMethodID (env, fd_class.class,                \
+                          name, signature);            \
+  assert (fd_class.c_name);
+  FIND_METHOD (close, "close", "()V");
+  FIND_METHOD (get_fd, "getFd", "()I");
+  FIND_METHOD (detach_fd, "detachFd", "()I");
+#undef FIND_METHOD
+}
+
+
+
+/* Delete redundant instances of `.' and `..' from NAME in-place.
+   NAME must be *LENGTH long, excluding a mandatory trailing NULL
+   byte.
+
+   Transform each directory component in NAME to avoid instances
+   of the `.' and `..' directories.  For example, turn:
+
+     a/../b/c/.
+
+   into
+
+     b/c/
+
+   and return NULL, writing the new length of NAME into *LENGTH.
+
+   If there are more `..' components in NAME than there are normal
+   file name components, return NAME incremented to the position after
+   the first `..' component that cannot be transformed.  For example,
+   if NAME is
+
+     a/../../a
+
+   value will be
+
+     a
+
+   If NAME is a directory separator and LENGTH is 1, return without
+   modifying NAME.  In any other case, omit any leading directory
+   separator when writing to NAME.  This is useful when a vnode that
+   can only be opened as a directory is desired, as this status is
+   made clear by suffixing the file name with a trailing
+   directory separator.  */
+
+static char *
+android_vfs_canonicalize_name (char *name, size_t *length)
+{
+  size_t nellipsis, i;
+  char *last_component, *prev_component, *fill, *orig_name;
+  size_t size;
+
+  /* Special case described in the last paragraph of the comment
+     above.  */
+
+  size = *length;
+  orig_name = name;
+
+  if (*name == '/' && size == 1)
+    return NULL;
+  else if (*name == '/')
+    size -= 1;
+
+  nellipsis = 0; /* Number of ellipsis encountered within the current
+                   file name component, or -1.  */
+  prev_component = NULL; /* Pointer to the separator character of
+                           the component immediately before the
+                           component currently being written.  */
+  last_component = name; /* Pointer to the separator character of
+                           the component currently being read.  */
+  fill = name; /* Pointer to the next character that will be written
+                 within NAME.  */
+
+  /* Adjust name to skip the leading directory separator.  But only
+     after fill is set.  */
+  if (*name == '/')
+    name++;
+
+  for (i = 0; i < size; ++i)
+    {
+      switch (name[i])
+       {
+       case '/':
+         /* See if the previous component was `..' or `.'.
+
+            If it is .., and if no previous directory separator was
+            encountered, return or look up a vnode representing the
+            parent.  */
+
+         if (nellipsis == 2)
+           {
+             /* .. */
+
+             if (!prev_component)
+               goto parent_vnode;
+
+             /* Return to the last component.  */
+             fill = prev_component;
+
+             /* Restore last_component to prev_component, and
+                prev_component back to the component before that.  */
+             last_component = prev_component;
+
+             if (last_component != name)
+               prev_component = memrchr (name, '/',
+                                         last_component - name - 1);
+             else
+               prev_component = NULL;
+
+             /* prev_component may now be NULL.  If last_component is
+                the same as NAME, then fill has really been returned
+                to the beginning of the string, so leave it be.  But
+                if it's something else, then it must be the first
+                separator character in the string, so set
+                prev_component to NAME itself.  */
+
+             if (!prev_component && last_component != name)
+               prev_component = name;
+           }
+         else if (nellipsis == 1)
+           /* If it's ., return to this component.  */
+           fill = last_component;
+         else
+           {
+             /* Record the position of the last directory separator,
+                so NAME can be overwritten from there onwards if `..'
+                or `.' are encountered.  */
+             prev_component = last_component;
+             last_component = fill;
+           }
+
+         /* Allow tracking ellipses again.  */
+         nellipsis = 0;
+         break;
+
+       case '.':
+         if (nellipsis != -1)
+           nellipsis++;
+         break;
+
+       default:
+         nellipsis = -1;
+         break;
+       }
+
+      /* Now copy this character over from NAME.  */
+      *fill++ = name[i];
+    }
+
+  /* See if the previous component was `..' or `.'.
+
+     If it is .., and if no previous directory separator was
+     encountered, return or look up a vnode representing the
+     parent.  */
+
+  if (nellipsis == 2)
+    {
+      /* .. */
+
+      if (!prev_component)
+       /* Look up the rest of the vnode in its parent.  */
+       goto parent_vnode;
+
+      /* Return to the last component.  */
+      fill = prev_component;
+      nellipsis = -2;
+    }
+  else if (nellipsis == 1)
+    {
+      /* If it's ., return to this component.  */
+      fill = last_component;
+      nellipsis = -2;
+    }
+
+  /* Now, if there's enough room and an ellipsis file name was the
+     last component of END, append a trailing `/' before NULL
+     terminating it, indicating that the file name must be a
+     directory.  */
+
+  if (fill + 1 < name + size && nellipsis == -2)
+    *fill++ = '/';
+
+  /* NULL terminate fill.  */
+  *fill = '\0';
+  *length = fill - orig_name;
+  return NULL;
+
+ parent_vnode:
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+  return name + i;
+}
+
+
+
+/* Unix vnode implementation.  These VFS nodes directly wrap around
+   the Unix filesystem, with the exception of the root vnode.  */
+
+struct android_unix_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* Length of the name without a trailing null byte.  */
+  size_t name_length;
+
+  /* Name of the vnode.  */
+  char *name;
+};
+
+struct android_unix_vdir
+{
+  /* The directory function table.  */
+  struct android_vdir vdir;
+
+  /* The directory stream.  */
+  DIR *directory;
+};
+
+/* The vnode representing the root filesystem.  */
+static struct android_unix_vnode root_vnode;
+
+static struct android_vnode *android_unix_name (struct android_vnode *,
+                                               char *, size_t);
+static int android_unix_open (struct android_vnode *, int,
+                             mode_t, bool, int *, AAsset **);
+static void android_unix_close (struct android_vnode *);
+static int android_unix_unlink (struct android_vnode *);
+static int android_unix_symlink (const char *, struct android_vnode *);
+static int android_unix_rmdir (struct android_vnode *);
+static int android_unix_rename (struct android_vnode *,
+                               struct android_vnode *, bool);
+static int android_unix_stat (struct android_vnode *, struct stat *);
+static int android_unix_access (struct android_vnode *, int);
+static int android_unix_mkdir (struct android_vnode *, mode_t);
+static int android_unix_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_unix_readlink (struct android_vnode *, char *,
+                                     size_t);
+static struct android_vdir *android_unix_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with Unix filesystem VFS
+   nodes.  */
+
+static struct android_vops unix_vfs_ops =
+  {
+    android_unix_name,
+    android_unix_open,
+    android_unix_close,
+    android_unix_unlink,
+    android_unix_symlink,
+    android_unix_rmdir,
+    android_unix_rename,
+    android_unix_stat,
+    android_unix_access,
+    android_unix_mkdir,
+    android_unix_chmod,
+    android_unix_readlink,
+    android_unix_opendir,
+  };
+
+static struct android_vnode *
+android_unix_name (struct android_vnode *vnode, char *name,
+                  size_t length)
+{
+  struct android_unix_vnode *vp, *input, temp;
+  char *fill, *remainder;
+  size_t j;
+
+  /* Canonicalize NAME.  */
+  input = (struct android_unix_vnode *) vnode;
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the parent
+     vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* Create a new unix vnode.  */
+  vp = xmalloc (sizeof *vp);
+
+  /* If name is empty, duplicate the current vnode.  */
+
+  if (length < 1)
+    {
+      memcpy (vp, vnode, sizeof *vp);
+      vp->name = xstrdup (vp->name);
+      return &vp->vnode;
+    }
+
+  /* Otherwise, fill in the vnode.  */
+
+  vp->vnode.ops = &unix_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_UNIX;
+  vp->vnode.flags = 0;
+
+  /* Generate the new name of the vnode.  Remove any trailing slash
+     from vp->name.  */
+
+  vp->name_length = input->name_length + length;
+  vp->name = xmalloc (vp->name_length + 2);
+
+  /* Copy the parent name over.  */
+  fill = mempcpy (vp->name, input->name, input->name_length);
+
+  /* Check if it contains a trailing slash.  input->name cannot be
+     empty, as the root vnode's name is `/'.  */
+
+  if (fill[-1] != '/' && *name != '/')
+    /* If not, append a trailing slash and adjust vp->name_length
+       correspondingly.  */
+    *fill++ = '/', vp->name_length++;
+  else if (fill[-1] == '/' && *name == '/')
+    /* If name has a leading slash and fill does too, move fill
+       backwards so that name's slash will override that of fill.  */
+    fill--, vp->name_length--;
+
+  /* Now copy NAME.  */
+  fill = mempcpy (fill, name, length);
+
+  /* And NULL terminate fill.  */
+  *fill = '\0';
+  return &vp->vnode;
+
+ parent_vnode:
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (input->name_length == 1)
+    /* This is the vnode representing the root directory; just look
+       within itself... */
+    vnode = &root_vnode.vnode;
+  else
+    {
+      /* Create a temporary asset vnode within the parent and use it
+         instead.  First, establish the length of vp->name before its
+         last component.  */
+
+      for (j = input->name_length - 1; j; --j)
+       {
+         if (input->name[j - 1] == '/')
+           break;
+       }
+
+      /* There must be at least one leading directory separator in an
+        asset vnode's `name' field.  */
+
+      if (!j)
+       abort ();
+
+      /* j is now the length of the string minus the size of its last
+        component.  Create a temporary vnode with that as its
+        name.  */
+
+      temp.vnode.ops = &unix_vfs_ops;
+      temp.vnode.type = ANDROID_VNODE_UNIX;
+      temp.vnode.flags = 0;
+      temp.name_length = j;
+      temp.name = xmalloc (j + 1);
+      fill = mempcpy (temp.name, input->name, j);
+      *fill = '\0';
+
+      /* Search for the remainder of NAME relative to its parent.  */
+      vnode = android_unix_name (&temp.vnode, remainder,
+                                strlen (remainder));
+      xfree (temp.name);
+      return vnode;
+    }
+
+  return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
+}
+
+/* Create a Unix vnode representing the given file NAME.  Use this
+   function to create vnodes that aren't rooted in the root VFS
+   node.  */
+
+static struct android_vnode *
+android_unix_vnode (const char *name)
+{
+  struct android_unix_vnode *vp;
+
+  vp = xmalloc (sizeof *vp);
+  vp->vnode.ops = &unix_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_UNIX;
+  vp->vnode.flags = 0;
+  vp->name_length = strlen (name);
+  vp->name = xstrdup (name);
+  return &vp->vnode;
+}
+
+static int
+android_unix_open (struct android_vnode *vnode, int flags,
+                  mode_t mode, bool asset_p, int *fd,
+                  AAsset **asset)
+{
+  struct android_unix_vnode *vp;
+  int fds;
+
+  vp = (struct android_unix_vnode *) vnode;
+  fds = open (vp->name, flags, mode);
+
+  if (fds < 0)
+    return -1;
+
+  *fd = fds;
+  return 0;
+}
+
+static void
+android_unix_close (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+  int save_errno;
+
+  save_errno = errno;
+  vp = (struct android_unix_vnode *) vnode;
+  xfree (vp->name);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_unix_unlink (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return unlink (vp->name);
+}
+
+static int
+android_unix_symlink (const char *target, struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return symlink (target, vp->name);
+}
+
+static int
+android_unix_rmdir (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return rmdir (vp->name);
+}
+
+static int
+android_unix_rename (struct android_vnode *src,
+                    struct android_vnode *dst,
+                    bool keep_existing)
+{
+  struct android_unix_vnode *vp, *dest;
+
+  if (src->type != dst->type)
+    {
+      /* If the types of both vnodes differ, complain that they're on
+        two different filesystems (which is correct from a abstract
+        viewpoint.)  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  vp = (struct android_unix_vnode *) src;
+  dest = (struct android_unix_vnode *) dst;
+
+  return (keep_existing
+         ? renameat_noreplace (AT_FDCWD, vp->name,
+                               AT_FDCWD, dest->name)
+         : rename (vp->name, dest->name));
+}
+
+static int
+android_unix_stat (struct android_vnode *vnode, struct stat *statb)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return stat (vp->name, statb);
+}
+
+static int
+android_unix_access (struct android_vnode *vnode, int mode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return access (vp->name, mode);
+}
+
+static int
+android_unix_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return mkdir (vp->name, mode);
+}
+
+static int
+android_unix_chmod (struct android_vnode *vnode, mode_t mode,
+                   int flags)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return fchmodat (AT_FDCWD, vp->name, mode, flags);
+}
+
+static ssize_t
+android_unix_readlink (struct android_vnode *vnode, char *buffer,
+                      size_t size)
+{
+  struct android_unix_vnode *vp;
+
+  vp = (struct android_unix_vnode *) vnode;
+  return readlink (vp->name, buffer, size);
+}
+
+static struct dirent *
+android_unix_readdir (struct android_vdir *vdir)
+{
+  struct android_unix_vdir *dir;
+
+  dir = (struct android_unix_vdir *) vdir;
+  return readdir (dir->directory);
+}
+
+static void
+android_unix_closedir (struct android_vdir *vdir)
+{
+  struct android_unix_vdir *dir;
+
+  dir = (struct android_unix_vdir *) vdir;
+  closedir (dir->directory);
+  xfree (vdir);
+}
+
+static int
+android_unix_dirfd (struct android_vdir *vdir)
+{
+  struct android_unix_vdir *dir;
+
+  dir = (struct android_unix_vdir *) vdir;
+  return dirfd (dir->directory);
+}
+
+static struct android_vdir *
+android_unix_opendir (struct android_vnode *vnode)
+{
+  struct android_unix_vnode *vp;
+  struct android_unix_vdir *dir;
+  DIR *directory;
+
+  /* Try to opendir the vnode.  */
+  vp = (struct android_unix_vnode *) vnode;
+  directory = opendir (vp->name);
+
+  if (!directory)
+    return NULL;
+
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_unix_readdir;
+  dir->vdir.closedir = android_unix_closedir;
+  dir->vdir.dirfd = android_unix_dirfd;
+  dir->directory = directory;
+  return &dir->vdir;
+}
+
+
+
+/* Asset directory handling functions.  ``directory-tree'' is a file in
+   the root of the assets directory describing its contents.
+
+   See lib-src/asset-directory-tool for more details.  */
+
+/* The Android directory tree.  */
+static const char *directory_tree;
+
+/* The size of the directory tree.  */
+static size_t directory_tree_size;
+
+/* The asset manager being used.  */
+static AAssetManager *asset_manager;
+
+/* Read an unaligned (32-bit) long from the address POINTER.  */
+
+static unsigned int
+android_extract_long (char *pointer)
+{
+  unsigned int number;
+
+  memcpy (&number, pointer, sizeof number);
+  return number;
+}
+
+/* Scan to the file FILE in the asset directory tree.  Return a
+   pointer to the end of that file (immediately before any children)
+   in the directory tree, or NULL if that file does not exist.
+
+   If returning non-NULL, also return the offset to the end of the
+   last subdirectory or file in *LIMIT_RETURN.  LIMIT_RETURN may be
+   NULL.
+
+   FILE must have less than 11 levels of nesting.  If it ends with a
+   trailing slash, then NULL will be returned if it is not actually a
+   directory.  */
+
+static const char *
+android_scan_directory_tree (char *file, size_t *limit_return)
+{
+  char *token, *saveptr, *copy, *copy1, *start, *max, *limit;
+  size_t token_length, ntokens, i;
+  char *tokens[10];
+
+  USE_SAFE_ALLOCA;
+
+  /* Skip past the 5 byte header.  */
+  start = (char *) directory_tree + 5;
+
+  /* Figure out the current limit.  */
+  limit = (char *) directory_tree + directory_tree_size;
+
+  /* Now, split `file' into tokens, with the delimiter being the file
+     name separator.  Look for the file and seek past it.  */
+
+  ntokens = 0;
+  saveptr = NULL;
+  copy = copy1 = xstrdup (file);
+  memset (tokens, 0, sizeof tokens);
+
+  while ((token = strtok_r (copy, "/", &saveptr)))
+    {
+      copy = NULL;
+
+      /* Make sure ntokens is within bounds.  */
+      if (ntokens == ARRAYELTS (tokens))
+       {
+         xfree (copy1);
+         goto fail;
+       }
+
+      tokens[ntokens] = SAFE_ALLOCA (strlen (token) + 1);
+      memcpy (tokens[ntokens], token, strlen (token) + 1);
+      ntokens++;
+    }
+
+  /* Free the copy created for strtok_r.  */
+  xfree (copy1);
+
+  /* If there are no tokens, just return the start of the directory
+     tree.  */
+
+  if (!ntokens)
+    {
+      SAFE_FREE ();
+
+      /* Return the size of the directory tree as the limit.
+         Do not subtract the initial header bytes, as the limit
+         is an offset from the start of the file.  */
+
+      if (limit_return)
+       *limit_return = directory_tree_size;
+
+      return start;
+    }
+
+  /* Loop through tokens, indexing the directory tree each time.  */
+
+  for (i = 0; i < ntokens; ++i)
+    {
+      token = tokens[i];
+
+      /* Figure out how many bytes to compare.  */
+      token_length = strlen (token);
+
+    again:
+
+      /* If this would be past the directory, return NULL.  */
+      if (start + token_length > limit)
+       goto fail;
+
+      /* Now compare the file name.  */
+      if (!memcmp (start, token, token_length))
+       {
+         /* They probably match.  Find the NULL byte.  It must be
+            either one byte past start + token_length, with the last
+            byte a trailing slash (indicating that it is a
+            directory), or just start + token_length.  Return 4 bytes
+            past the next NULL byte.  */
+
+         max = memchr (start, 0, limit - start);
+
+         if (max != start + token_length
+             && !(max == start + token_length + 1
+                  && *(max - 1) == '/'))
+           goto false_positive;
+
+         /* Return it if it exists and is in range, and this is the
+            last token.  Otherwise, set it as start and the limit as
+            start + the offset and continue the loop.  */
+
+         if (max && max + 5 <= limit)
+           {
+             if (i < ntokens - 1)
+               {
+                 start = max + 5;
+                 limit = ((char *) directory_tree
+                          + android_extract_long (max + 1));
+
+                 /* Make sure limit is still in range.  */
+                 if (limit > directory_tree + directory_tree_size
+                     || start > directory_tree + directory_tree_size)
+                   goto fail;
+
+                 continue;
+               }
+
+             /* Now see if max is not a directory and file is.  If
+                file is a directory, then return NULL.  */
+             if (*(max - 1) != '/' && file[strlen (file) - 1] == '/')
+               max = NULL;
+             else
+               {
+                 /* Figure out the limit.  */
+                 if (limit_return)
+                   *limit_return = android_extract_long (max + 1);
+
+                 /* Go to the end of this file.  */
+                 max += 5;
+               }
+
+             SAFE_FREE ();
+             return max;
+           }
+
+         /* Return NULL otherwise.  */
+         __android_log_print (ANDROID_LOG_WARN, __func__,
+                              "could not scan to end of directory tree"
+                              ": %s", file);
+         goto fail;
+       }
+
+    false_positive:
+
+      /* No match was found.  Set start to the next sibling and try
+        again.  */
+
+      start = memchr (start, 0, limit - start);
+
+      if (!start || start + 5 > limit)
+       goto fail;
+
+      start = ((char *) directory_tree
+              + android_extract_long (start + 1));
+
+      /* Make sure start is still in bounds.  */
+
+      if (start > limit)
+       goto fail;
+
+      /* Continue the loop.  */
+      goto again;
+    }
+
+ fail:
+  SAFE_FREE ();
+  return NULL;
+}
+
+/* Return whether or not the directory tree entry DIR is a
+   directory.
+
+   DIR should be a value returned by
+   `android_scan_directory_tree'.  */
+
+static bool
+android_is_directory (const char *dir)
+{
+  /* If the directory is the directory tree, then it is a
+     directory.  */
+  if (dir == directory_tree + 5)
+    return true;
+
+  /* Otherwise, look 5 bytes behind.  If it is `/', then it is a
+     directory.  */
+  return (dir - 6 >= directory_tree
+         && *(dir - 6) == '/');
+}
+
+/* Initialize asset retrieval.  ENV should be a JNI environment for
+   the Emacs thread, and MANAGER should be a local reference to a Java
+   asset manager object created for the Emacs service context.  */
+
+static void
+android_init_assets (JNIEnv *env, jobject manager)
+{
+  AAsset *asset;
+
+  /* Set the asset manager.  */
+  asset_manager = AAssetManager_fromJava (env, manager);
+
+  /* Initialize the directory tree.  */
+  asset = AAssetManager_open (asset_manager, "directory-tree",
+                             AASSET_MODE_BUFFER);
+
+  if (!asset)
+    {
+      __android_log_print (ANDROID_LOG_FATAL, __func__,
+                          "Failed to open directory tree");
+      emacs_abort ();
+    }
+
+  directory_tree = AAsset_getBuffer (asset);
+
+  if (!directory_tree)
+    emacs_abort ();
+
+  /* Now figure out how big the directory tree is, and compare the
+     first few bytes.  */
+  directory_tree_size = AAsset_getLength (asset);
+  if (directory_tree_size < 5
+      || memcmp (directory_tree, "EMACS", 5))
+    {
+      __android_log_print (ANDROID_LOG_FATAL, __func__,
+                          "Directory tree has bad magic");
+      emacs_abort ();
+    }
+
+  /* Hold a VM reference to the asset manager to prevent the native
+     object from being deleted.  */
+  (*env)->NewGlobalRef (env, manager);
+
+  /* Abort if there's no more memory for the global reference.  */
+  if ((*env)->ExceptionCheck (env))
+    abort ();
+}
+
+
+
+/* Asset-to-file descriptor conversion.  */
+
+/* Pointer to the `ASharedMemory_create' function which is loaded
+   dynamically.  */
+static int (*asharedmemory_create) (const char *, size_t);
+
+/* Do the same as android_hack_asset_fd, but use an unlinked temporary
+   file to cater to old Android kernels where ashmem files are not
+   readable.  */
+
+static int
+android_hack_asset_fd_fallback (AAsset *asset)
+{
+  int fd;
+  char filename[PATH_MAX];
+  size_t size;
+  void *mem;
+
+  /* Assets must be small enough to fit in size_t, if off_t is
+     larger.  */
+  size = AAsset_getLength (asset);
+
+  /* Get an unlinked file descriptor from a file in the cache
+     directory, which is guaranteed to only be written to by Emacs.
+     Creating an ashmem file descriptor and reading from it doesn't
+     work on these old Android versions.  */
+
+  snprintf (filename, PATH_MAX, "%s/temp~unlinked.%d",
+           android_cache_dir, getpid ());
+  fd = open (filename, O_CREAT | O_RDWR | O_TRUNC,
+            S_IRUSR | S_IWUSR);
+
+  if (fd < 0)
+    return -1;
+
+  if (unlink (filename))
+    goto fail;
+
+  if (ftruncate (fd, size))
+    goto fail;
+
+  mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+  if (mem == MAP_FAILED)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "mmap: %s", strerror (errno));
+      goto fail;
+    }
+
+  if (AAsset_read (asset, mem, size) != size)
+    {
+      /* Too little was read.  Close the file descriptor and
+        report an error.  */
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "AAsset_read: %s", strerror (errno));
+      goto fail;
+    }
+
+  munmap (mem, size);
+  return fd;
+
+ fail:
+  close (fd);
+  return -1;
+}
+
+/* Return whether or not shared memory file descriptors can also be
+   read from, and are thus suitable for creating asset files.
+
+   This does not work on some ancient Android systems running old
+   versions of the kernel.  */
+
+static bool
+android_detect_ashmem (void)
+{
+  int fd, rc;
+  void *mem;
+  char test_buffer[10];
+
+  memcpy (test_buffer, "abcdefghi", 10);
+
+  /* Create the file descriptor to be used for the test.  */
+
+  /* Android 28 and earlier let Emacs access /dev/ashmem directly, so
+     prefer that over using ASharedMemory.  */
+
+  if (android_get_current_api_level () <= 28)
+    {
+      fd = open ("/dev/ashmem", O_RDWR);
+
+      if (fd < 0)
+       return false;
+
+      /* An empty name means the memory area will exist until the file
+        descriptor is closed, because no other process can
+        attach.  */
+      rc = ioctl (fd, ASHMEM_SET_NAME, "");
+
+      if (rc < 0)
+       {
+         close (fd);
+         return false;
+       }
+
+      rc = ioctl (fd, ASHMEM_SET_SIZE, sizeof test_buffer);
+
+      if (rc < 0)
+       {
+         close (fd);
+         return false;
+       }
+    }
+  else
+    {
+      /* On the other hand, SELinux restrictions on Android 29 and
+        later require that Emacs use a system service to obtain
+        shared memory.  Load this dynamically, as this service is not
+        available on all versions of the NDK.  */
+
+      if (!asharedmemory_create)
+       {
+         *(void **) (&asharedmemory_create)
+           = dlsym (RTLD_DEFAULT, "ASharedMemory_create");
+
+         if (!asharedmemory_create)
+           {
+             __android_log_print (ANDROID_LOG_FATAL, __func__,
+                                  "dlsym: %s\n",
+                                  strerror (errno));
+             emacs_abort ();
+           }
+       }
+
+      fd = (*asharedmemory_create) ("", sizeof test_buffer);
+
+      if (fd < 0)
+       return false;
+    }
+
+  /* Now map the resource and write the test contents.  */
+
+  mem = mmap (NULL, sizeof test_buffer, PROT_WRITE,
+             MAP_SHARED, fd, 0);
+  if (mem == MAP_FAILED)
+    {
+      close (fd);
+      return false;
+    }
+
+  /* Copy over the test contents.  */
+  memcpy (mem, test_buffer, sizeof test_buffer);
+
+  /* Return anyway even if munmap fails.  */
+  munmap (mem, sizeof test_buffer);
+
+  /* Try to read the content back into test_buffer.  If this does not
+     compare equal to the original string, or the read fails, then
+     ashmem descriptors are not readable on this system.  */
+
+  if ((read (fd, test_buffer, sizeof test_buffer)
+       != sizeof test_buffer)
+      || memcmp (test_buffer, "abcdefghi", sizeof test_buffer))
+    {
+      __android_log_print (ANDROID_LOG_WARN, __func__,
+                          "/dev/ashmem does not produce real"
+                          " temporary files on this system, so"
+                          " Emacs will fall back to creating"
+                          " unlinked temporary files.");
+      close (fd);
+      return false;
+    }
+
+  close (fd);
+  return true;
+}
+
+/* Get a file descriptor backed by a temporary in-memory file for the
+   given asset.  */
+
+static int
+android_hack_asset_fd (AAsset *asset)
+{
+  static bool ashmem_readable_p;
+  static bool ashmem_initialized;
+  int fd, rc;
+  unsigned char *mem;
+  size_t size;
+
+  /* The first time this function is called, try to determine whether
+     or not ashmem file descriptors can be read from.  */
+
+  if (!ashmem_initialized)
+    ashmem_readable_p
+      = android_detect_ashmem ();
+  ashmem_initialized = true;
+
+  /* If it isn't, fall back.  */
+
+  if (!ashmem_readable_p)
+    return android_hack_asset_fd_fallback (asset);
+
+  /* Assets must be small enough to fit in size_t, if off_t is
+     larger.  */
+  size = AAsset_getLength (asset);
+
+  /* Android 28 and earlier let Emacs access /dev/ashmem directly, so
+     prefer that over using ASharedMemory.  */
+
+  if (android_get_current_api_level () <= 28)
+    {
+      fd = open ("/dev/ashmem", O_RDWR);
+
+      if (fd < 0)
+       return -1;
+
+      /* An empty name means the memory area will exist until the file
+        descriptor is closed, because no other process can
+        attach.  */
+      rc = ioctl (fd, ASHMEM_SET_NAME, "");
+
+      if (rc < 0)
+       {
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "ioctl ASHMEM_SET_NAME: %s",
+                              strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      rc = ioctl (fd, ASHMEM_SET_SIZE, size);
+
+      if (rc < 0)
+       {
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "ioctl ASHMEM_SET_SIZE: %s",
+                              strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      if (!size)
+       return fd;
+
+      /* Now map the resource.  */
+      mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+      if (mem == MAP_FAILED)
+       {
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "mmap: %s", strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      if (AAsset_read (asset, mem, size) != size)
+       {
+         /* Too little was read.  Close the file descriptor and
+            report an error.  */
+         __android_log_print (ANDROID_LOG_ERROR, __func__,
+                              "AAsset_read: %s", strerror (errno));
+         close (fd);
+         return -1;
+       }
+
+      /* Return anyway even if munmap fails.  */
+      munmap (mem, size);
+      return fd;
+    }
+
+  /* On the other hand, SELinux restrictions on Android 29 and later
+     require that Emacs use a system service to obtain shared memory.
+     Load this dynamically, as this service is not available on all
+     versions of the NDK.  */
+
+  if (!asharedmemory_create)
+    {
+      *(void **) (&asharedmemory_create)
+       = dlsym (RTLD_DEFAULT, "ASharedMemory_create");
+
+      if (!asharedmemory_create)
+       {
+         __android_log_print (ANDROID_LOG_FATAL, __func__,
+                              "dlsym: %s\n",
+                              strerror (errno));
+         emacs_abort ();
+       }
+    }
+
+  fd = (*asharedmemory_create) ("", size);
+
+  if (fd < 0)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "ASharedMemory_create: %s",
+                          strerror (errno));
+      return -1;
+    }
+
+  /* Now map the resource.  */
+  mem = mmap (NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
+  if (mem == MAP_FAILED)
+    {
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "mmap: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  if (AAsset_read (asset, mem, size) != size)
+    {
+      /* Too little was read.  Close the file descriptor and
+        report an error.  */
+      __android_log_print (ANDROID_LOG_ERROR, __func__,
+                          "AAsset_read: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  /* Return anyway even if munmap fails.  */
+  munmap (mem, size);
+  return fd;
+}
+
+
+
+/* ``Asset file system'' vnode implementation.  These vnodes map to
+   asset files within the application package, provided by the Android
+   ``asset manager''.  */
+
+struct android_afs_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* Length of the name without a trailing null byte.  */
+  size_t name_length;
+
+  /* Name of the vnode.  */
+  char *name;
+};
+
+struct android_afs_vdir
+{
+  /* The directory function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory stream in `all_afs_vdirs'.  */
+  struct android_afs_vdir *next;
+
+  /* Pointer to the directory in directory_tree.  */
+  char *asset_dir;
+
+  /* And the end of the files in asset_dir.  */
+  char *asset_limit;
+
+  /* Path to the directory relative to /.  */
+  char *asset_file;
+
+  /* File descriptor representing this directory stream, or NULL.  */
+  int fd;
+};
+
+struct android_afs_open_fd
+{
+  /* The next table entry.  */
+  struct android_afs_open_fd *next;
+
+  /* The open file descriptor.  */
+  int fd;
+
+  /* The stat buffer associated with this entry.  */
+  struct stat statb;
+};
+
+static struct android_vnode *android_afs_name (struct android_vnode *,
+                                              char *, size_t);
+static int android_afs_open (struct android_vnode *, int,
+                            mode_t, bool, int *, AAsset **);
+static void android_afs_close (struct android_vnode *);
+static int android_afs_unlink (struct android_vnode *);
+static int android_afs_symlink (const char *, struct android_vnode *);
+static int android_afs_rmdir (struct android_vnode *);
+static int android_afs_rename (struct android_vnode *,
+                              struct android_vnode *, bool);
+static int android_afs_stat (struct android_vnode *, struct stat *);
+static int android_afs_access (struct android_vnode *, int);
+static int android_afs_mkdir (struct android_vnode *, mode_t);
+static int android_afs_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_afs_readlink (struct android_vnode *, char *,
+                                    size_t);
+static struct android_vdir *android_afs_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with asset VFS nodes.  */
+
+static struct android_vops afs_vfs_ops =
+  {
+    android_afs_name,
+    android_afs_open,
+    android_afs_close,
+    android_afs_unlink,
+    android_afs_symlink,
+    android_afs_rmdir,
+    android_afs_rename,
+    android_afs_stat,
+    android_afs_access,
+    android_afs_mkdir,
+    android_afs_chmod,
+    android_afs_readlink,
+    android_afs_opendir,
+  };
+
+/* Chain consisting of all open asset directory streams.  */
+static struct android_afs_vdir *all_afs_vdirs;
+
+/* List linking open file descriptors to asset information.  This
+   assumes Emacs does not use dup on regular files.  */
+static struct android_afs_open_fd *afs_file_descriptors;
+
+static struct android_vnode *
+android_afs_name (struct android_vnode *vnode, char *name,
+                 size_t length)
+{
+  size_t j;
+  char *remainder, *fill;
+  struct android_afs_vnode *vp, *input;
+  struct android_afs_vnode temp;
+
+  input = (struct android_afs_vnode *) vnode;
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the parent
+     vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* Allocate a new vnode.  */
+  vp = xmalloc (sizeof *vp);
+
+  /* See the specified name is empty.  */
+
+  if (length < 1)
+    {
+      memcpy (vp, vnode, sizeof *vp);
+      vp->name = xstrdup (vp->name);
+      return &vp->vnode;
+    }
+
+  /* Recompute length.  */
+  vp->vnode.ops = &afs_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_AFS;
+  vp->vnode.flags = 0;
+
+  /* Generate the new name of the vnode.  Remove any trailing slash
+     from vp->name.  */
+
+  vp->name_length = input->name_length + length;
+  vp->name = xmalloc (vp->name_length + 2);
+
+  /* Copy the parent name over.  */
+  fill = mempcpy (vp->name, input->name, input->name_length);
+
+  /* Check if it contains a trailing slash.  input->name cannot be
+     empty, as the root vnode's name is `/'.  */
+
+  if (fill[-1] != '/' && *name != '/')
+    /* If not, append a trailing slash and adjust vp->name_length
+       correspondingly.  */
+    *fill++ = '/', vp->name_length++;
+  else if (fill[-1] == '/' && *name == '/')
+    /* If name has a leading slash and fill does too, move fill
+       backwards so that name's slash will override that of fill.  */
+    fill--, vp->name_length--;
+
+  /* Now copy NAME.  */
+  fill = mempcpy (fill, name, length);
+
+  /* And NULL terminate fill.  */
+  *fill = '\0';
+  return &vp->vnode;
+
+ parent_vnode:
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (input->name_length == 1)
+    /* This is the vnode representing the /assets directory... */
+    vnode = &root_vnode.vnode;
+  else
+    {
+      /* Create a temporary asset vnode within the parent and use it
+         instead.  First, establish the length of vp->name before its
+         last component.  */
+
+      for (j = input->name_length - 1; j; --j)
+       {
+         if (input->name[j - 1] == '/')
+           break;
+       }
+
+      /* There must be at least one leading directory separator in an
+        asset vnode's `name' field.  */
+
+      if (!j)
+       abort ();
+
+      /* j is now the length of the string minus the size of its last
+        component.  Create a temporary vnode with that as its
+        name.  */
+
+      temp.vnode.ops = &afs_vfs_ops;
+      temp.vnode.type = ANDROID_VNODE_AFS;
+      temp.vnode.flags = 0;
+      temp.name_length = j;
+      temp.name = xmalloc (j + 1);
+      fill = mempcpy (temp.name, input->name, j);
+      *fill = '\0';
+
+      /* Search for the remainder of NAME relative to its parent.  */
+      vnode = android_afs_name (&temp.vnode, remainder,
+                               strlen (remainder));
+      xfree (temp.name);
+      return vnode;
+    }
+
+  return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
+}
+
+/* Find the vnode designated by the normalized NAME relative to the
+   root of the asset file system.  NAME may be modified, and must be
+   LENGTH bytes long, excluding its terminating NULL byte.  */
+
+static struct android_vnode *
+android_afs_initial (char *name, size_t length)
+{
+  struct android_afs_vnode temp;
+
+  /* Create a temporary vnode at the root of the asset file
+     system.  */
+
+  temp.vnode.ops = &afs_vfs_ops;
+  temp.vnode.type = ANDROID_VNODE_AFS;
+  temp.vnode.flags = 0;
+  temp.name_length = 1;
+  temp.name = (char *) "/";
+
+  /* Try to name this vnode.  If NAME is empty, it will be duplicated
+     instead.  */
+  return android_afs_name (&temp.vnode, name, length);
+}
+
+/* Make FD close-on-exec.  If any system call fails, do not abort, but
+   log a warning to the system log.  */
+
+static void
+android_close_on_exec (int fd)
+{
+  int flags, rc;
+
+  flags = fcntl (fd, F_GETFD);
+
+  if (flags < 0)
+    {
+      __android_log_print (ANDROID_LOG_WARN, __func__,
+                          "fcntl: %s", strerror (errno));
+      return;
+    }
+
+  rc = fcntl (fd, F_SETFD, flags | O_CLOEXEC);
+
+  if (rc < 0)
+    {
+      __android_log_print (ANDROID_LOG_WARN, __func__,
+                          "fcntl: %s", strerror (errno));
+      return;
+    }
+}
+
+static int
+android_afs_open (struct android_vnode *vnode, int flags,
+                 mode_t mode, bool asset_p, int *fd_return,
+                 AAsset **asset_return)
+{
+  AAsset *asset;
+  struct android_afs_vnode *vp;
+  const char *asset_dir;
+  int fd;
+  struct android_afs_open_fd *info;
+
+  vp = (struct android_afs_vnode *) vnode;
+
+  /* Return suitable error indications for unsupported file
+     operations.  */
+
+  if ((flags & O_WRONLY) || (flags & O_RDWR))
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  if (flags & O_DIRECTORY)
+    {
+      errno = ENOSYS;
+      return -1;
+    }
+
+  /* Now try to open this asset.  Asset manager APIs expect there to
+     be no trailing directory separator.  */
+  asset = AAssetManager_open (asset_manager, vp->name + 1,
+                             AASSET_MODE_STREAMING);
+
+  /* If it can't be opened, return an error indication.  */
+
+  if (!asset)
+    {
+      /* Scan the directory tree for this file.  */
+      asset_dir = android_scan_directory_tree (vp->name, NULL);
+
+      /* Default errno to ENOTENT.  */
+      errno = ENOENT;
+
+      /* Maybe the caller wants to open a directory vnode as a
+        file?  */
+
+      if (asset_dir && android_is_directory (asset_dir))
+       /* In that case, set errno to ENOSYS.  */
+       errno = ENOSYS;
+
+      return -1;
+    }
+
+  /* An asset has been opened.  If the caller wants a file descriptor,
+     a temporary one must be created and the file contents read
+     inside.  */
+
+  if (!asset_p)
+    {
+      /* Create a shared memory file descriptor containing the asset
+        contents.
+
+         The documentation misleads people into thinking that
+         AAsset_openFileDescriptor does precisely this.  However, it
+         instead returns an offset into any uncompressed assets in the
+         ZIP archive.  This cannot be found in its documentation.  */
+
+      fd = android_hack_asset_fd (asset);
+
+      if (fd == -1)
+       {
+         AAsset_close (asset);
+         errno = EIO;
+         return -1;
+       }
+
+      /* If O_CLOEXEC is specified, make the file descriptor close on
+        exec too.  */
+
+      if (flags & O_CLOEXEC)
+       android_close_on_exec (fd);
+
+      /* Keep a record linking ``hacked'' file descriptors with
+        their file status.  */
+      info = xzalloc (sizeof *info);
+      info->fd   = fd;
+      info->next = afs_file_descriptors;
+
+      /* Fill in some information that will be reported to
+        callers of android_fstat, among others.  */
+      info->statb.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+
+      /* Owned by root.  */
+      info->statb.st_uid = 0;
+      info->statb.st_gid = 0;
+
+      /* Concoct a nonexistent device and an inode number.  */
+      info->statb.st_dev = -1;
+      info->statb.st_ino = 0;
+
+      /* Size of the file.  */
+      info->statb.st_size = AAsset_getLength (asset);
+
+      /* Chain info onto afs_file_descriptors.  */
+      afs_file_descriptors = info;
+
+      AAsset_close (asset);
+
+      /* Return the file descriptor.  */
+      *fd_return = fd;
+      return 0;
+    }
+
+  /* Return the asset itself.  */
+  *asset_return = asset;
+  return 1;
+}
+
+static void
+android_afs_close (struct android_vnode *vnode)
+{
+  struct android_afs_vnode *vp;
+  int save_errno;
+
+  save_errno = errno;
+  vp = (struct android_afs_vnode *) vnode;
+  xfree (vp->name);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_afs_unlink (struct android_vnode *vnode)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+
+  /* If the vnode already exists, return EROFS.  Else, return
+     ENOENT.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir)
+    errno = EROFS;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static int
+android_afs_symlink (const char *linkname, struct android_vnode *vnode)
+{
+  struct android_afs_vnode *vp;
+
+  /* If this vnode already exists, return EEXIST.  */
+  vp = (struct android_afs_vnode *) vnode;
+
+  if (android_scan_directory_tree (vp->name, NULL))
+    {
+      errno = EEXIST;
+      return -1;
+    }
+
+  /* Symlinks aren't supported on this (read-only) ``file system'',
+     so return -1 with EROFS.  */
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_afs_rmdir (struct android_vnode *vnode)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+
+  /* If the vnode already exists and is a directory, return EROFS.
+     Else, return ENOTDIR or ENOENT.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir && android_is_directory (dir))
+    errno = EROFS;
+  else if (dir)
+    errno = ENOTDIR;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static int
+android_afs_rename (struct android_vnode *src, struct android_vnode *dst,
+                   bool keep_existing)
+{
+  /* If src and dst are different kinds of vnodes, return EXDEV.
+     Else, return EROFS.  */
+
+  errno = EROFS;
+  if (src->type != dst->type)
+    errno = EXDEV;
+
+  return -1;
+}
+
+static int
+android_afs_stat (struct android_vnode *vnode, struct stat *statb)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+  AAsset *asset_desc;
+
+  /* Scan for the vnode to see whether or not it exists.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (!dir)
+    {
+      /* Return ENOENT; whether the lookup failed because directory
+        components within vp->path weren't really directories is not
+        important to Emacs's error reporting.  */
+      errno = ENOENT;
+      return -1;
+    }
+
+  if (android_is_directory (dir))
+    {
+      memset (statb, 0, sizeof *statb);
+
+      /* Fill in the stat buffer.  */
+      statb->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
+
+      /* Grant search permissions as well.  */
+      statb->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
+
+      /* Concoct a nonexistent device and an inode number.  */
+      statb->st_dev = -1;
+      statb->st_ino = 0;
+      return 0;
+    }
+
+  /* AASSET_MODE_STREAMING is fastest here.  */
+  asset_desc = AAssetManager_open (asset_manager, vp->name + 1,
+                                  AASSET_MODE_STREAMING);
+
+  if (!asset_desc)
+    {
+      /* If the asset exists in the directory tree but can't be
+        located by the asset manager, report OOM.  */
+      errno = ENOMEM;
+      return 1;
+    }
+
+  memset (statb, 0, sizeof *statb);
+
+  /* Fill in the stat buffer.  */
+  statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+  statb->st_dev = -1;
+  statb->st_ino = 0;
+  statb->st_size = AAsset_getLength (asset_desc);
+
+  /* Close the asset.  */
+  AAsset_close (asset_desc);
+  return 0;
+}
+
+static int
+android_afs_access (struct android_vnode *vnode, int mode)
+{
+  const char *dir;
+  struct android_afs_vnode *vp;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Scan for the vnode to see whether or not it exists.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir)
+    {
+      /* It exists.  If MODE contains W_OK or X_OK, return
+        EACCESS.  */
+
+      if (mode & (W_OK | X_OK))
+       {
+         errno = EACCES;
+         return -1;
+       }
+
+      /* If vp->name is a directory and DIR isn't, return ENOTDIR.  */
+
+      if (vp->name[vp->name_length] == '/'
+         && !android_is_directory (dir))
+       {
+         errno = ENOTDIR;
+         return -1;
+       }
+
+      return 0;
+    }
+
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_afs_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  struct android_afs_vnode *vp;
+  const char *dir;
+
+  /* If the vnode already exists, return EEXIST in lieu of EROFS.  */
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  if (dir)
+    errno = EEXIST;
+  else
+    errno = EROFS;
+
+  return -1;
+}
+
+static int
+android_afs_chmod (struct android_vnode *vnode, mode_t mode,
+                  int flags)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static ssize_t
+android_afs_readlink (struct android_vnode *vnode, char *buffer,
+                     size_t size)
+{
+  struct android_afs_vnode *vp;
+  const char *dir;
+
+  vp = (struct android_afs_vnode *) vnode;
+  dir = android_scan_directory_tree (vp->name, NULL);
+
+  /* As there are no symlinks in /assets, just return -1 with errno
+     set to a reasonable value contingent upon whether VP->name
+     actually exists.  */
+
+  if (dir)
+    errno = EINVAL;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static struct dirent *
+android_afs_readdir (struct android_vdir *vdir)
+{
+  static struct dirent dirent;
+  const char *last;
+  struct android_afs_vdir *dir;
+
+  dir = (struct android_afs_vdir *) vdir;
+
+  /* There are no more files to read.  */
+  if (dir->asset_dir >= dir->asset_limit)
+    return NULL;
+
+  /* Otherwise, scan forward looking for the next NULL byte.  */
+  last = memchr (dir->asset_dir, 0,
+                dir->asset_limit - dir->asset_dir);
+
+  /* No more NULL bytes remain.  */
+  if (!last)
+    return NULL;
+
+  /* Forward last past the NULL byte.  */
+  last++;
+
+  /* Make sure it is still within the directory tree.  */
+  if (last >= directory_tree + directory_tree_size)
+    return NULL;
+
+  /* Now, fill in the dirent with the name.  */
+  memset (&dirent, 0, sizeof dirent);
+  dirent.d_ino = 0;
+  dirent.d_off = 0;
+  dirent.d_reclen = sizeof dirent;
+
+  /* Note that dir->asset_dir is actually a NULL terminated
+     string.  */
+  memcpy (dirent.d_name, dir->asset_dir,
+         MIN (sizeof dirent.d_name,
+              last - dir->asset_dir));
+  dirent.d_name[sizeof dirent.d_name - 1] = '\0';
+
+  /* Strip off the trailing slash, if any.  */
+  if (dirent.d_name[MIN (sizeof dirent.d_name,
+                        last - dir->asset_dir)
+                   - 2] == '/')
+    dirent.d_name[MIN (sizeof dirent.d_name,
+                      last - dir->asset_dir)
+                 - 2] = '\0';
+
+  /* If this is not a directory, return DT_REG.  Otherwise, return
+     DT_DIR.  */
+
+  if (last - 2 >= directory_tree && last[-2] == '/')
+    dirent.d_type = DT_DIR;
+  else
+    dirent.d_type = DT_REG;
+
+  /* Forward dir->asset_dir to the file past last.  */
+  dir->asset_dir = ((char *) directory_tree
+                   + android_extract_long ((char *) last));
+
+  return &dirent;
+}
+
+static void
+android_afs_closedir (struct android_vdir *vdir)
+{
+  struct android_afs_vdir *dir, **next, *tem;
+
+  dir = (struct android_afs_vdir *) vdir;
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  xfree (dir->asset_file);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_afs_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  /* Free the directory itself.  */
+
+  xfree (dir);
+}
+
+static int
+android_afs_dirfd (struct android_vdir *vdir)
+{
+  struct android_afs_vdir *dir;
+
+  dir = (struct android_afs_vdir *) vdir;
+
+  /* Since `android_afs_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_afs_opendir (struct android_vnode *vnode)
+{
+  char *asset_dir;
+  struct android_afs_vdir *dir;
+  struct android_afs_vnode *vp;
+  size_t limit;
+
+  vp = (struct android_afs_vnode *) vnode;
+
+  /* Scan for the asset directory by vp->name.  */
+
+  asset_dir
+    = (char *) android_scan_directory_tree (vp->name, &limit);
+
+  if (!asset_dir)
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* Verify that asset_dir is indeed a directory.  */
+
+  if (!android_is_directory (asset_dir))
+    {
+      errno = ENOTDIR;
+      return NULL;
+    }
+
+  /* Fill in the directory stream.  */
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_afs_readdir;
+  dir->vdir.closedir = android_afs_closedir;
+  dir->vdir.dirfd = android_afs_dirfd;
+  dir->asset_dir = asset_dir;
+  dir->asset_limit = (char *) directory_tree + limit;
+  dir->fd = -1;
+  dir->asset_file = xzalloc (vp->name_length + 2);
+  strcpy (dir->asset_file, vp->name);
+
+  /* Make sure dir->asset_file is terminated with /.  */
+  if (dir->asset_file[vp->name_length - 1] != '/')
+    dir->asset_file[vp->name_length] = '/';
+
+  /* Make sure dir->asset_limit is within bounds.  It is a limit,
+     and as such can be exactly one byte past directory_tree.  */
+  if (dir->asset_limit > directory_tree + directory_tree_size)
+    {
+      xfree (dir);
+      xfree (dir->asset_file);
+      errno = EACCES;
+      return NULL;
+    }
+
+  dir->next = all_afs_vdirs;
+  all_afs_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Return the file name corresponding to DIRFD if it is a
+   ``directory'' file descriptor returned by `android_afs_dirfd' or
+   NULL otherwise.  These file names are relative to the `/assets'
+   directory, but with a leading separator character.  */
+
+static char *
+android_afs_get_directory_name (int dirfd)
+{
+  struct android_afs_vdir *dir;
+
+  for (dir = all_afs_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return dir->asset_file;
+    }
+
+  return NULL;
+}
+
+
+
+struct android_content_vdir
+{
+  /* The directory function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory stream in `all_content_vdirs'.  */
+  struct android_content_vdir *next;
+
+  /* Pointer to the next file to return.  */
+  const char **next_name;
+
+  /* Temporary file descriptor used to identify this directory to
+     at-funcs, or -1.  */
+  int fd;
+};
+
+static struct android_vnode *android_authority_initial (char *, size_t);
+static struct android_vnode *android_saf_root_initial (char *, size_t);
+
+/* Content provider meta-interface.  This implements a vnode at
+   /content, which is a directory itself containing two additional
+   directories.
+
+   /content/storage only exists on Android 5.0 and later, and contains
+   a list of each directory tree Emacs has been granted permanent
+   access to through the Storage Access Framework.
+
+   /content/by-authority exists on Android 4.4 and later; it contains
+   no directories, but provides a `name' function that converts
+   children into content URIs.  */
+
+static struct android_vnode *android_content_name (struct android_vnode *,
+                                                  char *, size_t);
+static int android_content_open (struct android_vnode *, int,
+                                mode_t, bool, int *, AAsset **);
+static void android_content_close (struct android_vnode *);
+static int android_content_unlink (struct android_vnode *);
+static int android_content_symlink (const char *, struct android_vnode *);
+static int android_content_rmdir (struct android_vnode *);
+static int android_content_rename (struct android_vnode *,
+                                  struct android_vnode *, bool);
+static int android_content_stat (struct android_vnode *, struct stat *);
+static int android_content_access (struct android_vnode *, int);
+static int android_content_mkdir (struct android_vnode *, mode_t);
+static int android_content_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_content_readlink (struct android_vnode *, char *,
+                                        size_t);
+static struct android_vdir *android_content_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with the content VFS node.  */
+
+static struct android_vops content_vfs_ops =
+  {
+    android_content_name,
+    android_content_open,
+    android_content_close,
+    android_content_unlink,
+    android_content_symlink,
+    android_content_rmdir,
+    android_content_rename,
+    android_content_stat,
+    android_content_access,
+    android_content_mkdir,
+    android_content_chmod,
+    android_content_readlink,
+    android_content_opendir,
+  };
+
+/* Table of directories contained within a top-level vnode.  */
+
+static const char *content_directory_contents[] =
+  {
+    "storage", "by-authority",
+  };
+
+/* Chain consisting of all open content directory streams.  */
+static struct android_content_vdir *all_content_vdirs;
+
+static struct android_vnode *
+android_content_name (struct android_vnode *vnode, char *name,
+                     size_t length)
+{
+  char *remainder;
+  struct android_vnode *vp;
+  char *component_end;
+  struct android_special_vnode *special;
+  size_t i;
+  int api;
+
+  static struct android_special_vnode content_vnodes[] = {
+    { "storage", 7, android_saf_root_initial,        },
+    { "by-authority", 12, android_authority_initial, },
+  };
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the root vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* If LENGTH is empty or NAME is a single directory separator,
+     return a copy of this vnode.  */
+
+  if (length < 1 || (*name == '/' && length == 1))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+      return vp;
+    }
+
+  api = android_get_current_api_level ();
+
+  /* If NAME starts with a directory separator, move it past that.  */
+
+  if (*name == '/')
+    name++, length -= 1;
+
+  /* Look for the first directory separator.  */
+  component_end = strchr (name, '/');
+
+  /* If not there, use name + length.  */
+
+  if (!component_end)
+    component_end = name + length;
+  else
+    /* Move past the separator character.  */
+    component_end++;
+
+  /* Now, find out if the first component is a special vnode; if so,
+     call its root lookup function with the rest of NAME there.  */
+
+  if (api < 19)
+    i = 2;
+  else if (api < 21)
+    i = 1;
+  else
+    i = 0;
+
+  for (; i < ARRAYELTS (content_vnodes); ++i)
+    {
+      special = &content_vnodes[i];
+
+      if (component_end - name == special->length
+         && !memcmp (special->name, name, special->length))
+       return (*special->initial) (component_end,
+                                   length - special->length);
+
+      /* Detect the case where a special is named with a trailing
+        directory separator.  */
+
+      if (component_end - name == special->length + 1
+         && !memcmp (special->name, name, special->length)
+         && name[special->length] == '/')
+       /* Make sure to include the directory separator.  */
+       return (*special->initial) (component_end - 1,
+                                   length - special->length);
+    }
+
+  errno = ENOENT;
+  return NULL;
+
+ parent_vnode:
+  /* The parent of this vnode is always the root filesystem.  */
+  vp = &root_vnode.vnode;
+  return (*vnode->ops->name) (vnode, remainder, strlen (remainder));
+}
+
+static int
+android_content_open (struct android_vnode *vnode, int flags,
+                     mode_t mode, bool asset_p, int *fd,
+                     AAsset **asset)
+{
+  /* Don't allow opening this special directory.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static void
+android_content_close (struct android_vnode *vnode)
+{
+  int save_errno;
+
+  save_errno = errno;
+  xfree (vnode);
+  errno = save_errno;
+}
+
+static int
+android_content_unlink (struct android_vnode *vnode)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_symlink (const char *target, struct android_vnode *vnode)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_rmdir (struct android_vnode *vnode)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_rename (struct android_vnode *src,
+                       struct android_vnode *dst,
+                       bool keep_existing)
+{
+  if (src->type != dst->type)
+    {
+      /* If the types of both vnodes differ, complain that they're on
+        two different filesystems (which is correct from a abstract
+        viewpoint.)  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  /* Otherwise, return ENOSYS.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_content_stat (struct android_vnode *vnode,
+                     struct stat *statb)
+{
+  memset (statb, 0, sizeof *statb);
+
+  statb->st_uid = getuid ();
+  statb->st_gid = getgid ();
+  statb->st_ino = 0;
+  statb->st_dev = -2;
+  statb->st_mode = S_IFDIR | S_IRUSR | S_IXUSR;
+  return 0;
+}
+
+static int
+android_content_access (struct android_vnode *vnode, int mode)
+{
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Return EROFS if the caller is trying to check for write access to
+     this vnode.  */
+
+  if (mode != F_OK && (mode & (W_OK | X_OK)))
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+android_content_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  errno = EEXIST;
+  return -1;
+}
+
+static int
+android_content_chmod (struct android_vnode *vnode, mode_t mode,
+                      int flags)
+{
+  errno = EACCES;
+  return -1;
+}
+
+static ssize_t
+android_content_readlink (struct android_vnode *vnode, char *buffer,
+                         size_t size)
+{
+  errno = EINVAL;
+  return -1;
+}
+
+static struct dirent *
+android_content_readdir (struct android_vdir *vdir)
+{
+  static struct dirent dirent;
+  struct android_content_vdir *dir;
+  const char *name;
+
+  dir = (struct android_content_vdir *) vdir;
+
+  /* There are no more files to be read.  */
+  if (dir->next_name == (content_directory_contents
+                        + ARRAYELTS (content_directory_contents)))
+    return NULL;
+
+  /* Get the next child.  */
+  name = *dir->next_name++;
+
+  /* Now, fill in the dirent with the name.  */
+  memset (&dirent, 0, sizeof dirent);
+  dirent.d_ino = 0;
+  dirent.d_off = 0;
+  dirent.d_reclen = sizeof dirent;
+  dirent.d_type = DT_DIR;
+  strcpy (dirent.d_name, name);
+  return &dirent;
+}
+
+static void
+android_content_closedir (struct android_vdir *vdir)
+{
+  struct android_content_vdir *dir, **next, *tem;
+
+  dir = (struct android_content_vdir *) vdir;
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_content_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  xfree (dir);
+}
+
+static int
+android_content_dirfd (struct android_vdir *vdir)
+{
+  struct android_content_vdir *dir;
+
+  dir = (struct android_content_vdir *) vdir;
+
+  /* Since `android_content_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_content_opendir (struct android_vnode *vnode)
+{
+  struct android_content_vdir *dir;
+  int api;
+
+  /* Allocate the virtual directory.  */
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_content_readdir;
+  dir->vdir.closedir = android_content_closedir;
+  dir->vdir.dirfd = android_content_dirfd;
+  dir->fd = -1;
+
+  /* Fill in the directory contents.  */
+  dir->next_name = content_directory_contents;
+  api = android_get_current_api_level ();
+
+  /* Android 4.4 and earlier don't support /content/storage.  */
+
+  if (api < 21)
+    dir->next_name++;
+
+  /* Android 4.3 and earlier don't support /content/by-authority.  */
+
+  if (api < 19)
+    dir->next_name++;
+
+  /* Link this stream onto the list of all content directory
+     streams.  */
+  dir->next = all_content_vdirs;
+  all_content_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Return the file name corresponding to DIRFD if it is a
+   ``directory'' file descriptor returned by `android_content_dirfd'
+   or NULL otherwise.  */
+
+static char *
+android_content_get_directory_name (int dirfd)
+{
+  struct android_content_vdir *dir;
+
+  for (dir = all_content_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return (char *) "/content";
+    }
+
+  return NULL;
+}
+
+/* Find the vnode designated by the normalized NAME relative to the
+   root of the content file system.  NAME may be modified, and must be
+   LENGTH bytes long, excluding its terminating NULL byte.  */
+
+static struct android_vnode *
+android_content_initial (char *name, size_t length)
+{
+  struct android_vnode temp;
+
+  /* Create a temporary vnode at the root of the asset file
+     system.  */
+
+  temp.ops = &content_vfs_ops;
+  temp.type = ANDROID_VNODE_CONTENT;
+  temp.flags = 0;
+
+  /* Try to name this vnode.  If NAME is empty, it will be duplicated
+     instead.  */
+  return android_content_name (&temp, name, length);
+}
+
+
+
+/* Content URI management functions.  */
+
+/* Return the content URI corresponding to a `/content/by-authority'
+   file name, or NULL if it is invalid for some reason.  FILENAME
+   should be relative to /content/by-authority, with no leading
+   directory separator character.  */
+
+static char *
+android_get_content_name (const char *filename)
+{
+  char *fill, *buffer;
+  size_t length;
+
+  /* Make sure FILENAME isn't obviously invalid: it must contain an
+     authority name and a file name component.  */
+
+  fill = strchr (filename, '/');
+  if (!fill || *(fill + 1) == '\0')
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* FILENAME must also not be a directory.  Accessing content
+     provider directories is not supported by this interface.  */
+
+  length = strlen (filename);
+  if (filename[length] == '/')
+    {
+      errno = ENOTDIR;
+      return NULL;
+    }
+
+  /* Prefix FILENAME with content:// and return the buffer containing
+     that URI.  */
+
+  buffer = xmalloc (sizeof "content://" + length);
+  sprintf (buffer, "content://%s", filename);
+  return buffer;
+}
+
+/* Return whether or not the specified URI is an accessible content
+   URI.  MODE specifies what to check.
+
+   URI must be a string in the JVM's extended UTF-8 format.  */
+
+static bool
+android_check_content_access (const char *uri, int mode)
+{
+  jobject string;
+  jboolean rc, read, write;
+
+  string = (*android_java_env)->NewStringUTF (android_java_env, uri);
+  android_exception_check ();
+
+  /* Establish what is being checked.  Checking for read access is
+     identical to checking if the file exists.  */
+
+  read = (bool) (mode & R_OK || (mode == F_OK));
+  write = (bool) (mode & W_OK);
+
+  rc = (*android_java_env)->CallBooleanMethod (android_java_env,
+                                              emacs_service,
+                                              service_class.check_content_uri,
+                                              string, read, write);
+  android_exception_check_1 (string);
+  ANDROID_DELETE_LOCAL_REF (string);
+  return rc;
+}
+
+
+
+/* Content authority-based vnode implementation.
+
+   /contents/by-authority is a simple vnode implementation that converts
+   components to content:// URIs.
+
+   It does not canonicalize file names by removing parent directory
+   separators, as these characters can appear in legitimate content
+   file names.  */
+
+struct android_authority_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* URI associated with this vnode, or NULL if this is the root of
+     the content authority tree.  */
+  char *uri;
+};
+
+static struct android_vnode *android_authority_name (struct android_vnode *,
+                                                    char *, size_t);
+static int android_authority_open (struct android_vnode *, int,
+                                  mode_t, bool, int *, AAsset **);
+static void android_authority_close (struct android_vnode *);
+static int android_authority_unlink (struct android_vnode *);
+static int android_authority_symlink (const char *, struct android_vnode *);
+static int android_authority_rmdir (struct android_vnode *);
+static int android_authority_rename (struct android_vnode *,
+                                    struct android_vnode *, bool);
+static int android_authority_stat (struct android_vnode *, struct stat *);
+static int android_authority_access (struct android_vnode *, int);
+static int android_authority_mkdir (struct android_vnode *, mode_t);
+static int android_authority_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_authority_readlink (struct android_vnode *, char *,
+                                          size_t);
+static struct android_vdir *android_authority_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with the content VFS node.  */
+
+static struct android_vops authority_vfs_ops =
+  {
+    android_authority_name,
+    android_authority_open,
+    android_authority_close,
+    android_authority_unlink,
+    android_authority_symlink,
+    android_authority_rmdir,
+    android_authority_rename,
+    android_authority_stat,
+    android_authority_access,
+    android_authority_mkdir,
+    android_authority_chmod,
+    android_authority_readlink,
+    android_authority_opendir,
+  };
+
+static struct android_vnode *
+android_authority_name (struct android_vnode *vnode, char *name,
+                       size_t length)
+{
+  struct android_authority_vnode *vp;
+  char *uri_name;
+
+  if (!android_init_gui)
+    {
+      errno = EIO;
+      return NULL;
+    }
+
+  /* If NAME is empty or consists of a single directory separator
+     _and_ VP->uri is NULL, return a copy of VNODE.  */
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  if (length < 1 || (*name == '/' && length == 1 && !vp->uri))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      if (vp->uri)
+       vp->uri = xstrdup (vp->uri);
+
+      return &vp->vnode;
+    }
+
+  /* Else, if VP->uri is NULL, then it is the root of the by-authority
+     tree.  If NAME starts with a directory separator character,
+     remove it.  */
+
+  if (!vp->uri)
+    {
+      if (*name == '/')
+       name++, length -= 1;
+
+      /* NAME must be a valid JNI string, so that it can be encoded
+        properly.  */
+
+      if (android_verify_jni_string (name))
+       goto no_entry;
+
+      uri_name = android_get_content_name (name);
+      if (!uri_name)
+       goto error;
+
+      /* Now fill in the vnode.  */
+      vp = xmalloc (sizeof *vp);
+      vp->vnode.ops = &authority_vfs_ops;
+      vp->vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY;
+      vp->vnode.flags = 0;
+      vp->uri = uri_name;
+      return &vp->vnode;
+    }
+
+  /* Content files can't have children.  */
+ no_entry:
+  errno = ENOENT;
+ error:
+  return NULL;
+}
+
+static int
+android_authority_open (struct android_vnode *vnode, int flags,
+                       mode_t mode, bool asset_p, int *fd_return,
+                       AAsset **asset)
+{
+  struct android_authority_vnode *vp;
+  size_t length;
+  jobject string;
+  int fd;
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  if (vp->uri == NULL)
+    {
+      /* This is the `by-authority' directory itself, which can't be
+        opened.  */
+      errno = ENOSYS;
+      return -1;
+    }
+
+  /* Allocate a buffer to hold the file name.  */
+  length = strlen (vp->uri);
+  string = (*android_java_env)->NewByteArray (android_java_env,
+                                             length);
+  if (!string)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      errno = ENOMEM;
+      return -1;
+    }
+
+  /* Copy the URI into this byte array.  */
+  (*android_java_env)->SetByteArrayRegion (android_java_env,
+                                          string, 0, length,
+                                          (jbyte *) vp->uri);
+
+  /* Try to open the file descriptor.  */
+
+  fd
+    = (*android_java_env)->CallIntMethod (android_java_env,
+                                         emacs_service,
+                                         service_class.open_content_uri,
+                                         string,
+                                         (jboolean) ((mode & O_WRONLY
+                                                      || mode & O_RDWR)
+                                                     != 0),
+                                         (jboolean) !(mode & O_WRONLY),
+                                         (jboolean) ((mode & O_TRUNC)
+                                                     != 0));
+
+  if ((*android_java_env)->ExceptionCheck (android_java_env))
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      errno = ENOMEM;
+      ANDROID_DELETE_LOCAL_REF (string);
+      return -1;
+    }
+
+  /* If fd is -1, just assume that the file does not exist,
+     and return -1 with errno set to ENOENT.  */
+
+  if (fd == -1)
+    {
+      errno = ENOENT;
+      goto skip;
+    }
+
+  if (mode & O_CLOEXEC)
+    android_close_on_exec (fd);
+
+ skip:
+  ANDROID_DELETE_LOCAL_REF (string);
+
+  if (fd == -1)
+    return -1;
+
+  *fd_return = fd;
+  return 0;
+}
+
+static void
+android_authority_close (struct android_vnode *vnode)
+{
+  struct android_authority_vnode *vp;
+  int save_errno;
+
+  vp = (struct android_authority_vnode *) vnode;
+  save_errno = errno;
+  xfree (vp->uri);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_authority_unlink (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_authority_symlink (const char *target,
+                          struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_authority_rmdir (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_authority_rename (struct android_vnode *src,
+                         struct android_vnode *dst,
+                         bool keep_existing)
+{
+  if (src->type != dst->type)
+    {
+      /* If the types of both vnodes differ, complain that they're on
+        two different filesystems (which is correct from a abstract
+        viewpoint.)  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  /* Otherwise, return ENOSYS.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static int
+android_authority_stat (struct android_vnode *vnode,
+                       struct stat *statb)
+{
+  int rc, fd, save_errno;
+  struct android_authority_vnode *vp;
+
+  /* If this is a vnode representing `by-authority', return some
+     information about this directory.  */
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  if (!vp->uri)
+    {
+      memset (statb, 0, sizeof *statb);
+      statb->st_uid = getuid ();
+      statb->st_gid = getgid ();
+      statb->st_ino = 0;
+      statb->st_dev = -3;
+      statb->st_mode = S_IFDIR | S_IRUSR;
+      return 0;
+    }
+
+  /* Try to open the file and call fstat.  */
+  rc = (*vnode->ops->open) (vnode, O_RDONLY, 0, false, &fd, NULL);
+
+  if (rc < 0)
+    return -1;
+
+  /* If rc is 1, then an asset file descriptor has been returned.
+     This is impossible, so assert that it doesn't transpire.  */
+  assert (rc != 1);
+
+  /* Now, try to stat the file.  */
+  rc = fstat (fd, statb);
+  save_errno = errno;
+
+  /* Close the file descriptor.  */
+  close (fd);
+
+  /* Restore errno.  */
+  errno = save_errno;
+  return rc;
+}
+
+static int
+android_authority_access (struct android_vnode *vnode, int mode)
+{
+  struct android_authority_vnode *vp;
+
+  vp = (struct android_authority_vnode *) vnode;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  if (!vp->uri)
+    {
+      /* Return EACCES if the caller is trying to check for write
+        access to `by-authority'.  */
+
+      if (mode != F_OK && (mode & (W_OK | X_OK)))
+       {
+         errno = EACCES;
+         return -1;
+       }
+
+      return 0;
+    }
+
+  return (android_check_content_access (vp->uri, mode)
+         ? 0 : -1);
+}
+
+static int
+android_authority_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  errno = EACCES;
+  return -1;
+}
+
+static int
+android_authority_chmod (struct android_vnode *vnode, mode_t mode,
+                        int flags)
+{
+  errno = EACCES;
+  return -1;
+}
+
+static ssize_t
+android_authority_readlink (struct android_vnode *vnode, char *buffer,
+                           size_t size)
+{
+  errno = EINVAL;
+  return -1;
+}
+
+static struct android_vdir *
+android_authority_opendir (struct android_vnode *vnode)
+{
+  struct android_authority_vnode *vp;
+
+  /* Forbid listing the `by-authority' directory.  */
+  vp = (struct android_authority_vnode *) vnode;
+  errno = vp->uri ? ENOTDIR : EACCES;
+  return NULL;
+}
+
+/* Find the vnode designated by NAME relative to the root of the
+   by-authority directory.
+
+   If NAME is empty or a single leading separator character, return
+   a vnode representing the by-authority directory itself.
+
+   Otherwise, represent the remainder of NAME as a URI (without
+   normalizing it) and return a vnode corresponding to that.
+
+   Value may also be NULL with errno set if the designated vnode is
+   not available, such as when Android windowing has not been
+   initialized.  */
+
+static struct android_vnode *
+android_authority_initial (char *name, size_t length)
+{
+  struct android_authority_vnode temp;
+
+  temp.vnode.ops = &authority_vfs_ops;
+  temp.vnode.type = ANDROID_VNODE_CONTENT_AUTHORITY;
+  temp.vnode.flags = 0;
+  temp.uri = NULL;
+
+  return android_authority_name (&temp.vnode, name, length);
+}
+
+
+
+/* SAF ``root'' vnode implementation.
+
+   The Storage Access Framework is a system service that manages a
+   registry of document providers, which are a type of file system
+   server.
+
+   Normally, document providers can only provide individual files
+   through preestablished ``content URIs''.  Android 5.0 and later add
+   to that ``tree URIs'', which designate entire file system trees.
+
+   Authorization to access document trees and content URIs is granted
+   transiently by default when an Intent is received, but Emacs can
+   also receive persistent authorization for a given document tree.
+
+   /content/storage returns a list of directories, each representing a
+   single authority providing at least one tree URI Emacs holds
+   persistent authorization for.
+
+   Each one of those directories then contains one document tree that
+   Emacs is authorized to access.  */
+
+struct android_saf_root_vnode
+{
+  /* The vnode data.  */
+  struct android_vnode vnode;
+
+  /* The name of the document authority this directory represents, or
+     NULL.  */
+  char *authority;
+};
+
+struct android_saf_root_vdir
+{
+  /* The directory stream function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory stream in `all_saf_root_vdirs'.  */
+  struct android_saf_root_vdir *next;
+
+  /* Array of strings, one for each directory to return.  */
+  jobjectArray array;
+
+  /* Name of the authority this directory lists, or NULL.  */
+  char *authority;
+
+  /* Length of that array, and the current within it.  */
+  jsize length, i;
+
+  /* ``Directory'' file descriptor associated with this stream, or
+     -1.  */
+  int fd;
+};
+
+static struct android_vnode *android_saf_root_name (struct android_vnode *,
+                                                   char *, size_t);
+static int android_saf_root_open (struct android_vnode *, int,
+                                 mode_t, bool, int *, AAsset **);
+static void android_saf_root_close (struct android_vnode *);
+static int android_saf_root_unlink (struct android_vnode *);
+static int android_saf_root_symlink (const char *, struct android_vnode *);
+static int android_saf_root_rmdir (struct android_vnode *);
+static int android_saf_root_rename (struct android_vnode *,
+                                   struct android_vnode *, bool);
+static int android_saf_root_stat (struct android_vnode *, struct stat *);
+static int android_saf_root_access (struct android_vnode *, int);
+static int android_saf_root_mkdir (struct android_vnode *, mode_t);
+static int android_saf_root_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_saf_root_readlink (struct android_vnode *, char *,
+                                         size_t);
+static struct android_vdir *android_saf_root_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with the SAF root VFS node.  */
+
+static struct android_vops saf_root_vfs_ops =
+  {
+    android_saf_root_name,
+    android_saf_root_open,
+    android_saf_root_close,
+    android_saf_root_unlink,
+    android_saf_root_symlink,
+    android_saf_root_rmdir,
+    android_saf_root_rename,
+    android_saf_root_stat,
+    android_saf_root_access,
+    android_saf_root_mkdir,
+    android_saf_root_chmod,
+    android_saf_root_readlink,
+    android_saf_root_opendir,
+  };
+
+/* Chain containing all SAF root directories.  */
+static struct android_saf_root_vdir *all_saf_root_vdirs;
+
+/* Defined in the next page.  */
+static struct android_vnode *android_saf_tree_from_name (char *, const char *,
+                                                        const char *);
+
+/* Ascertain and return whether or not AUTHORITY designates a content
+   provider offering at least one directory tree accessible to
+   Emacs.  */
+
+static bool
+android_saf_valid_authority_p (const char *authority)
+{
+  jobject string;
+  jboolean valid;
+  jmethodID method;
+
+  /* Make certain AUTHORITY can actually be represented as a Java
+     string.  */
+
+  if (android_verify_jni_string (authority))
+    return false;
+
+  /* Build a string containing AUTHORITY.  */
+
+  string = (*android_java_env)->NewStringUTF (android_java_env,
+                                             authority);
+  android_exception_check ();
+
+  method = service_class.valid_authority;
+  valid
+    = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env,
+                                                       emacs_service,
+                                                       service_class.class,
+                                                       method, string);
+  android_exception_check_1 (string);
+  ANDROID_DELETE_LOCAL_REF (string);
+  return valid;
+}
+
+static struct android_vnode *
+android_saf_root_name (struct android_vnode *vnode, char *name,
+                      size_t length)
+{
+  char *remainder, *component_end;
+  struct android_saf_root_vnode *vp;
+  struct android_vnode *new;
+  char component[PATH_MAX];
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the root vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* If LENGTH is empty or NAME is a single directory separator,
+     return a copy of this vnode.  */
+
+  if (length < 1 || (*name == '/' && length == 1))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      if (vp->authority)
+       vp->authority = xstrdup (vp->authority);
+
+      return &vp->vnode;
+    }
+
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  /* If NAME starts with a directory separator, move it past that.  */
+
+  if (*name == '/')
+    name++, length -= 1;
+
+  /* Look for the first directory separator.  */
+  component_end = strchr (name, '/');
+
+  /* If not there, use name + length.  */
+
+  if (!component_end)
+    component_end = name + length;
+
+  if (component_end - name >= PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      return NULL;
+    }
+
+  /* Copy the component over.  */
+  memcpy (component, name, component_end - name);
+  component[component_end - name] = '\0';
+
+  /* Create a SAF document vnode for this tree if it represents an
+     authority.  */
+
+  if (vp->authority)
+    return android_saf_tree_from_name (component_end, component,
+                                      vp->authority);
+
+  /* Create the vnode.  */
+  vp = xmalloc (sizeof *vp);
+  vp->vnode.ops = &saf_root_vfs_ops;
+  vp->vnode.type = ANDROID_VNODE_SAF_ROOT;
+  vp->vnode.flags = 0;
+  vp->authority = xstrdup (component);
+
+  /* If there is more of this component to be named, name it through
+     the new vnode.  */
+
+  if (component_end != name + length)
+    {
+      new = (*vp->vnode.ops->name) (&vp->vnode, component_end,
+                                   length - (component_end - name));
+      (*vp->vnode.ops->close) (&vp->vnode);
+
+      return new;
+    }
+
+  return &vp->vnode;
+
+ parent_vnode:
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (!vp->authority)
+    /* Look this file name up relative to the root of the contents
+       directory.  */
+    return android_content_initial (remainder, strlen (remainder));
+  else
+    /* Look this file name up relative to the root of the storage
+       directory.  */
+    return android_saf_root_initial (remainder, strlen (remainder));
+}
+
+static int
+android_saf_root_open (struct android_vnode *vnode, int flags,
+                      mode_t mode, bool asset_p, int *fd_return,
+                      AAsset **asset)
+{
+  /* /content/storage or one of its authority children cannot be
+     opened, as they are virtual directories.  */
+
+  errno = ENOSYS;
+  return -1;
+}
+
+static void
+android_saf_root_close (struct android_vnode *vnode)
+{
+  struct android_saf_root_vnode *vp;
+  int save_errno;
+
+  vp = (struct android_saf_root_vnode *) vnode;
+  save_errno = errno;
+  xfree (vp->authority);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_saf_root_unlink (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_symlink (const char *target,
+                         struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_rmdir (struct android_vnode *vnode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_rename (struct android_vnode *src,
+                        struct android_vnode *dst,
+                        bool keep_existing)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_stat (struct android_vnode *vnode,
+                      struct stat *statb)
+{
+  struct android_saf_root_vnode *vp;
+
+  /* Verify that the authority actually exists and return ENOENT
+     otherwise, lest `locate-dominating-file' & co call an operation
+     that doesn't require listing URIs under this authority, such as
+     access.  */
+
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  if (vp->authority
+      && !android_saf_valid_authority_p (vp->authority))
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+  /* Make up some imaginary statistics for this vnode.  */
+
+  memset (statb, 0, sizeof *statb);
+  statb->st_uid = getuid ();
+  statb->st_gid = getgid ();
+  statb->st_ino = 0;
+  statb->st_dev = -4;
+  statb->st_mode = S_IFDIR | S_IRUSR | S_IXUSR;
+  return 0;
+}
+
+static int
+android_saf_root_access (struct android_vnode *vnode, int mode)
+{
+  struct android_saf_root_vnode *vp;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* Now, don't allow writing or executing this directory.  */
+
+  if (mode != F_OK && (mode & (W_OK | X_OK)))
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  /* Verify that the authority actually exists and return ENOENT
+     otherwise, lest `locate-dominating-file' & co call an operation
+     that doesn't require listing URIs under this authority, such as
+     access.  */
+
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  if (vp->authority
+      && !android_saf_valid_authority_p (vp->authority))
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+  return 0;
+}
+
+static int
+android_saf_root_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  errno = EROFS;
+  return -1;
+}
+
+static int
+android_saf_root_chmod (struct android_vnode *vnode, mode_t mode,
+                       int flags)
+{
+  errno = EACCES;
+  return -1;
+}
+
+static ssize_t
+android_saf_root_readlink (struct android_vnode *vnode, char *buffer,
+                          size_t size)
+{
+  errno = EINVAL;
+  return -1;
+}
+
+static struct dirent *
+android_saf_root_readdir (struct android_vdir *vdir)
+{
+  static struct dirent *dirent;
+  jobject string;
+  const char *chars;
+  size_t length, size;
+  struct android_saf_root_vdir *dir;
+
+  dir = (struct android_saf_root_vdir *) vdir;
+
+  if (dir->i == dir->length)
+    {
+      /* At the end of the stream.  Free dirent and return NULL.  */
+
+      xfree (dirent);
+      dirent = NULL;
+      return NULL;
+    }
+
+  /* Get this string.  */
+  string = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                      dir->array, dir->i++);
+  android_exception_check ();
+  chars = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                 (jstring) string,
+                                                 NULL);
+  android_exception_check_nonnull ((void *) chars, string);
+
+  /* Figure out how large it is, and then resize dirent to fit.  */
+  length = strlen (chars) + 1;
+  size   = offsetof (struct dirent, d_name) + length;
+  dirent = xrealloc (dirent, size);
+
+  /* Clear dirent.  */
+  memset (dirent, 0, size);
+
+  /* Fill in the generic directory information and copy the string
+     over.  */
+  dirent->d_ino = 0;
+  dirent->d_off = 0;
+  dirent->d_reclen = size;
+  dirent->d_type = DT_DIR;
+  strcpy (dirent->d_name, chars);
+
+  /* Release the string data and the local reference to STRING.  */
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) string, chars);
+  ANDROID_DELETE_LOCAL_REF (string);
+  return dirent;
+}
+
+static void
+android_saf_root_closedir (struct android_vdir *vdir)
+{
+  struct android_saf_root_vdir *dir, **next, *tem;
+
+  dir = (struct android_saf_root_vdir *) vdir;
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  /* Delete the local reference to the file name array.  */
+  ANDROID_DELETE_LOCAL_REF (dir->array);
+
+  /* Free the authority name if set.  */
+  xfree (dir->authority);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_saf_root_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  /* Free the directory itself.  */
+  xfree (dir);
+}
+
+static int
+android_saf_root_dirfd (struct android_vdir *vdir)
+{
+  struct android_saf_root_vdir *dir;
+
+  dir = (struct android_saf_root_vdir *) vdir;
+
+  /* Since `android_saf_root_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_saf_root_opendir (struct android_vnode *vnode)
+{
+  struct android_saf_root_vnode *vp;
+  jobjectArray array;
+  jmethodID method;
+  jbyteArray authority;
+  struct android_saf_root_vdir *dir;
+  size_t length;
+
+  vp = (struct android_saf_root_vnode *) vnode;
+
+  if (vp->authority)
+    {
+      /* Build a string containing the authority.  */
+      length = strlen (vp->authority);
+      authority = (*android_java_env)->NewByteArray (android_java_env,
+                                                    length);
+      android_exception_check ();
+
+      /* Copy the authority name to that byte array.  */
+      (*android_java_env)->SetByteArrayRegion (android_java_env,
+                                              authority, 0, length,
+                                              (jbyte *) vp->authority);
+
+      /* Acquire a list of every tree provided by this authority.  */
+
+      method = service_class.get_document_trees;
+      array
+       = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                          emacs_service,
+                                                          service_class.class,
+                                                          method, authority);
+      android_exception_check_1 (authority);
+      ANDROID_DELETE_LOCAL_REF (authority);
+
+      /* Ascertain the length of the array.  If it is empty or NULL,
+         return ENOENT.  */
+
+      if (!array)
+       {
+         errno = ENOENT;
+         return NULL;
+       }
+
+      length = (*android_java_env)->GetArrayLength (android_java_env, array);
+
+      if (!length)
+       {
+         ANDROID_DELETE_LOCAL_REF (array);
+         errno = ENOENT;
+         return NULL;
+       }
+
+      /* Now allocate the directory stream.  It will retain a local
+        reference to the array, and should thus only be used within the
+        same JNI local reference frame.  */
+
+      dir = xmalloc (sizeof *dir);
+      dir->vdir.readdir = android_saf_root_readdir;
+      dir->vdir.closedir = android_saf_root_closedir;
+      dir->vdir.dirfd = android_saf_root_dirfd;
+      dir->fd = -1;
+      dir->array = array;
+      dir->length = length;
+      dir->i = 0;
+      dir->authority = xstrdup (vp->authority);
+
+      /* Link this stream onto the list of all SAF root directory
+        streams.  */
+      dir->next = all_saf_root_vdirs;
+      all_saf_root_vdirs = dir;
+      return &dir->vdir;
+    }
+
+  /* Acquire a list of every document authority.  */
+
+  method = service_class.get_document_authorities;
+  array = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                          emacs_service,
+                                                          service_class.class,
+                                                          method);
+  android_exception_check ();
+
+  if (!array)
+    emacs_abort ();
+
+  /* Now allocate the directory stream.  It will retain a local
+     reference to the array, and should thus only be used within the
+     same JNI local reference frame.  */
+
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_saf_root_readdir;
+  dir->vdir.closedir = android_saf_root_closedir;
+  dir->vdir.dirfd = android_saf_root_dirfd;
+  dir->fd = -1;
+  dir->array = array;
+  dir->length = (*android_java_env)->GetArrayLength (android_java_env,
+                                                    array);
+  dir->i = 0;
+  dir->authority = NULL;
+
+  /* Link this stream onto the list of all SAF root directory
+     streams.  */
+  dir->next = all_saf_root_vdirs;
+  all_saf_root_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Find the vnode designated by NAME relative to the root of the
+   storage directory.
+
+   If NAME is empty or a single leading separator character, return a
+   vnode representing the storage directory itself.
+
+   If NAME actually resides in a parent directory, look for it within
+   the vnode representing the content directory.  */
+
+static struct android_vnode *
+android_saf_root_initial (char *name, size_t length)
+{
+  struct android_saf_root_vnode temp;
+
+  temp.vnode.ops = &saf_root_vfs_ops;
+  temp.vnode.type = ANDROID_VNODE_SAF_ROOT;
+  temp.vnode.flags = 0;
+  temp.authority = NULL;
+
+  return android_saf_root_name (&temp.vnode, name, length);
+}
+
+/* Return any open SAF root directory stream for which dirfd has
+   returned the file descriptor DIRFD.  Return NULL otherwise.  */
+
+static struct android_saf_root_vdir *
+android_saf_root_get_directory (int dirfd)
+{
+  struct android_saf_root_vdir *dir;
+
+  for (dir = all_saf_root_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return dir;
+    }
+
+  return NULL;
+}
+
+
+
+/* Functions common to both SAF directory and file nodes.  */
+
+/* Whether or not Emacs is within an operation running from the SAF
+   thread.  */
+static bool inside_saf_critical_section;
+
+/* Check for JNI exceptions, clear them, and set errno accordingly.
+   Also, free each of the N local references given as arguments if an
+   exception takes place.
+
+   Value is 1 if an exception has taken place, 0 otherwise.
+
+   If the exception thrown derives from FileNotFoundException, set
+   errno to ENOENT.
+
+   If the exception thrown derives from SecurityException, set errno
+   to EACCES.
+
+   If the exception thrown derives from OperationCanceledException,
+   set errno to EINTR.
+
+   If the exception thrown derives from UnsupportedOperationException,
+   set errno to ENOSYS.
+
+   If the exception thrown derives from OutOfMemoryException, call
+   `memory_full'.
+
+   If the exception thrown is anything else, set errno to EIO.  */
+
+static int
+android_saf_exception_check (int n, ...)
+{
+  jthrowable exception;
+  JNIEnv *env;
+  va_list ap;
+  int new_errno;
+
+  env = android_java_env;
+  va_start (ap, n);
+
+  /* First, check for an exception.  */
+
+  if (!(*env)->ExceptionCheck (env))
+    /* No exception has taken place.  Return 0.  */
+    return 0;
+
+  /* Print the exception.  */
+  (*env)->ExceptionDescribe (env);
+
+  exception = (*env)->ExceptionOccurred (env);
+
+  if (!exception)
+    /* JNI couldn't return a local reference to the exception.  */
+    memory_full (0);
+
+  /* Clear the exception, making it safe to subsequently call other
+     JNI functions.  */
+  (*env)->ExceptionClear (env);
+
+  /* Delete each of the N arguments.  */
+
+  while (n > 0)
+    {
+      ANDROID_DELETE_LOCAL_REF (va_arg (ap, jobject));
+      n--;
+    }
+
+  /* Now set errno or signal memory_full as required.  */
+
+  if ((*env)->IsInstanceOf (env, (jobject) exception,
+                           file_not_found_exception))
+    new_errno = ENOENT;
+  else if ((*env)->IsInstanceOf (env, (jobject) exception,
+                                security_exception))
+    new_errno = EACCES;
+  else if ((*env)->IsInstanceOf (env, (jobject) exception,
+                                operation_canceled_exception))
+    new_errno = EINTR;
+  else if ((*env)->IsInstanceOf (env, (jobject) exception,
+                                unsupported_operation_exception))
+    new_errno = ENOSYS;
+  else if ((*env)->IsInstanceOf (env, (jobject) exception,
+                                out_of_memory_error))
+    {
+      ANDROID_DELETE_LOCAL_REF ((jobject) exception);
+      memory_full (0);
+    }
+  else
+    new_errno = EIO;
+
+  /* expression is still a local reference! */
+  ANDROID_DELETE_LOCAL_REF ((jobject) exception);
+  errno = new_errno;
+  return 1;
+}
+
+/* Return file status for the document designated by ID_NAME within
+   the document tree identified by URI_NAME.
+
+   If NO_CACHE, don't cache the resulting file status.  Enable this
+   option if the file status is subject to imminent change.
+
+   If the file status is available, place it within *STATB and return
+   0.  If not, return -1 and set errno to EPERM.  */
+
+static int
+android_saf_stat (const char *uri_name, const char *id_name,
+                 struct stat *statb, bool no_cache)
+{
+  jmethodID method;
+  jstring uri, id;
+  jobject status;
+  jlong mode, size, mtim, *longs;
+
+  /* Now guarantee that it is safe to call functions which
+     synchronize with the SAF thread.  */
+
+  if (inside_saf_critical_section)
+    {
+      errno = EIO;
+      return -1;
+    }
+
+  /* Build strings for both URI and ID.  */
+  uri = (*android_java_env)->NewStringUTF (android_java_env, uri_name);
+  android_exception_check ();
+
+  if (id_name)
+    {
+      id = (*android_java_env)->NewStringUTF (android_java_env,
+                                             id_name);
+      android_exception_check_1 (uri);
+    }
+  else
+    id = NULL;
+
+  /* Try to retrieve the file status.  */
+  method = service_class.stat_document;
+  inside_saf_critical_section = true;
+  status
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, uri, id,
+                                                      (jboolean) no_cache);
+  inside_saf_critical_section = false;
+
+  /* Check for exceptions and release unneeded local references.  */
+
+  if (id)
+    {
+      if (android_saf_exception_check (2, uri, id))
+       return -1;
+
+      ANDROID_DELETE_LOCAL_REF (id);
+    }
+  else if (android_saf_exception_check (1, uri))
+    return -1;
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  /* Check for failure.  */
+
+  if (!status)
+    {
+      errno = EPERM;
+      return -1;
+    }
+
+  /* Read the file status from the array returned.  */
+
+  longs = (*android_java_env)->GetLongArrayElements (android_java_env,
+                                                    status, NULL);
+  android_exception_check_nonnull (longs, status);
+  mode = longs[0];
+  size = longs[1];
+  mtim = longs[2];
+  (*android_java_env)->ReleaseLongArrayElements (android_java_env, status,
+                                                longs, JNI_ABORT);
+  ANDROID_DELETE_LOCAL_REF (status);
+
+  /* Fill in STATB with this information.  */
+  memset (statb, 0, sizeof *statb);
+  statb->st_size = MAX (0, MIN (TYPE_MAXIMUM (off_t), size));
+  statb->st_mode = mode;
+  statb->st_dev = -4;
+#ifdef STAT_TIMESPEC
+  STAT_TIMESPEC (statb, st_mtim).tv_sec = mtim / 1000;
+  STAT_TIMESPEC (statb, st_mtim).tv_nsec = (mtim % 1000) * 1000000;
+#else /* !STAT_TIMESPEC */
+  /* Headers supplied by the NDK r10b contain a `struct stat' without
+     POSIX fields for nano-second timestamps.  */
+  statb->st_mtime = mtim / 1000;
+  statb->st_mtime_nsec = (mtim % 1000) * 1000000;
+#endif /* STAT_TIMESPEC */
+  statb->st_uid = getuid ();
+  statb->st_gid = getgid ();
+  return 0;
+}
+
+/* Detect if Emacs has access to the document designated by the the
+   documen ID ID_NAME within the tree URI_NAME.  If ID_NAME is NULL,
+   use the document ID in URI_NAME itself.
+
+   If WRITABLE, also check that the file is writable, which is true
+   if it is either a directory or its flags contains
+   FLAG_SUPPORTS_WRITE.
+
+   Value is 0 if the file is accessible, and -1 with errno set
+   appropriately if not.  */
+
+static int
+android_saf_access (const char *uri_name, const char *id_name,
+                   bool writable)
+{
+  jmethodID method;
+  jstring uri, id;
+  jint rc;
+
+  /* Now guarantee that it is safe to call functions which
+     synchronize with the SAF thread.  */
+
+  if (inside_saf_critical_section)
+    {
+      errno = EIO;
+      return -1;
+    }
+
+  /* Build strings for both URI and ID.  */
+  uri = (*android_java_env)->NewStringUTF (android_java_env, uri_name);
+  android_exception_check ();
+
+  if (id_name)
+    {
+      id = (*android_java_env)->NewStringUTF (android_java_env,
+                                             id_name);
+      android_exception_check_1 (uri);
+    }
+  else
+    id = NULL;
+
+  /* Try to retrieve the file status.  */
+  method = service_class.access_document;
+  inside_saf_critical_section = true;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method, uri, id,
+                                                    (jboolean) writable);
+  inside_saf_critical_section = false;
+
+  /* Check for exceptions and release unneeded local references.  */
+
+  if (id)
+    {
+      if (android_saf_exception_check (2, uri, id))
+       return -1;
+
+      ANDROID_DELETE_LOCAL_REF (id);
+    }
+  else if (android_saf_exception_check (1, uri))
+    return -1;
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  switch (rc)
+    {
+    case -1:
+      /* -1 means it doesn't exist.  */
+      errno = ENOENT;
+      return -1;
+
+    case -2:
+      /* -2 means access has been denied.  */
+      errno = EACCES;
+      return -1;
+
+    case -3:
+      /* -3 refers to an internal error.  */
+      errno = EIO;
+      return -1;
+    }
+
+  /* Return success.  */
+  return 0;
+}
+
+/* Delete the document designated by DOC_ID within the tree identified
+   through the URI TREE.  Return 0 if the document has been deleted,
+   set errno and return -1 upon failure.
+
+   DOC_NAME should be the name of the file itself, as a file name
+   whose constituent components lead to a document named DOC_ID.  It
+   isn't used to search for a document ID, but is used to invalidate
+   the file cache.  */
+
+static int
+android_saf_delete_document (const char *tree, const char *doc_id,
+                            const char *doc_name)
+{
+  jobject id, uri, name;
+  jmethodID method;
+  jint rc;
+
+  /* Build the strings holding the ID, URI and NAME.  */
+  id = (*android_java_env)->NewStringUTF (android_java_env,
+                                         doc_id);
+  android_exception_check ();
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          tree);
+  android_exception_check_1 (id);
+  name = (*android_java_env)->NewStringUTF (android_java_env,
+                                           doc_name);
+  android_exception_check_2 (id, name);
+
+  /* Now, try to delete the document.  */
+  method = service_class.delete_document;
+  rc = (*android_java_env)->CallIntMethod (android_java_env,
+                                          emacs_service,
+                                          method, uri, id,
+                                          name);
+
+  if (android_saf_exception_check (3, id, uri, name))
+    return -1;
+
+  ANDROID_DELETE_LOCAL_REF (id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+  ANDROID_DELETE_LOCAL_REF (name);
+
+  if (rc)
+    {
+      errno = EACCES;
+      return -1;
+    }
+
+  return 0;
+}
+
+/* Declared further below.  */
+static int android_document_id_from_name (const char *, const char *,
+                                         char **);
+
+/* Rename the document designated by DOC_ID inside the directory tree
+   identified by URI, which should be within the directory by the name
+   of DIR, to NAME.  If the document can't be renamed, return -1 and
+   set errno to a value describing the error.  Return 0 if the rename
+   is successful.
+
+   Android permits the same document to appear in multiple
+   directories, but stores the display name inside the document
+   ``inode'' itself instead of the directory entries that refer to it.
+   Because of this, this operation may cause other directory entries
+   outside DIR to be renamed.  */
+
+static int
+android_saf_rename_document (const char *uri, const char *doc_id,
+                            const char *dir, const char *name)
+{
+  int rc;
+  jstring uri1, doc_id1, dir1, name1;
+  jmethodID method;
+
+  /* Now build the strings for the URI, document ID, directory name
+     and directory ID.  */
+
+  uri1 = (*android_java_env)->NewStringUTF (android_java_env, uri);
+  android_exception_check ();
+  doc_id1 = (*android_java_env)->NewStringUTF (android_java_env, doc_id);
+  android_exception_check_1 (uri1);
+  dir1 = (*android_java_env)->NewStringUTF (android_java_env, dir);
+  android_exception_check_2 (doc_id1, uri1);
+  name1 = (*android_java_env)->NewStringUTF (android_java_env, name);
+  android_exception_check_3 (dir1, doc_id1, uri1);
+
+  method = service_class.rename_document;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method, uri1, doc_id1,
+                                                    dir1, name1);
+
+  /* Check for exceptions.  */
+
+  if (android_saf_exception_check (4, uri1, doc_id1, dir1, name1))
+    {
+      /* Substitute EXDEV for ENOSYS, so callers fall back on
+        delete-then-copy.  */
+
+      if (errno == ENOSYS)
+       errno = EXDEV;
+
+      return -1;
+    }
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (uri1);
+  ANDROID_DELETE_LOCAL_REF (doc_id1);
+  ANDROID_DELETE_LOCAL_REF (dir1);
+  ANDROID_DELETE_LOCAL_REF (name1);
+
+  /* Then check for errors handled within the Java code.  */
+
+  if (rc == -1)
+    {
+      /* UnsupportedOperationException.  Trick the caller into falling
+        back on delete-then-copy code.  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  return 0;
+}
+
+/* Move the document designated by *DOC_ID from the directory under
+   DIR_NAME to the directory designated by DST_ID.  All three
+   directories are located within the tree identified by the given
+   URI.
+
+   If the document's ID changes as a result of the movement, free
+   *DOC_ID and store the new document ID within.
+
+   Value is 0 upon success, -1 otherwise with errno set.  */
+
+static int
+android_saf_move_document (const char *uri, char **doc_id,
+                          const char *dir_name, const char *dst_id)
+{
+  char *src_id, *id;
+  jobject uri1, doc_id1, dir_name1, dst_id1, src_id1;
+  jstring result;
+  jmethodID method;
+  int rc;
+  const char *new_id;
+
+  /* Obtain the name of the source directory.  */
+  src_id = NULL;
+  rc = android_document_id_from_name (uri, dir_name, &src_id);
+
+  if (rc != 1)
+    {
+      /* This file is either not a directory or nonexistent.  */
+      xfree (src_id);
+
+      switch (rc)
+       {
+       case 0:
+         errno = ENOTDIR;
+         return -1;
+
+       case -1:
+       case -2:
+         errno = ENOENT;
+         return -1;
+
+       default:
+         emacs_abort ();
+       }
+    }
+
+  /* Build Java strings for all five arguments.  */
+  id = *doc_id;
+  uri1 = (*android_java_env)->NewStringUTF (android_java_env, uri);
+  android_exception_check ();
+  doc_id1 = (*android_java_env)->NewStringUTF (android_java_env, id);
+  android_exception_check_1 (uri1);
+  dir_name1 = (*android_java_env)->NewStringUTF (android_java_env, dir_name);
+  android_exception_check_2 (doc_id1, uri1);
+  dst_id1 = (*android_java_env)->NewStringUTF (android_java_env, dst_id);
+  android_exception_check_3 (dir_name1, doc_id1, uri1);
+  src_id1 = (*android_java_env)->NewStringUTF (android_java_env, src_id);
+  xfree (src_id);
+  android_exception_check_4 (dst_id1, dir_name1, doc_id1, uri1);
+
+  /* Do the rename.  */
+  method = service_class.move_document;
+  result = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 emacs_service,
+                                                 method, uri1,
+                                                 doc_id1, dir_name1,
+                                                 dst_id1, src_id1);
+  if (android_saf_exception_check (5, src_id1, dst_id1, dir_name1,
+                                  doc_id1, uri1))
+    {
+      /* Substitute EXDEV for ENOSYS, so callers fall back on
+        delete-then-copy.  */
+
+      if (errno == ENOSYS)
+       errno = EXDEV;
+
+      return -1;
+    }
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (src_id1);
+  ANDROID_DELETE_LOCAL_REF (dst_id1);
+  ANDROID_DELETE_LOCAL_REF (dir_name1);
+  ANDROID_DELETE_LOCAL_REF (doc_id1);
+  ANDROID_DELETE_LOCAL_REF (uri1);
+
+  if (result)
+    {
+      /* The document ID changed.  Free id and replace *DOC_ID with
+        the new ID.  */
+      xfree (id);
+      new_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                      result, NULL);
+      android_exception_check_nonnull ((void *) new_id, result);
+      *doc_id = xstrdup (new_id);
+      (*android_java_env)->ReleaseStringUTFChars (android_java_env, result,
+                                                 new_id);
+      ANDROID_DELETE_LOCAL_REF (result);
+    }
+
+  return 0;
+}
+
+
+
+/* SAF directory vnode.  A file within a SAF directory tree is
+   identified by the URI of the directory tree itself, an opaque
+   ``file identifier'' value, and a display name.  This information is
+   recorded in each vnode representing either a directory or a file
+   itself.  */
+
+struct android_saf_tree_vnode
+{
+  /* The vnode data itself.  */
+  struct android_vnode vnode;
+
+  /* The URI of the directory tree represented.  This is Java string
+     data in ``modified UTF format'', which is essentially a modified
+     UTF-8 format capable of storing NULL bytes while also utilizing
+     NULL termination.  */
+  const char *tree_uri;
+
+  /* The ID of the document tree designated by TREE_URI.  */
+  char *tree_id;
+
+  /* The document ID of the directory represented, or NULL if this is
+     the root directory of the tree.  Since file and new vnodes don't
+     represent the root directory, this field is always set in
+     them.  */
+  char *document_id;
+
+  /* The file name of this tree vnode.  This is a ``path'' to the
+     file, where each directory component consists of the display name
+     of a directory leading up to a file within, terminated with a
+     directory separator character.  */
+  char *name;
+};
+
+struct android_saf_tree_vdir
+{
+  /* The virtual directory stream function table.  */
+  struct android_vdir vdir;
+
+  /* The next directory in `all_saf_tree_vdirs'.  */
+  struct android_saf_tree_vdir *next;
+
+  /* Name of this directory relative to the root file system.  */
+  char *name;
+
+  /* Local reference to the cursor representing the directory
+     stream.  */
+  jobject cursor;
+
+  /* The ``directory'' file descriptor used to identify this directory
+     stream, or -1.  */
+  int fd;
+};
+
+static struct android_vnode *android_saf_tree_name (struct android_vnode *,
+                                                   char *, size_t);
+static int android_saf_tree_open (struct android_vnode *, int,
+                                 mode_t, bool, int *, AAsset **);
+static void android_saf_tree_close (struct android_vnode *);
+static int android_saf_tree_unlink (struct android_vnode *);
+static int android_saf_tree_symlink (const char *, struct android_vnode *);
+static int android_saf_tree_rmdir (struct android_vnode *);
+static int android_saf_tree_rename (struct android_vnode *,
+                                   struct android_vnode *, bool);
+static int android_saf_tree_stat (struct android_vnode *, struct stat *);
+static int android_saf_tree_access (struct android_vnode *, int);
+static int android_saf_tree_mkdir (struct android_vnode *, mode_t);
+static int android_saf_tree_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_saf_tree_readlink (struct android_vnode *, char *,
+                                         size_t);
+static struct android_vdir *android_saf_tree_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with SAF tree VFS nodes.  */
+
+static struct android_vops saf_tree_vfs_ops =
+  {
+    android_saf_tree_name,
+    android_saf_tree_open,
+    android_saf_tree_close,
+    android_saf_tree_unlink,
+    android_saf_tree_symlink,
+    android_saf_tree_rmdir,
+    android_saf_tree_rename,
+    android_saf_tree_stat,
+    android_saf_tree_access,
+    android_saf_tree_mkdir,
+    android_saf_tree_chmod,
+    android_saf_tree_readlink,
+    android_saf_tree_opendir,
+  };
+
+/* Vector of VFS operations associated with SAF file VFS nodes.
+   Defined later in the next page.  */
+static struct android_vops saf_file_vfs_ops;
+
+/* Vector of VFS operations associated with SAF ``new'' VFS nodes.
+   Defined two pages below.  */
+static struct android_vops saf_new_vfs_ops;
+
+/* Chain of all open SAF directory streams.  */
+static struct android_saf_tree_vdir *all_saf_tree_vdirs;
+
+/* Find the document ID of the file within TREE_URI designated by
+   NAME.
+
+   NAME is a ``file name'' comprised of the display names of
+   individual files.  Each constituent component prior to the last
+   must name a directory file within TREE_URI.
+
+   If NAME is not correct for the Java ``modified UTF-8'' coding
+   system, return -1 and set errno to ENOENT.
+
+   Upon success, return 0 or 1 (contingent upon whether or not the
+   last component within NAME is a directory) and place the document
+   ID of the named file in ID.
+
+   If the designated file doesn't exist, but the penultimate component
+   within NAME does and is also a directory, return -2 and place the
+   document ID of that directory within *ID.
+
+   If the designated file can't be located, return -1 and set errno
+   accordingly.  The reasons for which a file can't be located are not
+   all immediately obvious: quitting, for example, can cause document
+   ID lookup to be canceled.  */
+
+static int
+android_document_id_from_name (const char *tree_uri, const char *name,
+                              char **id)
+{
+  jobjectArray result;
+  jstring uri;
+  jbyteArray java_name;
+  jint rc;
+  jmethodID method;
+  const char *doc_id;
+
+  /* Verify the format of NAME.  Don't allow creating files that
+     contain characters that can't be encoded in Java.  */
+
+  if (android_verify_jni_string (name))
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+  /* Now guarantee that it is safe to call
+     `document_id_from_name'.  */
+
+  if (inside_saf_critical_section)
+    {
+      errno = EIO;
+      return -1;
+    }
+
+  /* First, create the array that will hold the result.  */
+  result = (*android_java_env)->NewObjectArray (android_java_env, 1,
+                                               java_string_class,
+                                               NULL);
+  android_exception_check ();
+
+  /* Next, create the string for the tree URI and name.  */
+  java_name = (*android_java_env)->NewStringUTF (android_java_env,
+                                                name);
+  android_exception_check_1 (result);
+  uri = (*android_java_env)->NewStringUTF (android_java_env, tree_uri);
+  android_exception_check_2 (result, java_name);
+
+  /* Now, call documentIdFromName.  This will synchronize with the SAF
+     thread, so make sure reentrant calls don't happen.  */
+  method = service_class.document_id_from_name;
+  inside_saf_critical_section = true;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method,
+                                                    uri, java_name,
+                                                    result);
+  inside_saf_critical_section = false;
+
+  if (android_saf_exception_check (3, result, uri, java_name))
+    return -1;
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+  ANDROID_DELETE_LOCAL_REF (java_name);
+
+  /* If rc indicates failure, don't try to copy from result.  */
+
+  if (rc == -1)
+    {
+      ANDROID_DELETE_LOCAL_REF (result);
+      errno = ENOENT;
+      return -1;
+    }
+
+  eassert (rc == -2 || rc >= 0);
+
+  /* Otherwise, obtain the contents of the string returned in Java
+     ``UTF-8'' encoding.  */
+  uri = (*android_java_env)->GetObjectArrayElement (android_java_env,
+                                                   result, 0);
+  android_exception_check_nonnull (uri, result);
+  ANDROID_DELETE_LOCAL_REF (result);
+
+  doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                  uri, NULL);
+  android_exception_check_nonnull ((void *) doc_id, uri);
+
+  /* Make *ID its copy.  */
+  *id = xstrdup (doc_id);
+
+  /* And release it.  */
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) uri, doc_id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+  return rc;
+}
+
+static struct android_vnode *
+android_saf_tree_name (struct android_vnode *vnode, char *name,
+                      size_t length)
+{
+  char *remainder;
+  int rc;
+  struct android_saf_tree_vnode *vp, *new;
+  size_t vp_length;
+  char *filename, *fill, *doc_id, *end;
+  struct android_saf_root_vnode root;
+  struct android_saf_tree_vnode tree;
+
+  /* Canonicalize NAME.  */
+  remainder = android_vfs_canonicalize_name (name, &length);
+
+  /* If remainder is set, it's a name relative to the root vnode.  */
+  if (remainder)
+    goto parent_vnode;
+
+  /* If LENGTH is empty or NAME is a single directory separator,
+     return a copy of this vnode.  */
+
+  if (length < 1 || (*name == '/' && length == 1))
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      /* Duplicate the information contained within VNODE.  */
+
+      vp->tree_uri = xstrdup (vp->tree_uri);
+      vp->tree_id = xstrdup (vp->tree_id);
+      vp->name = xstrdup (vp->name);
+
+      if (vp->document_id)
+       vp->document_id = xstrdup (vp->name);
+
+      return &vp->vnode;
+    }
+
+  /* Now, search for the document ID of the file designated by NAME
+     relative to this vnode.  */
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+  vp_length = strlen (vp->name);
+
+  /* If NAME starts with a directory separator, move it past that.  */
+
+  if (*name == '/')
+    name++, length -= 1;
+
+  /* Concatenate VP->name with NAME.  Leave one byte at the end for an
+     extra trailing directory separator.  */
+
+  filename = xmalloc (vp_length + length + 2);
+  fill = stpcpy (filename, vp->name);
+  fill = stpcpy (fill, name);
+
+  /* And search for a document ID in the result.  */
+  rc = android_document_id_from_name (vp->tree_uri, name,
+                                     &doc_id);
+
+  if (rc < 0)
+    {
+      if (rc == -2)
+       {
+         /* This is a vnode representing a nonexistent file in a real
+            directory, so create a vnode whose sole use is to create
+            the file.  */
+
+         new = xmalloc (sizeof *new);
+         new->vnode.ops = &saf_new_vfs_ops;
+         new->vnode.type = ANDROID_VNODE_SAF_NEW;
+         new->vnode.flags = 0;
+
+         /* Here, doc_id is actually the ID of the penultimate
+            component in NAME.  */
+
+         new->document_id = doc_id;
+         new->tree_uri = xstrdup (vp->tree_uri);
+         new->tree_id = xstrdup (vp->tree_id);
+         new->name = filename;
+         return &new->vnode;
+       }
+
+      /* The document ID can't be found.  */
+      xfree (filename);
+      return NULL;
+    }
+
+  if (!rc)
+    {
+      /* rc set to 0 means that NAME is a regular file.  Detect if
+         NAME is supposed to be a directory; if it is, set errno to
+         ENODIR.  */
+
+      if (name[length - 1] == '/')
+       {
+         xfree (filename);
+         xfree (doc_id);
+         errno = ENOTDIR;
+         return NULL;
+       }
+    }
+
+  /* So this is either a directory or really a file.  Fortunately,
+     directory and file vnodes share everything in common except for a
+     few file operations, so create a new directory vnode with the new
+     file name and return it.  */
+
+  new = xmalloc (sizeof *new);
+  new->vnode.ops = (rc ? &saf_tree_vfs_ops
+                   : &saf_file_vfs_ops);
+  new->vnode.type = (rc ? ANDROID_VNODE_SAF_TREE
+                    : ANDROID_VNODE_SAF_FILE);
+  new->vnode.flags = 0;
+
+  if (rc)
+    {
+      /* If fill[-1] is not a directory separator character, append
+        one to the end of filename.  */
+
+      if (fill[-1] != '/')
+       {
+         *fill++ = '/';
+         *fill   = '\0';
+       }
+    }
+
+  new->document_id = doc_id;
+  new->tree_uri = xstrdup (vp->tree_uri);
+  new->tree_id = xstrdup (vp->tree_id);
+  new->name = filename;
+  return &new->vnode;
+
+ parent_vnode:
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* .. was encountered and the parent couldn't be found through
+     stripping off preceding components.
+
+     Find the parent vnode and name the rest of NAME starting from
+     there.  */
+
+  if (!vp->document_id)
+    {
+      /* VP->document_id is NULL, meaning this is the root of this
+        directory tree.  The parent vnode is an SAF root vnode with
+        VP->tree_uri's authority.  */
+
+      root.vnode.ops = &saf_root_vfs_ops;
+      root.vnode.type = ANDROID_VNODE_SAF_ROOT;
+      root.vnode.flags = 0;
+
+      /* Find the authority from the URI.  */
+
+      fill = (char *) vp->tree_uri;
+
+      if (strncmp (fill, "content://", 10))
+       emacs_abort ();
+
+      /* Skip the content header.  */
+      fill += sizeof "content://" - 1;
+
+      /* The authority segment of the URI is between here and the
+        next slash.  */
+
+      end = strchr (fill, '/');
+
+      if (!end)
+       emacs_abort ();
+
+      root.authority = xmalloc (end - fill + 1);
+      memcpy (root.authority, fill, end - fill);
+      root.authority[end - fill] = '\0';
+
+      /* Now search using this vnode.  */
+      vnode = (*root.vnode.ops->name) (&root.vnode, remainder,
+                                      strlen (remainder));
+      xfree (root.authority);
+      return vnode;
+    }
+
+  /* Otherwise, strip off the last directory component.  */
+
+  fill = strrchr (vp->name, '/');
+  if (!fill)
+    emacs_abort ();
+
+  /* Create a new vnode at the top of the directory tree, and search
+     for remainder from there.  */
+
+  tree.vnode.ops = &saf_tree_vfs_ops;
+  tree.vnode.type = ANDROID_VNODE_SAF_TREE;
+  tree.vnode.flags = 0;
+  tree.document_id = NULL;
+  tree.name = (char *) "/";
+  tree.tree_uri = vp->tree_uri;
+  tree.tree_id = vp->tree_id;
+
+  length   = strlen (remainder + (*remainder == '/'));
+  filename = xmalloc (fill - vp->name + length + 2);
+  fill = mempcpy (filename, vp->name,
+                 /* Include the separator character (*FILL) within
+                    this copy.  */
+                 fill - vp->name + 1);
+  /* Skip a leading separator in REMAINDER.  */
+  strcpy (fill, remainder + (*remainder == '/'));
+
+  /* Use this filename to find a vnode relative to the start of this
+     tree.  */
+
+  vnode = android_saf_tree_name (&tree.vnode, filename,
+                                strlen (filename));
+  xfree (filename);
+  return vnode;
+}
+
+static int
+android_saf_tree_open (struct android_vnode *vnode, int flags,
+                      mode_t mode, bool asset_p, int *fd,
+                      AAsset **asset)
+{
+  /* Don't allow opening this special directory.  */
+  errno = ENOSYS;
+  return -1;
+}
+
+static void
+android_saf_tree_close (struct android_vnode *vnode)
+{
+  struct android_saf_tree_vnode *vp;
+  int save_errno;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  save_errno = errno;
+  xfree ((void *) vp->tree_uri);
+  xfree (vp->tree_id);
+  xfree (vp->name);
+  xfree (vp->document_id);
+  xfree (vp);
+  errno = save_errno;
+}
+
+static int
+android_saf_tree_unlink (struct android_vnode *vnode)
+{
+  errno = EISDIR;
+  return -1;
+}
+
+static int
+android_saf_tree_symlink (const char *target, struct android_vnode *vnode)
+{
+  errno = EPERM;
+  return -1;
+}
+
+static int
+android_saf_tree_rmdir (struct android_vnode *vnode)
+{
+  struct android_saf_tree_vnode *vp;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* Don't allow deleting the root directory.  */
+
+  if (!vp->document_id)
+    {
+      errno = EROFS;
+      return -1;
+    }
+
+  return android_saf_delete_document (vp->tree_uri,
+                                     vp->document_id,
+                                     vp->name);
+}
+
+static int
+android_saf_tree_rename (struct android_vnode *src,
+                        struct android_vnode *dst,
+                        bool keep_existing)
+{
+  char *last, *dst_last;
+  struct android_saf_tree_vnode *vp, *vdst;
+  char path[PATH_MAX], path1[PATH_MAX];
+  char *fill, *dst_id;
+  int rc;
+
+  /* If dst isn't a tree, file or new vnode, return EXDEV.  */
+
+  if (dst->type != ANDROID_VNODE_SAF_TREE
+      && dst->type != ANDROID_VNODE_SAF_FILE
+      && dst->type != ANDROID_VNODE_SAF_NEW)
+    {
+      errno = EXDEV;
+      return -1;
+    }
+
+  vp = (struct android_saf_tree_vnode *) src;
+  vdst = (struct android_saf_tree_vnode *) dst;
+
+  /* if vp and vdst refer to different tree URIs, return EXDEV.  */
+
+  if (strcmp (vp->tree_uri, vdst->tree_uri))
+    {
+      errno = EXDEV;
+      return -1;
+    }
+
+  /* If `keep_existing' and the destination vnode designates an
+     existing file, return EEXIST.  */
+
+  if (keep_existing && dst->type != ANDROID_VNODE_SAF_NEW)
+    {
+      errno = EEXIST;
+      return -1;
+    }
+
+  /* Unix `rename' maps to two Android content provider operations.
+     The first case is a simple rename, where src and dst are both
+     located within the same directory.  Compare the file names of
+     both up to the component before the last.  */
+
+  last = strrchr (vp->name, '/');
+  eassert (last != NULL);
+
+  if (last[1] == '\0')
+    {
+      if (last == vp->name)
+       {
+         /* This means the caller is trying to rename the root
+            directory of the tree.  */
+         errno = EROFS;
+         return -1;
+       }
+
+      /* The name is terminated by a trailing directory separator.
+         Search backwards for the preceding directory separator.  */
+      last = memrchr (vp->name, '/', last - vp->name);
+      eassert (last != NULL);
+    }
+
+  /* Find the end of the second-to-last component in vdst's name.  */
+
+  dst_last = strrchr (vdst->name, '/');
+  eassert (dst_last != NULL);
+
+  if (dst_last[1] == '\0')
+    {
+      if (dst_last == vdst->name)
+       {
+         /* Forbid overwriting the root of the tree either.  */
+         errno = EROFS;
+         return -1;
+       }
+
+      dst_last = memrchr (vdst->name, '/', dst_last - vdst->name);
+      eassert (dst_last != NULL);
+    }
+
+  if (dst_last - vdst->name != last - vp->name
+      || memcmp (vp->name, vdst->name, last - vp->name))
+    {
+      /* The second case is where the file must be moved from one
+         directory to the other, and possibly then recreated under a
+         new name.  */
+
+      /* The names of the source and destination directories will have
+        to be copied to path.  */
+
+      if (last - vp->name >= PATH_MAX
+         || dst_last - vdst->name >= PATH_MAX)
+       {
+         errno = ENAMETOOLONG;
+         return -1;
+       }
+
+      fill = mempcpy (path, vp->name, last - vp->name);
+      *fill = '\0';
+
+      /* If vdst doesn't already exist, its document_id field is
+        already the name of its parent directory.  */
+
+      if (dst->type == ANDROID_VNODE_SAF_NEW)
+       {
+         /* First, move the document.  This will update
+            VP->document_id if it changes.  */
+
+         if (android_saf_move_document (vp->tree_uri,
+                                        &vp->document_id,
+                                        path,
+                                        vdst->document_id))
+           return -1;
+
+         fill = mempcpy (path, vdst->name, dst_last - vdst->name);
+         *fill = '\0';
+
+         /* Next, rename the document, if its display name differs
+            from that of the source.  */
+
+         if (strcmp (dst_last + 1, last + 1)
+             /* By now vp->document_id is already in the destination
+                directory.  */
+             && android_saf_rename_document (vp->tree_uri,
+                                             vp->document_id,
+                                             path,
+                                             dst_last + 1))
+           return -1;
+
+         return 0;
+       }
+
+      /* Retrieve the ID designating the destination document's parent
+        directory.  */
+
+      fill = mempcpy (path1, vdst->name, dst_last - vdst->name);
+      *fill = '\0';
+
+      rc = android_document_id_from_name (vp->tree_uri,
+                                         path1, &dst_id);
+
+      if (rc != 1)
+       {
+         /* This file is either not a directory or nonexistent.  */
+
+         switch (rc)
+           {
+           case 0:
+             errno = ENOTDIR;
+             goto error;
+
+           case -1:
+             /* dst_id is not set here, as the penultimate component
+                also couldn't be located.  */
+             errno = ENOENT;
+             return -1;
+
+           case -2:
+             errno = ENOENT;
+             goto error;
+
+           default:
+             emacs_abort ();
+           }
+       }
+
+      /* vdst already exists, so it needs to be deleted first.  */
+
+      if (android_saf_delete_document (vdst->tree_uri,
+                                      vdst->document_id,
+                                      vdst->name))
+        goto error;
+
+      /* First, move the document.  This will update
+        VP->document_id if it changes.  */
+
+      if (android_saf_move_document (vp->tree_uri,
+                                    &vp->document_id,
+                                    path, dst_id))
+        goto error;
+
+      /* Next, rename the document, if its display name differs from
+        that of the source.  */
+
+      if (strcmp (dst_last + 1, last + 1)
+         /* By now vp->document_id is already in the destination
+            directory.  */
+         && android_saf_rename_document (vp->tree_uri,
+                                         vp->document_id,
+                                         path1,
+                                         dst_last + 1))
+       goto error;
+
+      xfree (dst_id);
+      return 0;
+
+    error:
+      xfree (dst_id);
+      return 1;
+    }
+
+  /* Otherwise, do this simple rename.  The name of the parent
+     directory is required, as it provides the directory whose entries
+     will be modified.  */
+
+  if (last - vp->name >= PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      return -1;
+    }
+
+  /* If the destination document exists, delete it.  */
+
+  if (dst->type != ANDROID_VNODE_SAF_NEW
+      && android_saf_delete_document (vdst->tree_uri,
+                                     vdst->document_id,
+                                     vdst->name))
+    return -1;
+
+  fill = mempcpy (path, vp->name, last - vp->name);
+  *fill = '\0';
+  return android_saf_rename_document (vp->tree_uri,
+                                     vp->document_id,
+                                     path,
+                                     dst_last + 1);
+}
+
+static int
+android_saf_tree_stat (struct android_vnode *vnode,
+                      struct stat *statb)
+{
+  struct android_saf_tree_vnode *vp;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  return android_saf_stat (vp->tree_uri, vp->document_id,
+                          statb, false);
+}
+
+static int
+android_saf_tree_access (struct android_vnode *vnode, int mode)
+{
+  struct android_saf_tree_vnode *vp;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* Validate MODE.  */
+
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  return android_saf_access (vp->tree_uri, vp->document_id,
+                            mode & W_OK);
+}
+
+static int
+android_saf_tree_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  /* Since tree vnodes represent files that already exist, return
+     EEXIST.  */
+  errno = EEXIST;
+  return -1;
+}
+
+static int
+android_saf_tree_chmod (struct android_vnode *vnode, mode_t mode,
+                       int flags)
+{
+  /* Return EACCESS should MODE contain unusual bits besides the
+     standard file access permissions.  */
+
+  if (mode & ~0777)
+    {
+      errno = EACCES;
+      return -1;
+    }
+
+  /* Otherwise, no further action is necessary, as SAF nodes already
+     pretend to be S_IRUSR | S_IWUSR.  */
+  return 0;
+}
+
+static ssize_t
+android_saf_tree_readlink (struct android_vnode *vnode, char *buffer,
+                          size_t size)
+{
+  /* Return EINVAL.  Symlinks aren't exposed to clients by the
+     SAF.  */
+  errno = EINVAL;
+  return -1;
+}
+
+/* Open a database Cursor containing each directory entry within the
+   supplied SAF tree vnode VP.
+
+   Value is NULL upon failure with errno set to a suitable value, a
+   local reference to the Cursor object otherwise.  */
+
+static jobject
+android_saf_tree_opendir_1 (struct android_saf_tree_vnode *vp)
+{
+  jobject uri, id, cursor;
+  jmethodID method;
+
+  if (inside_saf_critical_section)
+    {
+      errno = EIO;
+      return NULL;
+    }
+
+  /* Build strings for both URI and ID.  */
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check ();
+
+  if (vp->document_id)
+    {
+      id = (*android_java_env)->NewStringUTF (android_java_env,
+                                             vp->document_id);
+      android_exception_check_1 (uri);
+    }
+  else
+    id = NULL;
+
+  /* Try to open the cursor.  */
+  method = service_class.open_document_directory;
+  inside_saf_critical_section = true;
+  cursor
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, uri, id);
+  inside_saf_critical_section = false;
+
+  if (id)
+    {
+      if (android_saf_exception_check (2, id, uri))
+       return NULL;
+
+      ANDROID_DELETE_LOCAL_REF (id);
+    }
+  else if (android_saf_exception_check (1, uri))
+    return NULL;
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  /* Return the resulting cursor.  */
+  return cursor;
+}
+
+static struct dirent *
+android_saf_tree_readdir (struct android_vdir *vdir)
+{
+  struct android_saf_tree_vdir *dir;
+  static struct dirent *dirent;
+  jobject entry, d_name;
+  jint d_type;
+  jmethodID method;
+  size_t length, size;
+  const char *chars;
+
+  dir = (struct android_saf_tree_vdir *) vdir;
+
+  /* Try to read one entry from the cursor.  */
+  method = service_class.read_directory_entry;
+  entry
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, dir->cursor);
+  android_exception_check ();
+
+  /* If ENTRY is NULL, we're at the end of the directory.  */
+
+  if (!entry)
+    {
+      xfree (entry);
+      entry = NULL;
+      return NULL;
+    }
+
+  /* Load both fields from ENTRY.  */
+  d_name = (*android_java_env)->GetObjectField (android_java_env, entry,
+                                               entry_class.d_name);
+  if (!d_name)
+    {
+      /* If an error transpires, d_name is set to NULL.  */
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (entry);
+
+      /* XXX: what would be a better error indication? */
+      errno = EIO;
+      return NULL;
+    }
+
+  /* d_type is 1 if this is a directory, and 0 if it's a regular
+     file.  */
+  d_type = (*android_java_env)->GetIntField (android_java_env, entry,
+                                            entry_class.d_type);
+  ANDROID_DELETE_LOCAL_REF (entry);
+
+  /* Copy the name of the directory over.  */
+  chars = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                 (jstring) d_name,
+                                                 NULL);
+  android_exception_check_nonnull ((void *) chars, d_name);
+
+  /* Figure out how large it is, and then resize dirent to fit.  */
+  length = strlen (chars) + 1;
+  size   = offsetof (struct dirent, d_name) + length;
+  dirent = xrealloc (dirent, size);
+
+  /* Clear dirent.  */
+  memset (dirent, 0, size);
+
+  /* Fill in the generic directory information and copy the string
+     over.  */
+  dirent->d_ino = 0;
+  dirent->d_off = 0;
+  dirent->d_reclen = size;
+  dirent->d_type = d_type ? DT_DIR : DT_UNKNOWN;
+  strcpy (dirent->d_name, chars);
+
+  /* Release the string data and the local reference to STRING.  */
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) d_name,
+                                             chars);
+  ANDROID_DELETE_LOCAL_REF (d_name);
+  return dirent;
+}
+
+static void
+android_saf_tree_closedir (struct android_vdir *vdir)
+{
+  struct android_saf_tree_vdir *dir, **next, *tem;
+
+  dir = (struct android_saf_tree_vdir *) vdir;
+
+  /* dir->name is allocated by asprintf, which uses regular
+     malloc.  */
+  free (dir->name);
+
+  /* Yes, DIR->cursor is a local reference.  */
+  ANDROID_DELETE_LOCAL_REF (dir->cursor);
+
+  /* If the ``directory file descriptor'' has been opened, close
+     it.  */
+  if (dir->fd != -1)
+    close (dir->fd);
+
+  /* Now unlink this directory.  */
+
+  for (next = &all_saf_tree_vdirs; (tem = *next);)
+    {
+      if (tem == dir)
+       *next = dir->next;
+      else
+       next = &(*next)->next;
+    }
+
+  xfree (dir);
+}
+
+static int
+android_saf_tree_dirfd (struct android_vdir *vdir)
+{
+  struct android_saf_tree_vdir *dir;
+
+  dir = (struct android_saf_tree_vdir *) vdir;
+
+  /* Since `android_saf_tree_opendir' tries to avoid opening a file
+     descriptor if readdir isn't called, dirfd can fail if open fails.
+
+     open sets errno to a set of errors different from what POSIX
+     stipulates for dirfd, but for ease of implementation the open
+     errors are used instead.  */
+
+  if (dir->fd >= 0)
+    return dir->fd;
+
+  dir->fd = open ("/dev/null", O_RDONLY | O_CLOEXEC);
+  return dir->fd;
+}
+
+static struct android_vdir *
+android_saf_tree_opendir (struct android_vnode *vnode)
+{
+  struct android_saf_tree_vnode *vp;
+  struct android_saf_tree_vdir *dir;
+  char *fill, *end;
+  jobject cursor;
+  char component[PATH_MAX];
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* First, fill the directory stream with the right functions and
+     file name.  */
+
+  dir = xmalloc (sizeof *dir);
+  dir->vdir.readdir = android_saf_tree_readdir;
+  dir->vdir.closedir = android_saf_tree_closedir;
+  dir->vdir.dirfd = android_saf_tree_dirfd;
+
+  /* Find the authority from the URI.  */
+
+  fill = (char *) vp->tree_uri;
+
+  if (strncmp (fill, "content://", 10))
+    emacs_abort ();
+
+  /* Skip the content header.  */
+  fill += sizeof "content://" - 1;
+
+  /* The authority segment of the URI is between here and the
+     next slash.  */
+
+  end = strchr (fill, '/');
+
+  if (!end)
+    emacs_abort ();
+
+  if (end - fill >= PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      xfree (dir);
+      return NULL;
+    }
+
+  /* Copy the authority over.  */
+
+  memcpy (component, fill, end - fill);
+  component[end - fill] = '\0';
+
+  if (asprintf (&dir->name, "/content/storage/%s/%s%s",
+               component, vp->tree_id, vp->name) < 0)
+    {
+      /* Out of memory.  */
+      xfree (dir);
+      memory_full (0);
+    }
+
+  /* Now open a cursor that iterates through each file in this
+     directory.  */
+
+  cursor = android_saf_tree_opendir_1 (vp);
+
+  if (!cursor)
+    {
+      xfree (dir);
+      xfree (dir->name);
+      return NULL;
+    }
+
+  dir->cursor = cursor;
+  dir->fd = -1;
+  dir->next = all_saf_tree_vdirs;
+  all_saf_tree_vdirs = dir;
+  return &dir->vdir;
+}
+
+/* Create a vnode designating the file NAME within a directory tree
+   whose identifier is TREE.  As with all other `name' functions, NAME
+   may be modified.
+
+   AUTHORITY is the name of the content provider authority that is
+   offering TREE.
+
+   Value is NULL and errno is set if no document tree or provider by
+   those names exists, or some other error takes place (for example,
+   if TREE and AUTHORITY aren't encoded correctly.)  */
+
+static struct android_vnode *
+android_saf_tree_from_name (char *name, const char *tree,
+                           const char *authority)
+{
+  struct android_saf_tree_vnode root;
+  jobject tree_string, authority_string, result;
+  jmethodID method;
+  const char *uri;
+  struct android_vnode *vp;
+
+  /* It's not a given that NAME and TREE are actually in the modified
+     UTF-8 format used by the JVM to encode strings, and the JVM
+     aborts when encountering a string that is not.  Make sure they
+     are valid before continuing.  */
+
+  if (android_verify_jni_string (name)
+      || android_verify_jni_string (authority))
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  tree_string = (*android_java_env)->NewStringUTF (android_java_env,
+                                                  tree);
+  android_exception_check ();
+
+  authority_string
+    = (*android_java_env)->NewStringUTF (android_java_env,
+                                        authority);
+  android_exception_check_1 (tree_string);
+
+  /* Now create the URI and detect if Emacs has the rights to access
+     it.  */
+
+  method = service_class.get_tree_uri;
+  result
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, tree_string,
+                                                      authority_string);
+  android_exception_check_2 (tree_string, authority_string);
+  ANDROID_DELETE_LOCAL_REF (tree_string);
+  ANDROID_DELETE_LOCAL_REF (authority_string);
+
+  /* If it doesn't, return NULL and set errno to ENOENT.  */
+
+  if (!result)
+    {
+      errno = ENOENT;
+      return NULL;
+    }
+
+  /* Otherwise, decode this string.  */
+  uri = (*android_java_env)->GetStringUTFChars (android_java_env, result,
+                                               NULL);
+  android_exception_check_nonnull ((void *) uri, result);
+
+  /* Fill in root.tree_uri with values that represent the root of this
+     document tree.  */
+
+  root.vnode.ops = &saf_tree_vfs_ops;
+  root.vnode.type = ANDROID_VNODE_SAF_TREE;
+  root.vnode.flags = 0;
+  root.tree_uri = uri;
+  root.tree_id = (char *) tree;
+  root.document_id = NULL;
+  root.name = (char *) "/";
+
+  vp = (*root.vnode.ops->name) (&root.vnode, name, strlen (name));
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) result, uri);
+  ANDROID_DELETE_LOCAL_REF (result);
+  return vp;
+}
+
+/* Return any open SAF tree directory stream for which dirfd has
+   returned the file descriptor DIRFD.  Return NULL otherwise.  */
+
+static struct android_saf_tree_vdir *
+android_saf_tree_get_directory (int dirfd)
+{
+  struct android_saf_tree_vdir *dir;
+
+  for (dir = all_saf_tree_vdirs; dir; dir = dir->next)
+    {
+      if (dir->fd == dirfd && dirfd != -1)
+       return dir;
+    }
+
+  return NULL;
+}
+
+
+
+/* SAF file vnode.  The information used to uniquely identify a file
+   is identical to that used to identify an SAF directory, but the
+   vnode operations are different.  */
+
+/* Define `struct android_saf_file_vnode' to be identical to a file
+   vnode.  */
+
+#define android_saf_file_vnode android_saf_tree_vnode
+
+/* Structure describing an open ParcelFileDescriptor.  */
+
+struct android_parcel_fd
+{
+  /* The next open parcel file descriptor.  */
+  struct android_parcel_fd *next;
+
+  /* Global reference to this parcel file descriptor.  */
+  jobject descriptor;
+
+  /* The modification time of this parcel file descriptor, or
+     `invalid_timespec'.  */
+  struct timespec mtime;
+
+  /* The file descriptor itself.  */
+  int fd;
+};
+
+static struct android_vnode *android_saf_file_name (struct android_vnode *,
+                                                   char *, size_t);
+static int android_saf_file_open (struct android_vnode *, int,
+                                 mode_t, bool, int *, AAsset **);
+static int android_saf_file_unlink (struct android_vnode *);
+static int android_saf_file_rmdir (struct android_vnode *);
+static struct android_vdir *android_saf_file_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with SAF tree VFS nodes.  */
+
+static struct android_vops saf_file_vfs_ops =
+  {
+    android_saf_file_name,
+    android_saf_file_open,
+    android_saf_tree_close,
+    android_saf_file_unlink,
+    android_saf_tree_symlink,
+    android_saf_file_rmdir,
+    android_saf_tree_rename,
+    android_saf_tree_stat,
+    android_saf_tree_access,
+    android_saf_tree_mkdir,
+    android_saf_tree_chmod,
+    android_saf_tree_readlink,
+    android_saf_file_opendir,
+  };
+
+/* Chain of all parcel file descriptors currently open.  */
+static struct android_parcel_fd *open_parcel_fds;
+
+static struct android_vnode *
+android_saf_file_name (struct android_vnode *vnode, char *name,
+                      size_t length)
+{
+  struct android_saf_file_vnode *vp;
+
+  /* If LENGTH is empty, make a copy of this vnode and return it.  */
+
+  if (length < 1)
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      /* Duplicate the information contained within VNODE.  */
+
+      vp->tree_uri = xstrdup (vp->tree_uri);
+      vp->tree_id = xstrdup (vp->tree_id);
+      vp->name = xstrdup (vp->name);
+      vp->document_id = xstrdup (vp->name);
+
+      return &vp->vnode;
+    }
+
+  /* A file vnode has no children of its own.  */
+  errno = ENOTDIR;
+  return NULL;
+}
+
+static int
+android_saf_file_open (struct android_vnode *vnode, int flags,
+                      mode_t mode, bool asset_p, int *fd_return,
+                      AAsset **asset)
+{
+  struct android_saf_file_vnode *vp;
+  jobject uri, id, descriptor;
+  jmethodID method;
+  jboolean read, trunc, write;
+  jint fd;
+  struct android_parcel_fd *info;
+  struct stat statb;
+
+  if (inside_saf_critical_section)
+    {
+      errno = EIO;
+      return -1;
+    }
+
+  /* O_APPEND isn't supported as a consequence of Android content
+     providers defaulting to truncating the file.  */
+
+  if (flags & O_APPEND)
+    {
+      errno = EOPNOTSUPP;
+      return -1;
+    }
+
+  /* Build strings for both the URI and ID.  */
+
+  vp = (struct android_saf_file_vnode *) vnode;
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check ();
+  id = (*android_java_env)->NewStringUTF (android_java_env,
+                                         vp->document_id);
+  android_exception_check_1 (uri);
+
+  /* Open a parcel file descriptor according to flags.  Documentation
+     for the SAF openDocument operation is scant and seldom helpful.
+     From observations made, it is clear that their file access modes
+     are inconsistently implemented, and that at least:
+
+       r   = either an FIFO or a real file, without truncation.
+       w   = either an FIFO or a real file, with OR without truncation.
+       wt  = either an FIFO or a real file, with truncation.
+       rw  = a real file, without truncation.
+       rwt = a real file, with truncation.
+
+     This diverges from the self-contradicting documentation, where
+     openDocument says nothing about truncation, and openFile mentions
+     that w can elect not to truncate and programs which rely on
+     truncation should use wt.
+
+     Since Emacs is prepared to handle FIFOs within fileio.c, simply
+     specify the straightforward relationship between FLAGS and the
+     file access modes listed above.  */
+
+  method = service_class.open_document;
+  read = trunc = write = false;
+
+  if ((flags & O_RDWR) == O_RDWR || (flags & O_WRONLY))
+    write = true;
+
+  if (flags & O_TRUNC)
+    trunc = true;
+
+  if ((flags & O_RDWR) == O_RDWR || !write)
+    read = true;
+
+  inside_saf_critical_section = true;
+  descriptor
+    = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                      emacs_service,
+                                                      service_class.class,
+                                                      method, uri, id,
+                                                      read, write, trunc);
+  inside_saf_critical_section = false;
+
+  if (android_saf_exception_check (2, uri, id))
+    return -1;
+
+  ANDROID_DELETE_LOCAL_REF (uri);
+  ANDROID_DELETE_LOCAL_REF (id);
+
+  if (!descriptor)
+    {
+      /* Assume that permission has been denied if DESCRIPTOR cannot
+        be opened.  */
+      errno = EPERM;
+      return -1;
+    }
+
+  /* Allocate a record for this file descriptor.  Parcel file
+     descriptors should be closed using their own `close' function,
+     which takes care of notifying the source that it has been
+     closed.  */
+  info = xmalloc (sizeof *info);
+
+  /* Now obtain the file descriptor.  */
+  fd = (*android_java_env)->CallIntMethod (android_java_env,
+                                          descriptor,
+                                          fd_class.get_fd);
+  android_exception_check_1 (descriptor);
+
+  /* Create a global reference to descriptor.  */
+  info->descriptor
+    = (*android_java_env)->NewGlobalRef (android_java_env,
+                                        descriptor);
+
+  if (!info->descriptor)
+    {
+      /* If the global reference can't be created, delete
+        descriptor.  */
+      (*android_java_env)->ExceptionClear (android_java_env);
+      (*android_java_env)->CallVoidMethod (android_java_env,
+                                          descriptor,
+                                          fd_class.close);
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (descriptor);
+
+      /* Free INFO.  */
+      xfree (info);
+
+      /* Set errno to EMFILE and return.  */
+      errno = EMFILE;
+      return -1;
+    }
+
+  /* Delete the local ref to DESCRIPTOR.  */
+  ANDROID_DELETE_LOCAL_REF (descriptor);
+
+  /* Try to retrieve the modification time of this file from the
+     content provider.
+
+     Refrain from introducing the file status into the file status
+     cache if FLAGS & O_RDWR or FLAGS & O_WRONLY: the cached file
+     status will contain a size and modification time inconsistent
+     with the result of any modifications that later transpire.  */
+
+  if (!android_saf_stat (vp->tree_uri, vp->document_id,
+                        &statb, write))
+    info->mtime = get_stat_mtime (&statb);
+  else
+    info->mtime = invalid_timespec ();
+
+  /* Set info->fd and chain it onto the list.  */
+  info->fd = fd;
+  info->next = open_parcel_fds;
+  open_parcel_fds = info;
+
+  /* Return the file descriptor.  */
+  *fd_return = fd;
+  return 0;
+}
+
+static int
+android_saf_file_unlink (struct android_vnode *vnode)
+{
+  struct android_saf_file_vnode *vp;
+
+  vp = (struct android_saf_file_vnode *) vnode;
+  return android_saf_delete_document (vp->tree_uri,
+                                     vp->document_id,
+                                     vp->name);
+}
+
+static int
+android_saf_file_rmdir (struct android_vnode *vnode)
+{
+  errno = ENOTDIR;
+  return -1;
+}
+
+static struct android_vdir *
+android_saf_file_opendir (struct android_vnode *vnode)
+{
+  errno = ENOTDIR;
+  return NULL;
+}
+
+/* Close FD if it's a parcel file descriptor and return true.
+   If FD isn't, return false.
+
+   Such file descriptors need to be closed using a function
+   written in Java, to tell the sender that it has been
+   closed.  */
+
+static bool
+android_close_parcel_fd (int fd)
+{
+  struct android_parcel_fd *tem, **next, *temp;
+
+  for (next = &open_parcel_fds; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         (*android_java_env)->CallVoidMethod (android_java_env,
+                                              tem->descriptor,
+                                              fd_class.close);
+
+         /* Ignore exceptions for the same reason EINTR errors from
+            `close' should be ignored.  */
+         (*android_java_env)->ExceptionClear (android_java_env);
+         (*android_java_env)->DeleteGlobalRef (android_java_env,
+                                               tem->descriptor);
+
+         temp = tem->next;
+         xfree (tem);
+         *next = temp;
+
+         return true;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+  return false;
+}
+
+
+
+/* SAF ``new'' vnodes.  These nodes share their data structures
+   with tree and file vnodes, but represent files that don't actually
+   exist within a directory.  In them, the document ID represents not
+   the file designated by the vnode itself, but rather its parent
+   directory.
+
+   The only vops defined serve to create directories or files, at
+   which point the vnode becomes invalid.  */
+
+#define android_saf_new_vnode android_saf_tree_vnode
+
+static struct android_vnode *android_saf_new_name (struct android_vnode *,
+                                                  char *, size_t);
+static int android_saf_new_open (struct android_vnode *, int,
+                                mode_t, bool, int *, AAsset **);
+static int android_saf_new_unlink (struct android_vnode *);
+static int android_saf_new_symlink (const char *, struct android_vnode *);
+static int android_saf_new_rmdir (struct android_vnode *);
+static int android_saf_new_rename (struct android_vnode *,
+                                  struct android_vnode *, bool);
+static int android_saf_new_stat (struct android_vnode *, struct stat *);
+static int android_saf_new_access (struct android_vnode *, int);
+static int android_saf_new_mkdir (struct android_vnode *, mode_t);
+static int android_saf_new_chmod (struct android_vnode *, mode_t, int);
+static ssize_t android_saf_new_readlink (struct android_vnode *, char *,
+                                        size_t);
+static struct android_vdir *android_saf_new_opendir (struct android_vnode *);
+
+/* Vector of VFS operations associated with SAF new VFS nodes.  */
+
+static struct android_vops saf_new_vfs_ops =
+  {
+    android_saf_new_name,
+    android_saf_new_open,
+    android_saf_tree_close,
+    android_saf_new_unlink,
+    android_saf_new_symlink,
+    android_saf_new_rmdir,
+    android_saf_new_rename,
+    android_saf_new_stat,
+    android_saf_new_access,
+    android_saf_new_mkdir,
+    android_saf_new_chmod,
+    android_saf_new_readlink,
+    android_saf_new_opendir,
+  };
+
+static struct android_vnode *
+android_saf_new_name (struct android_vnode *vnode, char *name,
+                     size_t length)
+{
+  struct android_saf_new_vnode *vp;
+
+  /* If LENGTH is empty, make a copy of this vnode and return it.  */
+
+  if (length < 1)
+    {
+      vp = xmalloc (sizeof *vp);
+      memcpy (vp, vnode, sizeof *vp);
+
+      /* Duplicate the information contained within VNODE.  */
+
+      vp->tree_uri = xstrdup (vp->tree_uri);
+      vp->tree_id = xstrdup (vp->tree_id);
+      vp->name = xstrdup (vp->name);
+      vp->document_id = xstrdup (vp->name);
+
+      return &vp->vnode;
+    }
+
+  /* A nonexistent vnode has no children of its own.  */
+  errno = ENOTDIR;
+  return NULL;
+}
+
+static int
+android_saf_new_open (struct android_vnode *vnode, int flags,
+                     mode_t mode, bool asset_p, int *fd_return,
+                     AAsset **asset)
+{
+  struct android_saf_new_vnode *vp;
+  char *end;
+  jstring name, id, uri, new_id;
+  const char *new_doc_id;
+  jmethodID method;
+
+  /* If creating a file wasn't intended, return ENOENT.  */
+
+  if (!(flags & O_CREAT))
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+  /* If vp->name indicates that it's a directory, return ENOENT.  */
+
+  vp = (struct android_saf_new_vnode *) vnode;
+  end = strrchr (vp->name, '/');
+
+  /* VP->name must contain at least one directory separator.  */
+  eassert (end);
+
+  if (end[1] == '\0')
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+  /* Otherwise, try to create a new document.  First, build strings
+     for the name, ID and document URI.  */
+
+  name = (*android_java_env)->NewStringUTF (android_java_env,
+                                           end + 1);
+  android_exception_check ();
+  id = (*android_java_env)->NewStringUTF (android_java_env,
+                                         vp->document_id);
+  android_exception_check_1 (name);
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check_2 (name, id);
+
+  /* Next, try to create a new document and retrieve its ID.  */
+
+  method = service_class.create_document;
+  new_id = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                           emacs_service,
+                                                           service_class.class,
+                                                           method, uri, id,
+                                                           name);
+
+  if (android_saf_exception_check (3, name, id, uri))
+    return -1;
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (name);
+  ANDROID_DELETE_LOCAL_REF (id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  if (!new_id)
+    {
+      /* The file couldn't be created for some reason.  */
+      errno = EIO;
+      return -1;
+    }
+
+  /* Now, free VP->document_id and replace it with the service
+     document ID.  */
+
+  new_doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                      new_id, NULL);
+  android_exception_check_nonnull ((void *) new_doc_id, new_id);
+
+  xfree (vp->document_id);
+  vp->document_id = xstrdup (new_doc_id);
+
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             new_id, new_doc_id);
+  ANDROID_DELETE_LOCAL_REF (new_id);
+
+  /* Finally, transform this vnode into a file vnode and call its
+     `open' function.  */
+  vp->vnode.type = ANDROID_VNODE_SAF_FILE;
+  vp->vnode.ops = &saf_file_vfs_ops;
+  return (*vp->vnode.ops->open) (vnode, flags, mode, asset_p,
+                                fd_return, asset);
+}
+
+static int
+android_saf_new_unlink (struct android_vnode *vnode)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_symlink (const char *target, struct android_vnode *vnode)
+{
+  errno = EPERM;
+  return -1;
+}
+
+static int
+android_saf_new_rmdir (struct android_vnode *vnode)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_rename (struct android_vnode *src,
+                       struct android_vnode *dst,
+                       bool keep_existing)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_stat (struct android_vnode *vnode,
+                     struct stat *statb)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static int
+android_saf_new_access (struct android_vnode *vnode, int mode)
+{
+  if (mode != F_OK && !(mode & (W_OK | X_OK | R_OK)))
+    errno = EINVAL;
+  else
+    errno = ENOENT;
+
+  return -1;
+}
+
+static int
+android_saf_new_mkdir (struct android_vnode *vnode, mode_t mode)
+{
+  struct android_saf_new_vnode *vp;
+  jstring name, id, uri, new_id;
+  jmethodID method;
+  const char *new_doc_id;
+  char *end;
+
+  vp = (struct android_saf_tree_vnode *) vnode;
+
+  /* Find the last component of vp->name.  */
+  end = strrchr (vp->name, '/');
+
+  /* VP->name must contain at least one directory separator.  */
+  eassert (end);
+
+  if (end[1] == '\0')
+    {
+      /* There's a trailing directory separator.  Search
+        backwards.  */
+
+      end--;
+      while (end != vp->name && *end != '/')
+       end--;
+
+      /* vp->name[0] is always a directory separator.  */
+      eassert (*end == '/');
+    }
+
+  /* Otherwise, try to create a new document.  First, build strings
+     for the name, ID and document URI.  */
+
+  name = (*android_java_env)->NewStringUTF (android_java_env,
+                                           end + 1);
+  android_exception_check ();
+  id = (*android_java_env)->NewStringUTF (android_java_env,
+                                         vp->document_id);
+  android_exception_check_1 (name);
+  uri = (*android_java_env)->NewStringUTF (android_java_env,
+                                          vp->tree_uri);
+  android_exception_check_2 (name, id);
+
+  /* Next, try to create a new document and retrieve its ID.  */
+
+  method = service_class.create_directory;
+  new_id = (*android_java_env)->CallNonvirtualObjectMethod (android_java_env,
+                                                           emacs_service,
+                                                           service_class.class,
+                                                           method, uri, id,
+                                                           name);
+
+  if (android_saf_exception_check (3, name, id, uri))
+    return -1;
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (name);
+  ANDROID_DELETE_LOCAL_REF (id);
+  ANDROID_DELETE_LOCAL_REF (uri);
+
+  if (!new_id)
+    {
+      /* The file couldn't be created for some reason.  */
+      errno = EIO;
+      return -1;
+    }
+
+  /* Now, free VP->document_id and replace it with the service
+     document ID.  */
+
+  new_doc_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                      new_id, NULL);
+
+  if (android_saf_exception_check (3, name, id, uri))
+    return -1;
+
+  xfree (vp->document_id);
+  vp->document_id = xstrdup (new_doc_id);
+
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             new_id, new_doc_id);
+  ANDROID_DELETE_LOCAL_REF (new_id);
+
+  /* Finally, transform this vnode into a directory vnode.  */
+  vp->vnode.type = ANDROID_VNODE_SAF_TREE;
+  vp->vnode.ops = &saf_tree_vfs_ops;
+  return 0;
+}
+
+static int
+android_saf_new_chmod (struct android_vnode *vnode, mode_t mode,
+                      int flags)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static ssize_t
+android_saf_new_readlink (struct android_vnode *vnode, char *buffer,
+                         size_t size)
+{
+  errno = ENOENT;
+  return -1;
+}
+
+static struct android_vdir *
+android_saf_new_opendir (struct android_vnode *vnode)
+{
+  errno = ENOENT;
+  return NULL;
+}
+
+
+
+/* Synchronization between SAF and Emacs.  Consult EmacsSafThread.java
+   for more details.  */
+
+/* Semaphore posted upon the completion of an SAF operation.  */
+static sem_t saf_completion_sem;
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#else /* GNUC */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif /* __clang__ */
+
+JNIEXPORT jint JNICALL
+NATIVE_NAME (safSyncAndReadInput) (JNIEnv *env, jobject object)
+{
+  while (sem_wait (&saf_completion_sem) < 0)
+    {
+      if (input_blocked_p ())
+       continue;
+
+      process_pending_signals ();
+
+      if (!NILP (Vquit_flag))
+       {
+         __android_log_print (ANDROID_LOG_VERBOSE, __func__,
+                              "quitting from IO operation");
+         return 1;
+       }
+    }
+
+  return 0;
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (safSync) (JNIEnv *env, jobject object)
+{
+  while (sem_wait (&saf_completion_sem) < 0)
+    process_pending_signals ();
+}
+
+JNIEXPORT void JNICALL
+NATIVE_NAME (safPostRequest) (JNIEnv *env, jobject object)
+{
+  sem_post (&saf_completion_sem);
+}
+
+JNIEXPORT jboolean JNICALL
+NATIVE_NAME (ftruncate) (JNIEnv *env, jobject object, jint fd)
+{
+  if (ftruncate (fd, 0) < 0)
+    return false;
+
+  /* Reset the file pointer.  */
+  if (lseek (fd, 0, SEEK_SET) < 0)
+    return false;
+
+  return true;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#else /* GNUC */
+#pragma GCC diagnostic pop
+#endif /* __clang__ */
+
+
+
+/* Root vnode.  This vnode represents the root inode, and is a regular
+   Unix vnode with modifications to `name' that make it return asset
+   vnodes.  */
+
+static struct android_vnode *android_root_name (struct android_vnode *,
+                                               char *, size_t);
+
+/* Vector of VFS operations associated with Unix root filesystem VFS
+   nodes.  */
+
+static struct android_vops root_vfs_ops =
+  {
+    android_root_name,
+    android_unix_open,
+    android_unix_close,
+    android_unix_unlink,
+    android_unix_symlink,
+    android_unix_rmdir,
+    android_unix_rename,
+    android_unix_stat,
+    android_unix_access,
+    android_unix_mkdir,
+    android_unix_chmod,
+    android_unix_readlink,
+    android_unix_opendir,
+  };
+
+/* Array of special named vnodes.  */
+
+static struct android_special_vnode special_vnodes[] =
+  {
+    { "assets",  6, android_afs_initial,       },
+    { "content", 7, android_content_initial,   },
+  };
+
+static struct android_vnode *
+android_root_name (struct android_vnode *vnode, char *name,
+                  size_t length)
+{
+  char *component_end;
+  struct android_special_vnode *special;
+  size_t i;
+
+  /* Skip any leading separator in NAME.  */
+
+  if (*name == '/')
+    name++, length--;
+
+  /* Look for the first directory separator.  */
+  component_end = strchr (name, '/');
+
+  /* If not there, use name + length.  */
+
+  if (!component_end)
+    component_end = name + length;
+  else
+    /* Move past the spearator character.  */
+    component_end++;
+
+  /* Now, find out if the first component is a special vnode; if so,
+     call its root lookup function with the rest of NAME there.  */
+
+  for (i = 0; i < ARRAYELTS (special_vnodes); ++i)
+    {
+      special = &special_vnodes[i];
+
+      if (component_end - name == special->length
+         && !memcmp (special->name, name, special->length))
+       return (*special->initial) (component_end,
+                                   length - special->length);
+
+      /* Detect the case where a special is named with a trailing
+        directory separator.  */
+
+      if (component_end - name == special->length + 1
+         && !memcmp (special->name, name, special->length)
+         && name[special->length] == '/')
+       /* Make sure to include the directory separator.  */
+       return (*special->initial) (component_end - 1,
+                                   length - special->length);
+    }
+
+  /* Otherwise, continue searching for a vnode normally.  */
+  return android_unix_name (vnode, name, length);
+}
+
+
+
+/* File system lookup.  */
+
+/* Look up the vnode that designates NAME, a file name that is at
+   least N bytes.
+
+   NAME may be either an absolute file name or a name relative to the
+   current working directory.  It must not be longer than PATH_MAX
+   bytes.
+
+   Value is NULL upon failure with errno set accordingly, or the
+   vnode.  */
+
+static struct android_vnode *
+android_name_file (const char *name)
+{
+  char buffer[PATH_MAX + 1], *head;
+  const char *end;
+  size_t len;
+  int nslash, c;
+  struct android_vnode *vp;
+
+  len = strlen (name);
+  if (len > PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      return NULL;
+    }
+
+  /* Now, try to ``normalize'' the file name by removing consecutive
+     slash characters while copying it to BUFFER.  */
+
+  head = buffer;
+  nslash = 0;
+  for (end = name + len; name < end; ++name)
+    {
+      c = *name;
+
+      switch (c)
+       {
+       case '/':
+         /* This is a directory separator character.  Two consecutive
+            separator characters should be replaced by a single
+            character; more than three in a row means that the
+            section of the file name before the last slash character
+            should be discarded.  */
+
+         if (!nslash)
+           *head++ = '/';
+
+         nslash++;
+
+         if (nslash >= 3)
+           /* Return to the root directory.  */
+           head = buffer, *head++ = '/', nslash = 0;
+         break;
+
+       default:
+         /* Otherwise, copy the file name over.  */
+         nslash = 0;
+         *head++ = *name;
+         break;
+       }
+    }
+
+  /* Terminate the file name.  */
+  *head = '\0';
+
+  /* If HEAD is a relative file name, it can't reside inside the
+     virtual mounts; create a Unix vnode instead.  */
+
+  if (head == buffer || buffer[0] != '/')
+    return android_unix_vnode (buffer);
+
+  /* Start looking from the root vnode.  */
+  vp = &root_vnode.vnode;
+
+  /* If buffer is empty, this will create a duplicate of the root
+     vnode.  */
+  return (*vp->ops->name) (vp, buffer + 1, head - buffer - 1);
+}
+
+
+
+/* Initialize the virtual filesystem layer.  Load the directory tree
+   from the given asset MANAGER (which should be a local reference
+   within ENV) that will be used to access assets in the future, and
+   create the root vnode.
+
+   ENV should be a JNI environment valid for future calls to VFS
+   functions.  */
+
+void
+android_vfs_init (JNIEnv *env, jobject manager)
+{
+  jclass old;
+
+  android_init_assets (env, manager);
+
+  /* Create the root vnode, which is used to locate all other
+     vnodes.  */
+  root_vnode.vnode.ops = &root_vfs_ops;
+  root_vnode.vnode.type = ANDROID_VNODE_UNIX;
+  root_vnode.vnode.flags = 0;
+  root_vnode.name_length = 1;
+  root_vnode.name = (char *) "/";
+
+  /* Initialize some required classes.  */
+  java_string_class = (*env)->FindClass (env, "java/lang/String");
+  assert (java_string_class);
+
+  old = java_string_class;
+  java_string_class = (jclass) (*env)->NewGlobalRef (env,
+                                                    java_string_class);
+  assert (java_string_class);
+  (*env)->DeleteLocalRef (env, old);
+
+  /* And initialize those used on Android 5.0 and later.  */
+
+  if (android_get_current_api_level () < 21)
+    return;
+
+  android_init_cursor_class (env);
+  android_init_entry_class (env);
+  android_init_fd_class (env);
+
+  /* Initialize each of the exception classes used by
+     `android_saf_exception_check'.  */
+
+  old = (*env)->FindClass (env, "java/io/FileNotFoundException");
+  file_not_found_exception = (*env)->NewGlobalRef (env, old);
+  (*env)->DeleteLocalRef (env, old);
+  eassert (file_not_found_exception);
+
+  old = (*env)->FindClass (env, "java/lang/SecurityException");
+  security_exception = (*env)->NewGlobalRef (env, old);
+  (*env)->DeleteLocalRef (env, old);
+  eassert (security_exception);
+
+  old = (*env)->FindClass (env, "android/os/OperationCanceledException");
+  operation_canceled_exception = (*env)->NewGlobalRef (env, old);
+  (*env)->DeleteLocalRef (env, old);
+  eassert (operation_canceled_exception);
+
+  old = (*env)->FindClass (env, "java/lang/UnsupportedOperationException");
+  unsupported_operation_exception = (*env)->NewGlobalRef (env, old);
+  (*env)->DeleteLocalRef (env, old);
+  eassert (unsupported_operation_exception);
+
+  old = (*env)->FindClass (env, "java/lang/OutOfMemoryError");
+  out_of_memory_error = (*env)->NewGlobalRef (env, old);
+  (*env)->DeleteLocalRef (env, old);
+  eassert (out_of_memory_error);
+
+  /* Initialize the semaphore used to wait for SAF operations to
+     complete.  */
+
+  if (sem_init (&saf_completion_sem, 0, 0) < 0)
+    emacs_abort ();
+}
+
+/* The replacement functions that follow have several major
+   drawbacks:
+
+   The first is that CWD relative file names will always be Unix
+   vnodes, and looking up their parents will always return another
+   Unix vnode.  For example, with the working directory set to
+   /sdcard:
+
+     ../content/storage
+
+   will find /sdcard/../content/storage on the Unix filesystem,
+   opposed to /content/storage within the ``content'' VFS.
+
+   Emacs only uses file names expanded through `expand-file-name', so
+   this is unproblematic in practice.
+
+   The second is that `..' components do not usually check that their
+   preceding component is a directory.  This is a side effect of their
+   removal from file names as part of a pre-processing step before
+   they are opened.  So, even if:
+
+     /sdcard/foo.txt
+
+   is a file, opening the directory:
+
+     /sdcard/foo.txt/..
+
+   will be successful.
+
+   The third is that the handling of `..' components relative to
+   another vnode hasn't been tested and is only assumed to work
+   because the code has been written.  It does not pose a practical
+   problem, however, as Emacs only names files starting from the root
+   vnode.
+
+   The fourth is that errno values from vnode operations don't always
+   reflect what the Unix system calls they emulate can return: for
+   example, `open' may return EIO, while trying to `mkdir' within
+   /content will return ENOENT instead of EROFS.  This is a
+   consequence of how accessing a non-existent file may fail at vnode
+   lookup, instead of when a vop is used.  This problem hasn't made a
+   sufficient nuisance of itself to justify its fix yet.
+
+   The fifth is that trailing directory separators may be lost when
+   naming files relative to another vnode, as a consequence of an
+   optimization used to avoid allocating too much stack or heap
+   space.
+
+   The sixth is that flags and other argument checking is nowhere near
+   exhaustive on vnode types other than Unix vnodes.
+
+   The seventh is that certain vnode types may read async input and
+   return EINTR not upon the arrival of a signal itself, but instead
+   if subsequently read input causes Vquit_flag to be set.  These
+   vnodes may not be reentrant, but operating on them from within an
+   async input handler will at worst cause an error to be returned.
+
+   The eight is that some vnode types do not support O_APPEND.
+
+   And the final drawback is that directories cannot be directly
+   opened.  Instead, `dirfd' must be called on a directory stream used
+   by `openat'.
+
+   Caveat emptor! */
+
+/* Open the VFS node designated by NAME, taking into account FLAGS and
+   MODE, both of which mean the same as they do in a call to `open'.
+
+   Value is -1 upon failure with errno set accordingly, and a file
+   descriptor otherwise.  */
+
+int
+android_open (const char *name, int flags, mode_t mode)
+{
+  struct android_vnode *vp;
+  int fd, rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->open) (vp, flags, mode, false, &fd, NULL);
+  (*vp->ops->close) (vp);
+
+  if (rc < 0)
+    return -1;
+
+  /* If rc is 1, then an asset file descriptor has been returned.
+     This is impossible, so assert that it doesn't transpire.  */
+  assert (rc != 1);
+  return fd;
+}
+
+/* Unlink the VFS node designated by the specified FILE.
+   Value is -1 upon failure with errno set, and 0 otherwise.  */
+
+int
+android_unlink (const char *name)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->unlink) (vp);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Symlink the VFS node designated by LINKPATH to TARGET.
+   Value is -1 upon failure with errno set, and 0 otherwise.  */
+
+int
+android_symlink (const char *target, const char *linkpath)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (linkpath);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->symlink) (target, vp);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Remove the empty directory at the VFS node designated by NAME.
+   Value is -1 upon failure with errno set, and 0 otherwise.  */
+
+int
+android_rmdir (const char *name)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->rmdir) (vp);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Create a directory at the VFS node designated by NAME and the given
+   access MODE.  Value is -1 upon failure with errno set, 0
+   otherwise.  */
+
+int
+android_mkdir (const char *name, mode_t mode)
+{
+  struct android_vnode *vp;
+  int rc;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->mkdir) (vp, mode);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Rename the vnode designated by SRC to the vnode designated by DST.
+   If DST already exists, return -1 and set errno to EEXIST.
+
+   SRCFD and DSTFD should be AT_FDCWD, or else value is -1 and errno
+   is ENOSYS.
+
+   If the filesystem or vnodes containing either DST or SRC does not
+   support rename operations that also check for a preexisting
+   destination, return -1 and set errno to ENOSYS.
+
+   Otherwise, value and errno are identical to that of Unix
+   `rename' with the same arguments.  */
+
+int
+android_renameat_noreplace (int srcfd, const char *src,
+                           int dstfd, const char *dst)
+{
+  struct android_vnode *vp, *vdst;
+  int rc;
+
+  if (srcfd != AT_FDCWD || dstfd != AT_FDCWD)
+    {
+      errno = ENOSYS;
+      return -1;
+    }
+
+  /* Find vnodes for both src and dst.  */
+
+  vp = android_name_file (src);
+  if (!vp)
+    goto error;
+
+  vdst = android_name_file (dst);
+  if (!vdst)
+    goto error1;
+
+  /* Now try to rename vp to vdst.  */
+  rc = (*vp->ops->rename) (vp, vdst, true);
+  (*vp->ops->close) (vp);
+  (*vdst->ops->close) (vdst);
+  return rc;
+
+ error1:
+  (*vp->ops->close) (vp);
+ error:
+  return -1;
+}
+
+/* Like `android_renameat_noreplace', but don't check for DST's
+   existence and don't accept placeholder SRCFD and DSTFD
+   arguments.  */
+
+int
+android_rename (const char *src, const char *dst)
+{
+  struct android_vnode *vp, *vdst;
+  int rc;
+
+  /* Find vnodes for both src and dst.  */
+
+  vp = android_name_file (src);
+  if (!vp)
+    goto error;
+
+  vdst = android_name_file (dst);
+  if (!vdst)
+    goto error1;
+
+  /* Now try to rename vp to vdst.  */
+  rc = (*vp->ops->rename) (vp, vdst, false);
+  (*vp->ops->close) (vp);
+  (*vdst->ops->close) (vdst);
+  return rc;
+
+ error1:
+  (*vp->ops->close) (vp);
+ error:
+  return -1;
+}
+
+
+
+/* fstat, fstatat, faccessat, close/fclose etc.  These functions are
+   somewhat tricky to wrap: they (at least partially) operate on file
+   descriptors, which sometimes provide a base directory for the
+   filesystem operations they perform.  VFS nodes aren't mapped to
+   file descriptors opened through them, which makes this troublesome.
+
+   openat is not wrapped at all; uses are defined out when Emacs is
+   being built for Android.  The other functions fall back to directly
+   making Unix system calls when their base directory arguments are
+   not AT_FDCWD and no directory stream returned from
+   `android_opendir' ever returned that file descriptor, which is
+   enough to satisfy Emacs's current requirements for those functions
+   when a directory file descriptor is supplied.
+
+   fclose and close are finally wrapped because they need to erase
+   information used to link file descriptors with file statistics from
+   their origins; fstat is also wrapped to take this information into
+   account, so that it can return correct file statistics for asset
+   directory files.  */
+
+/* Like fstat.  However, look up the asset corresponding to the file
+   descriptor.  If it exists, return the right information.  */
+
+int
+android_fstat (int fd, struct stat *statb)
+{
+  struct android_afs_open_fd *tem;
+  struct android_parcel_fd *parcel_fd;
+  int rc;
+
+  for (tem = afs_file_descriptors; tem; tem = tem->next)
+    {
+      if (tem->fd == fd)
+       {
+         memcpy (statb, &tem->statb, sizeof *statb);
+         return 0;
+       }
+    }
+
+  rc = fstat (fd, statb);
+
+  /* Now look for a matching parcel file descriptor and use its
+     mtime if available.  */
+
+  parcel_fd = open_parcel_fds;
+  for (; parcel_fd; parcel_fd = parcel_fd->next)
+    {
+      if (parcel_fd->fd == fd)
+       /* Set STATB->st_dev to a negative device number, signifying
+          that it's contained within a content provider.  */
+       statb->st_dev = -4;
+
+      if (parcel_fd->fd == fd
+         && timespec_valid_p (parcel_fd->mtime))
+       {
+#ifdef STAT_TIMESPEC
+         STAT_TIMESPEC (statb, st_mtim) = parcel_fd->mtime;
+#else /* !STAT_TIMESPEC */
+         statb->st_mtime = parcel_fd->mtime.tv_sec;
+         statb->st_mtime_nsec = parcel_fd->mtime.tv_nsec;
+#endif /* STAT_TIMESPEC */
+         break;
+       }
+    }
+
+  return rc;
+}
+
+/* If DIRFD is a file descriptor returned by `android_readdir' for a
+   non-Unix file stream, return FILENAME relative to the file name of
+   the directory represented by that stream within BUFFER, a buffer
+   SIZE bytes long.
+
+   Value is 0 if a file name is returned, 1 otherwise.  */
+
+static int
+android_fstatat_1 (int dirfd, const char *filename,
+                  char *restrict buffer, size_t size)
+{
+  char *dir_name;
+  struct android_saf_root_vdir *vdir;
+  struct android_saf_tree_vdir *vdir1;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open asset directory stream.  */
+
+  dir_name = android_afs_get_directory_name (dirfd);
+
+  if (dir_name)
+    {
+      /* Look for PATHNAME relative to this directory within an asset
+        vnode.  */
+      snprintf (buffer, size, "/assets%s%s", dir_name,
+               filename);
+      return 0;
+    }
+
+  /* Do the same, but for /content directories instead.  */
+
+  dir_name = android_content_get_directory_name (dirfd);
+
+  if (dir_name)
+    {
+      /* Look for PATHNAME relative to this directory within an asset
+        vnode.  */
+      snprintf (buffer, size, "%s/%s", dir_name,
+               filename);
+      return 0;
+    }
+
+  /* And for /content/storage.  */
+
+  vdir = android_saf_root_get_directory (dirfd);
+
+  if (vdir)
+    {
+      if (vdir->authority)
+       snprintf (buffer, size, "/content/storage/%s/%s",
+                 vdir->authority, filename);
+      else
+       snprintf (buffer, size, "/content/storage/%s",
+                 filename);
+
+      return 0;
+    }
+
+  /* /content/storage/foo/... */
+
+  vdir1 = android_saf_tree_get_directory (dirfd);
+
+  if (vdir1)
+    {
+      snprintf (buffer, size, "%s%s", vdir1->name, filename);
+      return 0;
+    }
+
+  return 1;
+}
+
+/* If DIRFD is AT_FDCWD or a file descriptor returned by
+   `android_dirfd', or PATHNAME is an absolute file name, return the
+   file status of the VFS node designated by PATHNAME relative to the
+   VFS node corresponding to DIRFD, or relative to the current working
+   directory if DIRFD is AT_FDCWD.
+
+   Otherwise, call `fstatat' with DIRFD, PATHNAME, STATBUF and
+   FLAGS.  */
+
+int
+android_fstatat (int dirfd, const char *restrict pathname,
+                struct stat *restrict statbuf, int flags)
+{
+  char buffer[PATH_MAX + 1];
+  struct android_vnode *vp;
+  int rc;
+
+  /* Emacs uses AT_SYMLINK_NOFOLLOW, but fortunately (?) DIRFD is
+     never known to Emacs or AT_FDCWD when it originates from a VFS
+     node representing a filesystem that supports symlinks.  */
+
+  if (dirfd == AT_FDCWD || pathname[0] == '/')
+    goto vfs;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open VFS directory stream.  */
+
+  if (!android_fstatat_1 (dirfd, pathname, buffer, PATH_MAX + 1))
+    {
+      pathname = buffer;
+      goto vfs;
+    }
+
+  /* Fall back to fstatat.  */
+  return fstatat (dirfd, pathname, statbuf, flags);
+
+ vfs:
+  vp = android_name_file (pathname);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->stat) (vp, statbuf);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Like `android_fstatat', but check file accessibility instead of
+   status.  */
+
+int
+android_faccessat (int dirfd, const char *restrict pathname,
+                  int mode, int flags)
+{
+  char buffer[PATH_MAX + 1];
+  struct android_vnode *vp;
+  int rc;
+
+  /* Emacs uses AT_SYMLINK_NOFOLLOW, but fortunately (?) DIRFD is
+     never known to Emacs or AT_FDCWD when it originates from a VFS
+     node representing a filesystem that supports symlinks.  */
+
+  if (dirfd == AT_FDCWD || pathname[0] == '/')
+    goto vfs;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open VFS directory stream.  */
+
+  if (!android_fstatat_1 (dirfd, pathname, buffer, PATH_MAX + 1))
+    {
+      pathname = buffer;
+      goto vfs;
+    }
+
+  /* Fall back to faccessat.  */
+  return faccessat (dirfd, pathname, mode, flags);
+
+ vfs:
+  vp = android_name_file (pathname);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->access) (vp, mode);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Like `android_fstatat', but set file modes instead of
+   checking file status and respect FLAGS.  */
+
+int
+android_fchmodat (int dirfd, const char *pathname, mode_t mode,
+                 int flags)
+{
+  char buffer[PATH_MAX + 1];
+  struct android_vnode *vp;
+  int rc;
+
+  if (dirfd == AT_FDCWD || pathname[0] == '/')
+    goto vfs;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open VFS directory stream.  */
+
+  if (!android_fstatat_1 (dirfd, pathname, buffer, PATH_MAX + 1))
+    {
+      pathname = buffer;
+      goto vfs;
+    }
+
+  /* Fall back to fchmodat.  */
+  return fchmodat (dirfd, pathname, mode, flags);
+
+ vfs:
+  vp = android_name_file (pathname);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->chmod) (vp, mode, flags);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Like `android_fstatat', but return the target of any symbolic link
+   at PATHNAME instead of checking file status.  */
+
+ssize_t
+android_readlinkat (int dirfd, const char *restrict pathname,
+                   char *restrict buf, size_t bufsiz)
+{
+  char buffer[PATH_MAX + 1];
+  struct android_vnode *vp;
+  ssize_t rc;
+
+  if (dirfd == AT_FDCWD || pathname[0] == '/')
+    goto vfs;
+
+  /* Now establish whether DIRFD is a file descriptor corresponding to
+     an open VFS directory stream.  */
+
+  if (!android_fstatat_1 (dirfd, pathname, buffer, PATH_MAX + 1))
+    {
+      pathname = buffer;
+      goto vfs;
+    }
+
+  /* Fall back to readlinkat.  */
+  return readlinkat (dirfd, pathname, buf, bufsiz);
+
+ vfs:
+  vp = android_name_file (pathname);
+  if (!vp)
+    return -1;
+
+  rc = (*vp->ops->readlink) (vp, buf, bufsiz);
+  (*vp->ops->close) (vp);
+  return rc;
+}
+
+/* Like `fdopen', but if FD is a parcel file descriptor, ``detach'' it
+   from the original.
+
+   This is necessary because ownership over parcel file descriptors is
+   retained by the ParcelFileDescriptor objects that return them,
+   while file streams also require ownership over file descriptors
+   they are created on behalf of.
+
+   Detaching the parcel file descriptor linked to FD consequentially
+   prevents the owner from being notified when it is eventually
+   closed, but for now that hasn't been demonstrated to be problematic
+   yet, as Emacs doesn't write to file streams.  */
+
+FILE *
+android_fdopen (int fd, const char *mode)
+{
+  struct android_parcel_fd *tem, **next, *temp;
+  int new_fd;
+
+  for (next = &open_parcel_fds; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         new_fd
+           = (*android_java_env)->CallIntMethod (android_java_env,
+                                                 tem->descriptor,
+                                                 fd_class.detach_fd);
+         temp = tem->next;
+         xfree (tem);
+         *next = temp;
+         android_exception_check ();
+
+         /* Assert that FD (returned from `getFd') is identical to
+            the file descriptor returned by `detachFd'.  */
+
+         if (fd != new_fd)
+           emacs_abort ();
+
+         break;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+  return fdopen (fd, mode);
+}
+
+/* Like close.  However, remove the file descriptor from the asset
+   table as well.  */
+
+int
+android_close (int fd)
+{
+  struct android_afs_open_fd *tem, **next, *temp;
+
+  if (android_close_parcel_fd (fd))
+    return 0;
+
+  for (next = &afs_file_descriptors; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         temp = tem->next;
+         xfree (tem);
+         *next = temp;
+
+         break;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+  return close (fd);
+}
+
+/* Like fclose.  However, remove any information associated with
+   FILE's file descriptor from the asset table as well.  */
+
+int
+android_fclose (FILE *stream)
+{
+  int fd;
+  struct android_afs_open_fd *tem, **next, *temp;
+
+  fd = fileno (stream);
+
+  if (fd == -1)
+    goto skip;
+
+  for (next = &afs_file_descriptors; (tem = *next);)
+    {
+      if (tem->fd == fd)
+       {
+         temp = tem->next;
+         xfree (*next);
+         *next = temp;
+
+         break;
+       }
+      else
+       next = &(*next)->next;
+    }
+
+ skip:
+  return fclose (stream);
+}
+
+
+
+/* External asset management interface.  By using functions here
+   to read and write from files, Emacs can avoid opening a
+   shared memory file descriptor for each ``asset'' file.  */
+
+/* Like android_open.  However, return a structure that can
+   either directly hold an AAsset or a file descriptor.
+
+   Value is the structure upon success.  Upon failure, value
+   consists of an uninitialized file descriptor, but its asset
+   field is set to -1, and errno is set accordingly.  */
+
+struct android_fd_or_asset
+android_open_asset (const char *filename, int oflag, mode_t mode)
+{
+  struct android_fd_or_asset fd;
+  AAsset *asset;
+  int rc;
+  struct android_vnode *vp;
+
+  /* Now name this file.  */
+  vp = android_name_file (filename);
+  if (!vp)
+    goto failure;
+
+  rc = (*vp->ops->open) (vp, oflag, mode, true, &fd.fd,
+                        &asset);
+  (*vp->ops->close) (vp);
+
+  /* Upon failure, return fd with its asset field set to (void *)
+     -1.  */
+
+  if (rc < 0)
+    {
+    failure:
+      fd.asset = (void *) -1;
+      fd.fd = -1;
+      return fd;
+    }
+
+  if (rc == 1)
+    {
+      /* An asset file was returned.  Return the structure containing
+        an asset.  */
+      fd.asset = asset;
+      fd.fd = -1;
+      return fd;
+    }
+
+  /* Otherwise, a file descriptor has been returned.  Set fd.asset to
+     NULL, signifying that it is a file descriptor.  */
+  fd.asset = NULL;
+  return fd;
+}
+
+/* Like android_close.  However, it takes a ``file descriptor''
+   opened using android_open_asset.  */
+
+int
+android_close_asset (struct android_fd_or_asset asset)
+{
+  if (!asset.asset)
+    return android_close (asset.fd);
+
+  AAsset_close (asset.asset);
+  return 0;
+}
+
+/* Like `emacs_read_quit'.  However, it handles file descriptors
+   opened using `android_open_asset' as well.  */
+
+ssize_t
+android_asset_read_quit (struct android_fd_or_asset asset,
+                        void *buffer, size_t size)
+{
+  if (!asset.asset)
+    return emacs_read_quit (asset.fd, buffer, size);
+
+  /* It doesn't seem possible to quit from inside AAsset_read,
+     sadly.  */
+  return AAsset_read (asset.asset, buffer, size);
+}
+
+/* Like `read'.  However, it handles file descriptors opened
+   using `android_open_asset' as well.  */
+
+ssize_t
+android_asset_read (struct android_fd_or_asset asset,
+                   void *buffer, size_t size)
+{
+  if (!asset.asset)
+    return read (asset.fd, buffer, size);
+
+  /* It doesn't seem possible to quit from inside AAsset_read,
+     sadly.  */
+  return AAsset_read (asset.asset, buffer, size);
+}
+
+/* Like `lseek', but it handles ``file descriptors'' opened with
+   android_open_asset.  */
+
+off_t
+android_asset_lseek (struct android_fd_or_asset asset, off_t off,
+                    int whence)
+{
+  if (!asset.asset)
+    return lseek (asset.fd, off, whence);
+
+  return AAsset_seek (asset.asset, off, whence);
+}
+
+/* Like `fstat'.  */
+
+int
+android_asset_fstat (struct android_fd_or_asset asset,
+                    struct stat *statb)
+{
+  if (!asset.asset)
+    return android_fstat (asset.fd, statb);
+
+  /* Clear statb.  */
+  memset (statb, 0, sizeof *statb);
+
+  /* Set the mode.  */
+  statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+
+  /* Concoct a nonexistent device and an inode number.  */
+  statb->st_dev = -1;
+  statb->st_ino = 0;
+
+  /* Owned by root.  */
+  statb->st_uid = 0;
+  statb->st_gid = 0;
+
+  /* Size of the file.  */
+  statb->st_size = AAsset_getLength (asset.asset);
+  return 0;
+}
+
+
+
+/* Directory listing emulation.  */
+
+/* Open a directory stream from the VFS node designated by NAME.
+   Value is NULL upon failure with errno set accordingly.  `errno' may
+   be set to EINTR.
+
+   The directory stream returned holds local references to JNI objects
+   and shouldn't be used after the current local reference frame is
+   popped.  */
+
+struct android_vdir *
+android_opendir (const char *name)
+{
+  struct android_vnode *vp;
+  struct android_vdir *dir;
+
+  vp = android_name_file (name);
+  if (!vp)
+    return NULL;
+
+  dir = (*vp->ops->opendir) (vp);
+  (*vp->ops->close) (vp);
+  return dir;
+}
+
+/* Like dirfd.  However, value is not a real directory file descriptor
+   if DIR is an asset directory.  */
+
+int
+android_dirfd (struct android_vdir *dirp)
+{
+  return (*dirp->dirfd) (dirp);
+}
+
+/* Like readdir, but for VFS directory streams instead.  */
+
+struct dirent *
+android_readdir (struct android_vdir *dirp)
+{
+  return (*dirp->readdir) (dirp);
+}
+
+/* Like closedir, but for VFS directory streams instead.  */
+
+void
+android_closedir (struct android_vdir *dirp)
+{
+  return (*dirp->closedir) (dirp);
+}
diff --git a/src/buffer.c b/src/buffer.c
index 0c46b201586..9facc4b7ab8 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -4719,6 +4719,7 @@ init_buffer_once (void)
 #ifdef HAVE_TREE_SITTER
   XSETFASTINT (BVAR (&buffer_local_flags, ts_parser_list), idx); ++idx;
 #endif
+  XSETFASTINT (BVAR (&buffer_local_flags, text_conversion_style), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_in_non_selected_windows), 
idx); ++idx;
 
   /* buffer_local_flags contains no pointers, so it's safe to treat it
@@ -4790,6 +4791,7 @@ init_buffer_once (void)
 #ifdef HAVE_TREE_SITTER
   bset_ts_parser_list (&buffer_defaults, Qnil);
 #endif
+  bset_text_conversion_style (&buffer_defaults, Qnil);
   bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);
 
   bset_enable_multibyte_characters (&buffer_defaults, Qt);
@@ -5866,6 +5868,26 @@ If t, displays a cursor related to the usual cursor type
 You can also specify the cursor type as in the `cursor-type' variable.
 Use Custom to set this variable and update the display.  */);
 
+  /* While this is defined here, each *term.c module must implement
+     the logic itself.  */
+
+  DEFVAR_PER_BUFFER ("text-conversion-style", &BVAR (current_buffer,
+                                                    text_conversion_style),
+                    Qnil,
+    doc: /* How the on screen keyboard's input method should insert in this 
buffer.
+When nil, the input method will be disabled and an ordinary keyboard
+will be displayed in its place.
+When the symbol `action', the input method will insert text directly, but
+will send `return' key events instead of inserting new line characters.
+Any other value means that the input method will insert text directly.
+
+If you need to make non-buffer local changes to this variable, use
+`overriding-text-conversion-style', which see.
+
+This variable does not take immediate effect when set; rather, it
+takes effect upon the next redisplay after the selected window or
+buffer changes.  */);
+
   DEFVAR_LISP ("kill-buffer-query-functions", Vkill_buffer_query_functions,
               doc: /* List of functions called with no args to query before 
killing a buffer.
 The buffer being killed will be current while the functions are running.
diff --git a/src/buffer.h b/src/buffer.h
index e700297a264..e71ffe28045 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -566,6 +566,11 @@ struct buffer
   /* A list of tree-sitter parsers for this buffer.  */
   Lisp_Object ts_parser_list_;
 #endif
+
+  /* What type of text conversion the input method should apply to
+     this buffer.  */
+  Lisp_Object text_conversion_style_;
+
   /* Cursor type to display in non-selected windows.
      t means to use hollow box cursor.
      See `cursor-type' for other values.  */
@@ -842,6 +847,12 @@ bset_width_table (struct buffer *b, Lisp_Object val)
   b->width_table_ = val;
 }
 
+INLINE void
+bset_text_conversion_style (struct buffer *b, Lisp_Object val)
+{
+  b->text_conversion_style_ = val;
+}
+
 /* BUFFER_CEILING_OF (resp. BUFFER_FLOOR_OF), when applied to n, return
    the max (resp. min) p such that
 
diff --git a/src/bytecode.c b/src/bytecode.c
index f3ff19269d0..993ff2e921b 100644
--- a/src/bytecode.c
+++ b/src/bytecode.c
@@ -1118,14 +1118,24 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t 
args_template,
          {
            Lisp_Object idxval = POP;
            Lisp_Object arrayval = TOP;
+           if (!FIXNUMP (idxval))
+             {
+               record_in_backtrace (Qaref, &TOP, 2);
+               wrong_type_argument (Qfixnump, idxval);
+             }
            ptrdiff_t size;
-           ptrdiff_t idx;
            if (((VECTORP (arrayval) && (size = ASIZE (arrayval), true))
-                || (RECORDP (arrayval) && (size = PVSIZE (arrayval), true)))
-               && FIXNUMP (idxval)
-               && (idx = XFIXNUM (idxval),
-                   idx >= 0 && idx < size))
-             TOP = AREF (arrayval, idx);
+                || (RECORDP (arrayval) && (size = PVSIZE (arrayval), true))))
+             {
+               ptrdiff_t idx = XFIXNUM (idxval);
+               if (idx >= 0 && idx < size)
+                 TOP = AREF (arrayval, idx);
+               else
+                 {
+                   record_in_backtrace (Qaref, &TOP, 2);
+                   args_out_of_range (arrayval, idxval);
+                 }
+             }
            else
              TOP = Faref (arrayval, idxval);
            NEXT;
@@ -1136,16 +1146,26 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t 
args_template,
            Lisp_Object newelt = POP;
            Lisp_Object idxval = POP;
            Lisp_Object arrayval = TOP;
+           if (!FIXNUMP (idxval))
+             {
+               record_in_backtrace (Qaset, &TOP, 3);
+               wrong_type_argument (Qfixnump, idxval);
+             }
            ptrdiff_t size;
-           ptrdiff_t idx;
            if (((VECTORP (arrayval) && (size = ASIZE (arrayval), true))
-                || (RECORDP (arrayval) && (size = PVSIZE (arrayval), true)))
-               && FIXNUMP (idxval)
-               && (idx = XFIXNUM (idxval),
-                   idx >= 0 && idx < size))
+                || (RECORDP (arrayval) && (size = PVSIZE (arrayval), true))))
              {
-               ASET (arrayval, idx, newelt);
-               TOP = newelt;
+               ptrdiff_t idx = XFIXNUM (idxval);
+               if (idx >= 0 && idx < size)
+                 {
+                   ASET (arrayval, idx, newelt);
+                   TOP = newelt;
+                 }
+               else
+                 {
+                   record_in_backtrace (Qaset, &TOP, 3);
+                   args_out_of_range (arrayval, idxval);
+                 }
              }
            else
              TOP = Faset (arrayval, idxval, newelt);
diff --git a/src/callint.c b/src/callint.c
index d8d2b278458..00e9a080654 100644
--- a/src/callint.c
+++ b/src/callint.c
@@ -537,7 +537,8 @@ invoke it (via an `interactive' spec that contains, for 
instance, an
                                make_fixnum (SCHARS (callint_message)),
                                Qface, Qminibuffer_prompt, callint_message);
            args[i] = Fread_key_sequence (callint_message,
-                                         Qnil, Qnil, Qnil, Qnil);
+                                         Qnil, Qnil, Qnil, Qnil,
+                                         Qnil);
            unbind_to (speccount1, Qnil);
            visargs[i] = Fkey_description (args[i], Qnil);
 
@@ -567,7 +568,8 @@ invoke it (via an `interactive' spec that contains, for 
instance, an
                                make_fixnum (SCHARS (callint_message)),
                                Qface, Qminibuffer_prompt, callint_message);
            args[i] = Fread_key_sequence_vector (callint_message,
-                                                Qnil, Qt, Qnil, Qnil);
+                                                Qnil, Qt, Qnil, Qnil,
+                                                Qnil);
            visargs[i] = Fkey_description (args[i], Qnil);
            unbind_to (speccount1, Qnil);
 
diff --git a/src/callproc.c b/src/callproc.c
index 6f3d4fad9be..082c65c4f14 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -92,6 +92,10 @@ extern char **environ;
 #include "pgtkterm.h"
 #endif
 
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif /* HAVE_ANDROID */
+
 /* Pattern used by call-process-region to make temp files.  */
 static Lisp_Object Vtemp_file_name_pattern;
 
@@ -144,7 +148,11 @@ static CHILD_SETUP_TYPE child_setup (int, int, int, char 
**, char **,
    directory if it's unreachable.  If ENCODE is true, return as a string
    suitable for a system call; otherwise, return a string in its
    internal representation.  Signal an error if the result would not be
-   an accessible directory.  */
+   an accessible directory.
+
+   If the default directory lies inside a special directory which
+   cannot be made the current working directory, and ENCODE is also
+   set, simply return the home directory.  */
 
 Lisp_Object
 get_current_directory (bool encode)
@@ -157,6 +165,20 @@ get_current_directory (bool encode)
   if (NILP (dir))
     dir = build_string ("~");
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+
+  /* If DIR is an asset directory or a content directory, return
+     the home directory instead.  */
+
+  if (encode
+      && (android_is_special_directory (SSDATA (dir),
+                                       "/assets")
+         || android_is_special_directory (SSDATA (dir),
+                                          "/content")))
+    dir = build_string ("~");
+
+#endif /* HAVE_ANDROID && ANDROID_STUBIFY */
+
   dir = expand_and_dir_to_file (dir);
   Lisp_Object encoded_dir = ENCODE_FILE (remove_slash_colon (dir));
 
@@ -193,7 +215,7 @@ record_kill_process (struct Lisp_Process *p, Lisp_Object 
tempfile)
 static void
 delete_temp_file (Lisp_Object name)
 {
-  unlink (SSDATA (name));
+  emacs_unlink (SSDATA (name));
 }
 
 static void
@@ -499,7 +521,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int 
filefd,
     int ok;
 
     ok = openp (Vexec_path, args[0], Vexec_suffixes, &path,
-               make_fixnum (X_OK), false, false);
+               make_fixnum (X_OK), false, false, NULL);
     if (ok < 0)
       report_file_error ("Searching for program", args[0]);
   }
@@ -1421,6 +1443,18 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int 
std_err,
              const char *pty_name, bool pty_in, bool pty_out,
              const sigset_t *oldset)
 {
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* Android 10 and later don't allow directly executing programs
+     installed in the application data directory.  Emacs provides a
+     loader binary which replaces the `execve' system call for it and
+     all its children.  On these systems, rewrite the command line to
+     call that loader binary instead.  */
+
+  if (android_rewrite_spawn_argv ((const char ***) &argv))
+    return 1;
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+
 #if USABLE_POSIX_SPAWN
   /* Prefer the simpler `posix_spawn' if available.  `posix_spawn'
      doesn't yet support setting up pseudoterminals, so we fall back
@@ -1988,7 +2022,12 @@ init_callproc (void)
     dir_warning ("arch-independent data dir", Vdata_directory);
 
   sh = getenv ("SHELL");
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* The Android shell is found under /system/bin, not /bin.  */
+  Vshell_file_name = build_string (sh ? sh : "/system/bin/sh");
+#else
   Vshell_file_name = build_string (sh ? sh : "/bin/sh");
+#endif
 
   Lisp_Object gamedir = Qnil;
   if (PATH_GAME)
@@ -2111,6 +2150,83 @@ use.
 See `setenv' and `getenv'.  */);
   Vprocess_environment = Qnil;
 
+  DEFVAR_LISP ("ctags-program-name", Vctags_program_name,
+    doc: /* Name of the `ctags' program distributed with Emacs.
+Use this instead of calling `ctags' directly, as `ctags' may have been
+renamed to comply with executable naming restrictions on the system.  */);
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  Vctags_program_name = build_pure_c_string ("ctags");
+#else
+  Vctags_program_name = build_pure_c_string ("libctags.so");
+#endif
+
+  DEFVAR_LISP ("etags-program-name", Vetags_program_name,
+    doc: /* Name of the `etags' program distributed with Emacs.
+Use this instead of calling `etags' directly, as `etags' may have been
+renamed to comply with executable naming restrictions on the system.  */);
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  Vetags_program_name = build_pure_c_string ("etags");
+#else
+  Vetags_program_name = build_pure_c_string ("libetags.so");
+#endif
+
+  DEFVAR_LISP ("hexl-program-name", Vhexl_program_name,
+    doc: /* Name of the `hexl' program distributed with Emacs.
+Use this instead of calling `hexl' directly, as `hexl' may have been
+renamed to comply with executable naming restrictions on the system.  */);
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  Vhexl_program_name = build_pure_c_string ("hexl");
+#else
+  Vhexl_program_name = build_pure_c_string ("libhexl.so");
+#endif
+
+  DEFVAR_LISP ("emacsclient-program-name", Vemacsclient_program_name,
+    doc: /* Name of the `emacsclient' program distributed with Emacs.
+Use this instead of calling `emacsclient' directly, as `emacsclient'
+may have been renamed to comply with executable naming restrictions on
+the system.  */);
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  Vemacsclient_program_name = build_pure_c_string ("emacsclient");
+#else
+  Vemacsclient_program_name = build_pure_c_string ("libemacsclient.so");
+#endif
+
+  DEFVAR_LISP ("movemail-program-name", Vmovemail_program_name,
+    doc: /* Name of the `movemail' program distributed with Emacs.
+Use this instead of calling `movemail' directly, as `movemail'
+may have been renamed to comply with executable naming restrictions on
+the system.  */);
+  /* Don't change the name of `movemail' if Emacs is being built to
+     use movemail from another source.  */
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY   \
+  || defined HAVE_MAILUTILS
+  Vmovemail_program_name = build_pure_c_string ("movemail");
+#else
+  Vmovemail_program_name = build_pure_c_string ("libmovemail.so");
+#endif
+
+  DEFVAR_LISP ("ebrowse-program-name", Vebrowse_program_name,
+    doc: /* Name of the `ebrowse' program distributed with Emacs.
+Use this instead of calling `ebrowse' directly, as `ebrowse'
+may have been renamed to comply with executable naming restrictions on
+the system.  */);
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  Vebrowse_program_name = build_pure_c_string ("ebrowse");
+#else
+  Vebrowse_program_name = build_pure_c_string ("libebrowse.so");
+#endif
+
+  DEFVAR_LISP ("rcs2log-program-name", Vrcs2log_program_name,
+    doc: /* Name of the `rcs2log' program distributed with Emacs.
+Use this instead of calling `rcs2log' directly, as `rcs2log'
+may have been renamed to comply with executable naming restrictions on
+the system.  */);
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  Vrcs2log_program_name = build_pure_c_string ("rcs2log");
+#else /* HAVE_ANDROID && !ANDROID_STUBIFY */
+  Vrcs2log_program_name = build_pure_c_string ("librcs2log.so");
+#endif /* !HAVE_ANDROID || ANDROID_STUBIFY */
+
   defsubr (&Scall_process);
   defsubr (&Sgetenv_internal);
   defsubr (&Scall_process_region);
diff --git a/src/character.c b/src/character.c
index ae153a579d6..2118b20a7c7 100644
--- a/src/character.c
+++ b/src/character.c
@@ -260,8 +260,12 @@ char_width (int c, struct Lisp_Char_Table *dp)
 
 
 DEFUN ("char-width", Fchar_width, Schar_width, 1, 1, 0,
-       doc: /* Return width of CHAR when displayed in the current buffer.
-The width is measured by how many columns it occupies on the screen.
+       doc: /* Return width of CHAR in columns when displayed in the current 
buffer.
+The width of CHAR is measured by how many columns it will occupy on the screen.
+This is based on data in `char-width-table', and ignores the actual
+metrics of the character's glyph as determined by its font.
+If the display table in effect replaces CHAR on display with
+something else, the function returns the width of the replacement.
 Tab is taken to occupy `tab-width' columns.
 usage: (char-width CHAR)  */)
   (Lisp_Object ch)
@@ -457,20 +461,26 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, 
ptrdiff_t to,
 }
 
 DEFUN ("string-width", Fstring_width, Sstring_width, 1, 3, 0,
-       doc: /* Return width of STRING when displayed in the current buffer.
-Width is measured by how many columns it occupies on the screen.
+       doc: /* Return width of STRING in columns when displayed in the current 
buffer.
+Width of STRING is measured by how many columns it will occupy on the screen.
+
 Optional arguments FROM and TO specify the substring of STRING to
 consider, and are interpreted as in `substring'.
 
-When calculating width of a multibyte character in STRING,
-only the base leading-code is considered; the validity of
-the following bytes is not checked.  Tabs in STRING are always
-taken to occupy `tab-width' columns.  The effect of faces and fonts
-used for non-Latin and other unusual characters (such as emoji) is
-ignored as well, as are display properties and invisible text.
-For these reasons, the results are not generally reliable;
-for accurate dimensions of text as it will be displayed,
-use `window-text-pixel-size' instead.
+Width of each character in STRING is generally taken according to
+`char-width', but character compositions and the display table in
+effect are taken into consideration.
+Tabs in STRING are always assumed to occupy `tab-width' columns,
+although they might take fewer columns depending on the column where
+they begin on display.
+The effect of faces and fonts, including fonts used for non-Latin and
+other unusual characters, such as emoji, is ignored, as are display
+properties and invisible text.
+
+For these reasons, the results are just an approximation, especially
+on GUI frames; for accurate dimensions of text as it will be
+displayed, use `string-pixel-width' or `window-text-pixel-size'
+instead.
 usage: (string-width STRING &optional FROM TO)  */)
   (Lisp_Object str, Lisp_Object from, Lisp_Object to)
 {
@@ -1107,6 +1117,14 @@ A char-table for width (columns) of each character.  */);
   char_table_set_range (Vchar_width_table, MAX_5_BYTE_CHAR + 1, MAX_CHAR,
                        make_fixnum (4));
 
+  DEFVAR_LISP ("ambiguous-width-chars", Vambiguous_width_chars,
+              doc: /*
+A char-table for characters whose width (columns) can be 1 or 2.
+
+The actual width depends on the language-environment and on the
+value of `cjk-ambiguous-chars-are-wide'.  */);
+  Vambiguous_width_chars = Fmake_char_table (Qnil, Qnil);
+
   DEFVAR_LISP ("printable-chars", Vprintable_chars,
               doc: /* A char-table for each printable character.  */);
   Vprintable_chars = Fmake_char_table (Qnil, Qnil);
diff --git a/src/charset.c b/src/charset.c
index 7987ffa0c5e..d5e42d038df 100644
--- a/src/charset.c
+++ b/src/charset.c
@@ -486,8 +486,9 @@ load_charset_map_from_file (struct charset *charset, 
Lisp_Object mapfile,
   specpdl_ref count = SPECPDL_INDEX ();
   record_unwind_protect_nothing ();
   specbind (Qfile_name_handler_alist, Qnil);
-  fd = openp (Vcharset_map_path, mapfile, suffixes, NULL, Qnil, false, false);
-  fp = fd < 0 ? 0 : fdopen (fd, "r");
+  fd = openp (Vcharset_map_path, mapfile, suffixes, NULL, Qnil, false, false,
+             NULL);
+  fp = fd < 0 ? 0 : emacs_fdopen (fd, "r");
   if (!fp)
     {
       int open_errno = errno;
@@ -544,7 +545,7 @@ load_charset_map_from_file (struct charset *charset, 
Lisp_Object mapfile,
       entries->entry[idx].c = c;
       n_entries++;
     }
-  fclose (fp);
+  emacs_fclose (fp);
   clear_unwind_protect (count);
 
   load_charset_map (charset, head, n_entries, control_flag);
diff --git a/src/cmds.c b/src/cmds.c
index 37e1779296a..a9b4bd9c321 100644
--- a/src/cmds.c
+++ b/src/cmds.c
@@ -518,7 +518,8 @@ syms_of_cmds (void)
 
   DEFVAR_LISP ("post-self-insert-hook", Vpost_self_insert_hook,
               doc: /* Hook run at the end of `self-insert-command'.
-This is run after inserting the character.  */);
+This is run after inserting a character.
+The hook can access the inserted character via `last-command-event'.  */);
   Vpost_self_insert_hook = Qnil;
 
   defsubr (&Sforward_char);
diff --git a/src/coding.c b/src/coding.c
index 899f8fc7b4d..b7f4120dc8d 100644
--- a/src/coding.c
+++ b/src/coding.c
@@ -8494,7 +8494,7 @@ preferred_coding_system (void)
   return CODING_ID_NAME (id);
 }
 
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
 
 Lisp_Object
 from_unicode (Lisp_Object str)
@@ -8512,10 +8512,31 @@ from_unicode (Lisp_Object str)
 Lisp_Object
 from_unicode_buffer (const wchar_t *wstr)
 {
+#if defined WINDOWSNT || defined CYGWIN
   /* We get one of the two final null bytes for free.  */
   ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr);
   AUTO_STRING_WITH_LEN (str, (char *) wstr, len);
   return from_unicode (str);
+#else
+  /* This code is used only on Android, where little endian UTF-16
+     strings are extended to 32-bit wchar_t.  */
+
+  uint16_t *words;
+  size_t length, i;
+
+  length = wcslen (wstr) + 1;
+
+  USE_SAFE_ALLOCA;
+  SAFE_NALLOCA (words, sizeof *words, length);
+
+  for (i = 0; i < length - 1; ++i)
+    words[i] = wstr[i];
+
+  words[i] = '\0';
+  AUTO_STRING_WITH_LEN (str, (char *) words,
+                       (length - 1) * sizeof *words);
+  return unbind_to (sa_count, from_unicode (str));
+#endif
 }
 
 wchar_t *
@@ -8535,7 +8556,7 @@ to_unicode (Lisp_Object str, Lisp_Object *buf)
   return WCSDATA (*buf);
 }
 
-#endif /* WINDOWSNT || CYGWIN */
+#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */
 
 
 /*** 8. Emacs Lisp library functions ***/
@@ -11745,7 +11766,7 @@ syms_of_coding (void)
   DEFSYM (Qutf_8_unix, "utf-8-unix");
   DEFSYM (Qutf_8_emacs, "utf-8-emacs");
 
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
   /* No, not utf-16-le: that one has a BOM.  */
   DEFSYM (Qutf_16le, "utf-16le");
 #endif
diff --git a/src/coding.h b/src/coding.h
index b8d4f5f27e1..08c29c884a5 100644
--- a/src/coding.h
+++ b/src/coding.h
@@ -709,7 +709,7 @@ extern void encode_coding_object (struct coding_system *,
 /* Defined in this file.  */
 INLINE int surrogates_to_codepoint (int, int);
 
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
 
 /* These functions use Lisp string objects to store the UTF-16LE
    strings that modern versions of Windows expect.  These strings are
@@ -732,7 +732,7 @@ extern Lisp_Object from_unicode (Lisp_Object str);
 /* Convert WSTR to an Emacs string.  */
 extern Lisp_Object from_unicode_buffer (const wchar_t *wstr);
 
-#endif /* WINDOWSNT || CYGWIN */
+#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */
 
 /* Macros for backward compatibility.  */
 
diff --git a/src/comp.c b/src/comp.c
index 7cd69a6c0b1..8ce3d89fe11 100644
--- a/src/comp.c
+++ b/src/comp.c
@@ -776,7 +776,7 @@ comp_hash_source_file (Lisp_Object filename)
 #else
   int res = md5_stream (f, SSDATA (digest));
 #endif
-  fclose (f);
+  emacs_fclose (f);
 
   if (res)
     xsignal2 (Qfile_notify_error, build_string ("hashing failed"), filename);
@@ -4749,7 +4749,7 @@ DEFUN ("comp--release-ctxt", Fcomp__release_ctxt, 
Scomp__release_ctxt,
     gcc_jit_context_release (comp.ctxt);
 
   if (logfile)
-    fclose (logfile);
+    emacs_fclose (logfile);
   comp.ctxt = NULL;
 
   return Qt;
diff --git a/src/conf_post.h b/src/conf_post.h
index 0d5f90a6910..f31e012dc6e 100644
--- a/src/conf_post.h
+++ b/src/conf_post.h
@@ -461,3 +461,13 @@ extern int emacs_setenv_TZ (char const *);
 #else
 # define UNINIT /* empty */
 #endif
+
+/* MB_CUR_MAX is often broken on systems which copy-paste LLVM
+   headers, so replace its definition with a working one if
+   necessary.  */
+
+#ifdef REPLACEMENT_MB_CUR_MAX
+#include <stdlib.h>
+#undef MB_CUR_MAX
+#define MB_CUR_MAX REPLACEMENT_MB_CUR_MAX
+#endif /* REPLACEMENT_MB_CUR_MAX */
diff --git a/src/data.c b/src/data.c
index a56efa7fcc4..8bf1402d500 100644
--- a/src/data.c
+++ b/src/data.c
@@ -2241,17 +2241,18 @@ Instead, use `add-hook' and specify t for the LOCAL 
argument.  */)
   if (sym->u.s.trapped_write == SYMBOL_NOWRITE)
     xsignal1 (Qsetting_constant, variable);
 
-  if (blv ? blv->local_if_set
-      : (forwarded && BUFFER_OBJFWDP (valcontents.fwd)))
-    {
-      tem = Fboundp (variable);
-      /* Make sure the symbol has a local value in this particular buffer,
-        by setting it to the same value it already has.  */
-      Fset (variable, (EQ (tem, Qt) ? Fsymbol_value (variable) : Qunbound));
-      return variable;
-    }
   if (!blv)
     {
+      if (forwarded && BUFFER_OBJFWDP (valcontents.fwd))
+        {
+          int offset = XBUFFER_OBJFWD (valcontents.fwd)->offset;
+          int idx = PER_BUFFER_IDX (offset);
+          eassert (idx);
+          if (idx > 0)
+            /* If idx < 0, it's always buffer local, like `mode-name`.  */
+            SET_PER_BUFFER_VALUE_P (current_buffer, idx, true);
+          return variable;
+        }
       blv = make_blv (sym, forwarded, valcontents);
       sym->u.s.redirect = SYMBOL_LOCALIZED;
       SET_SYMBOL_BLV (sym, blv);
@@ -4144,6 +4145,8 @@ syms_of_data (void)
   DEFSYM (Qelt, "elt");
   DEFSYM (Qsetcar, "setcar");
   DEFSYM (Qsetcdr, "setcdr");
+  DEFSYM (Qaref, "aref");
+  DEFSYM (Qaset, "aset");
 
   error_tail = pure_cons (Qerror, Qnil);
 
diff --git a/src/dired.c b/src/dired.c
index 3f55c4c3830..c10531cdb16 100644
--- a/src/dired.c
+++ b/src/dired.c
@@ -44,6 +44,21 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "msdos.h"     /* for fstatat */
 #endif
 
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+typedef DIR emacs_dir;
+#define emacs_readdir readdir
+#define emacs_closedir closedir
+#else
+
+#include "android.h"
+
+/* The Android emulation of dirent stuff is required to be able to
+   list the /assets special directory.  */
+typedef struct android_vdir emacs_dir;
+#define emacs_readdir android_readdir
+#define emacs_closedir android_closedir
+#endif
+
 #ifdef WINDOWSNT
 extern int is_slow_fs (const char *);
 #endif
@@ -78,19 +93,42 @@ dirent_type (struct dirent *dp)
 #endif
 }
 
-static DIR *
+static emacs_dir *
 open_directory (Lisp_Object dirname, Lisp_Object encoded_dirname, int *fdp)
 {
   char *name = SSDATA (encoded_dirname);
-  DIR *d;
+  emacs_dir *d;
   int fd, opendir_errno;
 
-#ifdef DOS_NT
-  /* Directories cannot be opened.  The emulation assumes that any
-     file descriptor other than AT_FDCWD corresponds to the most
-     recently opened directory.  This hack is good enough for Emacs.  */
+#if defined DOS_NT || (defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  /* On DOS_NT, directories cannot be opened.  The emulation assumes
+     that any file descriptor other than AT_FDCWD corresponds to the
+     most recently opened directory.  This hack is good enough for
+     Emacs.
+
+     This code is also used on Android for a different reason: a
+     special `assets' directory outside the normal file system is used
+     to open assets inside the Android application package, and must
+     be listed using the opendir-like interface provided in
+     android.h.  */
   fd = 0;
+#ifndef HAVE_ANDROID
   d = opendir (name);
+#else
+  /* `android_opendir' can return EINTR if DIRNAME designates a file
+     within a slow-to-respond document provider.  */
+
+ again:
+  d = android_opendir (name);
+
+  if (d)
+    fd = android_dirfd (d);
+  else if (errno == EINTR)
+    {
+      maybe_quit ();
+      goto again;
+    }
+#endif
   opendir_errno = errno;
 #else
   fd = emacs_open (name, O_RDONLY | O_DIRECTORY, 0);
@@ -125,7 +163,7 @@ directory_files_internal_w32_unwind (Lisp_Object arg)
 static void
 directory_files_internal_unwind (void *d)
 {
-  closedir (d);
+  emacs_closedir (d);
 }
 
 /* Return the next directory entry from DIR; DIR's name is DIRNAME.
@@ -133,12 +171,12 @@ directory_files_internal_unwind (void *d)
    Signal any unrecoverable errors.  */
 
 static struct dirent *
-read_dirent (DIR *dir, Lisp_Object dirname)
+read_dirent (emacs_dir *dir, Lisp_Object dirname)
 {
   while (true)
     {
       errno = 0;
-      struct dirent *dp = readdir (dir);
+      struct dirent *dp = emacs_readdir (dir);
       if (dp || errno == 0)
        return dp;
       if (! (errno == EAGAIN || errno == EINTR))
@@ -190,7 +228,10 @@ directory_files_internal (Lisp_Object directory, 
Lisp_Object full,
   Lisp_Object encoded_dirfilename = ENCODE_FILE (dirfilename);
 
   int fd;
-  DIR *d = open_directory (dirfilename, encoded_dirfilename, &fd);
+
+  /* Keep in mind that FD is not always a real file descriptor on
+     Android.  */
+  emacs_dir *d = open_directory (dirfilename, encoded_dirfilename, &fd);
 
   /* Unfortunately, we can now invoke expand-file-name and
      file-attributes on filenames, both of which can throw, so we must
@@ -300,7 +341,7 @@ directory_files_internal (Lisp_Object directory, 
Lisp_Object full,
       list = Fcons (attrs ? Fcons (finalname, fileattrs) : finalname, list);
     }
 
-  closedir (d);
+  emacs_closedir (d);
 #ifdef WINDOWSNT
   if (attrs)
     Vw32_get_true_file_attributes = w32_save;
@@ -514,7 +555,7 @@ file_name_completion (Lisp_Object file, Lisp_Object 
dirname, bool all_flag,
        }
     }
   int fd;
-  DIR *d = open_directory (dirname, encoded_dir, &fd);
+  emacs_dir *d = open_directory (dirname, encoded_dir, &fd);
   record_unwind_protect_ptr (directory_files_internal_unwind, d);
 
   /* Loop reading directory entries.  */
@@ -855,7 +896,9 @@ file_name_completion_dirp (int fd, struct dirent *dp, 
ptrdiff_t len)
   char *subdir_name = SAFE_ALLOCA (len + 2);
   memcpy (subdir_name, dp->d_name, len);
   strcpy (subdir_name + len, "/");
-  bool dirp = faccessat (fd, subdir_name, F_OK, AT_EACCESS) == 0;
+
+  bool dirp = sys_faccessat (fd, subdir_name,
+                            F_OK, AT_EACCESS) == 0;
   SAFE_FREE ();
   return dirp;
 }
@@ -979,14 +1022,15 @@ file_attributes (int fd, char const *name,
 
   int err = EINVAL;
 
-#if defined O_PATH && !defined HAVE_CYGWIN_O_PATH_BUG
+#if defined O_PATH && !defined HAVE_CYGWIN_O_PATH_BUG  \
+  && !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
   int namefd = emacs_openat (fd, name, O_PATH | O_CLOEXEC | O_NOFOLLOW, 0);
   if (namefd < 0)
     err = errno;
   else
     {
       record_unwind_protect_int (close_file_unwind, namefd);
-      if (fstat (namefd, &s) != 0)
+      if (sys_fstat (namefd, &s) != 0)
        {
          err = errno;
          /* The Linux kernel before version 3.6 does not support
diff --git a/src/dispextern.h b/src/dispextern.h
index ece128949f5..3a4d6095f73 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -53,8 +53,14 @@ typedef struct
   unsigned short red, green, blue;
 } Emacs_Color;
 
+#ifndef HAVE_ANDROID
 /* Accommodate X's usage of None as a null resource ID.  */
 #define No_Cursor (NULL)
+#else
+#define No_Cursor 0
+#endif
+
+#ifndef HAVE_ANDROID
 
 /* XRectangle-like struct used by non-X GUI code.  */
 typedef struct
@@ -63,6 +69,12 @@ typedef struct
   unsigned width, height;
 } Emacs_Rectangle;
 
+#else
+
+typedef struct android_rectangle Emacs_Rectangle;
+
+#endif
+
 /* XGCValues-like struct used by non-X GUI code.  */
 typedef struct
 {
@@ -144,6 +156,13 @@ typedef Emacs_Pixmap Emacs_Pix_Container;
 typedef Emacs_Pixmap Emacs_Pix_Context;
 #endif
 
+#ifdef HAVE_ANDROID
+#include "androidgui.h"
+typedef struct android_display_info Display_Info;
+typedef struct android_image *Emacs_Pix_Container;
+typedef struct android_image *Emacs_Pix_Context;
+#endif
+
 #ifdef HAVE_WINDOW_SYSTEM
 # include <time.h>
 # include "fontset.h"
@@ -157,6 +176,22 @@ typedef void *Emacs_Cursor;
 #define NativeRectangle int
 #endif
 
+#ifdef HAVE_WINDOW_SYSTEM
+
+/* ``box'' structure similar to that found in the X sample server,
+   meaning that X2 and Y2 are not actually the end of the box, but one
+   pixel past the end of the box, which makes checking for overlaps
+   less necessary.  This is convenient to use in every GUI port.  */
+
+struct gui_box
+{
+  /* Bounds of the box.  */
+  int x1, y1;
+  int x2, y2;
+};
+
+#endif
+
 /* Text cursor types.  */
 
 enum text_cursor_kinds
@@ -1401,6 +1436,8 @@ struct glyph_string
   /* The GC to use for drawing this glyph string.  */
 #if defined (HAVE_X_WINDOWS)
   GC gc;
+#elif defined HAVE_ANDROID
+  struct android_gc *gc;
 #endif
 #if defined (HAVE_NTGUI)
   Emacs_GC *gc;
@@ -1681,6 +1718,8 @@ struct face
      drawing the characters in this face.  */
 # ifdef HAVE_X_WINDOWS
   GC gc;
+# elif defined HAVE_ANDROID
+  struct android_gc *gc;
 # else
   Emacs_GC *gc;
 # endif
@@ -3057,8 +3096,9 @@ struct redisplay_interface
 
 #ifdef HAVE_WINDOW_SYSTEM
 
-# if (defined USE_CAIRO || defined HAVE_XRENDER \
-      || defined HAVE_NS || defined HAVE_NTGUI || defined HAVE_HAIKU)
+# if (defined USE_CAIRO || defined HAVE_XRENDER                                
\
+      || defined HAVE_NS || defined HAVE_NTGUI || defined HAVE_HAIKU   \
+      || defined HAVE_ANDROID)
 #  define HAVE_NATIVE_TRANSFORMS
 # endif
 
@@ -3094,6 +3134,13 @@ struct image
   int original_width, original_height;
 # endif
 #endif /* HAVE_X_WINDOWS */
+#ifdef HAVE_ANDROID
+  /* Android images of the image, corresponding to the above Pixmaps.
+     Non-NULL means it and its Pixmap counterpart may be out of sync
+     and the latter is outdated.  NULL means the X image has been
+     synchronized to Pixmap.  */
+  struct android_image *ximg, *mask_img;
+#endif /* HAVE_ANDROID */
 #ifdef HAVE_NTGUI
   XFORM xform;
 #endif
@@ -3316,9 +3363,13 @@ enum tool_bar_item_idx
   /* If we shall show the label only below the icon and not beside it.  */
   TOOL_BAR_ITEM_VERT_ONLY,
 
+  /* Whether or not this tool bar item is hidden and should cause
+     subsequent items to be displayed on a new line.  */
+  TOOL_BAR_ITEM_WRAP,
+
   /* Sentinel = number of slots in tool_bar_items occupied by one
      tool-bar item.  */
-  TOOL_BAR_ITEM_NSLOTS
+  TOOL_BAR_ITEM_NSLOTS,
 };
 
 
@@ -3480,6 +3531,7 @@ extern void get_glyph_string_clip_rect (struct 
glyph_string *,
                                         NativeRectangle *nr);
 extern Lisp_Object find_hot_spot (Lisp_Object, int, int);
 
+extern int get_tab_bar_item_kbd (struct frame *, int, int, int *, bool *);
 extern Lisp_Object handle_tab_bar_click (struct frame *,
                                         int, int, bool, int);
 extern void handle_tool_bar_click (struct frame *,
@@ -3491,6 +3543,9 @@ extern void expose_frame (struct frame *, int, int, int, 
int);
 extern bool gui_intersect_rectangles (const Emacs_Rectangle *,
                                       const Emacs_Rectangle *,
                                       Emacs_Rectangle *);
+extern void gui_union_rectangles (const Emacs_Rectangle *,
+                                 const Emacs_Rectangle *,
+                                 Emacs_Rectangle *);
 extern void gui_consider_frame_title (Lisp_Object);
 #endif /* HAVE_WINDOW_SYSTEM */
 
@@ -3499,9 +3554,11 @@ extern void gui_clear_window_mouse_face (struct window 
*);
 extern void cancel_mouse_face (struct frame *);
 extern bool clear_mouse_face (Mouse_HLInfo *);
 extern bool cursor_in_mouse_face_p (struct window *w);
+#ifndef HAVE_ANDROID
 extern void tty_draw_row_with_mouse_face (struct window *, struct glyph_row *,
                                          int, int, enum draw_glyphs_face);
 extern void display_tty_menu_item (const char *, int, int, int, int, bool);
+#endif
 extern struct glyph *x_y_to_hpos_vpos (struct window *, int, int, int *, int *,
                                       int *, int *, int *);
 /* Flags passed to try_window.  */
@@ -3563,7 +3620,7 @@ void prepare_image_for_display (struct frame *, struct 
image *);
 ptrdiff_t lookup_image (struct frame *, Lisp_Object, int);
 
 #if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined HAVE_NS \
-  || defined HAVE_HAIKU
+  || defined HAVE_HAIKU || defined HAVE_ANDROID
 #define RGB_PIXEL_COLOR unsigned long
 #endif
 
@@ -3644,6 +3701,9 @@ void gamma_correct (struct frame *, COLORREF *);
 #ifdef HAVE_HAIKU
 void gamma_correct (struct frame *, Emacs_Color *);
 #endif
+#ifdef HAVE_ANDROID
+extern void gamma_correct (struct frame *, Emacs_Color *);
+#endif
 
 #ifdef HAVE_WINDOW_SYSTEM
 
diff --git a/src/dispnew.c b/src/dispnew.c
index 82524d8cb8d..d6a27ac29ec 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -43,6 +43,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "xwidget.h"
 #include "pdumper.h"
 
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif
+
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -788,7 +792,7 @@ clear_current_matrices (register struct frame *f)
   if (f->current_matrix)
     clear_glyph_matrix (f->current_matrix);
 
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
   /* Clear the matrix of the menu bar window, if such a window exists.
      The menu bar window is currently used to display menus on X when
      no toolkit support is compiled in.  */
@@ -822,7 +826,7 @@ clear_desired_matrices (register struct frame *f)
   if (f->desired_matrix)
     clear_glyph_matrix (f->desired_matrix);
 
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
   if (WINDOWP (f->menu_bar_window))
     clear_glyph_matrix (XWINDOW (f->menu_bar_window)->desired_matrix);
 #endif
@@ -1156,6 +1160,7 @@ prepare_desired_row (struct window *w, struct glyph_row 
*row, bool mode_line_p)
     }
 }
 
+#ifndef HAVE_ANDROID
 
 /* Return a hash code for glyph row ROW, which may
    be from current or desired matrix of frame F.  */
@@ -1248,6 +1253,7 @@ line_draw_cost (struct frame *f, struct glyph_matrix 
*matrix, int vpos)
   return len;
 }
 
+#endif
 
 /* Return true if the glyph rows A and B have equal contents.
    MOUSE_FACE_P means compare the mouse_face_p flags of A and B, too.  */
@@ -2160,7 +2166,7 @@ adjust_frame_glyphs_for_window_redisplay (struct frame *f)
   /* Allocate/reallocate window matrices.  */
   allocate_matrices_for_window_redisplay (XWINDOW (FRAME_ROOT_WINDOW (f)));
 
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
   /* Allocate/ reallocate matrices of the dummy window used to display
      the menu bar under X when no X toolkit support is available.  */
   {
@@ -2321,7 +2327,7 @@ free_glyphs (struct frame *f)
       if (!NILP (f->root_window))
         free_window_matrices (XWINDOW (f->root_window));
 
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
       /* Free the dummy window for menu bars without X toolkit and its
         glyph matrices.  */
       if (!NILP (f->menu_bar_window))
@@ -3194,6 +3200,7 @@ redraw_frame (struct frame *f)
      its redisplay done.  */
   mark_window_display_accurate (FRAME_ROOT_WINDOW (f), 0);
   set_window_update_flags (XWINDOW (FRAME_ROOT_WINDOW (f)), true);
+
   f->garbaged = false;
 }
 
@@ -3259,7 +3266,7 @@ update_frame (struct frame *f, bool force_p, bool 
inhibit_hairy_id_p)
         when pending input is detected.  */
       update_begin (f);
 
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
       /* Update the menu bar on X frames that don't have toolkit
         support.  */
       if (WINDOWP (f->menu_bar_window))
@@ -5096,6 +5103,10 @@ update_frame_1 (struct frame *f, bool force_p, bool 
inhibit_id_p,
 static bool
 scrolling (struct frame *frame)
 {
+  /* In fact this code should never be reached at all under
+     Android.  */
+
+#ifndef HAVE_ANDROID
   int unchanged_at_top, unchanged_at_bottom;
   int window_size;
   int changed_lines;
@@ -5186,6 +5197,7 @@ scrolling (struct frame *frame)
                 free_at_end_vpos - unchanged_at_top);
 
   SAFE_FREE ();
+#endif
   return false;
 }
 
@@ -5227,7 +5239,9 @@ count_match (struct glyph *str1, struct glyph *end1, 
struct glyph *str2, struct
 
 /* Char insertion/deletion cost vector, from term.c */
 
+#ifndef HAVE_ANDROID
 #define char_ins_del_cost(f) (&char_ins_del_vector[FRAME_TOTAL_COLS ((f))])
+#endif
 
 
 /* Perform a frame-based update on line VPOS in frame FRAME.  */
@@ -5432,7 +5446,10 @@ update_frame_line (struct frame *f, int vpos, bool 
updating_menu_p)
   tem = (nlen - nsp) - (olen - osp);
   if (endmatch && tem
       && (!FRAME_CHAR_INS_DEL_OK (f)
-          || endmatch <= char_ins_del_cost (f)[tem]))
+#ifndef HAVE_ANDROID
+          || endmatch <= char_ins_del_cost (f)[tem]
+#endif
+         ))
     endmatch = 0;
 
   /* nsp - osp is the distance to insert or delete.
@@ -5442,7 +5459,10 @@ update_frame_line (struct frame *f, int vpos, bool 
updating_menu_p)
 
   if (nsp != osp
       && (!FRAME_CHAR_INS_DEL_OK (f)
-         || begmatch + endmatch <= char_ins_del_cost (f)[nsp - osp]))
+#ifndef HAVE_ANDROID
+         || begmatch + endmatch <= char_ins_del_cost (f)[nsp - osp]
+#endif
+         ))
     {
       begmatch = 0;
       endmatch = 0;
@@ -5636,6 +5656,15 @@ buffer_posn_from_coords (struct window *w, int *x, int 
*y, struct display_pos *p
      argument is ZV to prevent move_it_in_display_line from matching
      based on buffer positions.  */
   move_it_in_display_line (&it, ZV, to_x, MOVE_TO_X);
+  if (mouse_prefer_closest_glyph)
+    {
+      int next_x = it.current_x + it.pixel_width;
+      int before_dx = to_x - it.current_x;
+      int after_dx = next_x - to_x;
+      if (before_dx > after_dx)
+        move_it_in_display_line (&it, ZV, next_x, MOVE_TO_X);
+    }
+
   bidi_unshelve_cache (itdata, 0);
 
   Fset_buffer (old_current_buffer);
@@ -6071,7 +6100,7 @@ FILE = nil means just close any termscript file currently 
open.  */)
   if (tty->termscript != 0)
     {
       block_input ();
-      fclose (tty->termscript);
+      emacs_fclose (tty->termscript);
       tty->termscript = 0;
       unblock_input ();
     }
@@ -6565,6 +6594,15 @@ init_display_interactive (void)
     }
 #endif /* HAVE_X_WINDOWS */
 
+#ifdef HAVE_ANDROID
+  if (!inhibit_window_system && android_init_gui)
+    {
+      Vinitial_window_system = Qandroid;
+      android_term_init ();
+      return;
+    }
+#endif
+
 #ifdef HAVE_NTGUI
   if (!inhibit_window_system)
     {
@@ -6619,6 +6657,7 @@ init_display_interactive (void)
       exit (1);
     }
 
+#ifndef HAVE_ANDROID
   {
     struct terminal *t;
     struct frame *f = XFRAME (selected_frame);
@@ -6661,6 +6700,11 @@ init_display_interactive (void)
                                    : Qnil));
     Fmodify_frame_parameters (selected_frame, tty_arg);
   }
+#else
+  fatal ("Could not establish a connection to the Android application.\n"
+        "Emacs does not work on text terminals when built to run as"
+        " part of an Android application package.");
+#endif
 
   {
     struct frame *sf = SELECTED_FRAME ();
@@ -6805,6 +6849,7 @@ The value is a symbol:
  `pc' for a direct-write MS-DOS frame.
  `pgtk' for an Emacs frame using pure GTK facilities.
  `haiku' for an Emacs frame running in Haiku.
+ `android' for an Emacs frame running in Android.
 
 Use of this variable as a boolean is deprecated.  Instead,
 use `display-graphic-p' or any of the other `display-*-p'
@@ -6813,6 +6858,14 @@ predicates which report frame's specific UI-related 
capabilities.  */);
   DEFVAR_BOOL ("cursor-in-echo-area", cursor_in_echo_area,
               doc: /* Non-nil means put cursor in minibuffer, at end of any 
message there.  */);
 
+  DEFVAR_BOOL ("mouse-prefer-closest-glyph", mouse_prefer_closest_glyph,
+              doc: /* Non-nil means mouse click position is taken from glyph 
closest to click.
+
+When non-nil, mouse position lists will report buffer position set to
+the position of the glyph that is the closest to the mouse pointer
+at the time of the click, instead of the glyph immediately under it.  */);
+  mouse_prefer_closest_glyph = false;
+
   DEFVAR_LISP ("glyph-table", Vglyph_table,
               doc: /* Table defining how to output a glyph code to the frame.
 If not nil, this is a vector indexed by glyph code to define the glyph.
diff --git a/src/doc.c b/src/doc.c
index 174341523d7..37e04262681 100644
--- a/src/doc.c
+++ b/src/doc.c
@@ -37,6 +37,39 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "intervals.h"
 #include "keymap.h"
 
+
+
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY   \
+  || (__ANDROID_API__ < 9)
+#define doc_fd         int
+#define doc_fd_p(fd)   ((fd) >= 0)
+#define doc_open       emacs_open
+#define doc_read_quit  emacs_read_quit
+#define doc_lseek      lseek
+#else /* HAVE_ANDROID && !defined ANDROID_STUBIFY
+        && __ANDROID_API__ >= 9 */
+
+#include "android.h"
+
+/* Use an Android file descriptor under Android instead, as this
+   allows loading directly from asset files without loading each asset
+   into memory and creating a separate file descriptor every time.
+
+   However, lread requires the ability to seek inside asset files,
+   which is not provided under Android 2.2.  So when building for that
+   particular system, fall back to the usual file descriptor-based
+   code.  */
+
+#define doc_fd         struct android_fd_or_asset
+#define doc_fd_p(fd)   ((fd).asset != (void *) -1)
+#define doc_open       android_open_asset
+#define doc_read_quit  android_asset_read_quit
+#define doc_lseek      android_asset_lseek
+#define USE_ANDROID_ASSETS
+#endif /* !HAVE_ANDROID || ANDROID_STUBIFY || __ANDROID_API__ < 9 */
+
+
+
 /* Buffer used for reading from documentation file.  */
 static char *get_doc_string_buffer;
 static ptrdiff_t get_doc_string_buffer_size;
@@ -59,6 +92,22 @@ read_bytecode_char (bool unreadflag)
   return *read_bytecode_pointer++;
 }
 
+#ifdef USE_ANDROID_ASSETS
+
+/* Like `close_file_unwind'.  However, PTR is a pointer to an Android
+   file descriptor instead of a system file descriptor.  */
+
+static void
+close_file_unwind_android_fd (void *ptr)
+{
+  struct android_fd_or_asset *fd;
+
+  fd = ptr;
+  android_close_asset (*fd);
+}
+
+#endif /* USE_ANDROID_ASSETS */
+
 /* Extract a doc string from a file.  FILEPOS says where to get it.
    If it is an integer, use that position in the standard DOC file.
    If it is (FILE . INTEGER), use FILE as the file name
@@ -123,8 +172,8 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool 
definition)
   name = SAFE_ALLOCA (docdir_sizemax + SBYTES (file));
   lispstpcpy (lispstpcpy (name, docdir), file);
 
-  int fd = emacs_open (name, O_RDONLY, 0);
-  if (fd < 0)
+  doc_fd fd = doc_open (name, O_RDONLY, 0);
+  if (!doc_fd_p (fd))
     {
       if (will_dump_p ())
        {
@@ -132,9 +181,9 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool 
definition)
             So check in ../etc.  */
          lispstpcpy (stpcpy (name, sibling_etc), file);
 
-         fd = emacs_open (name, O_RDONLY, 0);
+         fd = doc_open (name, O_RDONLY, 0);
        }
-      if (fd < 0)
+      if (!doc_fd_p (fd))
        {
          if (errno != ENOENT && errno != ENOTDIR)
            report_file_error ("Read error on documentation file", file);
@@ -145,14 +194,18 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool 
definition)
          return concat3 (cannot_open, file, quote_nl);
        }
     }
+#ifndef USE_ANDROID_ASSETS
   record_unwind_protect_int (close_file_unwind, fd);
+#else /* USE_ANDROID_ASSETS */
+  record_unwind_protect_ptr (close_file_unwind_android_fd, &fd);
+#endif /* !USE_ANDROID_ASSETS */
 
   /* Seek only to beginning of disk block.  */
   /* Make sure we read at least 1024 bytes before `position'
      so we can check the leading text for consistency.  */
   int offset = min (position, max (1024, position % (8 * 1024)));
   if (TYPE_MAXIMUM (off_t) < position
-      || lseek (fd, position - offset, 0) < 0)
+      || doc_lseek (fd, position - offset, 0) < 0)
     error ("Position %"pI"d out of range in doc string file \"%s\"",
           position, name);
 
@@ -181,7 +234,7 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool 
definition)
          If we read the same block last time, maybe skip this?  */
       if (space_left > 1024 * 8)
        space_left = 1024 * 8;
-      int nread = emacs_read_quit (fd, p, space_left);
+      int nread = doc_read_quit (fd, p, space_left);
       if (nread < 0)
        report_file_error ("Read error on documentation file", file);
       p[nread] = 0;
@@ -504,7 +557,7 @@ That file is found in `../etc' now; later, when the dumped 
Emacs is run,
 the same file name is found in the `doc-directory'.  */)
   (Lisp_Object filename)
 {
-  int fd;
+  doc_fd fd;
   char buf[1024 + 1];
   int filled;
   EMACS_INT pos;
@@ -551,21 +604,25 @@ the same file name is found in the `doc-directory'.  */)
       Vbuild_files = Fpurecopy (Vbuild_files);
     }
 
-  fd = emacs_open (name, O_RDONLY, 0);
-  if (fd < 0)
+  fd = doc_open (name, O_RDONLY, 0);
+  if (!doc_fd_p (fd))
     {
       int open_errno = errno;
       report_file_errno ("Opening doc string file", build_string (name),
                         open_errno);
     }
+#ifndef USE_ANDROID_ASSETS
   record_unwind_protect_int (close_file_unwind, fd);
+#else /* USE_ANDROID_ASSETS */
+  record_unwind_protect_ptr (close_file_unwind_android_fd, &fd);
+#endif /* !USE_ANDROID_ASSETS */
   Vdoc_file_name = filename;
   filled = 0;
   pos = 0;
   while (true)
     {
       if (filled < 512)
-       filled += emacs_read_quit (fd, &buf[filled], sizeof buf - 1 - filled);
+       filled += doc_read_quit (fd, &buf[filled], sizeof buf - 1 - filled);
       if (!filled)
        break;
 
diff --git a/src/editfns.c b/src/editfns.c
index cb093be75d6..02fca3f5714 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -33,6 +33,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <sys/utsname.h>
 #endif
 
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif
+
 #include "lisp.h"
 
 #include <float.h>
@@ -1269,7 +1273,11 @@ is in general a comma-separated list.  */)
   if (!pw)
     return Qnil;
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  p = android_user_full_name (pw);
+#else
   p = USER_FULL_NAME;
+#endif
   /* Chop off everything after the first comma, since 'pw_gecos' is a
      comma-separated list. */
   q = strchr (p, ',');
@@ -2683,7 +2691,7 @@ DEFUN ("delete-and-extract-region", 
Fdelete_and_extract_region,
    labeled restriction was entered (which may be a narrowing that was
    set by the user and is visible on display).  This alist is used
    internally by narrow-to-region, internal--labeled-narrow-to-region,
-   widen, internal--unlabel-restriction and save-restriction.  For
+   widen, internal--labeled-widen and save-restriction.  For
    efficiency reasons, an alist is used instead of a buffer-local
    variable: otherwise reset_outermost_restrictions, which is called
    during each redisplay cycle, would have to loop through all live
@@ -2859,8 +2867,7 @@ labeled_restrictions_restore (Lisp_Object 
buf_and_restrictions)
 static void
 unwind_labeled_narrow_to_region (Lisp_Object label)
 {
-  Finternal__unlabel_restriction (label);
-  Fwiden ();
+  Finternal__labeled_widen (label);
 }
 
 /* Narrow current_buffer to BEGV-ZV with a restriction labeled with
@@ -2983,7 +2990,7 @@ argument.  To gain access to other portions of the 
buffer, use
 
 DEFUN ("internal--labeled-narrow-to-region", 
Finternal__labeled_narrow_to_region,
        Sinternal__labeled_narrow_to_region, 3, 3, 0,
-       doc: /* Restrict editing in this buffer to START-END, and label the 
restriction with LABEL.
+       doc: /* Restrict this buffer to START-END, and label the restriction 
with LABEL.
 
 This is an internal function used by `with-restriction'.  */)
   (Lisp_Object start, Lisp_Object end, Lisp_Object label)
@@ -3001,9 +3008,9 @@ This is an internal function used by `with-restriction'.  
*/)
   return Qnil;
 }
 
-DEFUN ("internal--unlabel-restriction", Finternal__unlabel_restriction,
-       Sinternal__unlabel_restriction, 1, 1, 0,
-       doc: /* If the current restriction is labeled with LABEL, remove its 
label.
+DEFUN ("internal--labeled-widen", Finternal__labeled_widen,
+       Sinternal__labeled_widen, 1, 1, 0,
+       doc: /* Remove the current restriction if it is labeled with LABEL, and 
widen.
 
 This is an internal function used by `without-restriction'.  */)
   (Lisp_Object label)
@@ -3011,6 +3018,7 @@ This is an internal function used by 
`without-restriction'.  */)
   Lisp_Object buf = Fcurrent_buffer ();
   if (EQ (labeled_restrictions_peek_label (buf), label))
     labeled_restrictions_pop (buf);
+  Fwiden ();
   return Qnil;
 }
 
@@ -4950,7 +4958,7 @@ it to be non-nil.  */);
   defsubr (&Swiden);
   defsubr (&Snarrow_to_region);
   defsubr (&Sinternal__labeled_narrow_to_region);
-  defsubr (&Sinternal__unlabel_restriction);
+  defsubr (&Sinternal__labeled_widen);
   defsubr (&Ssave_restriction);
   defsubr (&Stranspose_regions);
 }
diff --git a/src/emacs-module.c b/src/emacs-module.c
index 10699ec25d9..86360a0f225 100644
--- a/src/emacs-module.c
+++ b/src/emacs-module.c
@@ -206,7 +206,7 @@ static void module_non_local_exit_signal_1 (emacs_env *,
 static void module_non_local_exit_throw_1 (emacs_env *,
                                           Lisp_Object, Lisp_Object);
 static void module_out_of_memory (emacs_env *);
-static void module_reset_handlerlist (struct handler **);
+static void module_reset_handlerlist (struct handler *);
 static bool value_storage_contains_p (const struct emacs_value_storage *,
                                       emacs_value, ptrdiff_t *);
 
@@ -246,10 +246,6 @@ module_decode_utf_8 (const char *str, ptrdiff_t len)
    of `internal_condition_case' etc., and to avoid worrying about
    passing information to the handler functions.  */
 
-#if !HAS_ATTRIBUTE (cleanup)
- #error "__attribute__ ((cleanup)) not supported by this compiler; try GCC"
-#endif
-
 /* Place this macro at the beginning of a function returning a number
    or a pointer to handle non-local exits.  The function must have an
    ENV parameter.  The function will return the specified value if a
@@ -257,8 +253,8 @@ module_decode_utf_8 (const char *str, ptrdiff_t len)
 
 /* It is very important that pushing the handler doesn't itself raise
    a signal.  Install the cleanup only after the handler has been
-   pushed.  Use __attribute__ ((cleanup)) to avoid
-   non-local-exit-prone manual cleanup.
+   pushed.  All code following this point should use
+   MODULE_INTERNAL_CLEANUP before each return.
 
    The do-while forces uses of the macro to be followed by a semicolon.
    This macro cannot enclose its entire body inside a do-while, as the
@@ -278,17 +274,20 @@ module_decode_utf_8 (const char *str, ptrdiff_t len)
       return retval;                                                   \
     }                                                                  \
   struct handler *internal_cleanup                                      \
-    __attribute__ ((cleanup (module_reset_handlerlist)))                \
     = internal_handler;                                                 \
   if (sys_setjmp (internal_cleanup->jmp))                               \
     {                                                                  \
       module_handle_nonlocal_exit (env,                                 \
                                    internal_cleanup->nonlocal_exit,     \
                                    internal_cleanup->val);              \
+      module_reset_handlerlist (internal_cleanup);                     \
       return retval;                                                   \
     }                                                                  \
   do { } while (false)
 
+#define MODULE_INTERNAL_CLEANUP()              \
+  module_reset_handlerlist (internal_cleanup)
+
 
 /* Implementation of runtime and environment functions.
 
@@ -315,7 +314,10 @@ module_decode_utf_8 (const char *str, ptrdiff_t len)
       Emacs functions, by placing the macro
       MODULE_HANDLE_NONLOCAL_EXIT right after the above 2 tests.
 
-   5. Do NOT use 'eassert' for checking validity of user code in the
+   5. Finally, any code which expands MODULE_HANDLE_NONLOCAL_EXIT
+      should use MODULE_INTERNAL_CLEANUP prior to returning.
+
+   6. Do NOT use 'eassert' for checking validity of user code in the
       module.  Instead, make those checks part of the code, and if the
       check fails, call 'module_non_local_exit_signal_1' or
       'module_non_local_exit_throw_1' to report the error.  This is
@@ -438,6 +440,7 @@ module_make_global_ref (emacs_env *env, emacs_value value)
       bool overflow = ckd_add (&ref->refcount, ref->refcount, 1);
       if (overflow)
        overflow_error ();
+      MODULE_INTERNAL_CLEANUP ();
       return &ref->value;
     }
   else
@@ -450,6 +453,7 @@ module_make_global_ref (emacs_env *env, emacs_value value)
       Lisp_Object value;
       XSETPSEUDOVECTOR (value, ref, PVEC_OTHER);
       hash_put (h, new_obj, value, hashcode);
+      MODULE_INTERNAL_CLEANUP ();
       return &ref->value;
     }
 }
@@ -481,6 +485,8 @@ module_free_global_ref (emacs_env *env, emacs_value 
global_value)
       if (--ref->refcount == 0)
         hash_remove_from_table (h, obj);
     }
+
+  MODULE_INTERNAL_CLEANUP ();
 }
 
 static enum emacs_funcall_exit
@@ -574,6 +580,8 @@ static emacs_value
 module_make_function (emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity,
                      emacs_function func, const char *docstring, void *data)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
 
   if (! (0 <= min_arity
@@ -598,7 +606,9 @@ module_make_function (emacs_env *env, ptrdiff_t min_arity, 
ptrdiff_t max_arity,
   XSET_MODULE_FUNCTION (result, function);
   eassert (MODULE_FUNCTIONP (result));
 
-  return lisp_to_value (env, result);
+  value = lisp_to_value (env, result);
+  MODULE_INTERNAL_CLEANUP ();
+  return value;
 }
 
 static emacs_finalizer
@@ -607,6 +617,7 @@ module_get_function_finalizer (emacs_env *env, emacs_value 
arg)
   MODULE_FUNCTION_BEGIN (NULL);
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_MODULE_FUNCTION (lisp);
+  MODULE_INTERNAL_CLEANUP ();
   return XMODULE_FUNCTION (lisp)->finalizer;
 }
 
@@ -618,6 +629,7 @@ module_set_function_finalizer (emacs_env *env, emacs_value 
arg,
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_MODULE_FUNCTION (lisp);
   XMODULE_FUNCTION (lisp)->finalizer = fin;
+  MODULE_INTERNAL_CLEANUP ();
 }
 
 void
@@ -637,6 +649,7 @@ module_make_interactive (emacs_env *env, emacs_value 
function, emacs_value spec)
   /* Normalize (interactive nil) to (interactive). */
   XMODULE_FUNCTION (lisp_fun)->interactive_form
     = NILP (lisp_spec) ? list1 (Qinteractive) : list2 (Qinteractive, 
lisp_spec);
+  MODULE_INTERNAL_CLEANUP ();
 }
 
 Lisp_Object
@@ -670,21 +683,30 @@ module_funcall (emacs_env *env, emacs_value func, 
ptrdiff_t nargs,
     newargs[1 + i] = value_to_lisp (args[i]);
   emacs_value result = lisp_to_value (env, Ffuncall (nargs1, newargs));
   SAFE_FREE ();
+  MODULE_INTERNAL_CLEANUP ();
   return result;
 }
 
 static emacs_value
 module_intern (emacs_env *env, const char *name)
 {
+  emacs_value tem;
+
   MODULE_FUNCTION_BEGIN (NULL);
-  return lisp_to_value (env, intern (name));
+  tem = lisp_to_value (env, intern (name));
+  MODULE_INTERNAL_CLEANUP ();
+  return tem;
 }
 
 static emacs_value
 module_type_of (emacs_env *env, emacs_value arg)
 {
+  emacs_value tem;
+
   MODULE_FUNCTION_BEGIN (NULL);
-  return lisp_to_value (env, Ftype_of (value_to_lisp (arg)));
+  tem = lisp_to_value (env, Ftype_of (value_to_lisp (arg)));
+  MODULE_INTERNAL_CLEANUP ();
+  return tem;
 }
 
 static bool
@@ -710,14 +732,20 @@ module_extract_integer (emacs_env *env, emacs_value arg)
   intmax_t i;
   if (! integer_to_intmax (lisp, &i))
     xsignal1 (Qoverflow_error, lisp);
+  MODULE_INTERNAL_CLEANUP ();
   return i;
 }
 
 static emacs_value
 module_make_integer (emacs_env *env, intmax_t n)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
-  return lisp_to_value (env, make_int (n));
+  value = lisp_to_value (env, make_int (n));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 static double
@@ -726,14 +754,21 @@ module_extract_float (emacs_env *env, emacs_value arg)
   MODULE_FUNCTION_BEGIN (0);
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_TYPE (FLOATP (lisp), Qfloatp, lisp);
+  MODULE_INTERNAL_CLEANUP ();
+
   return XFLOAT_DATA (lisp);
 }
 
 static emacs_value
 module_make_float (emacs_env *env, double d)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
-  return lisp_to_value (env, make_float (d));
+  value = lisp_to_value (env, make_float (d));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 static bool
@@ -765,6 +800,7 @@ module_copy_string_contents (emacs_env *env, emacs_value 
value, char *buf,
   if (buf == NULL)
     {
       *len = required_buf_size;
+      MODULE_INTERNAL_CLEANUP ();
       return true;
     }
 
@@ -780,36 +816,51 @@ module_copy_string_contents (emacs_env *env, emacs_value 
value, char *buf,
   *len = required_buf_size;
   memcpy (buf, SDATA (lisp_str_utf8), raw_size + 1);
 
+  MODULE_INTERNAL_CLEANUP ();
   return true;
 }
 
 static emacs_value
 module_make_string (emacs_env *env, const char *str, ptrdiff_t len)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
   if (! (0 <= len && len <= STRING_BYTES_BOUND))
     overflow_error ();
   Lisp_Object lstr
     = len == 0 ? empty_multibyte_string : module_decode_utf_8 (str, len);
-  return lisp_to_value (env, lstr);
+  value = lisp_to_value (env, lstr);
+  MODULE_INTERNAL_CLEANUP ();
+  return value;
 }
 
 static emacs_value
 module_make_unibyte_string (emacs_env *env, const char *str, ptrdiff_t length)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
   if (! (0 <= length && length <= STRING_BYTES_BOUND))
     overflow_error ();
   Lisp_Object lstr
     = length == 0 ? empty_unibyte_string : make_unibyte_string (str, length);
-  return lisp_to_value (env, lstr);
+  value = lisp_to_value (env, lstr);
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 static emacs_value
 module_make_user_ptr (emacs_env *env, emacs_finalizer fin, void *ptr)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
-  return lisp_to_value (env, make_user_ptr (fin, ptr));
+  value = lisp_to_value (env, make_user_ptr (fin, ptr));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 static void *
@@ -818,6 +869,8 @@ module_get_user_ptr (emacs_env *env, emacs_value arg)
   MODULE_FUNCTION_BEGIN (NULL);
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_USER_PTR (lisp);
+  MODULE_INTERNAL_CLEANUP ();
+
   return XUSER_PTR (lisp)->p;
 }
 
@@ -828,6 +881,7 @@ module_set_user_ptr (emacs_env *env, emacs_value arg, void 
*ptr)
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_USER_PTR (lisp);
   XUSER_PTR (lisp)->p = ptr;
+  MODULE_INTERNAL_CLEANUP ();
 }
 
 static emacs_finalizer
@@ -836,6 +890,7 @@ module_get_user_finalizer (emacs_env *env, emacs_value arg)
   MODULE_FUNCTION_BEGIN (NULL);
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_USER_PTR (lisp);
+  MODULE_INTERNAL_CLEANUP ();
   return XUSER_PTR (lisp)->finalizer;
 }
 
@@ -847,6 +902,7 @@ module_set_user_finalizer (emacs_env *env, emacs_value arg,
   Lisp_Object lisp = value_to_lisp (arg);
   CHECK_USER_PTR (lisp);
   XUSER_PTR (lisp)->finalizer = fin;
+  MODULE_INTERNAL_CLEANUP ();
 }
 
 static void
@@ -866,15 +922,21 @@ module_vec_set (emacs_env *env, emacs_value vector, 
ptrdiff_t index,
   Lisp_Object lisp = value_to_lisp (vector);
   check_vec_index (lisp, index);
   ASET (lisp, index, value_to_lisp (value));
+  MODULE_INTERNAL_CLEANUP ();
 }
 
 static emacs_value
 module_vec_get (emacs_env *env, emacs_value vector, ptrdiff_t index)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
   Lisp_Object lisp = value_to_lisp (vector);
   check_vec_index (lisp, index);
-  return lisp_to_value (env, AREF (lisp, index));
+  value = lisp_to_value (env, AREF (lisp, index));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 static ptrdiff_t
@@ -883,6 +945,8 @@ module_vec_size (emacs_env *env, emacs_value vector)
   MODULE_FUNCTION_BEGIN (0);
   Lisp_Object lisp = value_to_lisp (vector);
   CHECK_VECTOR (lisp);
+  MODULE_INTERNAL_CLEANUP ();
+
   return ASIZE (lisp);
 }
 
@@ -898,23 +962,37 @@ module_should_quit (emacs_env *env)
 static enum emacs_process_input_result
 module_process_input (emacs_env *env)
 {
+  enum emacs_process_input_result rc;
+
   MODULE_FUNCTION_BEGIN (emacs_process_input_quit);
   maybe_quit ();
-  return emacs_process_input_continue;
+  rc = emacs_process_input_continue;
+  MODULE_INTERNAL_CLEANUP ();
+  return rc;
 }
 
 static struct timespec
 module_extract_time (emacs_env *env, emacs_value arg)
 {
+  struct timespec value;
+
   MODULE_FUNCTION_BEGIN ((struct timespec) {0});
-  return lisp_time_argument (value_to_lisp (arg));
+  value = lisp_time_argument (value_to_lisp (arg));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 static emacs_value
 module_make_time (emacs_env *env, struct timespec time)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
-  return lisp_to_value (env, timespec_to_lisp (time));
+  value = lisp_to_value (env, timespec_to_lisp (time));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return value;
 }
 
 /*
@@ -991,7 +1069,10 @@ module_extract_big_integer (emacs_env *env, emacs_value 
arg, int *sign,
       EMACS_INT x = XFIXNUM (o);
       *sign = (0 < x) - (x < 0);
       if (x == 0 || count == NULL)
-        return true;
+       {
+         MODULE_INTERNAL_CLEANUP ();
+         return true;
+       }
       /* As a simplification we don't check how many array elements
          are exactly required, but use a reasonable static upper
          bound.  For most architectures exactly one element should
@@ -1002,6 +1083,7 @@ module_extract_big_integer (emacs_env *env, emacs_value 
arg, int *sign,
       if (magnitude == NULL)
         {
           *count = required;
+         MODULE_INTERNAL_CLEANUP ();
           return true;
         }
       if (*count < required)
@@ -1020,12 +1102,16 @@ module_extract_big_integer (emacs_env *env, emacs_value 
arg, int *sign,
       verify (required * bits < PTRDIFF_MAX);
       for (ptrdiff_t i = 0; i < required; ++i)
         magnitude[i] = (emacs_limb_t) (u >> (i * bits));
+      MODULE_INTERNAL_CLEANUP ();
       return true;
     }
   const mpz_t *x = xbignum_val (o);
   *sign = mpz_sgn (*x);
   if (count == NULL)
-    return true;
+    {
+      MODULE_INTERNAL_CLEANUP ();
+      return true;
+    }
   size_t required_size = (mpz_sizeinbase (*x, 2) + numb - 1) / numb;
   eassert (required_size <= PTRDIFF_MAX);
   ptrdiff_t required = (ptrdiff_t) required_size;
@@ -1033,6 +1119,7 @@ module_extract_big_integer (emacs_env *env, emacs_value 
arg, int *sign,
   if (magnitude == NULL)
     {
       *count = required;
+      MODULE_INTERNAL_CLEANUP ();
       return true;
     }
   if (*count < required)
@@ -1045,6 +1132,7 @@ module_extract_big_integer (emacs_env *env, emacs_value 
arg, int *sign,
   size_t written;
   mpz_export (magnitude, &written, order, size, endian, nails, *x);
   eassert (written == required_size);
+  MODULE_INTERNAL_CLEANUP ();
   return true;
 }
 
@@ -1052,21 +1140,34 @@ static emacs_value
 module_make_big_integer (emacs_env *env, int sign,
                          ptrdiff_t count, const emacs_limb_t *magnitude)
 {
+  emacs_value value;
+
   MODULE_FUNCTION_BEGIN (NULL);
   if (sign == 0)
-    return lisp_to_value (env, make_fixed_natnum (0));
+    {
+      value = lisp_to_value (env, make_fixed_natnum (0));
+      MODULE_INTERNAL_CLEANUP ();
+      return value;
+    }
   enum { order = -1, size = sizeof *magnitude, endian = 0, nails = 0 };
   mpz_import (mpz[0], count, order, size, endian, nails, magnitude);
   if (sign < 0)
     mpz_neg (mpz[0], mpz[0]);
-  return lisp_to_value (env, make_integer_mpz ());
+  value = lisp_to_value (env, make_integer_mpz ());
+  MODULE_INTERNAL_CLEANUP ();
+  return value;
 }
 
 static int
 module_open_channel (emacs_env *env, emacs_value pipe_process)
 {
+  int rc;
+
   MODULE_FUNCTION_BEGIN (-1);
-  return open_channel_for_module (value_to_lisp (pipe_process));
+  rc = open_channel_for_module (value_to_lisp (pipe_process));
+  MODULE_INTERNAL_CLEANUP ();
+
+  return rc;
 }
 
 
@@ -1519,12 +1620,13 @@ finalize_runtime_unwind (void *raw_ert)
 /* Must be called after setting up a handler immediately before
    returning from the function.  See the comments in lisp.h and the
    code in eval.c for details.  The macros below arrange for this
-   function to be called automatically.  PHANDLERLIST points to a word
-   containing the handler list, for sanity checking.  */
+   function to be called automatically.  IHANDLERLIST points to the
+   handler list.  */
+
 static void
-module_reset_handlerlist (struct handler **phandlerlist)
+module_reset_handlerlist (struct handler *ihandlerlist)
 {
-  eassert (handlerlist == *phandlerlist);
+  eassert (handlerlist == ihandlerlist);
   handlerlist = handlerlist->next;
 }
 
diff --git a/src/emacs.c b/src/emacs.c
index 80a013b68df..6101ed4004c 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -33,6 +33,14 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "lisp.h"
 #include "sysstdio.h"
 
+#ifdef HAVE_ANDROID
+#include "androidterm.h"
+#endif
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#include "sfntfont.h"
+#endif
+
 #ifdef WINDOWSNT
 #include <fcntl.h>
 #include <sys/socket.h>
@@ -137,6 +145,10 @@ extern char etext;
 #include <sys/resource.h>
 #endif
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#include "android.h"
+#endif
+
 /* We don't guard this with HAVE_TREE_SITTER because treesit.o is
    always compiled (to provide treesit-available-p).  */
 #include "treesit.h"
@@ -411,7 +423,15 @@ using_utf8 (void)
      the result is known in advance anyway...  */
 #if defined HAVE_WCHAR_H && !defined WINDOWSNT
   wchar_t wc;
+#ifndef HAVE_ANDROID
   mbstate_t mbs = { 0 };
+#else
+  mbstate_t mbs;
+
+  /* Not sure how mbstate works on Android, but this seems to be
+     required.  */
+  memset (&mbs, 0, sizeof mbs);
+#endif
   return mbrtowc (&wc, "\xc4\x80", 2, &mbs) == 2 && wc == 0x100;
 #else
   return false;
@@ -511,7 +531,8 @@ init_cmdargs (int argc, char **argv, int skip_args, char 
const *original_pwd)
     {
       Lisp_Object found;
       int yes = openp (Vexec_path, Vinvocation_name, Vexec_suffixes,
-                      &found, make_fixnum (X_OK), false, false);
+                      &found, make_fixnum (X_OK), false, false,
+                      NULL);
       if (yes == 1)
        {
          /* Add /: to the front of the name
@@ -724,6 +745,8 @@ argmatch (char **argv, int argc, const char *sstr, const 
char *lstr,
     }
 }
 
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+
 /* Find a name (absolute or relative) of the Emacs executable whose
    name (as passed into this program) is ARGV0.  Called early in
    initialization by portable dumper loading code, so avoid Lisp and
@@ -823,6 +846,8 @@ find_emacs_executable (char const *argv0, ptrdiff_t 
*candidate_size)
 #endif /* !WINDOWSNT */
 }
 
+#endif
+
 #ifdef HAVE_PDUMPER
 
 static const char *
@@ -851,10 +876,38 @@ dump_error_to_string (int result)
     }
 }
 
-/* This function returns the Emacs executable.  */
+/* This function returns the Emacs executable.  DUMP_FILE is ignored
+   outside of Android.  Otherwise, it is the name of the dump file to
+   use, or NULL if Emacs should look for a ``--dump-file'' argument
+   instead.  */
+
 static char *
-load_pdump (int argc, char **argv)
+load_pdump (int argc, char **argv, char *dump_file)
 {
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  int skip_args = 0, result;
+
+  while (skip_args < argc - 1)
+    {
+      if (argmatch (argv, argc, "-dump-file", "--dump-file",
+                   6, &dump_file, &skip_args)
+         || argmatch (argv, argc, "--", NULL, 2, NULL,
+                      &skip_args))
+       break;
+      skip_args++;
+    }
+
+  if (!dump_file)
+    return argv[0];
+
+  result = pdumper_load (dump_file, argv[0]);
+
+  if (result != PDUMPER_LOAD_SUCCESS)
+    fatal ("could not load dump file \"%s\": %s",
+          dump_file, dump_error_to_string (result));
+  return argv[0];
+#else
+
   const char *const suffix = ".pdmp";
   int result;
   char *emacs_executable = argv[0];
@@ -885,7 +938,7 @@ load_pdump (int argc, char **argv)
 
   /* Look for an explicitly-specified dump file.  */
   const char *path_exec = PATH_EXEC;
-  char *dump_file = NULL;
+  dump_file = NULL;
   int skip_args = 0;
   while (skip_args < argc - 1)
     {
@@ -1047,6 +1100,7 @@ load_pdump (int argc, char **argv)
   xfree (dump_file);
 
   return emacs_executable;
+#endif
 }
 #endif /* HAVE_PDUMPER */
 
@@ -1123,7 +1177,7 @@ load_seccomp (const char *file)
       goto out;
     }
   struct stat stat;
-  if (fstat (fd, &stat) != 0)
+  if (sys_fstat (fd, &stat) != 0)
     {
       emacs_perror ("fstat");
       goto out;
@@ -1225,12 +1279,24 @@ maybe_load_seccomp (int argc, char **argv)
 
 #endif  /* SECCOMP_USABLE */
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+int
+android_emacs_init (int argc, char **argv, char *dump_file)
+#else
 int
 main (int argc, char **argv)
+#endif
 {
   /* Variable near the bottom of the stack, and aligned appropriately
      for pointers.  */
   void *stack_bottom_variable;
+  int old_argc;
+#if defined HAVE_PDUMPER && !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  char *dump_file;
+
+  /* This is just a dummy argument used to avoid extra defines.  */
+  dump_file = NULL;
+#endif
 
   /* First, check whether we should apply a seccomp filter.  This
      should come at the very beginning to allow the filter to protect
@@ -1360,7 +1426,7 @@ main (int argc, char **argv)
 
 #ifdef HAVE_PDUMPER
   if (attempt_load_pdump)
-    initial_emacs_executable = load_pdump (argc, argv);
+    initial_emacs_executable = load_pdump (argc, argv, dump_file);
 #else
   ptrdiff_t bufsize;
   initial_emacs_executable = find_emacs_executable (argv[0], &bufsize);
@@ -1425,8 +1491,9 @@ main (int argc, char **argv)
 
   bool only_version = false;
   sort_args (argc, argv);
-  argc = 0;
-  while (argv[argc]) argc++;
+  old_argc = argc, argc = 0;
+  /* Don't allow going past argv.  */
+  while (argc < old_argc && argv[argc]) argc++;
 
   skip_args = 0;
   if (argmatch (argv, argc, "-version", "--version", 3, NULL, &skip_args))
@@ -1934,6 +2001,9 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
 #ifdef HAVE_WINDOW_SYSTEM
       init_fringe_once ();     /* Swap bitmaps if necessary.  */
 #endif /* HAVE_WINDOW_SYSTEM */
+#ifdef HAVE_TEXT_CONVERSION
+      syms_of_textconv ();
+#endif
     }
 
   init_alloc ();
@@ -2007,15 +2077,16 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
     }
 #endif /* HAVE_NS */
 
-#ifdef HAVE_X_WINDOWS
   /* Stupid kludge to catch command-line display spec.  We can't
      handle this argument entirely in window system dependent code
      because we don't even know which window system dependent code
      to run until we've recognized this argument.  */
   {
-    char *displayname = 0;
     int count_before = skip_args;
 
+#ifdef HAVE_X_WINDOWS
+    char *displayname = 0;
+
     /* Skip any number of -d options, but only use the last one.  */
     while (!only_version)
       {
@@ -2045,12 +2116,15 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
          }
        argv[count_before + 1] = (char *) "-d";
       }
+#endif /* HAVE_X_WINDOWS */
 
     if (! no_site_lisp)
       {
-        if (argmatch (argv, argc, "-Q", "--quick", 3, NULL, &skip_args)
+
+       if (argmatch (argv, argc, "-Q", "--quick", 3, NULL, &skip_args)
             || argmatch (argv, argc, "-quick", 0, 2, NULL, &skip_args))
-          no_site_lisp = 1;
+         no_site_lisp = 1;
+
       }
 
     if (argmatch (argv, argc, "-x", 0, 1, &junk, &skip_args))
@@ -2066,18 +2140,6 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
     /* Don't actually discard this arg.  */
     skip_args = count_before;
   }
-#else  /* !HAVE_X_WINDOWS */
-  if (! no_site_lisp)
-  {
-    int count_before = skip_args;
-
-    if (argmatch (argv, argc, "-Q", "--quick", 3, NULL, &skip_args)
-        || argmatch (argv, argc, "-quick", 0, 2, NULL, &skip_args))
-      no_site_lisp = 1;
-
-    skip_args = count_before;
-  }
-#endif
 
   /* argmatch must not be used after here,
      except when building temacs
@@ -2375,6 +2437,18 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
 #endif
       syms_of_fontset ();
 #endif /* HAVE_HAIKU */
+#ifdef HAVE_ANDROID
+      syms_of_androidterm ();
+      syms_of_androidfns ();
+      syms_of_androidmenu ();
+      syms_of_fontset ();
+#if !defined ANDROID_STUBIFY
+      syms_of_androidfont ();
+      syms_of_androidselect ();
+      syms_of_sfntfont ();
+      syms_of_sfntfont_android ();
+#endif /* !ANDROID_STUBIFY */
+#endif /* HAVE_ANDROID */
 
       syms_of_gnutls ();
 
@@ -2471,6 +2545,17 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
   init_window ();
   init_font ();
 
+#ifdef HAVE_ANDROID
+  init_androidmenu ();
+#endif
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  init_androidfont ();
+  init_androidselect ();
+  init_sfntfont ();
+  init_sfntfont_android ();
+#endif
+
   if (!initialized)
     {
       char *file;
@@ -2525,6 +2610,16 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
   safe_run_hooks (Qafter_pdump_load_hook);
 #endif
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY && 0
+  /* This comes very late in the startup process because it requires
+     most of lisp/international to be loaded.  This approach doesn't
+     work because normal-top-level runs and creates the initial frame
+     before fonts are initialized.  So this is done in
+     normal-top-level instead.  */
+  Vtop_level = list3 (Qprogn, Vtop_level,
+                     list1 (Qandroid_enumerate_fonts));
+#endif
+
   /* Enter editor command loop.  This never returns.  */
   set_initial_minibuffer_mode ();
   Frecursive_edit ();
@@ -2850,7 +2945,14 @@ killed.  */
 #ifndef WINDOWSNT
   /* Do some checking before shutting down Emacs, because errors
      can't be meaningfully reported afterwards.  */
-  if (!NILP (restart))
+  if (!NILP (restart)
+      /* Don't perform the following checks when Emacs is running as
+        an Android GUI application, because there the system is
+        relied on to restart Emacs.  */
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+      && !android_init_gui
+#endif
+      )
     {
       /* This is very unlikely, but it's possible to execute a binary
         (on some systems) with no argv.  */
@@ -2902,7 +3004,7 @@ killed.  */
     {
       Lisp_Object listfile;
       listfile = Fexpand_file_name (Vauto_save_list_file_name, Qnil);
-      unlink (SSDATA (listfile));
+      emacs_unlink (SSDATA (listfile));
     }
 
 #ifdef HAVE_NATIVE_COMP
@@ -2912,6 +3014,13 @@ killed.  */
   if (!NILP (restart))
     {
       turn_on_atimers (false);
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+      /* Re-executing the Emacs process created by the system doesn't
+        work.  Instead, schedule a restart for a few hundered
+        milliseconds and exit Emacs.  */
+      if (android_init_gui)
+       android_restart_emacs ();
+#endif
 #ifdef WINDOWSNT
       if (w32_reexec_emacs (initial_cmdline, initial_wd) < 0)
 #else
@@ -2952,31 +3061,38 @@ shut_down_emacs (int sig, Lisp_Object stuff)
   Vinhibit_redisplay = Qt;
 
   /* If we are controlling the terminal, reset terminal modes.  */
-#ifndef DOS_NT
+#if !defined DOS_NT && !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
   pid_t tpgrp = tcgetpgrp (STDIN_FILENO);
   if (tpgrp != -1 && tpgrp == getpgrp ())
     {
       reset_all_sys_modes ();
       if (sig && sig != SIGTERM)
        {
-         static char const fmt[] = "Fatal error %d: %n%s\n";
 #ifdef HAVE_HAIKU
          if (haiku_debug_on_fatal_error)
            debugger ("Fatal error in Emacs");
 #endif
-         char buf[max ((sizeof fmt - sizeof "%d%n%s\n"
+         /* Output a "Fatal error NUM: DESC\n" diagnostic with a single write,
+            but use multiple writes if the diagnosic is absurdly long
+            and likely couldn't be written atomically anyway.  */
+         static char const fmt[] = "Fatal error %d: ";
+         char buf[max ((sizeof fmt - sizeof "%d"
                         + INT_STRLEN_BOUND (int) + 1),
                        min (PIPE_BUF, MAX_ALLOCA))];
          char const *sig_desc = safe_strsignal (sig);
-         int nlen;
-         int buflen = snprintf (buf, sizeof buf, fmt, sig, &nlen, sig_desc);
-         if (0 <= buflen && buflen < sizeof buf)
-           emacs_write (STDERR_FILENO, buf, buflen);
+         size_t sig_desclen = strlen (sig_desc);
+         int nlen = sprintf (buf, fmt, sig);
+         if (nlen + sig_desclen < sizeof buf - 1)
+           {
+             char *p = mempcpy (buf + nlen, sig_desc, sig_desclen);
+             *p++ = '\n';
+             emacs_write (STDERR_FILENO, buf, p - buf);
+           }
          else
            {
              emacs_write (STDERR_FILENO, buf, nlen);
-             emacs_write (STDERR_FILENO, sig_desc, strlen (sig_desc));
-             emacs_write (STDERR_FILENO, fmt + sizeof fmt - 2, 1);
+             emacs_write (STDERR_FILENO, sig_desc, sig_desclen);
+             emacs_write (STDERR_FILENO, "\n", 1);
            }
        }
     }
@@ -3471,6 +3587,7 @@ Special values:
   `windows-nt'   compiled as a native W32 application.
   `cygwin'       compiled using the Cygwin library.
   `haiku'        compiled for a Haiku system.
+  `android'     compiled for Android.
 Anything else (in Emacs 26, the possibilities are: aix, berkeley-unix,
 hpux, usg-unix-v) indicates some sort of Unix system.  */);
   Vsystem_type = intern_c_string (SYSTEM_TYPE);
diff --git a/src/epaths.in b/src/epaths.in
index b290f0243bc..afddd57763a 100644
--- a/src/epaths.in
+++ b/src/epaths.in
@@ -18,6 +18,7 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
 
 /* Together with PATH_SITELOADSEARCH, this gives the default value of
    load-path, which is the search path for the Lisp function "load".
@@ -79,3 +80,24 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 /* Where Emacs should look for the application default file. */
 #define PATH_X_DEFAULTS 
"/usr/lib/X11/%L/%T/%N%C%S:/usr/lib/X11/%l/%T/%N%C%S:/usr/lib/X11/%T/%N%C%S:/usr/lib/X11/%L/%T/%N%S:/usr/lib/X11/%l/%T/%N%S:/usr/lib/X11/%T/%N%S"
+
+#else
+
+/* Replace the defines above with links to files in the assets
+   pseudo-directory.  Preserve the extra spaces, or epaths.in will not
+   be generated correctly.  */
+
+ # define  PATH_EXEC                   (android_lib_dir)
+ # define  PATH_LOADSEARCH             "/assets/lisp/"
+ # define  PATH_SITELOADSEARCH         (android_site_load_path)
+ # define  PATH_DUMPLOADSEARCH         "/assets/lisp/"
+ # define  PATH_DATA                   "/assets/etc/"
+ # define  PATH_DOC                    "/assets/etc/"
+ # define  PATH_INFO                   "/assets/info/"
+ # define  PATH_GAME                   ""
+ # define  PATH_BITMAPS                        ""
+
+extern char *android_site_load_path;
+extern char *android_lib_dir;
+
+#endif
diff --git a/src/eval.c b/src/eval.c
index 4cd26afbf3e..697e73678f2 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -4236,7 +4236,7 @@ mark_specpdl (union specbinding *first, union specbinding 
*ptr)
 void
 get_backtrace (Lisp_Object array)
 {
-  union specbinding *pdl = backtrace_next (backtrace_top ());
+  union specbinding *pdl = backtrace_top ();
   ptrdiff_t i = 0, asize = ASIZE (array);
 
   /* Copy the backtrace contents into working memory.  */
@@ -4337,6 +4337,10 @@ See also the variable `debug-on-quit' and 
`inhibit-debugger'.  */);
 Each element may be a condition-name or a regexp that matches error messages.
 If any element applies to a given error, that error skips the debugger
 and just returns to top level.
+If you invoke Emacs with --debug-init, and want to remove some
+elements from the default value of this variable, use `setq' to
+change the value of the variable to a new list, rather than `delq'
+to remove some errors from the list.
 This overrides the variable `debug-on-error'.
 It does not apply to errors handled by `condition-case'.  */);
   Vdebug_ignored_errors = Qnil;
diff --git a/src/fileio.c b/src/fileio.c
index 0a967a6631f..23e1a83d8bf 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -56,6 +56,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "region-cache.h"
 #include "frame.h"
 
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif /* HAVE_ANDROID */
+
 #ifdef HAVE_LINUX_FS_H
 # include <sys/ioctl.h>
 # include <linux/fs.h>
@@ -109,6 +113,42 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include "commands.h"
 
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+
+/* Type describing a file descriptor used by functions such as
+   `insert-file-contents'.  */
+
+typedef int emacs_fd;
+
+/* Function used to read and open from such a file descriptor.  */
+
+#define emacs_fd_open          emacs_open
+#define emacs_fd_close         emacs_close
+#define emacs_fd_read          emacs_read_quit
+#define emacs_fd_lseek         lseek
+#define emacs_fd_fstat         sys_fstat
+#define emacs_fd_valid_p(fd)   ((fd) >= 0)
+
+/* This is not used on MS Windows.  */
+
+#ifndef WINDOWSNT
+#define emacs_fd_to_int(fds)   (fds)
+#endif /* WINDOWSNT */
+
+#else /* HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+typedef struct android_fd_or_asset emacs_fd;
+
+#define emacs_fd_open          android_open_asset
+#define emacs_fd_close         android_close_asset
+#define emacs_fd_read          android_asset_read_quit
+#define emacs_fd_lseek         android_asset_lseek
+#define emacs_fd_fstat         android_asset_fstat
+#define emacs_fd_valid_p(fd)   ((fd).asset != ((void *) -1))
+#define emacs_fd_to_int(fds)   ((fds).asset ? -1 : (fds).fd)
+
+#endif /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+
 /* True during writing of auto-save files.  */
 static bool auto_saving;
 
@@ -135,13 +175,51 @@ static dev_t timestamp_file_system;
 static Lisp_Object Vwrite_region_annotation_buffers;
 
 static Lisp_Object emacs_readlinkat (int, char const *);
-static Lisp_Object file_name_directory (Lisp_Object);
 static bool a_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t,
                     Lisp_Object *, struct coding_system *);
 static bool e_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t,
                     struct coding_system *);
 
 
+
+/* Establish that ENCODED is not contained within a special directory
+   whose contents are not eligible for Unix VFS operations.  Signal a
+   `file-error' with REASON if it does.  */
+
+static void
+check_vfs_filename (Lisp_Object encoded, const char *reason)
+{
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  const char *name;
+
+  name = SSDATA (encoded);
+
+  if (android_is_special_directory (name, "/assets")
+      || android_is_special_directory (name, "/content"))
+    xsignal2 (Qfile_error, build_string (reason), encoded);
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+}
+
+#ifdef HAVE_LIBSELINUX
+
+/* Return whether SELinux is enabled and pertinent to FILE.  Provide
+   for cases where FILE is or is a constitutent of a special
+   directory, such as /assets or /content on Android.  */
+
+static bool
+selinux_enabled_p (const char *file)
+{
+  return (is_selinux_enabled ()
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+         && !android_is_special_directory (file, "/assets")
+         && !android_is_special_directory (file, "/content")
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+         );
+}
+
+#endif /* HAVE_LIBSELINUX */
+
+
 /* Test whether FILE is accessible for AMODE.
    Return true if successful, false (setting errno) otherwise.  */
 
@@ -160,7 +238,7 @@ file_access_p (char const *file, int amode)
     }
 #endif
 
-  if (faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0)
+  if (sys_faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0)
     return true;
 
 #ifdef CYGWIN
@@ -264,11 +342,20 @@ close_file_unwind (int fd)
   emacs_close (fd);
 }
 
+static void
+close_file_unwind_emacs_fd (void *ptr)
+{
+  emacs_fd *fd;
+
+  fd = ptr;
+  emacs_fd_close (*fd);
+}
+
 void
 fclose_unwind (void *arg)
 {
   FILE *stream = arg;
-  fclose (stream);
+  emacs_fclose (stream);
 }
 
 /* Restore point, having saved it as a marker.  */
@@ -370,7 +457,7 @@ Given a Unix syntax file name, returns a string ending in 
slash.  */)
 /* Return the directory component of FILENAME, or nil if FILENAME does
    not contain a directory component.  */
 
-static Lisp_Object
+Lisp_Object
 file_name_directory (Lisp_Object filename)
 {
   char *beg = SSDATA (filename);
@@ -756,10 +843,10 @@ For that reason, you should normally use `make-temp-file' 
instead.  */)
 
 DEFUN ("file-name-concat", Ffile_name_concat, Sfile_name_concat, 1, MANY, 0,
        doc: /* Append COMPONENTS to DIRECTORY and return the resulting string.
-Elements in COMPONENTS must be a string or nil.
+Each element in COMPONENTS must be a string or nil.
 DIRECTORY or the non-final elements in COMPONENTS may or may not end
 with a slash -- if they don't end with a slash, a slash will be
-inserted before contatenating.
+inserted before concatenating.
 usage: (record DIRECTORY &rest COMPONENTS) */)
   (ptrdiff_t nargs, Lisp_Object *args)
 {
@@ -889,6 +976,10 @@ user_homedir (char const *name)
   p[length] = 0;
   struct passwd *pw = getpwnam (p);
   SAFE_FREE ();
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  if (pw && !pw->pw_dir && pw->pw_uid == getuid ())
+    return (char *) android_get_home_directory ();
+#endif
   if (!pw || (pw->pw_dir && !IS_ABSOLUTE_FILE_NAME (pw->pw_dir)))
     return NULL;
   return pw->pw_dir;
@@ -1879,6 +1970,11 @@ get_homedir (void)
        pw = getpwuid (getuid ());
       if (pw)
        home = pw->pw_dir;
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+      if (!home && pw && pw->pw_uid == getuid ())
+       return android_get_home_directory ();
+#endif
       if (!home)
        return "";
     }
@@ -2177,7 +2273,8 @@ permissions.  */)
 #else
   bool already_exists = false;
   mode_t new_mask;
-  int ifd, ofd;
+  emacs_fd ifd;
+  int ofd;
   struct stat st;
 #endif
 
@@ -2220,26 +2317,31 @@ permissions.  */)
       report_file_error ("Copying permissions to", newname);
     }
 #else /* not WINDOWSNT */
-  ifd = emacs_open (SSDATA (encoded_file), O_RDONLY | O_NONBLOCK, 0);
+  ifd = emacs_fd_open (SSDATA (encoded_file), O_RDONLY | O_NONBLOCK, 0);
 
-  if (ifd < 0)
+  if (!emacs_fd_valid_p (ifd))
     report_file_error ("Opening input file", file);
 
-  record_unwind_protect_int (close_file_unwind, ifd);
+  record_unwind_protect_ptr (close_file_unwind_emacs_fd, &ifd);
 
-  if (fstat (ifd, &st) != 0)
+  if (emacs_fd_fstat (ifd, &st) != 0)
     report_file_error ("Input file status", file);
 
   if (!NILP (preserve_permissions))
     {
 #if HAVE_LIBSELINUX
-      if (is_selinux_enabled ())
+      if (selinux_enabled_p (SSDATA (encoded_file))
+         /* Eschew copying SELinux contexts if they're inapplicable
+            to the destination file.  */
+         && selinux_enabled_p (SSDATA (encoded_newname))
+         && emacs_fd_to_int (ifd) != -1)
        {
-         conlength = fgetfilecon (ifd, &con);
+         conlength = fgetfilecon (emacs_fd_to_int (ifd),
+                                  &con);
          if (conlength == -1)
            report_file_error ("Doing fgetfilecon", file);
        }
-#endif
+#endif /* HAVE_LIBSELINUX */
     }
 
   /* We can copy only regular files.  */
@@ -2273,7 +2375,7 @@ permissions.  */)
   if (already_exists)
     {
       struct stat out_st;
-      if (fstat (ofd, &out_st) != 0)
+      if (sys_fstat (ofd, &out_st) != 0)
        report_file_error ("Output file status", newname);
       if (st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino)
        report_file_errno ("Input and output files are the same",
@@ -2284,7 +2386,8 @@ permissions.  */)
 
   maybe_quit ();
 
-  if (clone_file (ofd, ifd))
+  if (emacs_fd_to_int (ifd) != -1
+      && clone_file (ofd, emacs_fd_to_int (ifd)))
     newsize = st.st_size;
   else
     {
@@ -2292,30 +2395,38 @@ permissions.  */)
       ssize_t copied;
 
 #ifndef MSDOS
-      for (newsize = 0; newsize < insize; newsize += copied)
+      newsize = 0;
+
+      if (emacs_fd_to_int (ifd) != -1)
        {
-         /* Copy at most COPY_MAX bytes at a time; this is min
-            (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is
-            surely aligned well.  */
-         ssize_t ssize_max = TYPE_MAXIMUM (ssize_t);
-         ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30;
-         off_t intail = insize - newsize;
-         ptrdiff_t len = min (intail, copy_max);
-         copied = copy_file_range (ifd, NULL, ofd, NULL, len, 0);
-         if (copied <= 0)
-           break;
-         maybe_quit ();
+         for (; newsize < insize; newsize += copied)
+           {
+             /* Copy at most COPY_MAX bytes at a time; this is min
+                (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is
+                surely aligned well.  */
+             ssize_t ssize_max = TYPE_MAXIMUM (ssize_t);
+             ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30;
+             off_t intail = insize - newsize;
+             ptrdiff_t len = min (intail, copy_max);
+             copied = copy_file_range (emacs_fd_to_int (ifd), NULL,
+                                       ofd, NULL, len, 0);
+             if (copied <= 0)
+               break;
+             maybe_quit ();
+           }
        }
 #endif /* MSDOS */
 
       /* Fall back on read+write if copy_file_range failed, or if the
-        input is empty and so could be a /proc file.  read+write will
-        either succeed, or report an error more precisely than
-        copy_file_range would.  */
+        input is empty and so could be a /proc file, or if ifd is an
+        invention of android.c.  read+write will either succeed, or
+        report an error more precisely than copy_file_range
+        would.  */
       if (newsize != insize || insize == 0)
        {
          char buf[MAX_ALLOCA];
-         for (; (copied = emacs_read_quit (ifd, buf, sizeof buf));
+
+         for (; (copied = emacs_fd_read (ifd, buf, sizeof buf));
               newsize += copied)
            {
              if (copied < 0)
@@ -2363,8 +2474,10 @@ permissions.  */)
          }
       }
 
-    switch (!NILP (preserve_permissions)
-           ? qcopy_acl (SSDATA (encoded_file), ifd,
+    switch ((!NILP (preserve_permissions)
+            && emacs_fd_to_int (ifd) != -1)
+           ? qcopy_acl (SSDATA (encoded_file),
+                        emacs_fd_to_int (ifd),
                         SSDATA (encoded_newname), ofd,
                         preserved_permissions)
            : (already_exists
@@ -2383,11 +2496,18 @@ permissions.  */)
     {
       /* Set the modified context back to the file.  */
       bool fail = fsetfilecon (ofd, con) != 0;
+      freecon (con);
+
       /* See https://debbugs.gnu.org/11245 for ENOTSUP.  */
-      if (fail && errno != ENOTSUP)
+      if (fail
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+         /* Treat SELinux errors copying files leniently on Android,
+            since the system usually forbids user programs from
+            changing file contexts.  */
+         && errno != EACCES
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+         && errno != ENOTSUP)
        report_file_error ("Doing fsetfilecon", newname);
-
-      freecon (con);
     }
 #endif
 
@@ -2396,7 +2516,17 @@ permissions.  */)
       struct timespec ts[2];
       ts[0] = get_stat_atime (&st);
       ts[1] = get_stat_mtime (&st);
-      if (futimens (ofd, ts) != 0)
+      if (futimens (ofd, ts) != 0
+         /* Various versions of the Android C library are missing
+            futimens, prompting Gnulib to install a fallback that
+            uses fdutimens instead.  However, fdutimens is not
+            supported on many Android kernels, so just silently fail
+            if errno is ENOTSUP or ENOSYS.  */
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+         && errno != ENOTSUP
+         && errno != ENOSYS
+#endif
+         )
        xsignal2 (Qfile_date_error,
                  build_string ("Cannot set file date"), newname);
     }
@@ -2404,7 +2534,9 @@ permissions.  */)
   if (emacs_close (ofd) < 0)
     report_file_error ("Write error", newname);
 
-  emacs_close (ifd);
+  /* Note that ifd is not closed twice because unwind_protects are
+     discarded at the end of this function.  */
+  emacs_fd_close (ifd);
 
 #ifdef MSDOS
   /* In DJGPP v2.0 and later, fstat usually returns true file mode bits,
@@ -2437,7 +2569,7 @@ DEFUN ("make-directory-internal", 
Fmake_directory_internal,
 
   dir = SSDATA (encoded_dir);
 
-  if (mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
+  if (emacs_mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
     report_file_error ("Creating directory", directory);
 
   return Qnil;
@@ -2456,47 +2588,26 @@ DEFUN ("delete-directory-internal", 
Fdelete_directory_internal,
   encoded_dir = ENCODE_FILE (directory);
   dir = SSDATA (encoded_dir);
 
-  if (rmdir (dir) != 0)
+  if (emacs_rmdir (dir) != 0)
     report_file_error ("Removing directory", directory);
 
   return Qnil;
 }
 
-DEFUN ("delete-file", Fdelete_file, Sdelete_file, 1, 2,
-       "(list (read-file-name \
-                (if (and delete-by-moving-to-trash (null current-prefix-arg)) \
-                    \"Move file to trash: \" \"Delete file: \") \
-                nil default-directory (confirm-nonexistent-file-or-buffer)) \
-              (null current-prefix-arg))",
-       doc: /* Delete file named FILENAME.  If it is a symlink, remove the 
symlink.
-If file has multiple names, it continues to exist with the other names.
-TRASH non-nil means to trash the file instead of deleting, provided
-`delete-by-moving-to-trash' is non-nil.
-
-When called interactively, TRASH is t if no prefix argument is given.
-With a prefix argument, TRASH is nil.  */)
-  (Lisp_Object filename, Lisp_Object trash)
+DEFUN ("delete-file-internal", Fdelete_file_internal, Sdelete_file_internal, 
1, 1, 0,
+       doc: /* Delete file named FILENAME; internal use only.
+If it is a symlink, remove the symlink.
+If file has multiple names, it continues to exist with the other names. */)
+  (Lisp_Object filename)
 {
-  Lisp_Object handler;
   Lisp_Object encoded_file;
 
-  if (!NILP (Ffile_directory_p (filename))
-      && NILP (Ffile_symlink_p (filename)))
-    xsignal2 (Qfile_error,
-             build_string ("Removing old name: is a directory"),
-             filename);
+  CHECK_STRING (filename);
   filename = Fexpand_file_name (filename, Qnil);
-
-  handler = Ffind_file_name_handler (filename, Qdelete_file);
-  if (!NILP (handler))
-    return call3 (handler, Qdelete_file, filename, trash);
-
-  if (delete_by_moving_to_trash && !NILP (trash))
-    return call1 (Qmove_file_to_trash, filename);
-
   encoded_file = ENCODE_FILE (filename);
 
-  if (unlink (SSDATA (encoded_file)) != 0 && errno != ENOENT)
+  if (emacs_unlink (SSDATA (encoded_file)) != 0
+      && errno != ENOENT)
     report_file_error ("Removing old name", filename);
   return Qnil;
 }
@@ -2517,7 +2628,7 @@ internal_delete_file (Lisp_Object filename)
 {
   Lisp_Object tem;
 
-  tem = internal_condition_case_2 (Fdelete_file, filename, Qnil,
+  tem = internal_condition_case_1 (Fdelete_file_internal, filename,
                                   Qt, internal_delete_file_1);
   return NILP (tem);
 }
@@ -2525,7 +2636,7 @@ internal_delete_file (Lisp_Object filename)
 #endif
 
 /* Return -1 if FILE is a case-insensitive file name, 0 if not,
-   and a positive errno value if the result cannot be determined.  */
+   and 1 if the result cannot be determined.  */
 
 static int
 file_name_case_insensitive_err (Lisp_Object file)
@@ -2559,7 +2670,7 @@ file_name_case_insensitive_err (Lisp_Object file)
     return - (res == 0);
 # endif
   if (errno != EINVAL)
-    return errno;
+    return 1;
 #endif
 
 #if defined CYGWIN || defined DOS_NT
@@ -2659,8 +2770,10 @@ This is what happens in interactive use with M-x.  */)
   int rename_errno UNINIT;
   if (!plain_rename)
     {
-      if (renameat_noreplace (AT_FDCWD, SSDATA (encoded_file),
-                             AT_FDCWD, SSDATA (encoded_newname))
+      if (emacs_renameat_noreplace (AT_FDCWD,
+                                   SSDATA (encoded_file),
+                                   AT_FDCWD,
+                                   SSDATA (encoded_newname))
          == 0)
        return Qnil;
 
@@ -2682,7 +2795,8 @@ This is what happens in interactive use with M-x.  */)
 
   if (plain_rename)
     {
-      if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
+      if (emacs_rename (SSDATA (encoded_file),
+                       SSDATA (encoded_newname)) == 0)
        return Qnil;
       rename_errno = errno;
       /* Don't prompt again.  */
@@ -2725,7 +2839,7 @@ This is what happens in interactive use with M-x.  */)
   if (dirp)
     call2 (Qdelete_directory, file, Qt);
   else
-    Fdelete_file (file, Qnil);
+    call2 (Qdelete_file, file, Qnil);
   return unbind_to (count, Qnil);
 }
 
@@ -2763,6 +2877,10 @@ This is what happens in interactive use with M-x.  */)
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
+  check_vfs_filename (encoded_file, "Trying to create hard link to "
+                     "file within special directory");
+  check_vfs_filename (encoded_newname, "Trying to create hard link"
+                     " within special directory");
 
   if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
     return Qnil;
@@ -2773,7 +2891,7 @@ This is what happens in interactive use with M-x.  */)
          || FIXNUMP (ok_if_already_exists))
        barf_or_query_if_file_exists (newname, true, "make it a new name",
                                      FIXNUMP (ok_if_already_exists), false);
-      unlink (SSDATA (newname));
+      emacs_unlink (SSDATA (newname));
       if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
        return Qnil;
     }
@@ -2817,7 +2935,8 @@ This happens for interactive use with M-x.  */)
   encoded_target = ENCODE_FILE (target);
   encoded_linkname = ENCODE_FILE (linkname);
 
-  if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0)
+  if (emacs_symlink (SSDATA (encoded_target),
+                    SSDATA (encoded_linkname)) == 0)
     return Qnil;
 
   if (errno == ENOSYS)
@@ -2830,8 +2949,9 @@ This happens for interactive use with M-x.  */)
          || FIXNUMP (ok_if_already_exists))
        barf_or_query_if_file_exists (linkname, true, "make it a link",
                                      FIXNUMP (ok_if_already_exists), false);
-      unlink (SSDATA (encoded_linkname));
-      if (symlink (SSDATA (encoded_target), SSDATA (encoded_linkname)) == 0)
+      emacs_unlink (SSDATA (encoded_linkname));
+      if (emacs_symlink (SSDATA (encoded_target),
+                        SSDATA (encoded_linkname)) == 0)
        return Qnil;
     }
 
@@ -2971,7 +3091,8 @@ If there is no error, returns nil.  */)
 
   encoded_filename = ENCODE_FILE (absname);
 
-  if (faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, AT_EACCESS) != 0)
+  if (sys_faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK,
+                    AT_EACCESS) != 0)
     report_file_error (SSDATA (string), filename);
 
   return Qnil;
@@ -2979,15 +3100,29 @@ If there is no error, returns nil.  */)
 
 /* Relative to directory FD, return the symbolic link value of FILENAME.
    On failure, return nil (setting errno).  */
+
 static Lisp_Object
 emacs_readlinkat (int fd, char const *filename)
 {
-  static struct allocator const emacs_norealloc_allocator =
-    { xmalloc, NULL, xfree, memory_full };
+  static struct allocator const emacs_norealloc_allocator = {
+    xmalloc,
+    NULL,
+    xfree,
+    memory_full,
+  };
+
   Lisp_Object val;
   char readlink_buf[1024];
-  char *buf = careadlinkat (fd, filename, readlink_buf, sizeof readlink_buf,
-                           &emacs_norealloc_allocator, readlinkat);
+  char *buf;
+
+  buf = careadlinkat (fd, filename, readlink_buf, sizeof readlink_buf,
+                     &emacs_norealloc_allocator,
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+                     android_readlinkat
+#else /* !HAVE_ANDROID || ANDROID_STUBIFY */
+                     readlinkat
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY */
+                     );
   if (!buf)
     return Qnil;
 
@@ -3073,12 +3208,13 @@ file_directory_p (Lisp_Object file)
 {
 #ifdef DOS_NT
   /* This is cheaper than 'stat'.  */
-  bool retval = faccessat (AT_FDCWD, SSDATA (file), D_OK, AT_EACCESS) == 0;
+  bool retval = sys_faccessat (AT_FDCWD, SSDATA (file),
+                              D_OK, AT_EACCESS) == 0;
   if (!retval && errno == EACCES)
     errno = ENOTDIR;   /* like the non-DOS_NT branch below does */
   return retval;
 #else
-# ifdef O_PATH
+# if defined O_PATH && !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
   /* Use O_PATH if available, as it avoids races and EOVERFLOW issues.  */
   int fd = emacs_openat (AT_FDCWD, SSDATA (file),
                         O_PATH | O_CLOEXEC | O_DIRECTORY, 0);
@@ -3187,7 +3323,11 @@ file_accessible_directory_p (Lisp_Object file)
      There are three exceptions: "", "/", and "//".  Leave "" alone,
      as it's invalid.  Append only "." to the other two exceptions as
      "/" and "//" are distinct on some platforms, whereas "/", "///",
-     "////", etc. are all equivalent.  */
+     "////", etc. are all equivalent.
+
+     Android has a special directory named "/assets".  There is no "."
+     directory there, but appending a "/" is sufficient to check
+     whether or not it is a directory.  */
   if (! len)
     dir = data;
   else
@@ -3197,6 +3337,7 @@ file_accessible_directory_p (Lisp_Object file)
         special cases "/" and "//", and it's a safe optimization
         here.  After appending '.', append another '/' to work around
         a macOS bug (Bug#30350).  */
+
       static char const appended[] = "/./";
       char *buf = SAFE_ALLOCA (len + sizeof appended);
       memcpy (buf, data, len);
@@ -3256,6 +3397,9 @@ or if SELinux is disabled, or if Emacs lacks SELinux 
support.  */)
 {
   Lisp_Object user = Qnil, role = Qnil, type = Qnil, range = Qnil;
   Lisp_Object absname = expand_and_dir_to_file (filename);
+#ifdef HAVE_LIBSELINUX
+  const char *file;
+#endif /* HAVE_LIBSELINUX */
 
   /* If the file name has special constructs in it,
      call the corresponding file name handler.  */
@@ -3264,11 +3408,13 @@ or if SELinux is disabled, or if Emacs lacks SELinux 
support.  */)
   if (!NILP (handler))
     return call2 (handler, Qfile_selinux_context, absname);
 
-#if HAVE_LIBSELINUX
-  if (is_selinux_enabled ())
+#ifdef HAVE_LIBSELINUX
+  file = SSDATA (ENCODE_FILE (absname));
+
+  if (selinux_enabled_p (file))
     {
       char *con;
-      int conlength = lgetfilecon (SSDATA (ENCODE_FILE (absname)), &con);
+      int conlength = lgetfilecon (file, &con);
       if (conlength > 0)
        {
          context_t context = context_new (con);
@@ -3287,7 +3433,7 @@ or if SELinux is disabled, or if Emacs lacks SELinux 
support.  */)
                  || errno == ENOTSUP))
        report_file_error ("getting SELinux context", absname);
     }
-#endif
+#endif /* HAVE_LIBSELINUX */
 
   return list4 (user, role, type, range);
 }
@@ -3313,10 +3459,11 @@ or if Emacs was not compiled with SELinux support.  */)
   Lisp_Object type = CAR_SAFE (CDR_SAFE (CDR_SAFE (context)));
   Lisp_Object range = CAR_SAFE (CDR_SAFE (CDR_SAFE (CDR_SAFE (context))));
   char *con;
+  const char *name;
   bool fail;
   int conlength;
   context_t parsed_con;
-#endif
+#endif /* HAVE_LIBSELINUX */
 
   absname = Fexpand_file_name (filename, BVAR (current_buffer, directory));
 
@@ -3327,11 +3474,13 @@ or if Emacs was not compiled with SELinux support.  */)
     return call3 (handler, Qset_file_selinux_context, absname, context);
 
 #if HAVE_LIBSELINUX
-  if (is_selinux_enabled ())
+  encoded_absname = ENCODE_FILE (absname);
+  name = SSDATA (encoded_absname);
+
+  if (selinux_enabled_p (name))
     {
       /* Get current file context. */
-      encoded_absname = ENCODE_FILE (absname);
-      conlength = lgetfilecon (SSDATA (encoded_absname), &con);
+      conlength = lgetfilecon (name, &con);
       if (conlength > 0)
        {
          parsed_con = context_new (con);
@@ -3361,18 +3510,18 @@ or if Emacs was not compiled with SELinux support.  */)
          fail = (lsetfilecon (SSDATA (encoded_absname),
                               context_str (parsed_con))
                  != 0);
+         context_free (parsed_con);
+         freecon (con);
+
           /* See https://debbugs.gnu.org/11245 for ENOTSUP.  */
          if (fail && errno != ENOTSUP)
            report_file_error ("Doing lsetfilecon", absname);
-
-         context_free (parsed_con);
-         freecon (con);
          return fail ? Qnil : Qt;
        }
       else
        report_file_error ("Doing lgetfilecon", absname);
     }
-#endif
+#endif /* HAVE_LIBSELINUX */
 
   return Qnil;
 }
@@ -3468,10 +3617,10 @@ support.  */)
       fail = (acl_set_file (SSDATA (encoded_absname), ACL_TYPE_ACCESS,
                            acl)
              != 0);
+      acl_free (acl);
       if (fail && acl_errno_valid (errno))
        report_file_error ("Setting ACL", absname);
 
-      acl_free (acl);
       return fail ? Qnil : Qt;
     }
 # endif
@@ -3521,6 +3670,8 @@ Interactively, prompt for FILENAME, and read MODE with
 command from GNU Coreutils.  */)
   (Lisp_Object filename, Lisp_Object mode, Lisp_Object flag)
 {
+  Lisp_Object encoded;
+
   CHECK_FIXNUM (mode);
   int nofollow = symlink_nofollow_flag (flag);
   Lisp_Object absname = Fexpand_file_name (filename,
@@ -3532,9 +3683,10 @@ command from GNU Coreutils.  */)
   if (!NILP (handler))
     return call4 (handler, Qset_file_modes, absname, mode, flag);
 
-  char *fname = SSDATA (ENCODE_FILE (absname));
+  encoded = ENCODE_FILE (absname);
+  char *fname = SSDATA (encoded);
   mode_t imode = XFIXNUM (mode) & 07777;
-  if (fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
+  if (emacs_fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
     report_file_error ("Doing chmod", absname);
 
   return Qnil;
@@ -3551,7 +3703,9 @@ in the permissions of newly created files will be 
disabled.
 
 Note that when `write-region' creates a file, it resets the
 execute bit, even if the mask set by this function allows that bit
-by having the corresponding bit in the mask reset.  */)
+by having the corresponding bit in the mask reset.
+
+See also `with-file-modes'.  */)
   (Lisp_Object mode)
 {
   mode_t oldrealmask, oldumask, newumask;
@@ -3604,6 +3758,8 @@ TIMESTAMP is in the format of `current-time'. */)
     return call4 (handler, Qset_file_times, absname, timestamp, flag);
 
   Lisp_Object encoded_absname = ENCODE_FILE (absname);
+  check_vfs_filename (encoded_absname, "Trying to set access times of"
+                     " file within special directory");
 
   if (utimensat (AT_FDCWD, SSDATA (encoded_absname), ts, nofollow) != 0)
     {
@@ -3636,6 +3792,7 @@ otherwise, if FILE2 does not exist, the answer is t.  */)
   (Lisp_Object file1, Lisp_Object file2)
 {
   struct stat st1, st2;
+  Lisp_Object encoded;
 
   CHECK_STRING (file1);
   CHECK_STRING (file2);
@@ -3652,8 +3809,10 @@ otherwise, if FILE2 does not exist, the answer is t.  */)
   if (!NILP (handler))
     return call3 (handler, Qfile_newer_than_file_p, absname1, absname2);
 
+  encoded = ENCODE_FILE (absname1);
+
   int err1;
-  if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname1)), &st1, 0) == 0)
+  if (emacs_fstatat (AT_FDCWD, SSDATA (encoded), &st1, 0) == 0)
     err1 = 0;
   else
     {
@@ -3742,7 +3901,7 @@ union read_non_regular
 {
   struct
   {
-    int fd;
+    emacs_fd fd;
     ptrdiff_t inserted, trytry;
   } s;
   GCALIGNED_UNION_MEMBER
@@ -3753,11 +3912,12 @@ static Lisp_Object
 read_non_regular (Lisp_Object state)
 {
   union read_non_regular *data = XFIXNUMPTR (state);
-  int nbytes = emacs_read_quit (data->s.fd,
-                               ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
-                                + data->s.inserted),
-                               data->s.trytry);
-  return make_fixnum (nbytes);
+  intmax_t nbytes
+    = emacs_fd_read (data->s.fd,
+                    ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
+                     + data->s.inserted),
+                    data->s.trytry);
+  return make_int (nbytes);
 }
 
 
@@ -3885,15 +4045,22 @@ characters in the buffer.  If VISIT is non-nil, BEG and 
END must be nil.
 
 When inserting data from a special file (e.g., /dev/urandom), you
 can't specify VISIT or BEG, and END should be specified to avoid
-inserting unlimited data into the buffer.
-
-If optional fifth argument REPLACE is non-nil, replace the current
-buffer contents (in the accessible portion) with the file contents.
-This is better than simply deleting and inserting the whole thing
-because (1) it preserves some marker positions (in unchanged portions
-at the start and end of the buffer) and (2) it puts less data in the
-undo list.  When REPLACE is non-nil, the second return value is the
-number of characters that replace previous buffer contents.
+inserting unlimited data into the buffer from some special files
+which otherwise could supply infinite amounts of data.
+
+If optional fifth argument REPLACE is non-nil and FILENAME names a
+regular file, replace the current buffer contents (in the accessible
+portion) with the file's contents.  This is better than simply
+deleting and inserting the whole thing because (1) it preserves some
+marker positions (in unchanged portions at the start and end of the
+buffer) and (2) it puts less data in the undo list.  When REPLACE is
+non-nil, the second element of the return value is the number of
+characters that replace the previous buffer contents.
+
+If FILENAME is not a regular file and REPLACE is `if-regular', erase
+the accessible portion of the buffer and insert the new contents.  Any
+other non-nil value of REPLACE will signal an error if FILENAME is not
+a regular file.
 
 This function does code conversion according to the value of
 `coding-system-for-read' or `file-coding-system-alist', and sets the
@@ -3901,11 +4068,12 @@ variable `last-coding-system-used' to the coding system 
actually used.
 
 In addition, this function decodes the inserted text from known formats
 by calling `format-decode', which see.  */)
-  (Lisp_Object filename, Lisp_Object visit, Lisp_Object beg, Lisp_Object end, 
Lisp_Object replace)
+  (Lisp_Object filename, Lisp_Object visit, Lisp_Object beg, Lisp_Object end,
+   Lisp_Object replace)
 {
   struct stat st;
   struct timespec mtime;
-  int fd;
+  emacs_fd fd;
   ptrdiff_t inserted = 0;
   int unprocessed;
   specpdl_ref count = SPECPDL_INDEX ();
@@ -3983,8 +4151,8 @@ by calling `format-decode', which see.  */)
   orig_filename = filename;
   filename = ENCODE_FILE (filename);
 
-  fd = emacs_open (SSDATA (filename), O_RDONLY, 0);
-  if (fd < 0)
+  fd = emacs_fd_open (SSDATA (filename), O_RDONLY, 0);
+  if (!emacs_fd_valid_p (fd))
     {
       save_errno = errno;
       if (NILP (visit))
@@ -4002,7 +4170,7 @@ by calling `format-decode', which see.  */)
     }
 
   specpdl_ref fd_index = SPECPDL_INDEX ();
-  record_unwind_protect_int (close_file_unwind, fd);
+  record_unwind_protect_ptr (close_file_unwind_emacs_fd, &fd);
 
   /* Replacement should preserve point as it preserves markers.  */
   if (!NILP (replace))
@@ -4012,32 +4180,42 @@ by calling `format-decode', which see.  */)
                             XCAR (XCAR (window_markers)));
     }
 
-  if (fstat (fd, &st) != 0)
+  if (emacs_fd_fstat (fd, &st) != 0)
     report_file_error ("Input file status", orig_filename);
   mtime = get_stat_mtime (&st);
 
-  /* This code will need to be changed in order to work on named
-     pipes, and it's probably just not worth it.  So we should at
-     least signal an error.  */
+  /* The REPLACE code will need to be changed in order to work on
+     named pipes, and it's probably just not worth it.  So we should
+     at least signal an error.  */
+
   if (!S_ISREG (st.st_mode))
     {
       regular = false;
 
-      if (! NILP (visit))
-        {
-          eassert (inserted == 0);
-         goto notfound;
-        }
-
       if (!NILP (replace))
-       xsignal2 (Qfile_error,
-                 build_string ("not a regular file"), orig_filename);
+       {
+         if (!EQ (replace, Qif_regular))
+           xsignal2 (Qfile_error,
+                     build_string ("not a regular file"), orig_filename);
+         else
+           /* Set REPLACE to Qunbound, indicating that we are trying
+              to replace the buffer contents with that of a
+              non-regular file.  */
+           replace = Qunbound;
+       }
+
+      /* Forbid specifying BEG together with a special file, as per
+        the doc string.  */
 
-      seekable = lseek (fd, 0, SEEK_CUR) < 0;
-      if (!NILP (beg) && !seekable)
+      if (!NILP (beg))
        xsignal2 (Qfile_error,
                  build_string ("cannot use a start position in a non-seekable 
file/device"),
                  orig_filename);
+
+      /* Now ascertain if this file is seekable, by detecting if
+        seeking leads to -1 being returned.  */
+      seekable
+       = emacs_fd_lseek (fd, 0, SEEK_CUR) != (off_t) -1;
     }
 
   if (end_offset < 0)
@@ -4112,17 +4290,17 @@ by calling `format-decode', which see.  */)
              int nread;
 
              if (st.st_size <= (1024 * 4))
-               nread = emacs_read_quit (fd, read_buf, 1024 * 4);
+               nread = emacs_fd_read (fd, read_buf, 1024 * 4);
              else
                {
-                 nread = emacs_read_quit (fd, read_buf, 1024);
+                 nread = emacs_fd_read (fd, read_buf, 1024);
                  if (nread == 1024)
                    {
                      int ntail;
-                     if (lseek (fd, st.st_size - 1024 * 3, SEEK_CUR) < 0)
+                     if (emacs_fd_lseek (fd, st.st_size - 1024 * 3, SEEK_CUR) 
< 0)
                        report_file_error ("Setting file position",
                                           orig_filename);
-                     ntail = emacs_read_quit (fd, read_buf + nread, 1024 * 3);
+                     ntail = emacs_fd_read (fd, read_buf + nread, 1024 * 3);
                      nread = ntail < 0 ? ntail : nread + ntail;
                    }
                }
@@ -4163,7 +4341,7 @@ by calling `format-decode', which see.  */)
                  specpdl_ptr--;
 
                  /* Rewind the file for the actual read done later.  */
-                 if (lseek (fd, 0, SEEK_SET) < 0)
+                 if (emacs_fd_lseek (fd, 0, SEEK_SET) < 0)
                    report_file_error ("Setting file position", orig_filename);
                }
            }
@@ -4209,7 +4387,8 @@ by calling `format-decode', which see.  */)
      method and hope for the best.
      But if we discover the need for conversion, we give up on this method
      and let the following if-statement handle the replace job.  */
-  if (!NILP (replace)
+  if ((!NILP (replace)
+       && !BASE_EQ (replace, Qunbound))
       && BEGV < ZV
       && (NILP (coding_system)
          || ! CODING_REQUIRE_DECODING (&coding)))
@@ -4222,7 +4401,7 @@ by calling `format-decode', which see.  */)
 
       if (beg_offset != 0)
        {
-         if (lseek (fd, beg_offset, SEEK_SET) < 0)
+         if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0)
            report_file_error ("Setting file position", orig_filename);
        }
 
@@ -4230,7 +4409,7 @@ by calling `format-decode', which see.  */)
         match the text at the beginning of the buffer.  */
       while (true)
        {
-         int nread = emacs_read_quit (fd, read_buf, sizeof read_buf);
+         int nread = emacs_fd_read (fd, read_buf, sizeof read_buf);
          if (nread < 0)
            report_file_error ("Read error", orig_filename);
          else if (nread == 0)
@@ -4265,7 +4444,7 @@ by calling `format-decode', which see.  */)
         there's no need to replace anything.  */
       if (same_at_start - BEGV_BYTE == end_offset - beg_offset)
        {
-         emacs_close (fd);
+         emacs_fd_close (fd);
          clear_unwind_protect (fd_index);
 
          /* Truncate the buffer to the size of the file.  */
@@ -4288,14 +4467,14 @@ by calling `format-decode', which see.  */)
            break;
          /* How much can we scan in the next step?  */
          trial = min (curpos, sizeof read_buf);
-         if (lseek (fd, curpos - trial, SEEK_SET) < 0)
+         if (emacs_fd_lseek (fd, curpos - trial, SEEK_SET) < 0)
            report_file_error ("Setting file position", orig_filename);
 
          total_read = nread = 0;
          while (total_read < trial)
            {
-             nread = emacs_read_quit (fd, read_buf + total_read,
-                                      trial - total_read);
+             nread = emacs_fd_read (fd, read_buf + total_read,
+                                    trial - total_read);
              if (nread < 0)
                report_file_error ("Read error", orig_filename);
              else if (nread == 0)
@@ -4396,7 +4575,9 @@ by calling `format-decode', which see.  */)
      is needed, in a simple way that needs a lot of memory.
      The preceding if-statement handles the case of no conversion
      in a more optimized way.  */
-  if (!NILP (replace) && ! replace_handled && BEGV < ZV)
+  if ((!NILP (replace)
+       && !BASE_EQ (replace, Qunbound))
+      && ! replace_handled && BEGV < ZV)
     {
       ptrdiff_t same_at_start_charpos;
       ptrdiff_t inserted_chars;
@@ -4415,7 +4596,7 @@ by calling `format-decode', which see.  */)
       /* First read the whole file, performing code conversion into
         CONVERSION_BUFFER.  */
 
-      if (lseek (fd, beg_offset, SEEK_SET) < 0)
+      if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0)
        report_file_error ("Setting file position", orig_filename);
 
       inserted = 0;            /* Bytes put into CONVERSION_BUFFER so far.  */
@@ -4426,8 +4607,8 @@ by calling `format-decode', which see.  */)
          /* Read at most READ_BUF_SIZE bytes at a time, to allow
             quitting while reading a huge file.  */
 
-         this = emacs_read_quit (fd, read_buf + unprocessed,
-                                 READ_BUF_SIZE - unprocessed);
+         this = emacs_fd_read (fd, read_buf + unprocessed,
+                               READ_BUF_SIZE - unprocessed);
          if (this <= 0)
            break;
 
@@ -4442,7 +4623,7 @@ by calling `format-decode', which see.  */)
 
       if (this < 0)
        report_file_error ("Read error", orig_filename);
-      emacs_close (fd);
+      emacs_fd_close (fd);
       clear_unwind_protect (fd_index);
 
       if (unprocessed > 0)
@@ -4581,6 +4762,13 @@ by calling `format-decode', which see.  */)
       prepare_to_modify_buffer (PT, PT, NULL);
     }
 
+  /* If REPLACE is Qunbound, buffer contents are being replaced with
+     text read from a FIFO or a device.  Erase the entire accessible
+     portion of the buffer.  */
+
+  if (BASE_EQ (replace, Qunbound))
+    del_range (BEGV, ZV);
+
   move_gap_both (PT, PT_BYTE);
 
   /* Ensure the gap is at least one byte larger than needed for the
@@ -4589,9 +4777,10 @@ by calling `format-decode', which see.  */)
   if (GAP_SIZE <= total)
     make_gap (total - GAP_SIZE + 1);
 
-  if (beg_offset != 0 || !NILP (replace))
+  if (beg_offset != 0 || (!NILP (replace)
+                         && !EQ (replace, Qunbound)))
     {
-      if (lseek (fd, beg_offset, SEEK_SET) < 0)
+      if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0)
        report_file_error ("Setting file position", orig_filename);
     }
 
@@ -4622,6 +4811,7 @@ by calling `format-decode', which see.  */)
        if (!seekable && NILP (end))
          {
            Lisp_Object nbytes;
+           intmax_t number;
 
            /* Read from the file, capturing `quit'.  When an
               error occurs, end the loop, and arrange for a quit
@@ -4637,18 +4827,20 @@ by calling `format-decode', which see.  */)
                break;
              }
 
-           this = XFIXNUM (nbytes);
+           if (!integer_to_intmax (nbytes, &number)
+               && number > PTRDIFF_MAX)
+             buffer_overflow ();
+
+           this = number;
          }
        else
-         {
-           /* Allow quitting out of the actual I/O.  We don't make text
-              part of the buffer until all the reading is done, so a C-g
-              here doesn't do any harm.  */
-           this = emacs_read_quit (fd,
-                                   ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
-                                    + inserted),
-                                   trytry);
-         }
+         /* Allow quitting out of the actual I/O.  We don't make text
+            part of the buffer until all the reading is done, so a
+            C-g here doesn't do any harm.  */
+         this = emacs_fd_read (fd,
+                               ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE
+                                + inserted),
+                               trytry);
 
        if (this <= 0)
          {
@@ -4674,7 +4866,7 @@ by calling `format-decode', which see.  */)
   else
     Fset (Qdeactivate_mark, Qt);
 
-  emacs_close (fd);
+  emacs_fd_close (fd);
   clear_unwind_protect (fd_index);
 
   if (read_quit < 0)
@@ -4833,9 +5025,14 @@ by calling `format-decode', which see.  */)
            Funlock_file (BVAR (current_buffer, file_truename));
          Funlock_file (filename);
        }
+
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+      /* Under Android, modtime and st.st_size can be valid even if FD
+        is not a regular file.  */
       if (!regular)
        xsignal2 (Qfile_error,
                  build_string ("not a regular file"), orig_filename);
+#endif /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
     }
 
   if (set_coding_system)
@@ -4853,8 +5050,10 @@ by calling `format-decode', which see.  */)
        }
     }
 
-  /* Decode file format.  */
-  if (inserted > 0)
+  /* Decode file format.  Don't do this if Qformat_decode is not
+     bound, which can happen when called early during loadup.  */
+
+  if (inserted > 0 && !NILP (Ffboundp (Qformat_decode)))
     {
       /* Don't run point motion or modification hooks when decoding.  */
       specpdl_ref count1 = SPECPDL_INDEX ();
@@ -5299,6 +5498,7 @@ write_region (Lisp_Object start, Lisp_Object end, 
Lisp_Object filename,
     }
 
   encoded_filename = ENCODE_FILE (filename);
+
   fn = SSDATA (encoded_filename);
   open_flags = O_WRONLY | O_CREAT;
   open_flags |= EQ (mustbenew, Qexcl) ? O_EXCL : !NILP (append) ? 0 : O_TRUNC;
@@ -5385,7 +5585,7 @@ write_region (Lisp_Object start, Lisp_Object end, 
Lisp_Object filename,
   modtime = invalid_timespec ();
   if (visiting)
     {
-      if (fstat (desc, &st) == 0)
+      if (sys_fstat (desc, &st) == 0)
        modtime = get_stat_mtime (&st);
       else
        ok = 0, save_errno = errno;
@@ -5419,42 +5619,44 @@ write_region (Lisp_Object start, Lisp_Object end, 
Lisp_Object filename,
   if (timespec_valid_p (modtime)
       && ! (valid_timestamp_file_system && st.st_dev == timestamp_file_system))
     {
-      int desc1 = emacs_open (fn, O_WRONLY, 0);
-      if (desc1 >= 0)
+      struct stat st1;
+
+      /* The code below previously tried to open FN O_WRONLY,
+         subsequently calling fstat on the opened file descriptor.
+         This proved inefficient and resulted in FN being truncated
+         under several Android filesystems, and as such has been
+         changed to a call to `stat'.  */
+
+      if (emacs_fstatat (AT_FDCWD, fn, &st1, 0) == 0
+         && st.st_dev == st1.st_dev && st.st_ino == st1.st_ino)
        {
-         struct stat st1;
-         if (fstat (desc1, &st1) == 0
-             && st.st_dev == st1.st_dev && st.st_ino == st1.st_ino)
+         /* Use the heuristic if it appears to be valid.  With neither
+            O_EXCL nor O_TRUNC, if Emacs happened to write nothing to the
+            file, the time stamp won't change.  Also, some non-POSIX
+            systems don't update an empty file's time stamp when
+            truncating it.  Finally, file systems with 100 ns or worse
+            resolution sometimes seem to have bugs: on a system with ns
+            resolution, checking ns % 100 incorrectly avoids the heuristic
+            1% of the time, but the problem should be temporary as we will
+            try again on the next time stamp.  */
+         bool use_heuristic
+           = ((open_flags & (O_EXCL | O_TRUNC)) != 0
+              && st.st_size != 0
+              && modtime.tv_nsec % 100 != 0);
+
+         struct timespec modtime1 = get_stat_mtime (&st1);
+         if (use_heuristic
+             && timespec_cmp (modtime, modtime1) == 0
+             && st.st_size == st1.st_size)
            {
-             /* Use the heuristic if it appears to be valid.  With neither
-                O_EXCL nor O_TRUNC, if Emacs happened to write nothing to the
-                file, the time stamp won't change.  Also, some non-POSIX
-                systems don't update an empty file's time stamp when
-                truncating it.  Finally, file systems with 100 ns or worse
-                resolution sometimes seem to have bugs: on a system with ns
-                resolution, checking ns % 100 incorrectly avoids the heuristic
-                1% of the time, but the problem should be temporary as we will
-                try again on the next time stamp.  */
-             bool use_heuristic
-               = ((open_flags & (O_EXCL | O_TRUNC)) != 0
-                  && st.st_size != 0
-                  && modtime.tv_nsec % 100 != 0);
-
-             struct timespec modtime1 = get_stat_mtime (&st1);
-             if (use_heuristic
-                 && timespec_cmp (modtime, modtime1) == 0
-                 && st.st_size == st1.st_size)
-               {
-                 timestamp_file_system = st.st_dev;
-                 valid_timestamp_file_system = 1;
-               }
-             else
-               {
-                 st.st_size = st1.st_size;
-                 modtime = modtime1;
-               }
+             timestamp_file_system = st.st_dev;
+             valid_timestamp_file_system = 1;
+           }
+         else
+           {
+             st.st_size = st1.st_size;
+             modtime = modtime1;
            }
-         emacs_close (desc1);
        }
     }
 
@@ -5800,7 +6002,6 @@ See Info node `(elisp)Modification Time' for more 
details.  */)
     return call2 (handler, Qverify_visited_file_modtime, buf);
 
   filename = ENCODE_FILE (BVAR (b, filename));
-
   mtime = (emacs_fstatat (AT_FDCWD, SSDATA (filename), &st, 0) == 0
           ? get_stat_mtime (&st)
           : time_error_value (errno));
@@ -5860,7 +6061,7 @@ in `current-time' or an integer flag as returned by 
`visited-file-modtime'.  */)
     error ("An indirect buffer does not have a visited file");
   else
     {
-      register Lisp_Object filename;
+      register Lisp_Object filename, encoded;
       struct stat st;
       Lisp_Object handler;
 
@@ -5873,7 +6074,9 @@ in `current-time' or an integer flag as returned by 
`visited-file-modtime'.  */)
        /* The handler can find the file name the same way we did.  */
        return call2 (handler, Qset_visited_file_modtime, Qnil);
 
-      if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (filename)), &st, 0)
+      encoded = ENCODE_FILE (filename);
+
+      if (emacs_fstatat (AT_FDCWD, SSDATA (encoded), &st, 0)
          == 0)
         {
          current_buffer->modtime = get_stat_mtime (&st);
@@ -5946,7 +6149,7 @@ do_auto_save_unwind (void *arg)
   if (stream != NULL)
     {
       block_input ();
-      fclose (stream);
+      emacs_fclose (stream);
       unblock_input ();
     }
 }
@@ -6272,6 +6475,11 @@ effect except for flushing STREAM's data.  */)
 
 #ifndef DOS_NT
 
+#if defined STAT_STATFS2_BSIZE || defined STAT_STATFS2_FRSIZE  \
+  || defined STAT_STATFS2_FSIZE || defined STAT_STATFS3_OSF1   \
+  || defined STAT_STATFS4 || defined STAT_STATVFS              \
+  || defined STAT_STATVFS64
+
 /* Yield a Lisp number equal to BLOCKSIZE * BLOCKS, with the result
    negated if NEGATE.  */
 static Lisp_Object
@@ -6286,6 +6494,8 @@ blocks_to_bytes (uintmax_t blocksize, uintmax_t blocks, 
bool negate)
   return CALLN (Ftimes, bs, make_uint (blocks));
 }
 
+#endif
+
 DEFUN ("file-system-info", Ffile_system_info, Sfile_system_info, 1, 1, 0,
        doc: /* Return storage information about the file system FILENAME is on.
 Value is a list of numbers (TOTAL FREE AVAIL), where TOTAL is the total
@@ -6307,6 +6517,11 @@ If the underlying system call fails, value is nil.  */)
       error ("Invalid handler in `file-name-handler-alist'");
     }
 
+  /* Try to detect whether or not fsusage.o is actually built.  */
+#if defined STAT_STATFS2_BSIZE || defined STAT_STATFS2_FRSIZE  \
+  || defined STAT_STATFS2_FSIZE || defined STAT_STATFS3_OSF1   \
+  || defined STAT_STATFS4 || defined STAT_STATVFS              \
+  || defined STAT_STATVFS64
   struct fs_usage u;
   if (get_fs_usage (SSDATA (ENCODE_FILE (filename)), NULL, &u) != 0)
     return errno == ENOSYS ? Qnil : file_attribute_errno (filename, errno);
@@ -6314,6 +6529,9 @@ If the underlying system call fails, value is nil.  */)
                blocks_to_bytes (u.fsu_blocksize, u.fsu_bfree, false),
                blocks_to_bytes (u.fsu_blocksize, u.fsu_bavail,
                                 u.fsu_bavail_top_bit_set));
+#else
+  return Qnil;
+#endif
 }
 
 #endif /* !DOS_NT */
@@ -6344,7 +6562,7 @@ syms_of_fileio (void)
   DEFSYM (Qcopy_file, "copy-file");
   DEFSYM (Qmake_directory_internal, "make-directory-internal");
   DEFSYM (Qmake_directory, "make-directory");
-  DEFSYM (Qdelete_file, "delete-file");
+  DEFSYM (Qdelete_file_internal, "delete-file-internal");
   DEFSYM (Qfile_name_case_insensitive_p, "file-name-case-insensitive-p");
   DEFSYM (Qrename_file, "rename-file");
   DEFSYM (Qadd_name_to_file, "add-name-to-file");
@@ -6608,8 +6826,8 @@ This includes interactive calls to `delete-file' and
   delete_by_moving_to_trash = 0;
   DEFSYM (Qdelete_by_moving_to_trash, "delete-by-moving-to-trash");
 
-  /* Lisp function for moving files to trash.  */
-  DEFSYM (Qmove_file_to_trash, "move-file-to-trash");
+  /* Lisp function for interactive file delete with trashing */
+  DEFSYM (Qdelete_file, "delete-file");
 
   /* Lisp function for recursively copying directories.  */
   DEFSYM (Qcopy_directory, "copy-directory");
@@ -6639,7 +6857,7 @@ This includes interactive calls to `delete-file' and
   defsubr (&Scopy_file);
   defsubr (&Smake_directory_internal);
   defsubr (&Sdelete_directory_internal);
-  defsubr (&Sdelete_file);
+  defsubr (&Sdelete_file_internal);
   defsubr (&Sfile_name_case_insensitive_p);
   defsubr (&Srename_file);
   defsubr (&Sadd_name_to_file);
@@ -6681,9 +6899,11 @@ This includes interactive calls to `delete-file' and
 
 #ifndef DOS_NT
   defsubr (&Sfile_system_info);
-#endif
+#endif /* DOS_NT */
 
 #ifdef HAVE_SYNC
   defsubr (&Sunix_sync);
-#endif
+#endif /* HAVE_SYNC */
+
+  DEFSYM (Qif_regular, "if-regular");
 }
diff --git a/src/filelock.c b/src/filelock.c
index be9f8f488d9..c2b306ab47d 100644
--- a/src/filelock.c
+++ b/src/filelock.c
@@ -36,13 +36,9 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <sys/file.h>
 #include <fcntl.h>
 #include <unistd.h>
-
-#ifdef __FreeBSD__
-#include <sys/sysctl.h>
-#endif /* __FreeBSD__ */
-
 #include <errno.h>
 
+#include <boot-time.h>
 #include <c-ctype.h>
 
 #include "lisp.h"
@@ -55,19 +51,9 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #ifndef MSDOS
 
-#ifdef HAVE_UTMP_H
-#include <utmp.h>
-#endif
-
-/* A file whose last-modified time is just after the most recent boot.
-   Define this to be NULL to disable checking for this file.  */
-#ifndef BOOT_TIME_FILE
-#define BOOT_TIME_FILE "/var/run/random-seed"
-#endif
-
-#if !defined WTMP_FILE && !defined WINDOWSNT && defined BOOT_TIME
-#define WTMP_FILE "/var/log/wtmp"
-#endif
+#ifdef HAVE_ANDROID
+#include "android.h" /* For `android_is_special_directory'.  */
+#endif /* HAVE_ANDROID */
 
 /* Normally use a symbolic link to represent a lock.
    The strategy: to lock a file FN, create a symlink .#FN in FN's
@@ -121,157 +107,22 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
      hard nor symbolic links.  */
 
 
-/* Return the time of the last system boot.  */
-
-static time_t boot_time;
-static bool boot_time_initialized;
-
-#ifdef BOOT_TIME
-static void get_boot_time_1 (const char *, bool);
-#endif
+/* Return the time of the last system boot, or 0 if that information
+   is unavailable.  */
 
 static time_t
-get_boot_time (void)
+get_boot_sec (void)
 {
-#if defined (BOOT_TIME)
-  int counter;
-#endif
-
-  if (boot_time_initialized)
-    return boot_time;
-  boot_time_initialized = 1;
-
-#if defined (CTL_KERN) && defined (KERN_BOOTTIME)
-  {
-    int mib[2];
-    size_t size;
-    struct timeval boottime_val;
-
-    mib[0] = CTL_KERN;
-    mib[1] = KERN_BOOTTIME;
-    size = sizeof (boottime_val);
-
-    if (sysctl (mib, 2, &boottime_val, &size, NULL, 0) >= 0 && size != 0)
-      {
-       boot_time = boottime_val.tv_sec;
-       return boot_time;
-      }
-  }
-#endif /* defined (CTL_KERN) && defined (KERN_BOOTTIME) */
-
-  if (BOOT_TIME_FILE)
-    {
-      struct stat st;
-      if (stat (BOOT_TIME_FILE, &st) == 0)
-       {
-         boot_time = st.st_mtime;
-         return boot_time;
-       }
-    }
-
-#if defined (BOOT_TIME)
-  /* The utmp routines maintain static state.  Don't touch that state
+  /* get_boot_time maintains static state.  Don't touch that state
      if we are going to dump, since it might not survive dumping.  */
   if (will_dump_p ())
-    return boot_time;
-
-  /* Try to get boot time from utmp before wtmp,
-     since utmp is typically much smaller than wtmp.
-     Passing a null pointer causes get_boot_time_1
-     to inspect the default file, namely utmp.  */
-  get_boot_time_1 (0, 0);
-  if (boot_time)
-    return boot_time;
-
-  /* Try to get boot time from the current wtmp file.  */
-  get_boot_time_1 (WTMP_FILE, 1);
-
-  /* If we did not find a boot time in wtmp, look at wtmp, and so on.  */
-  for (counter = 0; counter < 20 && ! boot_time; counter++)
-    {
-      Lisp_Object filename = Qnil;
-      bool delete_flag = false;
-      char cmd_string[sizeof WTMP_FILE ".19.gz"];
-      AUTO_STRING_WITH_LEN (tempname, cmd_string,
-                           sprintf (cmd_string, "%s.%d", WTMP_FILE, counter));
-      if (! NILP (Ffile_exists_p (tempname)))
-       filename = tempname;
-      else
-       {
-         tempname = make_formatted_string (cmd_string, "%s.%d.gz",
-                                           WTMP_FILE, counter);
-         if (! NILP (Ffile_exists_p (tempname)))
-           {
-             /* The utmp functions on older systems accept only file
-                names up to 8 bytes long.  Choose a 2 byte prefix, so
-                the 6-byte suffix does not make the name too long.  */
-             filename = Fmake_temp_file_internal (build_string ("wt"), Qnil,
-                                                  empty_unibyte_string, Qnil);
-             CALLN (Fcall_process, build_string ("gzip"), Qnil,
-                    list2 (QCfile, filename), Qnil,
-                    build_string ("-cd"), tempname);
-             delete_flag = true;
-           }
-       }
-
-      if (! NILP (filename))
-       {
-         get_boot_time_1 (SSDATA (filename), 1);
-         if (delete_flag)
-           unlink (SSDATA (filename));
-       }
-    }
-
-  return boot_time;
-#else
-  return 0;
-#endif
-}
-
-#ifdef BOOT_TIME
-/* Try to get the boot time from wtmp file FILENAME.
-   This succeeds if that file contains a reboot record.
-
-   If FILENAME is zero, use the same file as before;
-   if no FILENAME has ever been specified, this is the utmp file.
-   Use the newest reboot record if NEWEST,
-   the first reboot record otherwise.
-   Ignore all reboot records on or before BOOT_TIME.
-   Success is indicated by setting BOOT_TIME to a larger value.  */
-
-void
-get_boot_time_1 (const char *filename, bool newest)
-{
-  struct utmp ut, *utp;
-
-  if (filename)
-    utmpname (filename);
-
-  setutent ();
+    return 0;
 
-  while (1)
-    {
-      /* Find the next reboot record.  */
-      ut.ut_type = BOOT_TIME;
-      utp = getutid (&ut);
-      if (! utp)
-       break;
-      /* Compare reboot times and use the newest one.  */
-      if (utp->ut_time > boot_time)
-       {
-         boot_time = utp->ut_time;
-         if (! newest)
-           break;
-       }
-      /* Advance on element in the file
-        so that getutid won't repeat the same one.  */
-      utp = getutent ();
-      if (! utp)
-       break;
-    }
-  endutent ();
+  struct timespec boot_time;
+  boot_time.tv_sec = 0;
+  get_boot_time (&boot_time);
+  return boot_time.tv_sec;
 }
-#endif /* BOOT_TIME */
 
 /* An arbitrary limit on lock contents length.  8 K should be plenty
    big enough in practice.  */
@@ -313,11 +164,12 @@ rename_lock_file (char const *old, char const *new, bool 
force)
     {
       struct stat st;
 
-      int r = renameat_noreplace (AT_FDCWD, old, AT_FDCWD, new);
+      int r = emacs_renameat_noreplace (AT_FDCWD, old,
+                                       AT_FDCWD, new);
       if (! (r < 0 && errno == ENOSYS))
        return r;
       if (link (old, new) == 0)
-       return unlink (old) == 0 || errno == ENOENT ? 0 : -1;
+       return emacs_unlink (old) == 0 || errno == ENOENT ? 0 : -1;
       if (errno != ENOSYS && errno != LINKS_MIGHT_NOT_WORK)
        return -1;
 
@@ -337,7 +189,7 @@ rename_lock_file (char const *old, char const *new, bool 
force)
        return -1;
     }
 
-  return rename (old, new);
+  return emacs_rename (old, new);
 #endif
 }
 
@@ -355,13 +207,13 @@ create_lock_file (char *lfname, char *lock_info_str, bool 
force)
      pretending that 'symlink' does not work.  */
   int err = ENOSYS;
 #else
-  int err = symlink (lock_info_str, lfname) == 0 ? 0 : errno;
+  int err = emacs_symlink (lock_info_str, lfname) == 0 ? 0 : errno;
 #endif
 
   if (err == EEXIST && force)
     {
-      unlink (lfname);
-      err = symlink (lock_info_str, lfname) == 0 ? 0 : errno;
+      emacs_unlink (lfname);
+      err = emacs_symlink (lock_info_str, lfname) == 0 ? 0 : errno;
     }
 
   if (err == ENOSYS || err == LINKS_MIGHT_NOT_WORK || err == ENAMETOOLONG)
@@ -399,7 +251,7 @@ create_lock_file (char *lfname, char *lock_info_str, bool 
force)
          if (!err && rename_lock_file (nonce, lfname, force) != 0)
            err = errno;
          if (err)
-           unlink (nonce);
+           emacs_unlink (nonce);
        }
 
       SAFE_FREE ();
@@ -415,7 +267,7 @@ create_lock_file (char *lfname, char *lock_info_str, bool 
force)
 static int
 lock_file_1 (Lisp_Object lfname, bool force)
 {
-  intmax_t boot = get_boot_time ();
+  intmax_t boot = get_boot_sec ();
   Lisp_Object luser_name = Fuser_login_name (Qnil);
   Lisp_Object lhost_name = Fsystem_name ();
 
@@ -431,18 +283,12 @@ lock_file_1 (Lisp_Object lfname, bool force)
   char lock_info_str[MAX_LFINFO + 1];
   intmax_t pid = getpid ();
 
-  if (boot)
-    {
-      if (sizeof lock_info_str
-          <= snprintf (lock_info_str, sizeof lock_info_str,
-                      "%s@%s.%"PRIdMAX":%"PRIdMAX,
-                       user_name, host_name, pid, boot))
-        return ENAMETOOLONG;
-    }
-  else if (sizeof lock_info_str
-           <= snprintf (lock_info_str, sizeof lock_info_str,
-                       "%s@%s.%"PRIdMAX,
-                        user_name, host_name, pid))
+  char const *lock_info_fmt = (boot
+                              ? "%s@%s.%"PRIdMAX":%"PRIdMAX
+                              : "%s@%s.%"PRIdMAX);
+  int len = snprintf (lock_info_str, sizeof lock_info_str,
+                     lock_info_fmt, user_name, host_name, pid, boot);
+  if (! (0 <= len && len < sizeof lock_info_str))
     return ENAMETOOLONG;
 
   return create_lock_file (SSDATA (lfname), lock_info_str, force);
@@ -607,12 +453,12 @@ current_lock_owner (lock_info_type *owner, Lisp_Object 
lfname)
                && (kill (pid, 0) >= 0 || errno == EPERM)
               && (boot_time == 0
                   || (boot_time <= TYPE_MAXIMUM (time_t)
-                      && within_one_second (boot_time, get_boot_time ()))))
+                      && within_one_second (boot_time, get_boot_sec ()))))
         return ANOTHER_OWNS_IT;
       /* The owner process is dead or has a strange pid, so try to
          zap the lockfile.  */
       else
-        return unlink (SSDATA (lfname)) < 0 ? errno : 0;
+        return emacs_unlink (SSDATA (lfname)) < 0 ? errno : 0;
     }
   else
     { /* If we wanted to support the check for stale locks on remote machines,
@@ -653,8 +499,27 @@ lock_if_free (lock_info_type *clasher, Lisp_Object lfname)
 static Lisp_Object
 make_lock_file_name (Lisp_Object fn)
 {
-  Lisp_Object lock_file_name = call1 (Qmake_lock_file_name,
-                                     Fexpand_file_name (fn, Qnil));
+  Lisp_Object lock_file_name;
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  char *name;
+#endif
+
+  fn = Fexpand_file_name (fn, Qnil);
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* Files in /assets and /contents can't have lock files on Android
+     as these directories are fabrications of android.c, and backed by
+     read only data.  */
+
+  name = SSDATA (fn);
+
+  if (android_is_special_directory (name, "/assets")
+      || android_is_special_directory (name, "/content"))
+  return Qnil;
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+  lock_file_name = call1 (Qmake_lock_file_name, fn);
+
   return !NILP (lock_file_name) ? ENCODE_FILE (lock_file_name) : Qnil;
 }
 
@@ -741,7 +606,8 @@ unlock_file (Lisp_Object fn)
   int err = current_lock_owner (0, lfname);
   if (! (err == 0 || err == ANOTHER_OWNS_IT
         || (err == I_OWN_IT
-            && (unlink (SSDATA (lfname)) == 0 || (err = errno) == ENOENT))))
+            && (emacs_unlink (SSDATA (lfname)) == 0
+                || (err = errno) == ENOENT))))
     report_file_errno ("Unlocking file", fn, err);
 
   return Qnil;
diff --git a/src/fns.c b/src/fns.c
index 602c0868c5b..ae9969a5432 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -746,7 +746,8 @@ usage: (vconcat &rest SEQUENCES)   */)
 DEFUN ("copy-sequence", Fcopy_sequence, Scopy_sequence, 1, 1, 0,
        doc: /* Return a copy of a list, vector, string, char-table or record.
 The elements of a list, vector or record are not copied; they are
-shared with the original.
+shared with the original.  See Info node `(elisp) Sequence Functions'
+for more details about this sharing and its effects.
 If the original sequence is empty, this function may return
 the same empty object instead of its copy.  */)
   (Lisp_Object arg)
@@ -3560,6 +3561,10 @@ The data read from the system are decoded using 
`locale-coding-system'.  */)
   (Lisp_Object item)
 {
   char *str = NULL;
+
+  /* STR is apparently unused on Android.  */
+  ((void) str);
+
 #ifdef HAVE_LANGINFO_CODESET
   if (EQ (item, Qcodeset))
     {
@@ -6156,6 +6161,9 @@ from the absolute start of the buffer, disregarding the 
narrowing.  */)
 {
   ptrdiff_t pos_byte, start_byte = BEGV_BYTE;
 
+  if (!BUFFER_LIVE_P (current_buffer))
+    error ("Attempt to count lines in a dead buffer");
+
   if (MARKERP (position))
     {
       /* We don't trust the byte position if the marker's buffer is
diff --git a/src/font.c b/src/font.c
index e586277a5d3..ff81fefcad0 100644
--- a/src/font.c
+++ b/src/font.c
@@ -177,9 +177,35 @@ font_make_entity (void)
        allocate_pseudovector (VECSIZE (struct font_entity),
                              FONT_ENTITY_MAX, FONT_ENTITY_MAX, PVEC_FONT));
   XSETFONT (font_entity, entity);
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  entity->is_android = false;
+#endif
+
   return font_entity;
 }
 
+#ifdef HAVE_ANDROID
+
+Lisp_Object
+font_make_entity_android (int size)
+{
+  Lisp_Object font_entity;
+  struct font_entity *entity
+    = ((struct font_entity *)
+       allocate_pseudovector (size, FONT_ENTITY_MAX, FONT_ENTITY_MAX,
+                             PVEC_FONT));
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  entity->is_android = true;
+#endif
+
+  XSETFONT (font_entity, entity);
+  return font_entity;
+}
+
+#endif
+
 /* Create a font-object whose structure size is SIZE.  If ENTITY is
    not nil, copy properties from ENTITY to the font-object.  If
    PIXELSIZE is positive, set the `size' property to PIXELSIZE.  */
@@ -322,7 +348,7 @@ font_pixel_size (struct frame *f, Lisp_Object spec)
       if (FIXNUMP (val))
        dpi = XFIXNUM (val);
       else
-       dpi = FRAME_RES_Y (f);
+       dpi = FRAME_RES (f);
       pixel_size = POINT_TO_PIXEL (point_size, dpi);
       return pixel_size;
     }
@@ -1853,7 +1879,11 @@ font_rescale_ratio (Lisp_Object font_entity)
            {
              if (NILP (name))
                name = Ffont_xlfd_name (font_entity, Qnil);
-             if (fast_string_match_ignore_case (XCAR (elt), name) >= 0)
+
+             /* N.B. that `name' is set to nil if the resulting XLFD
+                is too long.  */
+             if (!NILP (name)
+                 && fast_string_match_ignore_case (XCAR (elt), name) >= 0)
                return XFLOAT_DATA (XCDR (elt));
            }
          else if (FONT_SPEC_P (XCAR (elt)))
@@ -2997,7 +3027,7 @@ font_find_for_lface (struct frame *f, Lisp_Object *attrs, 
Lisp_Object spec, int
     {
       double pt = XFIXNUM (attrs[LFACE_HEIGHT_INDEX]);
 
-      pixel_size = POINT_TO_PIXEL (pt / 10, FRAME_RES_Y (f));
+      pixel_size = POINT_TO_PIXEL (pt / 10, FRAME_RES (f));
       if (pixel_size < 1)
        pixel_size = 1;
     }
@@ -3149,13 +3179,13 @@ font_open_for_lface (struct frame *f, Lisp_Object 
entity, Lisp_Object *attrs, Li
            }
 
          pt /= 10;
-         size = POINT_TO_PIXEL (pt, FRAME_RES_Y (f));
+         size = POINT_TO_PIXEL (pt, FRAME_RES (f));
 #ifdef HAVE_NS
          if (size == 0)
            {
              Lisp_Object ffsize = get_frame_param (f, Qfontsize);
              size = (NUMBERP (ffsize)
-                     ? POINT_TO_PIXEL (XFLOATINT (ffsize), FRAME_RES_Y (f))
+                     ? POINT_TO_PIXEL (XFLOATINT (ffsize), FRAME_RES (f))
                      : 0);
            }
 #endif
@@ -4056,7 +4086,7 @@ are to be displayed on.  If omitted, the selected frame 
is used.  */)
   if (FIXNUMP (val))
     {
       Lisp_Object font_dpi = AREF (font, FONT_DPI_INDEX);
-      int dpi = FIXNUMP (font_dpi) ? XFIXNUM (font_dpi) : FRAME_RES_Y (f);
+      int dpi = FIXNUMP (font_dpi) ? XFIXNUM (font_dpi) : FRAME_RES (f);
       plist[n++] = QCheight;
       plist[n++] = make_fixnum (PIXEL_TO_POINT (XFIXNUM (val) * 10, dpi));
     }
@@ -4960,7 +4990,7 @@ DEFUN ("open-font", Fopen_font, Sopen_font, 1, 3, 0,
     {
       CHECK_NUMBER (size);
       if (FLOATP (size))
-       isize = POINT_TO_PIXEL (XFLOAT_DATA (size), FRAME_RES_Y (f));
+       isize = POINT_TO_PIXEL (XFLOAT_DATA (size), FRAME_RES (f));
       else if (! integer_to_intmax (size, &isize))
        args_out_of_range (font_entity, size);
       if (! (INT_MIN <= isize && isize <= INT_MAX))
diff --git a/src/font.h b/src/font.h
index 492c2e58b09..ed3b17db994 100644
--- a/src/font.h
+++ b/src/font.h
@@ -260,6 +260,11 @@ struct font_entity
 {
   union vectorlike_header header;
   Lisp_Object props[FONT_ENTITY_MAX];
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* Whether or not this is an Android font entity.  */
+  bool is_android;
+#endif
 };
 
 /* A value which may appear in the member `encoding' of struct font
@@ -547,8 +552,14 @@ CHECK_FONT_GET_OBJECT (Lisp_Object x)
   return XFONT_OBJECT (x);
 }
 
+#ifndef HAVE_ANDROID
 /* Number of pt per inch (from the TeXbook).  */
 #define PT_PER_INCH 72.27
+#else
+/* Android uses this value instead to compensate for different device
+   dimensions.  */
+#define PT_PER_INCH 160.00
+#endif
 
 /* Return a pixel size (integer) corresponding to POINT size (double)
    on resolution DPI.  */
@@ -823,6 +834,9 @@ extern Lisp_Object copy_font_spec (Lisp_Object);
 extern Lisp_Object merge_font_spec (Lisp_Object, Lisp_Object);
 
 extern Lisp_Object font_make_entity (void);
+#ifdef HAVE_ANDROID
+extern Lisp_Object font_make_entity_android (int);
+#endif
 extern Lisp_Object font_make_object (int, Lisp_Object, int);
 #if defined (HAVE_XFT) || defined (HAVE_FREETYPE) || defined (HAVE_NS)
 extern Lisp_Object font_build_object (int, Lisp_Object, Lisp_Object, double);
diff --git a/src/fontset.c b/src/fontset.c
index c0e00cfa346..139dcb6eb89 100644
--- a/src/fontset.c
+++ b/src/fontset.c
@@ -667,8 +667,35 @@ fontset_find_font (Lisp_Object fontset, int c, struct face 
*face,
            }
          font_object = font_open_for_lface (f, font_entity, face->lface,
                                             FONT_DEF_SPEC (font_def));
+
+         /* If the font registry is not the same as explicitly
+            specified in the font spec, do not cache the font.
+            TrueType fonts have contrived character map selection
+            semantics which makes determining the repertory at font
+            spec matching time unduly expensive.  */
+
+         {
+           Lisp_Object spec;
+
+           spec = FONT_DEF_SPEC (font_def);
+
+           if (!NILP (font_object)
+               && !NILP (AREF (spec, FONT_REGISTRY_INDEX))
+               && !NILP (AREF (font_object, FONT_REGISTRY_INDEX))
+               && !EQ (AREF (spec, FONT_REGISTRY_INDEX),
+                       AREF (font_object, FONT_REGISTRY_INDEX))
+               /* See sfntfont_registries_compatible_p in
+                  sfntfont.c.  */
+               && !(EQ (AREF (spec, FONT_REGISTRY_INDEX),
+                        Qiso8859_1)
+                    && EQ (AREF (font_object, FONT_REGISTRY_INDEX),
+                           Qiso10646_1)))
+             goto strangeness;
+         }
+
          if (NILP (font_object))
            {
+           strangeness:
              /* Something strange happened, perhaps because of a
                 Font-backend problem.  To avoid crashing, record
                 that this spec is unusable.  It may be better to find
diff --git a/src/frame.c b/src/frame.c
index 83925d4742a..38ac316ce87 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -228,6 +228,7 @@ Value is:
  `pc' for a direct-write MS-DOS frame,
  `pgtk' for an Emacs frame running on pure GTK.
  `haiku' for an Emacs frame running in Haiku.
+ `android' for an Emacs frame running in Android.
 See also `frame-live-p'.  */)
   (Lisp_Object object)
 {
@@ -250,6 +251,8 @@ See also `frame-live-p'.  */)
       return Qpgtk;
     case output_haiku:
       return Qhaiku;
+    case output_android:
+      return Qandroid;
     default:
       emacs_abort ();
     }
@@ -279,6 +282,7 @@ The value is a symbol:
  `pc' for a direct-write MS-DOS frame.
  `pgtk' for an Emacs frame using pure GTK facilities.
  `haiku' for an Emacs frame running in Haiku.
+ `android' for an Emacs frame running in Android.
 
 FRAME defaults to the currently selected frame.
 
@@ -982,6 +986,7 @@ make_frame (bool mini_p)
   f->last_tab_bar_item = -1;
 #ifndef HAVE_EXT_TOOL_BAR
   f->last_tool_bar_item = -1;
+  f->tool_bar_wraps_p = false;
 #endif
 #ifdef NS_IMPL_COCOA
   f->ns_appearance = ns_appearance_system_default;
@@ -991,6 +996,16 @@ make_frame (bool mini_p)
   f->select_mini_window_flag = false;
   /* This one should never be zero.  */
   f->change_stamp = 1;
+
+#ifdef HAVE_TEXT_CONVERSION
+  f->conversion.compose_region_start = Qnil;
+  f->conversion.compose_region_end = Qnil;
+  f->conversion.compose_region_overlay = Qnil;
+  f->conversion.batch_edit_count = 0;
+  f->conversion.batch_edit_flags = 0;
+  f->conversion.actions = NULL;
+#endif
+
   root_window = make_window ();
   rw = XWINDOW (root_window);
   if (mini_p)
@@ -1226,6 +1241,7 @@ make_initial_frame (void)
   return f;
 }
 
+#ifndef HAVE_ANDROID
 
 static struct frame *
 make_terminal_frame (struct terminal *terminal)
@@ -1315,6 +1331,8 @@ get_future_frame_param (Lisp_Object parameter,
   return result;
 }
 
+#endif
+
 DEFUN ("make-terminal-frame", Fmake_terminal_frame, Smake_terminal_frame,
        1, 1, 0,
        doc: /* Create an additional terminal frame, possibly on another 
terminal.
@@ -1334,6 +1352,10 @@ Note that changing the size of one terminal frame 
automatically
 affects all frames on the same terminal device.  */)
   (Lisp_Object parms)
 {
+#ifdef HAVE_ANDROID
+  error ("Text terminals are not supported on this platform");
+  return Qnil;
+#else
   struct frame *f;
   struct terminal *t = NULL;
   Lisp_Object frame;
@@ -1434,6 +1456,7 @@ affects all frames on the same terminal device.  */)
   f->after_make_frame = true;
 
   return frame;
+#endif
 }
 
 
@@ -2189,12 +2212,15 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
        }
 #ifdef NS_IMPL_COCOA
       else
-       /* Under NS, there is no system mechanism for choosing a new
-          window to get focus -- it is left to application code.
-          So the portion of THIS application interfacing with NS
-          needs to know about it.  We call Fraise_frame, but the
-          purpose is really to transfer focus.  */
-       Fraise_frame (frame1);
+       {
+         /* Under NS, there is no system mechanism for choosing a new
+            window to get focus -- it is left to application code.
+            So the portion of THIS application interfacing with NS
+            needs to make the frame we switch to the key window.  */
+         struct frame *f1 = XFRAME (frame1);
+         if (FRAME_NS_P (f1))
+           ns_make_frame_key_window (f1);
+       }
 #endif
 
       do_switch_frame (frame1, 0, 1, Qnil);
@@ -2292,6 +2318,13 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
     f->terminal = 0;             /* Now the frame is dead.  */
     unblock_input ();
 
+  /* Clear markers and overlays set by F on behalf of an input
+     method.  */
+#ifdef HAVE_TEXT_CONVERSION
+  if (FRAME_WINDOW_P (f))
+    reset_frame_state (f);
+#endif
+
     /* If needed, delete the terminal that this frame was on.
        (This must be done after the frame is killed.)  */
     terminal->reference_count--;
@@ -5343,16 +5376,23 @@ gui_display_get_resource (Display_Info *dpyinfo, 
Lisp_Object attribute,
   *nz++ = '.';
   lispstpcpy (nz, attribute);
 
-  const char *value =
-    dpyinfo->terminal->get_string_resource_hook (&dpyinfo->rdb,
-                                                 name_key,
-                                                 class_key);
-  SAFE_FREE();
+#ifndef HAVE_ANDROID
+  const char *value
+    = dpyinfo->terminal->get_string_resource_hook (&dpyinfo->rdb,
+                                                  name_key,
+                                                  class_key);
+
+  SAFE_FREE ();
 
   if (value && *value)
     return build_string (value);
   else
     return Qnil;
+#else
+
+  SAFE_FREE ();
+  return Qnil;
+#endif
 }
 
 
@@ -5687,6 +5727,8 @@ On Nextstep, this just calls `ns-parse-geometry'.  */)
   int x UNINIT, y UNINIT;
   unsigned int width, height;
 
+  width = height = 0;
+
   CHECK_STRING (string);
 
 #ifdef HAVE_NS
@@ -6159,8 +6201,11 @@ make_monitor_attribute_list (struct MonitorInfo 
*monitors,
                         mi->work.width, mi->work.height);
       geometry = list4i (mi->geom.x, mi->geom.y,
                         mi->geom.width, mi->geom.height);
-      attributes = Fcons (Fcons (Qsource, build_string (source)),
-                          attributes);
+
+      if (source)
+       attributes = Fcons (Fcons (Qsource, build_string (source)),
+                           attributes);
+
       attributes = Fcons (Fcons (Qframes, AREF (monitor_frames, i)),
                          attributes);
 #ifdef HAVE_PGTK
@@ -6258,6 +6303,7 @@ syms_of_frame (void)
   DEFSYM (Qns, "ns");
   DEFSYM (Qpgtk, "pgtk");
   DEFSYM (Qhaiku, "haiku");
+  DEFSYM (Qandroid, "android");
   DEFSYM (Qvisible, "visible");
   DEFSYM (Qbuffer_predicate, "buffer-predicate");
   DEFSYM (Qbuffer_list, "buffer-list");
@@ -6446,7 +6492,7 @@ Setting this variable does not affect existing frames, 
only new ones.  */);
 
   DEFVAR_LISP ("default-frame-scroll-bars", Vdefault_frame_scroll_bars,
               doc: /* Default position of vertical scroll bars on this 
window-system.  */);
-#ifdef HAVE_WINDOW_SYSTEM
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_ANDROID
 #if defined (HAVE_NTGUI) || defined (NS_IMPL_COCOA) || (defined (USE_GTK) && 
defined (USE_TOOLKIT_SCROLL_BARS))
   /* MS-Windows, macOS, and GTK have scroll bars on the right by
      default.  */
@@ -6454,9 +6500,9 @@ Setting this variable does not affect existing frames, 
only new ones.  */);
 #else
   Vdefault_frame_scroll_bars = Qleft;
 #endif
-#else
+#else /* !HAVE_WINDOW_SYSTEM || HAVE_ANDROID */
   Vdefault_frame_scroll_bars = Qnil;
-#endif
+#endif /* HAVE_WINDOW_SYSTEM && !HAVE_ANDROID */
 
   DEFVAR_BOOL ("scroll-bar-adjust-thumb-portion",
                scroll_bar_adjust_thumb_portion_p,
@@ -6664,7 +6710,7 @@ implicitly when there's no window system support.
 Note that when a frame is not large enough to accommodate a change of
 any of the parameters listed above, Emacs may try to enlarge the frame
 even if this option is non-nil.  */);
-#if defined (HAVE_WINDOW_SYSTEM)
+#if defined (HAVE_WINDOW_SYSTEM) && !defined (HAVE_ANDROID)
 #if defined (USE_GTK) || defined (HAVE_NS)
   frame_inhibit_implied_resize = list1 (Qtab_bar_lines);
 #else
diff --git a/src/frame.h b/src/frame.h
index 12a3c460a5f..f4726f1c0e5 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -76,6 +76,65 @@ enum ns_appearance_type
 #endif
 #endif /* HAVE_WINDOW_SYSTEM */
 
+#ifdef HAVE_TEXT_CONVERSION
+
+enum text_conversion_operation
+  {
+    TEXTCONV_START_BATCH_EDIT,
+    TEXTCONV_END_BATCH_EDIT,
+    TEXTCONV_COMMIT_TEXT,
+    TEXTCONV_FINISH_COMPOSING_TEXT,
+    TEXTCONV_SET_COMPOSING_TEXT,
+    TEXTCONV_SET_COMPOSING_REGION,
+    TEXTCONV_SET_POINT_AND_MARK,
+    TEXTCONV_DELETE_SURROUNDING_TEXT,
+    TEXTCONV_REQUEST_POINT_UPDATE,
+    TEXTCONV_BARRIER,
+  };
+
+/* Structure describing a single edit being performed by the input
+   method that should be executed in the context of
+   kbd_buffer_get_event.  */
+
+struct text_conversion_action
+{
+  /* The next text conversion action.  */
+  struct text_conversion_action *next;
+
+  /* Any associated data.  */
+  Lisp_Object data;
+
+  /* The operation being performed.  */
+  enum text_conversion_operation operation;
+
+  /* Counter value.  */
+  unsigned long counter;
+};
+
+/* Structure describing the text conversion state associated with a
+   frame.  */
+
+struct text_conversion_state
+{
+  /* List of text conversion actions associated with this frame.  */
+  struct text_conversion_action *actions;
+
+  /* Markers representing the composing region.  */
+  Lisp_Object compose_region_start, compose_region_end;
+
+  /* Overlay representing the composing region.  */
+  Lisp_Object compose_region_overlay;
+
+  /* The number of ongoing ``batch edits'' that are causing point
+     reporting to be delayed.  */
+  int batch_edit_count;
+
+  /* Mask containing what must be updated after batch edits end.  */
+  int batch_edit_flags;
+};
+
+#endif
+
 /* The structure representing a frame.  */
 
 struct frame
@@ -181,7 +240,7 @@ struct frame
      most recently buried buffer is first.  For last-buffer.  */
   Lisp_Object buried_buffer_list;
 
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
   /* A dummy window used to display menu bars under X when no X
      toolkit support is available.  */
   Lisp_Object menu_bar_window;
@@ -285,6 +344,10 @@ struct frame
   /* Set to true to minimize tool-bar height even when
      auto-resize-tool-bar is set to grow-only.  */
   bool_bf minimize_tool_bar_window_p : 1;
+
+  /* Whether or not the tool bar contains a ``new line'' item.  If
+     true, tool bar rows will be allowed to differ in height.  */
+  bool_bf tool_bar_wraps_p : 1;
 #endif
 
 #ifdef HAVE_EXT_TOOL_BAR
@@ -375,7 +438,7 @@ struct frame
   /* The output method says how the contents of this frame are
      displayed.  It could be using termcap, or using an X window.
      This must be the same as the terminal->type. */
-  ENUM_BF (output_method) output_method : 3;
+  ENUM_BF (output_method) output_method : 4;
 
 #ifdef HAVE_WINDOW_SYSTEM
   /* True if this frame is a tooltip frame.  */
@@ -584,20 +647,22 @@ struct frame
      well.  */
   union output_data
   {
-    struct tty_output *tty;     /* From termchar.h.  */
-    struct x_output *x;         /* From xterm.h.  */
-    struct w32_output *w32;     /* From w32term.h.  */
-    struct ns_output *ns;       /* From nsterm.h.  */
-    struct pgtk_output *pgtk; /* From pgtkterm.h. */
-    struct haiku_output *haiku; /* From haikuterm.h. */
+    struct tty_output *tty;            /* From termchar.h.  */
+    struct x_output *x;                        /* From xterm.h.  */
+    struct w32_output *w32;            /* From w32term.h.  */
+    struct ns_output *ns;              /* From nsterm.h.  */
+    struct pgtk_output *pgtk;          /* From pgtkterm.h. */
+    struct haiku_output *haiku;                /* From haikuterm.h. */
+    struct android_output *android;    /* From androidterm.h.  */
   }
   output_data;
 
   /* List of font-drivers available on the frame.  */
   struct font_driver_list *font_driver_list;
 
-#if defined (HAVE_X_WINDOWS)
-  /* Used by x_wait_for_event when watching for an X event on this frame.  */
+#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
+  /* Used by x_wait_for_event when watching for an X event on this
+     frame.  */
   int wait_event_type;
 #endif
 
@@ -660,6 +725,11 @@ struct frame
   enum ns_appearance_type ns_appearance;
   bool_bf ns_transparent_titlebar;
 #endif
+
+#ifdef HAVE_TEXT_CONVERSION
+  /* Text conversion state used by certain input methods.  */
+  struct text_conversion_state conversion;
+#endif
 } GCALIGNED_STRUCT;
 
 /* Most code should use these functions to set Lisp fields in struct frame.  */
@@ -711,7 +781,7 @@ fset_menu_bar_vector (struct frame *f, Lisp_Object val)
 {
   f->menu_bar_vector = val;
 }
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
 INLINE void
 fset_menu_bar_window (struct frame *f, Lisp_Object val)
 {
@@ -872,6 +942,11 @@ default_pixels_per_inch_y (void)
 #else
 #define FRAME_HAIKU_P(f) ((f)->output_method == output_haiku)
 #endif
+#ifndef HAVE_ANDROID
+#define FRAME_ANDROID_P(f) false
+#else
+#define FRAME_ANDROID_P(f) ((f)->output_method == output_android)
+#endif
 
 /* FRAME_WINDOW_P tests whether the frame is a graphical window system
    frame.  */
@@ -890,6 +965,9 @@ default_pixels_per_inch_y (void)
 #ifdef HAVE_HAIKU
 #define FRAME_WINDOW_P(f) FRAME_HAIKU_P (f)
 #endif
+#ifdef HAVE_ANDROID
+#define FRAME_WINDOW_P(f) FRAME_ANDROID_P (f)
+#endif
 #ifndef FRAME_WINDOW_P
 #define FRAME_WINDOW_P(f) ((void) (f), false)
 #endif
@@ -903,12 +981,26 @@ default_pixels_per_inch_y (void)
 #define FRAME_RES_Y(f)                                         \
   (eassert (FRAME_WINDOW_P (f)), FRAME_DISPLAY_INFO (f)->resy)
 
+#ifdef HAVE_ANDROID
+
+/* Android systems use a font scaling factor independent from the
+   display DPI.  */
+
+#define FRAME_RES(f)                                           \
+  (eassert (FRAME_WINDOW_P (f)),                               \
+   FRAME_DISPLAY_INFO (f)->font_resolution)
+
+#else /* !HAVE_ANDROID */
+#define FRAME_RES(f) (FRAME_RES_Y (f))
+#endif /* HAVE_ANDROID */
+
 #else /* !HAVE_WINDOW_SYSTEM */
 
 /* Defaults when no window system available.  */
 
-#define FRAME_RES_X(f) default_pixels_per_inch_x ()
-#define FRAME_RES_Y(f) default_pixels_per_inch_y ()
+#define FRAME_RES_X(f) default_pixels_per_inch_x ()
+#define FRAME_RES_Y(f) default_pixels_per_inch_y ()
+#define FRAME_RES(f)   default_pixels_per_inch_y ()
 
 #endif /* HAVE_WINDOW_SYSTEM */
 
@@ -917,11 +1009,17 @@ default_pixels_per_inch_y (void)
    frame F.  We need to define two versions because a TTY-only build
    does not have FRAME_DISPLAY_INFO.  */
 #ifdef HAVE_WINDOW_SYSTEM
+#ifndef HAVE_ANDROID
 # define MOUSE_HL_INFO(F)                                      \
-  (FRAME_WINDOW_P(F)                                           \
+  (FRAME_WINDOW_P (F)                                          \
    ? &FRAME_DISPLAY_INFO(F)->mouse_highlight                   \
    : &(F)->output_data.tty->display_info->mouse_highlight)
 #else
+/* There is no "struct tty_output" on Android at all.  */
+# define MOUSE_HL_INFO(F)                                      \
+  (&FRAME_DISPLAY_INFO(F)->mouse_highlight)
+#endif
+#else
 # define MOUSE_HL_INFO(F)                                      \
   (&(F)->output_data.tty->display_info->mouse_highlight)
 #endif
@@ -1423,6 +1521,10 @@ extern Lisp_Object mouse_position (bool);
 extern void frame_size_history_plain (struct frame *, Lisp_Object);
 extern void frame_size_history_extra (struct frame *, Lisp_Object,
                                      int, int, int, int, int, int);
+#ifdef NS_IMPL_COCOA
+/* Implemented in nsfns.m.  */
+extern void ns_make_frame_key_window (struct frame *);
+#endif
 extern Lisp_Object Vframe_list;
 
 /* Value is a pointer to the selected frame.  If the selected frame
diff --git a/src/fringe.c b/src/fringe.c
index ed257c073b9..3452c8503f0 100644
--- a/src/fringe.c
+++ b/src/fringe.c
@@ -1422,25 +1422,30 @@ If BITMAP overrides a standard fringe bitmap, the 
original bitmap is restored.
    On X, we bit-swap the built-in bitmaps and reduce bitmap
    from short to char array if width is <= 8 bits.
 
+   The Android port tries to follow X as closely as possible, so do
+   that there too.
+
    On MAC with big-endian CPU, we need to byte-swap each short.
 
    On W32 and MAC (little endian), there's no need to do this.
 */
 
-#if defined (HAVE_X_WINDOWS) || defined (HAVE_PGTK)
-static const unsigned char swap_nibble[16] = {
-  0x0, 0x8, 0x4, 0xc,           /* 0000 1000 0100 1100 */
-  0x2, 0xa, 0x6, 0xe,           /* 0010 1010 0110 1110 */
-  0x1, 0x9, 0x5, 0xd,           /* 0001 1001 0101 1101 */
-  0x3, 0xb, 0x7, 0xf};          /* 0011 1011 0111 1111 */
-#endif                          /* HAVE_X_WINDOWS */
+#if defined (HAVE_X_WINDOWS) || defined (HAVE_PGTK) || defined (HAVE_ANDROID)
+static const unsigned char swap_nibble[16] =
+  {
+    0x0, 0x8, 0x4, 0xc,           /* 0000 1000 0100 1100 */
+    0x2, 0xa, 0x6, 0xe,           /* 0010 1010 0110 1110 */
+    0x1, 0x9, 0x5, 0xd,           /* 0001 1001 0101 1101 */
+    0x3, 0xb, 0x7, 0xf,           /* 0011 1011 0111 1111 */
+  };
+#endif
 
 static void
 init_fringe_bitmap (int which, struct fringe_bitmap *fb, int once_p)
 {
   if (once_p || fb->dynamic)
     {
-#if defined (HAVE_X_WINDOWS)
+#if defined (HAVE_X_WINDOWS) || defined (HAVE_ANDROID)
       unsigned short *bits = fb->bits;
       int j;
 
@@ -1488,7 +1493,7 @@ init_fringe_bitmap (int which, struct fringe_bitmap *fb, 
int once_p)
            }
        }
 #endif /* not USE_CAIRO */
-#endif /* HAVE_X_WINDOWS */
+#endif /* HAVE_X_WINDOWS || HAVE_ANDROID */
 
 #if !defined(HAVE_X_WINDOWS) && defined (HAVE_PGTK)
       unsigned short *bits = fb->bits;
diff --git a/src/haiku_io.c b/src/haiku_io.c
index 4f1b1435b4b..c6d7108bf49 100644
--- a/src/haiku_io.c
+++ b/src/haiku_io.c
@@ -111,6 +111,8 @@ haiku_len (enum haiku_event_type type)
       return sizeof (struct haiku_clipboard_changed_event);
     case FONT_CHANGE_EVENT:
       return sizeof (struct haiku_font_change_event);
+    case NOTIFICATION_CLICK_EVENT:
+      return sizeof (struct haiku_notification_click_event);
     }
 
   emacs_abort ();
diff --git a/src/haiku_select.cc b/src/haiku_select.cc
index fe46075a007..76e2d829204 100644
--- a/src/haiku_select.cc
+++ b/src/haiku_select.cc
@@ -17,15 +17,24 @@ You should have received a copy of the GNU General Public 
License
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <config.h>
+#include <intprops.h>
 
 #include <Application.h>
+#include <Bitmap.h>
 #include <Clipboard.h>
+#include <Entry.h>
 #include <Message.h>
+#include <Notification.h>
+#include <OS.h>
 #include <Path.h>
-#include <Entry.h>
+#include <String.h>
+
+#include <translation/TranslationUtils.h>
 
 #include <cstdlib>
 #include <cstring>
+#include <cstdint>
+#include <cstdio>
 
 #include "haikuselect.h"
 
@@ -58,6 +67,10 @@ static bool owned_secondary;
 /* And the clipboard.  */
 static bool owned_clipboard;
 
+
+
+/* C++ clipboard support.  */
+
 static BClipboard *
 get_clipboard_object (enum haiku_clipboard clipboard)
 {
@@ -517,3 +530,134 @@ be_get_clipboard_count (enum haiku_clipboard id)
   clipboard = get_clipboard_object (id);
   return clipboard->SystemCount ();
 }
+
+
+
+/* C++ notifications support.
+
+   Desktop notifications on Haiku lack some of the features furnished
+   by notifications.el, specifically displaying multiple titled
+   actions within a single notification, sending callbacks when the
+   notification is dismissed, and providing a timeout after which the
+   notification is hidden.
+
+   Other features, such as notification categories and identifiers,
+   have clean straightforward relationships with their counterparts in
+   notifications.el.  */
+
+/* The last notification ID allocated.  */
+static intmax_t last_notification_id;
+
+/* Return the `enum notification_type' for TYPE.  TYPE is the TYPE
+   argument to a call to `be_display_notification'.  */
+
+static enum notification_type
+type_for_type (int type)
+{
+  switch (type)
+    {
+    case 0:
+      return B_INFORMATION_NOTIFICATION;
+
+    case 1:
+      return B_IMPORTANT_NOTIFICATION;
+
+    case 2:
+      return B_ERROR_NOTIFICATION;
+    }
+
+  abort ();
+}
+
+/* Return the ID of this team.  */
+
+static team_id
+my_team_id (void)
+{
+  thread_id id;
+  thread_info info;
+
+  id = find_thread (NULL);
+  get_thread_info (id, &info);
+
+  return info.team;
+}
+
+/* Display a desktop notification and return its identifier.
+
+   TITLE is the title text of the notification, encoded as UTF-8 text.
+
+   BODY is the text to be displayed within the body of the
+   notification.
+
+   SUPERSEDES is the identifier of a previous notification to replace,
+   or -1 if a new notification should be displayed.
+
+   TYPE states the urgency of the notification.  If 0, the
+   notification is displayed without special decoration.  If 1, the
+   notification is displayed with a blue band to its left, identifying
+   it as a notification of medium importance.  If 2, the notification
+   is displayed with a red band to its left, marking it as one of
+   critical importance.
+
+   ICON is the name of a file containing the icon of the notification,
+   or NULL, in which case Emacs's app icon will be displayed.  */
+
+intmax_t
+be_display_notification (const char *title, const char *body,
+                        intmax_t supersedes, int type, const char *icon)
+{
+  intmax_t id;
+  BNotification notification (type_for_type (type));
+  char buffer[INT_STRLEN_BOUND (team_id)
+             + INT_STRLEN_BOUND (intmax_t)
+             + sizeof "."];
+  BBitmap *bitmap;
+
+  if (supersedes < 0)
+    {
+      /* SUPERSEDES hasn't been provided, so allocate a new
+        notification ID.  */
+
+      INT_ADD_WRAPV (last_notification_id, 1,
+                    &last_notification_id);
+      id = last_notification_id;
+    }
+  else
+    id = supersedes;
+
+  /* Set the title and body text.  */
+  notification.SetTitle (title);
+  notification.SetContent (body);
+
+  /* Derive the notification ID from the ID of this team, so as to
+     avoid abrogating notifications from other Emacs sessions.  */
+  sprintf (buffer, "%d.%jd", my_team_id (), id);
+  notification.SetMessageID (BString (buffer));
+
+  /* Now set the bitmap icon, if given.  */
+
+  if (icon)
+    {
+      bitmap = BTranslationUtils::GetBitmap (icon);
+
+      if (bitmap)
+       {
+         notification.SetIcon (bitmap);
+         delete bitmap;
+       }
+    }
+
+  /* After this, Emacs::ArgvReceived should be called when the
+     notification is clicked.  Lamentably, this does not come about,
+     probably because arguments are only passed to applications if
+     they are not yet running.  */
+#if 0
+  notification.SetOnClickApp ("application/x-vnd.GNU-emacs");
+  notification.AddOnClickArg (BString ("-Notification,") += buffer);
+#endif /* 0 */
+
+  /* Finally, send the notification.  */
+  notification.Send ();
+  return id;
+}
diff --git a/src/haiku_support.cc b/src/haiku_support.cc
index 28d8fae39b7..12a84687180 100644
--- a/src/haiku_support.cc
+++ b/src/haiku_support.cc
@@ -581,6 +581,24 @@ public:
   }
 };
 
+#if 0
+
+/* Return the ID of this team.  */
+
+static team_id
+my_team_id (void)
+{
+  thread_id id;
+  thread_info info;
+
+  id = find_thread (NULL);
+  get_thread_info (id, &info);
+
+  return info.team;
+}
+
+#endif /* 0 */
+
 class Emacs : public BApplication
 {
 public:
@@ -621,7 +639,8 @@ public:
   {
     BAlert *about = new BAlert (PACKAGE_NAME,
                                PACKAGE_STRING
-                               "\nThe extensible, self-documenting, real-time 
display editor.",
+                               "\nThe extensible, self-documenting, "
+                               "real-time display editor.",
                                "Close");
     about->Go ();
   }
@@ -674,6 +693,39 @@ public:
     else
       BApplication::MessageReceived (msg);
   }
+
+  /* The code below doesn't function; see `be_display_notification'
+     for further specifics.  */
+
+#if 0
+  void
+  ArgvReceived (int32 argc, char **argv)
+  {
+    struct haiku_notification_click_event rq;
+    intmax_t id;
+    team_id team;
+
+    /* ArgvReceived is called after Emacs is first started, with each
+       command line argument passed to Emacs.  It is, moreover, called
+       with ARGC set to 1 and ARGV[0] a string starting with
+       -Notification, after a notification is clicked.  This string
+       both incorporates the team ID and the notification ID.  */
+
+    if (argc == 1
+       && sscanf (argv[0], "-Notification,%d.%jd", &team, &id) == 2)
+      {
+       /* Since this is a valid notification message, generate an
+          event if the team ID matches.  */
+       if (team == my_team_id ())
+         {
+           rq.id = id;
+           haiku_write (NOTIFICATION_CLICK_EVENT, &rq);
+         }
+      }
+
+    BApplication::ArgvReceived (argc, argv);
+  }
+#endif /* 0 */
 };
 
 class EmacsWindow : public BWindow
@@ -3263,9 +3315,7 @@ class EmacsFilePanelCallbackLooper : public BLooper
              {
                str_buf = (char *) alloca (std::strlen (str_path)
                                           + std::strlen (name) + 2);
-               snprintf (str_buf, std::strlen (str_path)
-                         + std::strlen (name) + 2, "%s/%s",
-                         str_path, name);
+               sprintf (str_buf, "%s/%s", str_path, name);
                file_name = strdup (str_buf);
              }
          }
diff --git a/src/haiku_support.h b/src/haiku_support.h
index 564f61f57c7..5c22eb3b0db 100644
--- a/src/haiku_support.h
+++ b/src/haiku_support.h
@@ -116,6 +116,7 @@ enum haiku_event_type
     MENU_BAR_LEFT,
     CLIPBOARD_CHANGED_EVENT,
     FONT_CHANGE_EVENT,
+    NOTIFICATION_CLICK_EVENT,
   };
 
 struct haiku_clipboard_changed_event
@@ -464,6 +465,12 @@ struct haiku_font_change_event
   enum haiku_what_font what;
 };
 
+struct haiku_notification_click_event
+{
+  /* ID uniquely designating a single notification.  */
+  intmax_t id;
+};
+
 struct haiku_session_manager_reply
 {
   bool quit_reply;
diff --git a/src/haikuselect.c b/src/haikuselect.c
index b57c336c264..608b8e8fe30 100644
--- a/src/haikuselect.c
+++ b/src/haikuselect.c
@@ -1255,6 +1255,120 @@ haiku_start_watching_selections (void)
   be_start_watching_selection (CLIPBOARD_SECONDARY);
 }
 
+
+
+/* Notification support.  */
+
+static intmax_t
+haiku_notifications_notify_1 (Lisp_Object title, Lisp_Object body,
+                             Lisp_Object replaces_id,
+                             Lisp_Object app_icon, Lisp_Object urgency)
+{
+  int type;
+  intmax_t supersedes;
+  const char *icon;
+
+  if (EQ (urgency, Qlow))
+    type = 0;
+  else if (EQ (urgency, Qnormal))
+    type = 1;
+  else if (EQ (urgency, Qcritical))
+    type = 2;
+  else
+    signal_error ("Invalid notification type provided", urgency);
+
+  supersedes = -1;
+
+  if (!NILP (replaces_id))
+    {
+      CHECK_INTEGER (replaces_id);
+      if (!integer_to_intmax (replaces_id, &supersedes))
+       supersedes = -1;
+    }
+
+  icon = NULL;
+
+  if (!NILP (app_icon))
+    icon = SSDATA (ENCODE_FILE (app_icon));
+
+  /* GC should not transpire from here onwards.  */
+  return be_display_notification (SSDATA (title), SSDATA (body),
+                                 supersedes, type, icon);
+}
+
+DEFUN ("haiku-notifications-notify", Fhaiku_notifications_notify,
+       Shaiku_notifications_notify, 0, MANY, 0, doc:
+       /* Display a desktop notification.
+ARGS must contain keywords followed by values.  Each of the following
+keywords is understood:
+
+  :title        The notification title.
+  :body         The notification body.
+  :replaces-id  The ID of a previous notification to supersede.
+  :app-icon     The file name of the notification's icon, if any.
+  :urgency      One of the symbols `low', `normal' or `critical',
+                specifying the importance of the notification.
+
+:title and :body must be provided.  Value is an integer (fixnum or
+bignum) identifying the notification displayed.
+
+usage: (haiku-notifications-notify &rest ARGS)  */)
+  (ptrdiff_t nargs, Lisp_Object *args)
+{
+  Lisp_Object title, body, replaces_id, app_icon, urgency;
+  Lisp_Object key, value;
+  ptrdiff_t i;
+
+  /* First, clear each of the variables above.  */
+  title = body = replaces_id = app_icon = urgency = Qnil;
+
+  /* If NARGS is odd, error.  */
+
+  if (nargs & 1)
+    error ("Odd number of arguments in call to `haiku-notifications-notify'");
+
+  /* Next, iterate through ARGS, searching for arguments.  */
+
+  for (i = 0; i < nargs; i += 2)
+    {
+      key = args[i];
+      value = args[i + 1];
+
+      if (EQ (key, QCtitle))
+       title = value;
+      else if (EQ (key, QCbody))
+       body = value;
+      else if (EQ (key, QCreplaces_id))
+       replaces_id = value;
+      else if (EQ (key, QCapp_icon))
+       app_icon = value;
+      else if (EQ (key, QCurgency))
+       urgency = value;
+    }
+
+  /* Demand at least TITLE and BODY be present.  */
+
+  if (NILP (title) || NILP (body))
+    error ("Title or body not provided");
+
+  /* Now check the type and possibly expand each non-nil argument.  */
+
+  CHECK_STRING (title);
+  title = ENCODE_UTF_8 (title);
+  CHECK_STRING (body);
+  body = ENCODE_UTF_8 (body);
+
+  if (NILP (urgency))
+    urgency = Qlow;
+
+  if (!NILP (app_icon))
+    app_icon = Fexpand_file_name (app_icon, Qnil);
+
+  return make_int (haiku_notifications_notify_1 (title, body,
+                                                replaces_id,
+                                                app_icon, urgency));
+}
+
 void
 syms_of_haikuselect (void)
 {
@@ -1312,6 +1426,16 @@ keyboard modifiers currently held down.  */);
   DEFSYM (Qdouble, "double");
   DEFSYM (Qalready_running, "already-running");
 
+  DEFSYM (QCtitle, ":title");
+  DEFSYM (QCbody, ":body");
+  DEFSYM (QCreplaces_id, ":replaces-id");
+  DEFSYM (QCapp_icon, ":app-icon");
+  DEFSYM (QCurgency, ":urgency");
+
+  DEFSYM (Qlow, "low");
+  DEFSYM (Qnormal, "normal");
+  DEFSYM (Qcritical, "critical");
+
   defsubr (&Shaiku_selection_data);
   defsubr (&Shaiku_selection_timestamp);
   defsubr (&Shaiku_selection_put);
@@ -1320,6 +1444,7 @@ keyboard modifiers currently held down.  */);
   defsubr (&Shaiku_roster_launch);
   defsubr (&Shaiku_write_node_attribute);
   defsubr (&Shaiku_send_message);
+  defsubr (&Shaiku_notifications_notify);
 
   haiku_dnd_frame = NULL;
 }
diff --git a/src/haikuselect.h b/src/haikuselect.h
index 28a1682e587..c117a2ab4f9 100644
--- a/src/haikuselect.h
+++ b/src/haikuselect.h
@@ -21,9 +21,11 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #ifdef __cplusplus
 #include <cstdio>
-#else
+#include <cstdint>
+#else /* !__cplusplus */
 #include <stdio.h>
-#endif
+#include <stdint.h>
+#endif /* __cplusplus */
 
 #include <SupportDefs.h>
 
@@ -37,15 +39,16 @@ enum haiku_clipboard
 #ifdef __cplusplus
 extern "C"
 {
-#endif
+#endif /* __cplusplus */
 /* Defined in haikuselect.c.  */
 extern void haiku_selection_disowned (enum haiku_clipboard, int64);
 
 /* Defined in haiku_select.cc.  */
 extern void be_clipboard_init (void);
-extern char *be_find_clipboard_data (enum haiku_clipboard, const char *, 
ssize_t *);
-extern void be_set_clipboard_data (enum haiku_clipboard, const char *, const 
char *,
-                                  ssize_t, bool);
+extern char *be_find_clipboard_data (enum haiku_clipboard, const char *,
+                                    ssize_t *);
+extern void be_set_clipboard_data (enum haiku_clipboard, const char *,
+                                  const char *, ssize_t, bool);
 extern bool be_clipboard_owner_p (enum haiku_clipboard);
 extern void be_update_clipboard_count (enum haiku_clipboard);
 
@@ -58,7 +61,8 @@ extern uint32 be_get_message_type (void *);
 extern void be_set_message_type (void *, uint32);
 extern void *be_get_message_message (void *, const char *, int32);
 extern void *be_create_simple_message (void);
-extern int be_add_message_data (void *, const char *, int32, const void *, 
ssize_t);
+extern int be_add_message_data (void *, const char *, int32, const void *,
+                               ssize_t);
 extern int be_add_refs_data (void *, const char *, const char *);
 extern int be_add_point_data (void *, const char *, float, float);
 extern int be_add_message_message (void *, const char *, void *);
@@ -69,9 +73,14 @@ extern void be_start_watching_selection (enum 
haiku_clipboard);
 extern bool be_selection_outdated_p (enum haiku_clipboard, int64);
 extern int64 be_get_clipboard_count (enum haiku_clipboard);
 
+
+
+extern intmax_t be_display_notification (const char *, const char *,
+                                        intmax_t, int, const char *);
+
 #ifdef __cplusplus
 };
-#endif
+#endif /* __cplusplus */
 #endif /* _HAIKU_SELECT_H_ */
 
 // Local Variables:
diff --git a/src/haikuterm.c b/src/haikuterm.c
index ed28a806ff2..b1a016b49a9 100644
--- a/src/haikuterm.c
+++ b/src/haikuterm.c
@@ -1219,7 +1219,7 @@ static void
 haiku_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
 {
   struct glyph *glyph = s->first_glyph;
-  unsigned char2b[8];
+  static unsigned char2b[8];
   int x, i, j;
   struct face *face = s->face;
   unsigned long color;
@@ -1399,7 +1399,7 @@ haiku_draw_stretch_glyph_string (struct glyph_string *s)
        }
 
       if (background_width > 0)
-       haiku_draw_background_rect (s, s->face, s->x, s->y,
+       haiku_draw_background_rect (s, s->face, x, s->y,
                                    background_width, s->height);
     }
   s->background_filled_p = 1;
@@ -4042,6 +4042,19 @@ haiku_read_socket (struct terminal *terminal, struct 
input_event *hold_quit)
             handled in Lisp.  */
          haiku_handle_font_change_event (buf, &inev);
          break;
+
+       case NOTIFICATION_CLICK_EVENT:
+         /* This code doesn't function, but the why is unknown.  */
+#if 0
+         {
+           struct haiku_notification_click_event *b = buf;
+
+           inev.kind = NOTIFICATION_CLICKED_EVENT;
+           inev.arg  = make_int (b->id);
+           break;
+         }
+#endif /* 0 */
+
        case KEY_UP:
        case DUMMY_EVENT:
        default:
diff --git a/src/image.c b/src/image.c
index c9420b48f4a..a4b8d21cee6 100644
--- a/src/image.c
+++ b/src/image.c
@@ -175,6 +175,31 @@ typedef struct haiku_bitmap_record Bitmap_Record;
 
 #endif
 
+#ifdef HAVE_ANDROID
+#include "androidterm.h"
+
+typedef struct android_bitmap_record Bitmap_Record;
+
+typedef struct android_image XImage;
+typedef android_pixmap Pixmap;
+
+#define GET_PIXEL(ximg, x, y)          android_get_pixel (ximg, x, y)
+#define PUT_PIXEL(ximg, x, y, pixel)    android_put_pixel (ximg, x, y, pixel)
+#define NO_PIXMAP                      0
+
+#define PIX_MASK_RETAIN        0
+#define PIX_MASK_DRAW  1
+
+#define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b))
+#define RED_FROM_ULONG(color)  (((color) >> 16) & 0xff)
+#define GREEN_FROM_ULONG(color)        (((color) >> 8) & 0xff)
+#define BLUE_FROM_ULONG(color) ((color) & 0xff)
+#define RED16_FROM_ULONG(color)                (RED_FROM_ULONG (color) * 0x101)
+#define GREEN16_FROM_ULONG(color)      (GREEN_FROM_ULONG (color) * 0x101)
+#define BLUE16_FROM_ULONG(color)       (BLUE_FROM_ULONG (color) * 0x101)
+
+#endif
+
 static void image_disable_image (struct frame *, struct image *);
 static void image_edge_detection (struct frame *, struct image *, Lisp_Object,
                                   Lisp_Object);
@@ -441,32 +466,101 @@ image_reference_bitmap (struct frame *f, ptrdiff_t id)
 }
 
 #ifdef HAVE_PGTK
+
+/* Create a Cairo pattern from the bitmap BITS, which should be WIDTH
+   and HEIGHT in size.  BITS's fill order is LSB first, meaning that
+   the value of the left most pixel within a byte is its least
+   significant bit.  */
+
 static cairo_pattern_t *
-image_create_pattern_from_pixbuf (struct frame *f, GdkPixbuf * pixbuf)
+image_bitmap_to_cr_pattern (char *bits, int width, int height)
 {
-  GdkPixbuf *pb = gdk_pixbuf_add_alpha (pixbuf, TRUE, 255, 255, 255);
-  cairo_surface_t *surface =
-    cairo_surface_create_similar_image (cairo_get_target
-                                       (f->output_data.pgtk->cr_context),
-                                       CAIRO_FORMAT_A1,
-                                       gdk_pixbuf_get_width (pb),
-                                       gdk_pixbuf_get_height (pb));
+  cairo_surface_t *surface;
+  unsigned char *data;
+  int stride;
+  cairo_pattern_t *pattern;
+#ifdef WORDS_BIGENDIAN
+  int x;
+  static const unsigned char table[] = {
+    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+    0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+    0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+    0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+    0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+    0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+    0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+    0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+    0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+    0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+    0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+    0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+    0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+    0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+    0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+    0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+    0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+    0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+    0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+    0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+    0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+  };
+#endif /* WORDS_BIGENDIAN */
 
-  cairo_t *cr = cairo_create (surface);
-  gdk_cairo_set_source_pixbuf (cr, pb, 0, 0);
-  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
-  cairo_paint (cr);
-  cairo_destroy (cr);
+  surface = cairo_image_surface_create (CAIRO_FORMAT_A1, width,
+                                       height);
 
-  cairo_pattern_t *pat = cairo_pattern_create_for_surface (surface);
-  cairo_pattern_set_extend (pat, CAIRO_EXTEND_REPEAT);
+  if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
+    memory_full (0);
 
-  cairo_surface_destroy (surface);
-  g_object_unref (pb);
+  cairo_surface_flush (surface);
+  data = cairo_image_surface_get_data (surface);
+  stride = cairo_image_surface_get_stride (surface);
 
-  return pat;
+#ifdef WORDS_BIGENDIAN
+  /* Big endian systems require that individual bytes be inverted to
+     compensate for the different fill order used by Cairo.  */
+  while (height--)
+    {
+      memcpy (data, bits, (width + 7) / 8);
+      for (x = 0; x < (width + 7) / 8; ++x)
+       data[x] = table[data[x]];
+      data += stride;
+      bits += (width + 7) / 8;
+    }
+#else /* !WORDS_BIGENDIAN */
+  /* Cairo uses LSB first fill order for bitmaps on little-endian
+     systems, so copy each row over.  */
+
+  while (height--)
+    {
+      memcpy (data, bits, (width + 7) / 8);
+      data += stride;
+      bits += (width + 7) / 8;
+    }
+#endif /* WORDS_BIGENDIAN */
+
+  cairo_surface_mark_dirty (surface);
+  pattern = cairo_pattern_create_for_surface (surface);
+  if (cairo_pattern_status (pattern) != CAIRO_STATUS_SUCCESS)
+    memory_full (0);
+
+  /* The pattern now holds a reference to the surface.  */
+  cairo_surface_destroy (surface);
+  cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+  return pattern;
 }
-#endif
+
+#endif /* HAVE_PGTK */
 
 /* Create a bitmap for frame F from a HEIGHT x WIDTH array of bits at BITS.  */
 
@@ -486,6 +580,18 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
     return -1;
 #endif /* HAVE_X_WINDOWS */
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  android_pixmap bitmap;
+
+  bitmap = android_create_bitmap_from_data (bits, width, height);
+
+  if (!bitmap)
+    return -1;
+#elif defined HAVE_ANDROID
+  ((void) dpyinfo);
+  emacs_abort ();
+#endif /* HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
 #ifdef HAVE_NTGUI
   Lisp_Object frame UNINIT;    /* The value is not used.  */
   Emacs_Pixmap bitmap;
@@ -504,46 +610,9 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
 #endif
 
 #ifdef HAVE_PGTK
-  GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
-                                     FALSE,
-                                     8,
-                                     width,
-                                     height);
-  {
-    char *sp = bits;
-    int mask = 0x01;
-    unsigned char *buf = gdk_pixbuf_get_pixels (pixbuf);
-    int rowstride = gdk_pixbuf_get_rowstride (pixbuf);
-    for (int y = 0; y < height; y++)
-      {
-       unsigned char *dp = buf + rowstride * y;
-       for (int x = 0; x < width; x++)
-         {
-           if (*sp & mask)
-             {
-               *dp++ = 0xff;
-               *dp++ = 0xff;
-               *dp++ = 0xff;
-             }
-           else
-             {
-               *dp++ = 0x00;
-               *dp++ = 0x00;
-               *dp++ = 0x00;
-             }
-           if ((mask <<= 1) >= 0x100)
-             {
-               mask = 0x01;
-               sp++;
-             }
-         }
-       if (mask != 0x01)
-         {
-           mask = 0x01;
-           sp++;
-         }
-      }
-  }
+  cairo_pattern_t *pattern;
+
+  pattern = image_bitmap_to_cr_pattern (bits, width, height);
 #endif /* HAVE_PGTK */
 
 #ifdef HAVE_HAIKU
@@ -577,10 +646,8 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
 #endif
 
 #ifdef HAVE_PGTK
-  dpyinfo->bitmaps[id - 1].img = pixbuf;
   dpyinfo->bitmaps[id - 1].depth = 1;
-  dpyinfo->bitmaps[id - 1].pattern =
-    image_create_pattern_from_pixbuf (f, pixbuf);
+  dpyinfo->bitmaps[id - 1].pattern = pattern;
 #endif
 
 #ifdef HAVE_HAIKU
@@ -598,14 +665,16 @@ image_create_bitmap_from_data (struct frame *f, char 
*bits,
   dpyinfo->bitmaps[id - 1].width = width;
   dpyinfo->bitmaps[id - 1].refcount = 1;
 
-#ifdef HAVE_X_WINDOWS
+#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
+#ifndef ANDROID_STUBIFY
   dpyinfo->bitmaps[id - 1].pixmap = bitmap;
+#endif /* ANDROID_STUBIFY */
   dpyinfo->bitmaps[id - 1].have_mask = false;
   dpyinfo->bitmaps[id - 1].depth = 1;
 #ifdef USE_CAIRO
   dpyinfo->bitmaps[id - 1].stipple = NULL;
 #endif /* USE_CAIRO */
-#endif /* HAVE_X_WINDOWS */
+#endif /* HAVE_X_WINDOWS || HAVE_ANDROID */
 
 #ifdef HAVE_NTGUI
   dpyinfo->bitmaps[id - 1].pixmap = bitmap;
@@ -616,9 +685,20 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
   return id;
 }
 
-#if defined HAVE_HAIKU || defined HAVE_NS
-static char *slurp_file (int, ptrdiff_t *);
-static Lisp_Object image_find_image_fd (Lisp_Object, int *);
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#include "android.h"
+
+/* This abstraction allows directly loading images from assets without
+   copying them to a file descriptor first.  */
+typedef struct android_fd_or_asset image_fd;
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+typedef int image_fd;
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+#if defined HAVE_HAIKU || defined HAVE_NS || defined HAVE_PGTK \
+  || defined HAVE_ANDROID
+static char *slurp_file (image_fd, ptrdiff_t *);
+static Lisp_Object image_find_image_fd (Lisp_Object, image_fd *);
 static bool xbm_read_bitmap_data (struct frame *, char *, char *,
                                  int *, int *, char **, bool);
 #endif
@@ -680,25 +760,38 @@ image_create_bitmap_from_file (struct frame *f, 
Lisp_Object file)
 #endif
 
 #ifdef HAVE_PGTK
-  GError *err = NULL;
-  ptrdiff_t id;
-  void * bitmap = gdk_pixbuf_new_from_file (SSDATA (file), &err);
+  ptrdiff_t id, size;
+  int fd, width, height, rc;
+  char *contents, *data;
+  void *bitmap;
 
-  if (!bitmap)
+  if (!STRINGP (image_find_image_fd (file, &fd)))
+    return -1;
+
+  contents = slurp_file (fd, &size);
+
+  if (!contents)
+    return -1;
+
+  rc = xbm_read_bitmap_data (f, contents, contents + size,
+                            &width, &height, &data, 0);
+
+  if (!rc)
     {
-      g_error_free (err);
+      xfree (contents);
       return -1;
     }
 
   id = image_allocate_bitmap_record (f);
 
-  dpyinfo->bitmaps[id - 1].img = bitmap;
   dpyinfo->bitmaps[id - 1].refcount = 1;
   dpyinfo->bitmaps[id - 1].file = xlispstrdup (file);
-  dpyinfo->bitmaps[id - 1].height = gdk_pixbuf_get_width (bitmap);
-  dpyinfo->bitmaps[id - 1].width = gdk_pixbuf_get_height (bitmap);
+  dpyinfo->bitmaps[id - 1].height = width;
+  dpyinfo->bitmaps[id - 1].width = height;
   dpyinfo->bitmaps[id - 1].pattern
-    = image_create_pattern_from_pixbuf (f, bitmap);
+    = image_bitmap_to_cr_pattern (data, width, height);
+  xfree (contents);
+  xfree (data);
   return id;
 #endif
 
@@ -724,7 +817,7 @@ image_create_bitmap_from_file (struct frame *f, Lisp_Object 
file)
 
   /* Search bitmap-file-path for the file, if appropriate.  */
   if (openp (Vx_bitmap_file_path, file, Qnil, &found,
-            make_fixnum (R_OK), false, false)
+            make_fixnum (R_OK), false, false, NULL)
       < 0)
     return -1;
 
@@ -773,7 +866,7 @@ image_create_bitmap_from_file (struct frame *f, Lisp_Object 
file)
 
   /* Search bitmap-file-path for the file, if appropriate.  */
   if (openp (Vx_bitmap_file_path, file, Qnil, &found,
-            make_fixnum (R_OK), false, false)
+            make_fixnum (R_OK), false, false, NULL)
       < 0)
     return -1;
 
@@ -834,6 +927,73 @@ image_create_bitmap_from_file (struct frame *f, 
Lisp_Object file)
   xfree (contents);
   return id;
 #endif
+
+#ifdef HAVE_ANDROID
+#ifdef ANDROID_STUBIFY
+  ((void) dpyinfo);
+
+  /* This function should never be called when building stubs.  */
+  emacs_abort ();
+#else
+  ptrdiff_t id, size;
+  int width, height, rc;
+  image_fd fd;
+  char *contents, *data;
+  Lisp_Object found;
+  android_pixmap bitmap;
+
+  /* Look for an existing bitmap with the same name.  */
+  for (id = 0; id < dpyinfo->bitmaps_last; ++id)
+    {
+      if (dpyinfo->bitmaps[id].refcount
+         && dpyinfo->bitmaps[id].file
+         && !strcmp (dpyinfo->bitmaps[id].file, SSDATA (file)))
+       {
+         ++dpyinfo->bitmaps[id].refcount;
+         return id + 1;
+       }
+    }
+
+  /* Search bitmap-file-path for the file, if appropriate.  */
+  if (openp (Vx_bitmap_file_path, file, Qnil, &found,
+            make_fixnum (R_OK), false, false, NULL)
+      < 0)
+    return -1;
+
+  if (!STRINGP (image_find_image_fd (file, &fd))
+      && !STRINGP (image_find_image_fd (found, &fd)))
+    return -1;
+
+  contents = slurp_file (fd, &size);
+
+  if (!contents)
+    return -1;
+
+  rc = xbm_read_bitmap_data (f, contents, contents + size,
+                            &width, &height, &data, 0);
+
+  if (!rc)
+    {
+      xfree (contents);
+      return -1;
+    }
+
+  xfree (contents);
+  bitmap = android_create_bitmap_from_data (data, width, height);
+  xfree (data);
+
+  id = image_allocate_bitmap_record (f);
+  dpyinfo->bitmaps[id - 1].pixmap = bitmap;
+  dpyinfo->bitmaps[id - 1].have_mask = false;
+  dpyinfo->bitmaps[id - 1].refcount = 1;
+  dpyinfo->bitmaps[id - 1].file = xlispstrdup (file);
+  dpyinfo->bitmaps[id - 1].depth = 1;
+  dpyinfo->bitmaps[id - 1].height = height;
+  dpyinfo->bitmaps[id - 1].width = width;
+
+  return id;
+#endif
+#endif
 }
 
 /* Free bitmap B.  */
@@ -859,6 +1019,13 @@ free_bitmap_record (Display_Info *dpyinfo, Bitmap_Record 
*bm)
 #endif /* USE_CAIRO */
 #endif /* HAVE_X_WINDOWS */
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  android_free_pixmap (bm->pixmap);
+
+  if (bm->have_mask)
+    android_free_pixmap (bm->pixmap);
+#endif
+
 #ifdef HAVE_NTGUI
   DeleteObject (bm->pixmap);
 #endif /* HAVE_NTGUI */
@@ -946,7 +1113,7 @@ static void image_unget_x_image (struct image *, bool, 
Emacs_Pix_Container);
   image_unget_x_image (img, mask_p, ximg)
 #endif
 
-#ifdef HAVE_X_WINDOWS
+#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
 
 #ifndef USE_CAIRO
 static void image_sync_to_pixmaps (struct frame *, struct image *);
@@ -960,6 +1127,8 @@ static bool x_create_x_image_and_pixmap (struct frame *, 
int, int, int,
                                         XImage **, Pixmap *);
 static void x_destroy_x_image (XImage *);
 
+#if defined HAVE_X_WINDOWS
+
 /* Create a mask of a bitmap. Note is this not a perfect mask.
    It's nicer with some borders in this context */
 
@@ -1056,7 +1225,9 @@ x_create_bitmap_mask (struct frame *f, ptrdiff_t id)
   x_destroy_x_image (mask_img);
 }
 
-#endif /* HAVE_X_WINDOWS */
+#endif
+
+#endif /* HAVE_X_WINDOWS || defined HAVE_ANDROID*/
 
 /***********************************************************************
                            Image types
@@ -1090,7 +1261,7 @@ struct image_type
 #if defined HAVE_RSVG || defined HAVE_PNG || defined HAVE_GIF || \
   defined HAVE_TIFF || defined HAVE_JPEG || defined HAVE_XPM || \
   defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_PGTK || \
-  defined HAVE_WEBP
+  defined HAVE_WEBP || defined HAVE_ANDROID
 # ifdef WINDOWSNT
 #  define IMAGE_TYPE_INIT(f) f
 # else
@@ -1165,6 +1336,18 @@ image_error (const char *format, ...)
   va_end (ap);
 }
 
+static void
+image_invalid_data_error (Lisp_Object data)
+{
+  image_error ("Invalid image data `%s'", data);
+}
+
+static void
+image_not_found_error (Lisp_Object filename)
+{
+  image_error ("Cannot find image file `%s'", filename);
+}
+
 static void
 image_size_error (void)
 {
@@ -1592,7 +1775,7 @@ prepare_image_for_display (struct frame *f, struct image 
*img)
        }
       unblock_input ();
     }
-#elif defined HAVE_X_WINDOWS
+#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
   if (!img->load_failed_p)
     {
       block_input ();
@@ -1799,7 +1982,7 @@ image_clear_image_1 (struct frame *f, struct image *img, 
int flags)
          /* NOTE (HAVE_NS): background color is NOT an indexed color! */
          img->background_valid = 0;
        }
-#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
+#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
       if (img->ximg)
        {
          image_destroy_x_image (img->ximg);
@@ -1817,7 +2000,7 @@ image_clear_image_1 (struct frame *f, struct image *img, 
int flags)
          img->mask = NO_PIXMAP;
          img->background_transparent_valid = 0;
        }
-#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
+#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
       if (img->mask_img)
        {
          image_destroy_x_image (img->mask_img);
@@ -2189,11 +2372,11 @@ image_size_in_bytes (struct image *img)
   if (msk)
     size += msk->height * msk->bytes_per_line;
 
-#elif defined HAVE_X_WINDOWS
-  /* Use a nominal depth of 24 bpp for pixmap and 1 bpp for mask,
-     to avoid having to query the server. */
+#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
+  /* Use a nominal depth of 24 and a bpp of 32 for pixmap and 1 bpp
+     for mask, to avoid having to query the server. */
   if (img->pixmap != NO_PIXMAP)
-    size += img->width * img->height * 3;
+    size += img->width * img->height * 4;
   if (img->mask != NO_PIXMAP)
     size += img->width * img->height / 8;
 
@@ -2528,11 +2711,11 @@ compute_image_size (double width, double height,
    finally move the origin back to the top left of the image, which
    may now be a different corner.
 
-   Note that different GUI backends (X, Cairo, w32, NS, Haiku) want
-   the transform matrix defined as transform from the original image
-   to the transformed image, while others want the matrix to describe
-   the transform of the space, which boils down to inverting the
-   matrix.
+   Note that different GUI backends (X, Cairo, w32, NS, Haiku,
+   Android) want the transform matrix defined as transform from the
+   original image to the transformed image, while others want the
+   matrix to describe the transform of the space, which boils down to
+   inverting the matrix.
 
    It's possible to pre-calculate the matrix multiplications and just
    generate one transform matrix that will do everything we need in a
@@ -2574,6 +2757,96 @@ compute_image_rotation (struct image *img, double 
*rotation)
     *rotation = XFIXNUM (reduced_angle);
 }
 
+#ifdef HAVE_ANDROID
+
+static void
+matrix_identity (matrix3x3 matrix)
+{
+  memset (matrix, 0, sizeof (matrix3x3));
+
+  matrix[0][0] = 1.0;
+  matrix[1][1] = 1.0;
+  matrix[2][2] = 1.0;
+}
+
+/* Translate the matrix TRANSFORM to X, Y, and then perform clockwise
+   rotation by the given angle THETA in radians and translate back.
+   As the transform is being performed in a coordinate system where Y
+   grows downwards, the given angle describes a clockwise
+   rotation.  */
+
+static void
+matrix_rotate (matrix3x3 transform, double theta, double x, double y)
+{
+  matrix3x3 temp, copy;
+
+  /* 1. Translate the matrix so X and Y are in the center.  */
+
+  matrix_identity (temp);
+  memcpy (copy, transform, sizeof copy);
+
+  temp[0][2] = x;
+  temp[1][2] = y;
+
+  matrix3x3_mult (copy, temp, transform);
+  matrix_identity (temp);
+  memcpy (copy, transform, sizeof copy);
+
+  /* 2. Rotate the matrix counter-clockwise, assuming a coordinate
+     system where Y grows downwards.  */
+
+  temp[0][0] = cos (theta);
+  temp[0][1] = -sin (theta);
+  temp[1][0] = sinf (theta);
+  temp[1][1] = cosf (theta);
+
+  matrix3x3_mult (copy, temp, transform);
+  matrix_identity (temp);
+  memcpy (copy, transform, sizeof copy);
+
+  /* 3. Translate back.  */
+
+  temp[0][2] = -x;
+  temp[1][2] = -y;
+
+  matrix3x3_mult (copy, temp, transform);
+}
+
+/* Scale the matrix TRANSFORM by -1, and then apply a TX of width, in
+   effect flipping the image horizontally.  */
+
+static void
+matrix_mirror_horizontal (matrix3x3 transform, double width)
+{
+  matrix3x3 temp, copy;
+
+  matrix_identity (temp);
+  memcpy (copy, transform, sizeof copy);
+
+  temp[0][0] = -1.0f;
+  temp[0][2] = width;
+
+  matrix3x3_mult (copy, temp, transform);
+}
+
+static void
+matrix_translate (matrix3x3 transform, float tx, float ty)
+{
+  matrix3x3 temp, copy;
+
+  matrix_identity (temp);
+  memcpy (copy, transform, sizeof copy);
+
+  /* Set the tx and ty.  */
+  temp[0][2] = tx;
+  temp[1][2] = ty;
+
+  /* Multiply it with the transform.  */
+  matrix3x3_mult (copy, temp, transform);
+}
+
+#endif
+
 static void
 image_set_transform (struct frame *f, struct image *img)
 {
@@ -2593,6 +2866,14 @@ image_set_transform (struct frame *f, struct image *img)
   memcpy (&img->transform, identity, sizeof identity);
 #endif
 
+#if defined HAVE_ANDROID
+  matrix3x3 identity = {
+    { 1, 0, 0 },
+    { 0, 1, 0 },
+    { 0, 0, 1 },
+  };
+#endif
+
 # if (defined HAVE_IMAGEMAGICK \
       && !defined DONT_CREATE_TRANSFORMED_IMAGEMAGICK_IMAGE)
   /* ImageMagick images already have the correct transform.  */
@@ -2630,7 +2911,8 @@ image_set_transform (struct frame *f, struct image *img)
   /* Determine flipping.  */
   flip = !NILP (image_spec_value (img->spec, QCflip, NULL));
 
-# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined 
HAVE_HAIKU
+# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined 
HAVE_HAIKU \
+  || defined HAVE_ANDROID
   /* We want scale up operations to use a nearest neighbor filter to
      show real pixels instead of munging them, but scale down
      operations to use a blended filter, to avoid aliasing and the like.
@@ -2652,7 +2934,7 @@ image_set_transform (struct frame *f, struct image *img)
 
   matrix3x3 matrix
     = {
-# if defined USE_CAIRO || defined HAVE_XRENDER
+# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_ANDROID
        [0][0] = (!IEEE_FLOATING_POINT && width == 0 ? DBL_MAX
                  : img->width / (double) width),
        [1][1] = (!IEEE_FLOATING_POINT && height == 0 ? DBL_MAX
@@ -2675,7 +2957,7 @@ image_set_transform (struct frame *f, struct image *img)
 
   /* Haiku needs this, since the transformation is done on the basis
      of the view, and not the image.  */
-#ifdef HAVE_HAIKU
+#if defined HAVE_HAIKU
   int extra_tx, extra_ty;
 
   extra_tx = 0;
@@ -2686,8 +2968,9 @@ image_set_transform (struct frame *f, struct image *img)
     rotate_flag = 0;
   else
     {
-# if (defined USE_CAIRO || defined HAVE_XRENDER \
-      || defined HAVE_NTGUI || defined HAVE_NS \
+#ifndef HAVE_ANDROID
+# if (defined USE_CAIRO || defined HAVE_XRENDER                \
+      || defined HAVE_NTGUI || defined HAVE_NS         \
       || defined HAVE_HAIKU)
       int cos_r, sin_r;
       if (rotation == 0)
@@ -2714,7 +2997,7 @@ image_set_transform (struct frame *f, struct image *img)
          sin_r = 1;
          rotate_flag = 1;
 
-#ifdef HAVE_HAIKU
+#if defined HAVE_HAIKU
          if (!flip)
            extra_ty = height;
          extra_tx = 0;
@@ -2750,7 +3033,7 @@ image_set_transform (struct frame *f, struct image *img)
 
       if (0 < rotate_flag)
        {
-#  if defined USE_CAIRO || defined HAVE_XRENDER
+#  if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_ANDROID
          /* 1. Translate so (0, 0) is in the center of the image.  */
          matrix3x3 t
            = { [0][0] = 1,
@@ -2801,6 +3084,93 @@ image_set_transform (struct frame *f, struct image *img)
          img->height = height;
        }
 # endif
+#else
+      /* Calculate the inverse transform from the destination to the
+        source.  The matrix is currently identity with scale
+        applied.
+
+         This code makes more sense to me than what lies above.  But
+         I'm not touching what works.  */
+
+      if (rotation != 0 && rotation != 90
+         && rotation != 180 && rotation != 270)
+       {
+         rotate_flag = 0;
+         goto bail;
+       }
+
+      rotate_flag = 1;
+
+      switch ((int) rotation + (flip ? 1 : 0))
+       {
+       case 0:
+         break;
+
+       case 90:
+         /* Rotate the image 90 degrees clockwise.  IOW, rotate the
+            destination by 90 degrees counterclockwise, which is 270
+            degrees clockwise.  */
+         matrix_rotate (matrix, M_PI * 1.5, 0, 0);
+         matrix_translate (matrix, -height, 0);
+         break;
+
+       case 180:
+         /* Apply clockwise 180 degree rotation around the
+            center.  */
+         matrix_rotate (matrix, M_PI, width / 2.0, height / 2.0);
+         break;
+
+       case 270:
+         /* Apply 270 degree counterclockwise rotation to the
+            destination, which is 90 degrees clockwise.  */
+         matrix_rotate (matrix, M_PI * 0.5, 0, 0);
+         matrix_translate (matrix, 0, -width);
+         break;
+
+       case 1:
+         /* Flipped.  Apply horizontal flip.  */
+         matrix_mirror_horizontal (matrix, width);
+         break;
+
+       case 91:
+         /* Apply a flip but otherwise treat this the same as 90.  */
+         matrix_rotate (matrix, M_PI * 1.5, 0, 0);
+         matrix_translate (matrix, -height, 0);
+         matrix_mirror_horizontal (matrix, height);
+         break;
+
+       case 181:
+         /* Flipped 180 degrees.  Apply a flip and treat this the
+            same as 180.  */
+         matrix_rotate (matrix, M_PI, width / 2.0, height / 2.0);
+         matrix_mirror_horizontal (matrix, width);
+         break;
+
+       case 271:
+         /* Flipped 270 degrees.  Apply a flip and treat this the
+            same as 270.  */
+         matrix_rotate (matrix, M_PI * 0.5, 0, 0);
+         matrix_translate (matrix, 0, -width);
+         matrix_mirror_horizontal (matrix, height);
+         break;
+       }
+
+      /* Now set img->width and img->height.  Flip them if the
+        rotation being applied requires so.  */
+
+      if (rotation != 270 && rotation != 90)
+       {
+         img->width = width;
+         img->height = height;
+       }
+      else
+       {
+         img->height = width;
+         img->width = height;
+       }
+    bail:
+      ;
+#endif
     }
 
   if (rotate_flag < 0)
@@ -2865,6 +3235,103 @@ image_set_transform (struct frame *f, struct image *img)
       img->transform[0][2] = extra_tx;
       img->transform[1][2] = extra_ty;
     }
+# elif defined HAVE_ANDROID
+  /* Create a new image of the right size, then turn it into a pixmap
+     and set that as img->pixmap.  Destroy img->mask for now (this is
+     not right.)  */
+
+  struct android_image *transformed_image, *image;
+  struct android_transform transform;
+
+  /* If there is no transform, simply return.  */
+  if (!memcmp (&matrix, &identity, sizeof matrix))
+    return;
+
+  /* First, get the source image.  */
+  image = image_get_x_image (f, img, false);
+
+  /* Make the transformed image.  */
+  transformed_image = android_create_image (image->depth,
+                                           ANDROID_Z_PIXMAP,
+                                           NULL, img->width,
+                                           img->height);
+
+  /* Allocate memory for that image.  */
+  transformed_image->data
+    = xmalloc (transformed_image->bytes_per_line
+              * transformed_image->height);
+
+  /* Do the transform.  */
+  transform.m1 = matrix[0][0];
+  transform.m2 = matrix[0][1];
+  transform.m3 = matrix[0][2];
+  transform.m4 = matrix[1][0];
+  transform.m5 = matrix[1][1];
+  transform.m6 = matrix[1][2];
+
+  if (image->depth == 24 && smoothing)
+    android_project_image_bilinear (image, transformed_image,
+                                   &transform);
+  else
+    android_project_image_nearest (image, transformed_image,
+                                  &transform);
+
+  image_unget_x_image (img, false, image);
+
+  /* Now replace the image.  */
+
+  if (img->ximg)
+    image_destroy_x_image (img->ximg);
+
+  img->ximg = transformed_image;
+
+#ifndef ANDROID_STUBIFY
+  /* Then replace the pixmap.  */
+  android_free_pixmap (img->pixmap);
+
+  /* In case android_create_pixmap signals.  */
+  img->pixmap = ANDROID_NONE;
+  img->pixmap = android_create_pixmap (img->width, img->height,
+                                      transformed_image->depth);
+  android_put_image (img->pixmap, transformed_image);
+#else
+  emacs_abort ();
+#endif
+
+  /* Now, transform the mask.  The mask should be depth 1, and is
+     always transformed using a nearest neighbor filter.  */
+
+  if (img->mask_img || img->mask)
+    {
+      image = image_get_x_image (f, img, true);
+      transformed_image = android_create_image (1, ANDROID_Z_PIXMAP,
+                                               NULL, img->width,
+                                               img->height);
+      transformed_image->data
+       = xmalloc (transformed_image->bytes_per_line
+                  * transformed_image->height);
+      android_project_image_nearest (image, transformed_image,
+                                    &transform);
+      image_unget_x_image (img, true, image);
+
+      /* Now replace the image.  */
+
+      if (img->mask_img)
+       image_destroy_x_image (img->mask_img);
+
+      img->mask_img = transformed_image;
+
+#ifndef ANDROID_STUBIFY
+      if (img->mask)
+       android_free_pixmap (img->mask);
+
+      img->mask = ANDROID_NONE;
+      img->mask = android_create_pixmap (img->width, img->height, 1);
+      android_put_image (img->mask, transformed_image);
+#endif
+    }
+
+  /* Done! */
 #endif
 }
 
@@ -3172,9 +3639,12 @@ mark_image_cache (struct image_cache *c)
 
 /***********************************************************************
                          X / NS / W32 support code
+             Most of this code is shared with Android to make
+             it easier to maintain.
  ***********************************************************************/
 
-#ifdef HAVE_X_WINDOWS
+#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
+
 static bool
 x_check_image_size (XImage *ximg, int width, int height)
 {
@@ -3190,7 +3660,11 @@ x_check_image_size (XImage *ximg, int width, int height)
   int bitmap_pad, depth, bytes_per_line;
   if (ximg)
     {
+#ifndef HAVE_ANDROID
       bitmap_pad = ximg->bitmap_pad;
+#else
+      bitmap_pad = (ximg->depth == 1 ? 8 : 32);
+#endif
       depth = ximg->depth;
       bytes_per_line = ximg->bytes_per_line;
     }
@@ -3208,16 +3682,23 @@ static bool
 x_create_x_image_and_pixmap (struct frame *f, int width, int height, int depth,
                             XImage **ximg, Pixmap *pixmap)
 {
+#ifndef HAVE_ANDROID
   Display *display = FRAME_X_DISPLAY (f);
   Drawable drawable = FRAME_X_DRAWABLE (f);
+#endif
 
   eassert (input_blocked_p ());
 
   if (depth <= 0)
     depth = FRAME_DISPLAY_INFO (f)->n_planes;
+#ifndef HAVE_ANDROID
   *ximg = XCreateImage (display, FRAME_X_VISUAL (f),
                        depth, ZPixmap, 0, NULL, width, height,
                        depth > 16 ? 32 : depth > 8 ? 16 : 8, 0);
+#else
+  *ximg = android_create_image (depth, ANDROID_Z_PIXMAP, NULL, width,
+                               height);
+#endif
   if (*ximg == NULL)
     {
       image_error ("Unable to allocate X image");
@@ -3237,7 +3718,15 @@ x_create_x_image_and_pixmap (struct frame *f, int width, 
int height, int depth,
   (*ximg)->data = xmalloc ((*ximg)->bytes_per_line * height);
 
   /* Allocate a pixmap of the same size.  */
+#ifndef HAVE_ANDROID
   *pixmap = XCreatePixmap (display, drawable, width, height, depth);
+#else
+#ifndef ANDROID_STUBIFY
+  *pixmap = android_create_pixmap (width, height, depth);
+#else
+  emacs_abort ();
+#endif
+#endif
   if (*pixmap == NO_PIXMAP)
     {
       x_destroy_x_image (*ximg);
@@ -3258,7 +3747,11 @@ x_destroy_x_image (XImage *ximg)
       ximg->data = NULL;
     }
 
+#ifndef HAVE_ANDROID
   XDestroyImage (ximg);
+#else
+  android_destroy_image (ximg);
+#endif
 }
 
 # if !defined USE_CAIRO && defined HAVE_XRENDER
@@ -3325,7 +3818,7 @@ x_create_xrender_picture (struct frame *f, Emacs_Pixmap 
pixmap, int depth)
 static bool
 image_check_image_size (Emacs_Pix_Container ximg, int width, int height)
 {
-#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
+#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
   return x_check_image_size (ximg, width, height);
 #else
   /* FIXME: Implement this check for the HAVE_NS and HAVE_NTGUI cases.
@@ -3357,13 +3850,13 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int 
width, int height, int d
   if (*pixmap == NO_PIXMAP)
     {
       *pimg = NULL;
-      image_error ("Unable to create X pixmap", Qnil, Qnil);
-      return 0;
+      image_error ("Unable to create X pixmap");
+      return false;
     }
 
   *pimg = *pixmap;
   return 1;
-#elif defined HAVE_X_WINDOWS
+#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
   if (!x_create_x_image_and_pixmap (f, width, height, depth, pimg, pixmap))
     return 0;
 # ifdef HAVE_XRENDER
@@ -3390,8 +3883,8 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int 
width, int height, int d
   if (*pixmap == NO_PIXMAP)
     {
       *pimg = NULL;
-      image_error ("Unable to create pixmap", Qnil, Qnil);
-      return 0;
+      image_error ("Unable to create pixmap");
+      return false;
     }
 
   *pimg = *pixmap;
@@ -3509,7 +4002,7 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int 
width, int height, int d
 static void
 image_destroy_x_image (Emacs_Pix_Container pimg)
 {
-#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
+#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
   x_destroy_x_image (pimg);
 #else
   eassert (input_blocked_p ());
@@ -3548,7 +4041,9 @@ gui_put_x_image (struct frame *f, Emacs_Pix_Container 
pimg,
   XPutImage (FRAME_X_DISPLAY (f), pixmap, gc, pimg, 0, 0, 0, 0,
              pimg->width, pimg->height);
   XFreeGC (FRAME_X_DISPLAY (f), gc);
-#endif /* HAVE_X_WINDOWS */
+#elif defined HAVE_ANDROID
+  android_put_image (pixmap, pimg);
+#endif
 
 #ifdef HAVE_NS
   eassert (pimg == pixmap);
@@ -3585,7 +4080,7 @@ static void
 image_put_x_image (struct frame *f, struct image *img, Emacs_Pix_Container 
ximg,
                   bool mask_p)
 {
-#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
+#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
   if (!mask_p)
     {
       eassert (img->ximg == NULL);
@@ -3603,7 +4098,7 @@ image_put_x_image (struct frame *f, struct image *img, 
Emacs_Pix_Container ximg,
 #endif
 }
 
-#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
+#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
 /* Put the X images recorded in IMG on frame F into pixmaps, then free
    the X images and their buffers.  */
 
@@ -3659,7 +4154,7 @@ image_get_x_image (struct frame *f, struct image *img, 
bool mask_p)
 {
 #if defined USE_CAIRO || defined (HAVE_HAIKU)
   return !mask_p ? img->pixmap : img->mask;
-#elif defined HAVE_X_WINDOWS
+#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
   XImage *ximg_in_img = !mask_p ? img->ximg : img->mask_img;
 
   if (ximg_in_img)
@@ -3669,9 +4164,15 @@ image_get_x_image (struct frame *f, struct image *img, 
bool mask_p)
     return XGetImage (FRAME_X_DISPLAY (f), !mask_p ? img->pixmap : img->mask,
                      0, 0, img->original_width, img->original_height, ~0, 
ZPixmap);
 #endif
+#ifndef HAVE_ANDROID
   else
     return XGetImage (FRAME_X_DISPLAY (f), !mask_p ? img->pixmap : img->mask,
                      0, 0, img->width, img->height, ~0, ZPixmap);
+#else
+  else
+    return android_get_image (!mask_p ? img->pixmap : img->mask,
+                             ANDROID_Z_PIXMAP);
+#endif
 #elif defined (HAVE_NS)
   Emacs_Pix_Container pixmap = !mask_p ? img->pixmap : img->mask;
 
@@ -3684,13 +4185,18 @@ static void
 image_unget_x_image (struct image *img, bool mask_p, Emacs_Pix_Container ximg)
 {
 #ifdef USE_CAIRO
-#elif defined HAVE_X_WINDOWS
+#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
   XImage *ximg_in_img = !mask_p ? img->ximg : img->mask_img;
 
   if (ximg_in_img)
     eassert (ximg == ximg_in_img);
+#ifdef HAVE_ANDROID
+  else
+    android_destroy_image (ximg);
+#else
   else
     XDestroyImage (ximg);
+#endif
 #elif defined (HAVE_NS)
   ns_release_object (ximg);
 #endif
@@ -3709,10 +4215,11 @@ image_unget_x_image (struct image *img, bool mask_p, 
Emacs_Pix_Container ximg)
    PFD is null, do not open the file.  */
 
 static Lisp_Object
-image_find_image_fd (Lisp_Object file, int *pfd)
+image_find_image_fd (Lisp_Object file, image_fd *pfd)
 {
   Lisp_Object file_found, search_path;
   int fd;
+  void *platform;
 
   /* TODO I think this should use something like image-load-path
      instead.  Unfortunately, that can contain non-string elements.  */
@@ -3721,8 +4228,10 @@ image_find_image_fd (Lisp_Object file, int *pfd)
                       Vx_bitmap_file_path);
 
   /* Try to find FILE in data-directory/images, then x-bitmap-file-path.  */
+  platform = NULL;
   fd = openp (search_path, file, Qnil, &file_found,
-             pfd ? Qt : make_fixnum (R_OK), false, false);
+             pfd ? Qt : make_fixnum (R_OK), false, false,
+             pfd ? &platform : NULL);
   if (fd == -2)
     {
       /* The file exists locally, but has a file name handler.
@@ -3732,10 +4241,23 @@ image_find_image_fd (Lisp_Object file, int *pfd)
       Lisp_Object encoded_name = ENCODE_FILE (file_found);
       fd = emacs_open (SSDATA (encoded_name), O_RDONLY, 0);
     }
-  else if (fd < 0)
+  /* FD is -3 if PLATFORM is set to a valid asset file descriptor on
+     Android.  */
+  else if (fd < 0 && fd != -3)
     return Qnil;
+
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
   if (pfd)
     *pfd = fd;
+#else
+  /* Construct an asset file descriptor.  */
+
+  if (pfd)
+    {
+      pfd->fd = fd;
+      pfd->asset = platform;
+    }
+#endif
   return file_found;
 }
 
@@ -3749,15 +4271,26 @@ image_find_image_file (Lisp_Object file)
   return image_find_image_fd (file, 0);
 }
 
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+
+static void
+close_android_fd (void *ptr)
+{
+  android_close_asset (*(struct android_fd_or_asset *) ptr);
+}
+
+#endif
+
 /* Read FILE into memory.  Value is a pointer to a buffer allocated
    with xmalloc holding FILE's contents.  Value is null if an error
    occurred.  FD is a file descriptor open for reading FILE.  Set
    *SIZE to the size of the file.  */
 
 static char *
-slurp_file (int fd, ptrdiff_t *size)
+slurp_file (image_fd fd, ptrdiff_t *size)
 {
-  FILE *fp = fdopen (fd, "rb");
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+  FILE *fp = emacs_fdopen (fd, "rb");
 
   char *buf = NULL;
   struct stat st;
@@ -3767,7 +4300,7 @@ slurp_file (int fd, ptrdiff_t *size)
       specpdl_ref count = SPECPDL_INDEX ();
       record_unwind_protect_ptr (fclose_unwind, fp);
 
-      if (fstat (fileno (fp), &st) == 0
+      if (sys_fstat (fileno (fp), &st) == 0
          && 0 <= st.st_size && st.st_size < min (PTRDIFF_MAX, SIZE_MAX))
        {
          /* Report an error if we read past the purported EOF.
@@ -3785,6 +4318,39 @@ slurp_file (int fd, ptrdiff_t *size)
 
       unbind_to (count, Qnil);
     }
+#else
+  char *buf;
+  struct stat st;
+  specpdl_ref count;
+
+  if (!android_asset_fstat (fd, &st)
+      && (0 <= st.st_size
+         && st.st_size < min (PTRDIFF_MAX, SIZE_MAX)))
+    {
+      count = SPECPDL_INDEX ();
+      record_unwind_protect_ptr (close_android_fd, &fd);
+      buf = xmalloc (st.st_size + 1);
+
+      /* Read one byte past the end of the file.  That allows
+        detecting if the file grows as it is being read.  */
+
+      if (android_asset_read (fd, buf,
+                             st.st_size + 1) == st.st_size)
+       *size = st.st_size;
+      else
+       {
+         xfree (buf);
+         buf = NULL;
+       }
+
+      unbind_to (count, Qnil);
+    }
+  else
+    {
+      buf = NULL;
+      android_close_asset (fd);
+    }
+#endif
 
   return buf;
 }
@@ -4213,6 +4779,15 @@ Create_Pixmap_From_Bitmap_Data (struct frame *f, struct 
image *img, char *data,
     img->picture = x_create_xrender_picture (f, img->pixmap, 0);
 # endif
 
+#elif defined HAVE_ANDROID
+#ifndef ANDROID_STUBIFY
+  img->pixmap
+    = android_create_pixmap_from_bitmap_data (data, img->width, img->height,
+                                             fg, bg,
+                                             FRAME_DISPLAY_INFO (f)->n_planes);
+#else
+  emacs_abort ();
+#endif
 #elif defined HAVE_NTGUI
   img->pixmap
     = w32_create_pixmap_from_bitmap_data (img->width, img->height, data);
@@ -4498,12 +5073,12 @@ xbm_load (struct frame *f, struct image *img)
   file_name = image_spec_value (img->spec, QCfile, NULL);
   if (STRINGP (file_name))
     {
-      int fd;
+      image_fd fd;
       Lisp_Object file = image_find_image_fd (file_name, &fd);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", file_name);
-         return 0;
+         image_not_found_error (file_name);
+         return false;
        }
 
       ptrdiff_t size;
@@ -4643,7 +5218,8 @@ xbm_load (struct frame *f, struct image *img)
                              XPM images
  ***********************************************************************/
 
-#if defined (HAVE_XPM) || defined (HAVE_NS) || defined (HAVE_PGTK)
+#if defined (HAVE_XPM) || defined (HAVE_NS) || defined (HAVE_PGTK) \
+  || defined (HAVE_ANDROID)
 
 static bool xpm_image_p (Lisp_Object object);
 static bool xpm_load (struct frame *f, struct image *img);
@@ -4673,7 +5249,8 @@ static bool xpm_load (struct frame *f, struct image *img);
 #endif /* not HAVE_NTGUI */
 #endif /* HAVE_XPM */
 
-#if defined HAVE_XPM || defined USE_CAIRO || defined HAVE_NS || defined 
HAVE_HAIKU
+#if defined HAVE_XPM || defined USE_CAIRO || defined HAVE_NS   \
+  || defined HAVE_HAIKU || defined HAVE_ANDROID
 
 /* Indices of image specification fields in xpm_format, below.  */
 
@@ -4693,7 +5270,8 @@ enum xpm_keyword_index
   XPM_LAST
 };
 
-#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU || defined 
HAVE_PGTK
+#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU  \
+  || defined HAVE_PGTK || defined HAVE_ANDROID
 /* Vector of image_keyword structures describing the format
    of valid XPM image specifications.  */
 
@@ -4935,7 +5513,8 @@ init_xpm_functions (void)
 
 #endif /* WINDOWSNT */
 
-#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU || defined 
HAVE_PGTK
+#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU  \
+  || defined HAVE_PGTK || defined HAVE_ANDROID
 /* Value is true if COLOR_SYMBOLS is a valid color symbols list
    for XPM images.  Such a list must consist of conses whose car and
    cdr are strings.  */
@@ -4971,9 +5550,9 @@ xpm_image_p (Lisp_Object object)
          && (! fmt[XPM_COLOR_SYMBOLS].count
              || xpm_valid_color_symbols_p (fmt[XPM_COLOR_SYMBOLS].value)));
 }
-#endif /* HAVE_XPM || HAVE_NS || HAVE_HAIKU || HAVE_PGTK */
+#endif /* HAVE_XPM || HAVE_NS || HAVE_HAIKU || HAVE_PGTK || HAVE_ANDROID */
 
-#endif /* HAVE_XPM || USE_CAIRO || HAVE_NS || HAVE_HAIKU */
+#endif /* HAVE_XPM || USE_CAIRO || HAVE_NS || HAVE_HAIKU || HAVE_ANDROID */
 
 #if defined HAVE_XPM && defined HAVE_X_WINDOWS && !defined USE_GTK
 ptrdiff_t
@@ -5150,12 +5729,12 @@ xpm_load (struct frame *f, struct image *img)
       Lisp_Object file = image_find_image_file (specified_file);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", specified_file);
+         image_not_found_error (specified_file);
 #ifdef ALLOC_XPM_COLORS
          xpm_free_color_cache ();
 #endif
          SAFE_FREE ();
-         return 0;
+         return false;
        }
 
       file = ENCODE_FILE (file);
@@ -5182,7 +5761,7 @@ xpm_load (struct frame *f, struct image *img)
       Lisp_Object buffer = image_spec_value (img->spec, QCdata, NULL);
       if (!STRINGP (buffer))
        {
-         image_error ("Invalid image data `%s'", buffer);
+         image_invalid_data_error (buffer);
 #ifdef ALLOC_XPM_COLORS
          xpm_free_color_cache ();
 #endif
@@ -5346,10 +5925,12 @@ xpm_load (struct frame *f, struct image *img)
 #if (defined USE_CAIRO && defined HAVE_XPM)    \
   || (defined HAVE_NS && !defined HAVE_XPM)    \
   || (defined HAVE_HAIKU && !defined HAVE_XPM)  \
-  || (defined HAVE_PGTK && !defined HAVE_XPM)
+  || (defined HAVE_PGTK && !defined HAVE_XPM)  \
+  || (defined HAVE_ANDROID && !defined HAVE_XPM)
 
-/* XPM support functions for NS and Haiku where libxpm is not available, and 
for
-   Cairo.  Only XPM version 3 (without any extensions) is supported.  */
+/* XPM support functions for NS, Haiku and Android where libxpm is not
+   available, and for Cairo.  Only XPM version 3 (without any
+   extensions) is supported.  */
 
 static void xpm_put_color_table_v (Lisp_Object, const char *,
                                    int, Lisp_Object);
@@ -5788,12 +6369,12 @@ xpm_load (struct frame *f,
   file_name = image_spec_value (img->spec, QCfile, NULL);
   if (STRINGP (file_name))
     {
-      int fd;
+      image_fd fd;
       Lisp_Object file = image_find_image_fd (file_name, &fd);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", file_name);
-         return 0;
+         image_not_found_error (file_name);
+         return false;
        }
 
       ptrdiff_t size;
@@ -5814,8 +6395,8 @@ xpm_load (struct frame *f,
       data = image_spec_value (img->spec, QCdata, NULL);
       if (!STRINGP (data))
        {
-         image_error ("Invalid image data `%s'", data);
-         return 0;
+         image_invalid_data_error (data);
+         return false;
        }
       success_p = xpm_load_image (f, img, SSDATA (data),
                                  SSDATA (data) + SBYTES (data));
@@ -6085,7 +6666,8 @@ lookup_rgb_color (struct frame *f, int r, int g, int b)
 {
 #ifdef HAVE_NTGUI
   return PALETTERGB (r >> 8, g >> 8, b >> 8);
-#elif defined USE_CAIRO || defined HAVE_NS || defined HAVE_HAIKU
+#elif defined USE_CAIRO || defined HAVE_NS || defined HAVE_HAIKU       \
+  || defined HAVE_ANDROID
   return RGB_TO_ULONG (r >> 8, g >> 8, b >> 8);
 #else
   xsignal1 (Qfile_error,
@@ -6158,7 +6740,8 @@ image_to_emacs_colors (struct frame *f, struct image 
*img, bool rgb_p)
   p = colors;
   for (y = 0; y < img->height; ++y)
     {
-#if !defined USE_CAIRO && !defined HAVE_NS && !defined HAVE_HAIKU
+#if !defined USE_CAIRO && !defined HAVE_NS && !defined HAVE_HAIKU      \
+  && !defined HAVE_ANDROID
       Emacs_Color *row = p;
       for (x = 0; x < img->width; ++x, ++p)
        p->pixel = GET_PIXEL (ximg, x, y);
@@ -6166,7 +6749,7 @@ image_to_emacs_colors (struct frame *f, struct image 
*img, bool rgb_p)
         {
           FRAME_TERMINAL (f)->query_colors (f, row, img->width);
         }
-#else  /* USE_CAIRO || HAVE_NS || HAVE_HAIKU */
+#else  /* USE_CAIRO || HAVE_NS || HAVE_HAIKU || HAVE_ANDROID */
       for (x = 0; x < img->width; ++x, ++p)
        {
          p->pixel = GET_PIXEL (ximg, x, y);
@@ -6177,7 +6760,7 @@ image_to_emacs_colors (struct frame *f, struct image 
*img, bool rgb_p)
              p->blue = BLUE16_FROM_ULONG (p->pixel);
            }
        }
-#endif /* USE_CAIRO || HAVE_NS */
+#endif /* USE_CAIRO || HAVE_NS || HAVE_ANDROID */
     }
 
   image_unget_x_image_or_dc (img, 0, ximg, prev);
@@ -6242,7 +6825,11 @@ image_from_emacs_colors (struct frame *f, struct image 
*img, Emacs_Color *colors
   Emacs_Pix_Container ximage;
   Emacs_Color *p;
 
+#ifndef HAVE_ANDROID
   ximage = NULL;
+#else
+  ximage = 0;
+#endif
 
   init_color_table ();
 
@@ -6404,7 +6991,9 @@ image_edge_detection (struct frame *f, struct image *img,
 }
 
 
-#if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined HAVE_HAIKU
+#if defined HAVE_X_WINDOWS || defined USE_CAIRO || defined HAVE_HAIKU  \
+  || defined HAVE_ANDROID
+
 static void
 image_pixmap_draw_cross (struct frame *f, Emacs_Pixmap pixmap,
                         int x, int y, unsigned int width, unsigned int height,
@@ -6440,8 +7029,21 @@ image_pixmap_draw_cross (struct frame *f, Emacs_Pixmap 
pixmap,
   XFreeGC (dpy, gc);
 #elif HAVE_HAIKU
   be_draw_cross_on_pixmap (pixmap, x, y, width, height, color);
+#elif HAVE_ANDROID
+#ifndef ANDROID_STUBIFY
+  struct android_gc *gc;
+
+  gc = android_create_gc (0, NULL);
+  android_set_foreground (gc, color);
+  android_draw_line (pixmap, gc, x, y, x + width - 1, y + height - 1);
+  android_draw_line (pixmap, gc, x, y + height - 1, x + width - 1, y);
+  android_free_gc (gc);
+#else
+  emacs_abort ();
+#endif
 #endif
 }
+
 #endif /* HAVE_X_WINDOWS || USE_CAIRO || HAVE_HAIKU */
 
 /* Transform image IMG on frame F so that it looks disabled.  */
@@ -6485,7 +7087,7 @@ image_disable_image (struct frame *f, struct image *img)
 #ifndef HAVE_NTGUI
 #ifndef HAVE_NS  /* TODO: NS support, however this not needed for toolbars */
 
-#if !defined USE_CAIRO && !defined HAVE_HAIKU
+#if !defined USE_CAIRO && !defined HAVE_HAIKU && !defined HAVE_ANDROID
 #define CrossForeground(f) BLACK_PIX_DEFAULT (f)
 #define MaskForeground(f)  WHITE_PIX_DEFAULT (f)
 #else  /* USE_CAIRO || HAVE_HAIKU */
@@ -6796,12 +7398,12 @@ pbm_load (struct frame *f, struct image *img)
 
   if (STRINGP (specified_file))
     {
-      int fd;
+      image_fd fd;
       Lisp_Object file = image_find_image_fd (specified_file, &fd);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", specified_file);
-         return 0;
+         image_not_found_error (specified_file);
+         return false;
        }
 
       ptrdiff_t size;
@@ -6821,8 +7423,8 @@ pbm_load (struct frame *f, struct image *img)
       data = image_spec_value (img->spec, QCdata, NULL);
       if (!STRINGP (data))
        {
-         image_error ("Invalid image data `%s'", data);
-         return 0;
+         image_invalid_data_error (data);
+         return false;
        }
       p = SSDATA (data);
       end = p + SBYTES (data);
@@ -7464,15 +8066,18 @@ png_load_body (struct frame *f, struct image *img, 
struct png_load_context *c)
   if (NILP (specified_data))
     {
       int fd;
-      Lisp_Object file = image_find_image_fd (specified_file, &fd);
-      if (!STRINGP (file))
+      Lisp_Object file = image_find_image_file (specified_file);
+
+      if (!STRINGP (file)
+         || (fd = emacs_open (SSDATA (ENCODE_FILE (file)),
+                              O_RDONLY, 0)) < 0)
        {
-         image_error ("Cannot find image file `%s'", specified_file);
-         return 0;
+         image_not_found_error (specified_file);
+         return false;
        }
 
       /* Open the image file.  */
-      fp = fdopen (fd, "rb");
+      fp = emacs_fdopen (fd, "rb");
       if (!fp)
        {
          image_error ("Cannot open image file `%s'", file);
@@ -7483,7 +8088,7 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
       if (fread (sig, 1, sizeof sig, fp) != sizeof sig
          || png_sig_cmp (sig, 0, sizeof sig))
        {
-         fclose (fp);
+         emacs_fclose (fp);
          image_error ("Not a PNG file: `%s'", file);
          return 0;
        }
@@ -7492,8 +8097,8 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
     {
       if (!STRINGP (specified_data))
        {
-         image_error ("Invalid image data `%s'", specified_data);
-         return 0;
+         image_invalid_data_error (specified_data);
+         return false;
        }
 
       /* Read from memory.  */
@@ -7537,7 +8142,7 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
     }
   if (! png_ptr)
     {
-      if (fp) fclose (fp);
+      if (fp) emacs_fclose (fp);
       return 0;
     }
 
@@ -7551,7 +8156,7 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
       xfree (c->pixels);
       xfree (c->rows);
       if (c->fp)
-       fclose (c->fp);
+       emacs_fclose (c->fp);
       return 0;
     }
 
@@ -7619,7 +8224,6 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
      simple transparency, we prefer a clipping mask.  */
   if (!transparent_p)
     {
-      /* png_color_16 *image_bg; */
       Lisp_Object specified_bg
        = image_spec_value (img->spec, QCbackground, NULL);
       Emacs_Color color;
@@ -7676,7 +8280,7 @@ png_load_body (struct frame *f, struct image *img, struct 
png_load_context *c)
   png_read_end (png_ptr, info_ptr);
   if (fp)
     {
-      fclose (fp);
+      emacs_fclose (fp);
       c->fp = NULL;
     }
 
@@ -8192,14 +8796,16 @@ jpeg_load_body (struct frame *f, struct image *img,
   if (NILP (specified_data))
     {
       int fd;
-      Lisp_Object file = image_find_image_fd (specified_file, &fd);
-      if (!STRINGP (file))
+      Lisp_Object file = image_find_image_file (specified_file);
+      if (!STRINGP (file)
+         || (fd = emacs_open (SSDATA (ENCODE_FILE (file)),
+                              O_RDONLY, 0)) < 0)
        {
-         image_error ("Cannot find image file `%s'", specified_file);
-         return 0;
+         image_not_found_error (specified_file);
+         return false;
        }
 
-      fp = fdopen (fd, "rb");
+      fp = emacs_fdopen (fd, "rb");
       if (fp == NULL)
        {
          image_error ("Cannot open `%s'", file);
@@ -8208,8 +8814,8 @@ jpeg_load_body (struct frame *f, struct image *img,
     }
   else if (!STRINGP (specified_data))
     {
-      image_error ("Invalid image data `%s'", specified_data);
-      return 0;
+      image_invalid_data_error (specified_data);
+      return false;
     }
 
   /* Customize libjpeg's error handling to call my_error_exit when an
@@ -8239,11 +8845,12 @@ jpeg_load_body (struct frame *f, struct image *img,
 
       /* Close the input file and destroy the JPEG object.  */
       if (fp)
-       fclose (fp);
+       emacs_fclose (fp);
       jpeg_destroy_decompress (&mgr->cinfo);
 
       /* If we already have an XImage, free that.  */
-      image_destroy_x_image (ximg);
+      if (ximg)
+       image_destroy_x_image (ximg);
       /* Free pixmap and colors.  */
       image_clear_image (f, img);
       return 0;
@@ -8334,7 +8941,7 @@ jpeg_load_body (struct frame *f, struct image *img,
   jpeg_finish_decompress (&mgr->cinfo);
   jpeg_destroy_decompress (&mgr->cinfo);
   if (fp)
-    fclose (fp);
+    emacs_fclose (fp);
 
   /* Maybe fill in the background field while we have ximg handy. */
   if (NILP (image_spec_value (img->spec, QCbackground, NULL)))
@@ -8649,8 +9256,8 @@ tiff_load (struct frame *f, struct image *img)
       Lisp_Object file = image_find_image_file (specified_file);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", specified_file);
-         return 0;
+         image_not_found_error (specified_file);
+         return false;
        }
 
       Lisp_Object encoded_file = ENCODE_FILE (file);
@@ -8670,8 +9277,8 @@ tiff_load (struct frame *f, struct image *img)
     {
       if (!STRINGP (specified_data))
        {
-         image_error ("Invalid image data `%s'", specified_data);
-         return 0;
+         image_invalid_data_error (specified_data);
+         return false;
        }
 
       /* Memory source! */
@@ -9078,7 +9685,7 @@ gif_load (struct frame *f, struct image *img)
          Lisp_Object file = image_find_image_file (specified_file);
          if (!STRINGP (file))
            {
-             image_error ("Cannot find image file `%s'", specified_file);
+             image_not_found_error (specified_file);
              return false;
            }
 
@@ -9108,17 +9715,22 @@ gif_load (struct frame *f, struct image *img)
 
          /* Get the file size so that we can report it in
             `image-cache-size'.  */
-         struct stat st;
-         FILE *fp = fopen (SSDATA (encoded_file), "rb");
-         if (fstat (fileno (fp), &st) == 0)
-           byte_size = st.st_size;
-         fclose (fp);
+         {
+           struct stat st;
+           int fd;
+
+           fd = emacs_open (SSDATA (encoded_file), O_RDONLY,
+                            0);
+           if (!sys_fstat (fd, &st))
+             byte_size = st.st_size;
+           emacs_close (fd);
+         }
        }
       else
        {
          if (!STRINGP (specified_data))
            {
-             image_error ("Invalid image data `%s'", specified_data);
+             image_invalid_data_error (specified_data);
              return false;
            }
 
@@ -9690,11 +10302,11 @@ webp_load (struct frame *f, struct image *img)
 
   if (NILP (specified_data))
     {
-      int fd;
+      image_fd fd;
       file = image_find_image_fd (specified_file, &fd);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", specified_file);
+         image_not_found_error (specified_file);
          return false;
        }
 
@@ -9709,7 +10321,7 @@ webp_load (struct frame *f, struct image *img)
     {
       if (!STRINGP (specified_data))
        {
-         image_error ("Invalid image data `%s'", specified_data);
+         image_invalid_data_error (specified_data);
          return false;
        }
       contents = SDATA (specified_data);
@@ -9850,22 +10462,36 @@ webp_load (struct frame *f, struct image *img)
     }
 
   /* Create the x image and pixmap.  */
-  Emacs_Pix_Container ximg, mask_img = NULL;
+  Emacs_Pix_Container ximg;
   if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, 
false))
     goto webp_error2;
 
-  /* Create an image and pixmap serving as mask if the WebP image
-     contains an alpha channel.  */
-  if (features.has_alpha
-      && !image_create_x_image_and_pixmap (f, img, width, height, 1,
-                                          &mask_img, true))
+  /* Find the background to use if the WebP image contains an alpha
+     channel.  */
+  Emacs_Color bg_color;
+  if (features.has_alpha)
     {
-      image_destroy_x_image (ximg);
-      image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP);
-      goto webp_error2;
+      Lisp_Object specified_bg
+       = image_spec_value (img->spec, QCbackground, NULL);
+
+      /* If the user specified a color, try to use it; if not, use the
+        current frame background, ignoring any default background
+        color set by the image.  */
+      if (STRINGP (specified_bg))
+       FRAME_TERMINAL (f)->defined_color_hook (f,
+                                               SSDATA (specified_bg),
+                                               &bg_color,
+                                               false,
+                                               false);
+      else
+       FRAME_TERMINAL (f)->query_frame_background_color (f, &bg_color);
+      bg_color.red   >>= 8;
+      bg_color.green >>= 8;
+      bg_color.blue  >>= 8;
     }
 
-  /* Fill the X image and mask from WebP data.  */
+  /* Fill the X image from WebP data.  */
+
   init_color_table ();
 
   img->corners[TOP_CORNER] = 0;
@@ -9880,21 +10506,24 @@ webp_load (struct frame *f, struct image *img)
     {
       for (int x = 0; x < width; ++x)
        {
-         int r = *p++ << 8;
-         int g = *p++ << 8;
-         int b = *p++ << 8;
-         PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b));
-
-         /* An alpha channel associates variable transparency with an
-            image.  WebP allows up to 256 levels of partial transparency.
-            We handle this like with PNG (which see), using the frame's
-            background color to combine the image with.  */
+         int r, g, b;
+         /* The WebP alpha channel allows 256 levels of partial
+            transparency.  Blend it with the background manually.  */
          if (features.has_alpha || anim)
            {
-             if (mask_img)
-               PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : 
PIX_MASK_RETAIN);
-             ++p;
+             float a = (float) p[3] / UINT8_MAX;
+             r = (int)(a * p[0] + (1 - a) * bg_color.red)   << 8;
+             g = (int)(a * p[1] + (1 - a) * bg_color.green) << 8;
+             b = (int)(a * p[2] + (1 - a) * bg_color.blue)  << 8;
+             p += 4;
            }
+         else
+           {
+             r = *p++ << 8;
+             g = *p++ << 8;
+             b = *p++ << 8;
+           }
+         PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b));
        }
     }
 
@@ -9907,16 +10536,6 @@ webp_load (struct frame *f, struct image *img)
   /* Put ximg into the image.  */
   image_put_x_image (f, img, ximg, 0);
 
-  /* Same for the mask.  */
-  if (mask_img)
-    {
-      /* Fill in the background_transparent field while we have the
-        mask handy.  Casting avoids a GCC warning.  */
-      image_background_transparent (img, f, (Emacs_Pix_Context)mask_img);
-
-      image_put_x_image (f, img, mask_img, 1);
-    }
-
   img->width = width;
   img->height = height;
 
@@ -10373,7 +10992,8 @@ imagemagick_load_image (struct frame *f, struct image 
*img,
       return 0;
     }
 
-#ifdef HAVE_MAGICKAUTOORIENTIMAGE
+#if defined HAVE_MAGICKAUTOORIENTIMAGE         \
+  || HAVE_DECL_MAGICKAUTOORIENTIMAGE
   /* If no :rotation is explicitly specified, apply the automatic
      rotation from EXIF. */
   if (NILP (image_spec_value (img->spec, QCrotation, NULL)))
@@ -10530,7 +11150,8 @@ imagemagick_load_image (struct frame *f, struct image 
*img,
   {
     MagickWand *new_wand;
     MagickSetImageBackgroundColor (image_wand, bg_wand);
-#ifdef HAVE_MAGICKMERGEIMAGELAYERS
+#if defined HAVE_MAGICKMERGEIMAGELAYERS                \
+  || HAVE_DECL_MAGICKMERGEIMAGELAYERS
     new_wand = MagickMergeImageLayers (image_wand, MergeLayer);
 #else
     new_wand = MagickFlattenImages (image_wand);
@@ -10559,8 +11180,9 @@ imagemagick_load_image (struct frame *f, struct image 
*img,
 
   init_color_table ();
 
-#if defined (HAVE_MAGICKEXPORTIMAGEPIXELS) && \
-  ! defined (HAVE_NS) && ! defined (HAVE_HAIKU)
+#if (defined (HAVE_MAGICKEXPORTIMAGEPIXELS)         \
+     || HAVE_DECL_MAGICKEXPORTIMAGEPIXELS)          \
+  && ! defined (HAVE_NS) && ! defined (HAVE_HAIKU)
   if (imagemagick_render_type != 0)
     {
       /* Magicexportimage is normally faster than pixelpushing.  This
@@ -10708,8 +11330,8 @@ imagemagick_load (struct frame *f, struct image *img)
       Lisp_Object file = image_find_image_file (file_name);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", file_name);
-         return 0;
+         image_not_found_error (file_name);
+         return false;
        }
       file = ENCODE_FILE (file);
 #ifdef WINDOWSNT
@@ -10726,8 +11348,8 @@ imagemagick_load (struct frame *f, struct image *img)
       data = image_spec_value (img->spec, QCdata, NULL);
       if (!STRINGP (data))
        {
-         image_error ("Invalid image data `%s'", data);
-         return 0;
+         image_invalid_data_error (data);
+         return false;
        }
       success_p = imagemagick_load_image (f, img, SDATA (data),
                                           SBYTES (data), NULL);
@@ -11086,12 +11708,12 @@ svg_load (struct frame *f, struct image *img)
   base_uri = image_spec_value (img->spec, QCbase_uri, NULL);
   if (STRINGP (file_name))
     {
-      int fd;
+      image_fd fd;
       Lisp_Object file = image_find_image_fd (file_name, &fd);
       if (!STRINGP (file))
        {
-         image_error ("Cannot find image file `%s'", file_name);
-         return 0;
+         image_not_found_error (file_name);
+         return false;
        }
 
       /* Read the entire file into memory.  */
@@ -11118,8 +11740,8 @@ svg_load (struct frame *f, struct image *img)
       data = image_spec_value (img->spec, QCdata, NULL);
       if (!STRINGP (data))
        {
-         image_error ("Invalid image data `%s'", data);
-         return 0;
+         image_invalid_data_error (data);
+         return false;
        }
       if (!STRINGP (base_uri))
         base_uri = BVAR (current_buffer, filename);
@@ -11137,6 +11759,12 @@ svg_css_length_to_pixels (RsvgLength length, double 
dpi, int font_size)
 {
   double value = length.length;
 
+#if ! LIBRSVG_CHECK_VERSION (2, 48, 0)
+  /* librsvg 2.48 lets us define the font size, but earlier versions
+     default to 12 pixels.  */
+  font_size = 12;
+#endif
+
   switch (length.unit)
     {
     case RSVG_UNIT_PX:
@@ -11161,16 +11789,31 @@ svg_css_length_to_pixels (RsvgLength length, double 
dpi, int font_size)
     case RSVG_UNIT_IN:
       value *= dpi;
       break;
-#if LIBRSVG_CHECK_VERSION (2, 48, 0)
-      /* We don't know exactly what font size is used on older librsvg
-        versions.  */
     case RSVG_UNIT_EM:
       value *= font_size;
       break;
-#endif
+    case RSVG_UNIT_EX:
+      /* librsvg uses an ex height of half the em height, so we match
+        that here.  */
+      value = value * font_size / 2.0;
+      break;
+    case RSVG_UNIT_PERCENT:
+      /* Percent is a ratio of the containing "viewport".  We don't
+        have a viewport, as such, as we try to draw the image to it's
+        'natural' size rather than dictate the size as if we were
+        drawing icons on a toolbar or similar.  This means that
+        percent values are useless to us and we are best off just
+        drawing the image according to whatever other sizes we can
+        derive.
+
+        If we do set explicit width and height values in the image
+        spec, this will work out correctly as librsvg will still
+        honour the percentage sizes in its final rendering no matter
+        what size we make the image.  */
+      value = 0;
+      break;
     default:
-      /* Probably ex or %.  We can't know what the pixel value is
-         without more information.  */
+      /* We should never reach this.  */
       value = 0;
     }
 
@@ -11301,7 +11944,8 @@ svg_load_image (struct frame *f, struct image *img, 
char *contents,
     }
   else
     {
-      RsvgRectangle zero_rect, viewbox, out_logical_rect;
+      RsvgRectangle  viewbox;
+      double explicit_width = 0, explicit_height = 0;
 
       /* Try the intrinsic dimensions first.  */
       gboolean has_width, has_height;
@@ -11313,34 +11957,27 @@ svg_load_image (struct frame *f, struct image *img, 
char *contents,
                                            &has_height, &iheight,
                                            &has_viewbox, &viewbox);
 
-      if (has_width && has_height)
-       {
-         /* Success!  We can use these values directly.  */
-         viewbox_width = svg_css_length_to_pixels (iwidth, dpi,
+      if (has_width)
+       explicit_width = svg_css_length_to_pixels (iwidth, dpi,
+                                                  img->face_font_size);
+      if (has_height)
+       explicit_height = svg_css_length_to_pixels (iheight, dpi,
                                                    img->face_font_size);
-         viewbox_height = svg_css_length_to_pixels (iheight, dpi,
-                                                    img->face_font_size);
 
-         /* Here one dimension could be zero because in percent unit.
-            So calculate this dimension with the other.  */
-         if (! (0 < viewbox_width) && (iwidth.unit == RSVG_UNIT_PERCENT))
-           viewbox_width = (viewbox_height * viewbox.width / viewbox.height)
-             * iwidth.length;
-         else if (! (0 < viewbox_height) && (iheight.unit == 
RSVG_UNIT_PERCENT))
-           viewbox_height = (viewbox_width * viewbox.height / viewbox.width)
-             * iheight.length;
+      if (explicit_width > 0 && explicit_height > 0)
+       {
+         viewbox_width = explicit_width;
+         viewbox_height = explicit_height;
        }
-      else if (has_width && has_viewbox)
+      else if (explicit_width > 0 && has_viewbox)
        {
-         viewbox_width = svg_css_length_to_pixels (iwidth, dpi,
-                                                   img->face_font_size);
-         viewbox_height = viewbox_width * viewbox.height / viewbox.width;
+         viewbox_width = explicit_width;
+         viewbox_height = explicit_width * viewbox.height / viewbox.width;
        }
-      else if (has_height && has_viewbox)
+      else if (explicit_height > 0 && has_viewbox)
        {
-         viewbox_height = svg_css_length_to_pixels (iheight, dpi,
-                                                    img->face_font_size);
-         viewbox_width = viewbox_height * viewbox.width / viewbox.height;
+         viewbox_height = explicit_height;
+         viewbox_width = explicit_height * viewbox.width / viewbox.height;
        }
       else if (has_viewbox)
        {
@@ -11354,8 +11991,15 @@ svg_load_image (struct frame *f, struct image *img, 
char *contents,
        {
          /* We haven't found a usable set of sizes, so try working out
             the visible area.  */
+
+         /* FIXME: I'm not sure exactly how librsvg uses this
+            viewport input here, so I'm not sure what values I should
+            set. */
+         RsvgRectangle max_viewport_rect = {0, 0, UINT_MAX, UINT_MAX};
+         RsvgRectangle out_logical_rect;
+
          rsvg_handle_get_geometry_for_layer (rsvg_handle, NULL,
-                                             &zero_rect, &viewbox,
+                                             &max_viewport_rect, &viewbox,
                                              &out_logical_rect, NULL);
          viewbox_width = viewbox.x + viewbox.width;
          viewbox_height = viewbox.y + viewbox.height;
@@ -11909,7 +12553,7 @@ The list of capabilities can include one or more of the 
following:
     {
 #ifdef HAVE_NATIVE_TRANSFORMS
 # if defined HAVE_IMAGEMAGICK || defined (USE_CAIRO) || defined (HAVE_NS) \
-  || defined (HAVE_HAIKU)
+  || defined (HAVE_HAIKU) | defined HAVE_ANDROID
       return list2 (Qscale, Qrotate90);
 # elif defined (HAVE_X_WINDOWS) && defined (HAVE_XRENDER)
       if (FRAME_DISPLAY_INFO (f)->xrender_supported_p)
@@ -12020,7 +12664,8 @@ static struct image_type const image_types[] =
  { SYMBOL_INDEX (Qjpeg), jpeg_image_p, jpeg_load, image_clear_image,
    IMAGE_TYPE_INIT (init_jpeg_functions) },
 #endif
-#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU || defined 
HAVE_PGTK
+#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU  \
+  || defined HAVE_PGTK || defined HAVE_ANDROID
  { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image,
    IMAGE_TYPE_INIT (init_xpm_functions) },
 #endif
@@ -12182,7 +12827,8 @@ non-numeric, there is no explicit limit on the size of 
images.  */);
   add_image_type (Qxbm);
 
 #if defined (HAVE_XPM) || defined (HAVE_NS) \
-  || defined (HAVE_HAIKU) || defined (HAVE_PGTK)
+  || defined (HAVE_HAIKU) || defined (HAVE_PGTK) \
+  || defined (HAVE_ANDROID)
   DEFSYM (Qxpm, "xpm");
   add_image_type (Qxpm);
 #endif
@@ -12207,8 +12853,10 @@ non-numeric, there is no explicit limit on the size of 
images.  */);
   add_image_type (Qpng);
 #endif
 
-#if defined (HAVE_WEBP) || (defined (HAVE_NATIVE_IMAGE_API) \
-                           && defined (HAVE_HAIKU))
+#if defined (HAVE_WEBP)                                                \
+  || (defined (HAVE_NATIVE_IMAGE_API)                          \
+      && ((defined (HAVE_NS) && defined (NS_IMPL_COCOA))       \
+         || defined (HAVE_HAIKU)))
   DEFSYM (Qwebp, "webp");
   DEFSYM (Qwebpdemux, "webpdemux");
   add_image_type (Qwebp);
diff --git a/src/inotify.c b/src/inotify.c
index 7562ffb1701..105ff5a9d8a 100644
--- a/src/inotify.c
+++ b/src/inotify.c
@@ -40,6 +40,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 # define IN_ONLYDIR 0
 #endif
 
+#ifdef HAVE_ANDROID
+#include "android.h" /* For `android_is_special_directory'.  */
+#endif /* HAVE_ANDROID */
+
 /* File handle for inotify.  */
 static int inotifyfd = -1;
 
@@ -419,6 +423,7 @@ IN_ONESHOT  */)
   int wd = -1;
   uint32_t imask = aspect_to_inotifymask (aspect);
   uint32_t mask = imask | IN_MASK_ADD | IN_EXCL_UNLINK;
+  char *name;
 
   CHECK_STRING (filename);
 
@@ -432,7 +437,19 @@ IN_ONESHOT  */)
     }
 
   encoded_file_name = ENCODE_FILE (filename);
-  wd = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
+  name = SSDATA (encoded_file_name);
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* If FILENAME actually lies in a special directory, return now
+     instead of letting inotify fail.  These directories cannot
+     receive file notifications as they are read only.  */
+
+  if (android_is_special_directory (name, "/assets")
+      || android_is_special_directory (name, "/content"))
+    return Qnil;
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+  wd = inotify_add_watch (inotifyfd, name, mask);
   if (wd < 0)
     report_file_notify_error ("Could not add watch for file", filename);
 
diff --git a/src/keyboard.c b/src/keyboard.c
index b61b1766856..6ab1cdebc12 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -44,6 +44,15 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "atimer.h"
 #include "process.h"
 #include "menu.h"
+
+#ifdef HAVE_TEXT_CONVERSION
+#include "textconv.h"
+#endif /* HAVE_TEXT_CONVERSION */
+
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif /* HAVE_ANDROID */
+
 #include <errno.h>
 
 #ifdef HAVE_PTHREAD
@@ -62,6 +71,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include "syssignal.h"
 
+#if defined HAVE_STACK_OVERFLOW_HANDLING && !defined WINDOWSNT
+#include <setjmp.h>
+#endif
+
 #include <sys/types.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -102,6 +115,13 @@ static KBOARD *all_kboards;
 /* True in the single-kboard state, false in the any-kboard state.  */
 static bool single_kboard;
 
+#ifdef HAVE_TEXT_CONVERSION
+
+/* True if a key sequence is currently being read.  */
+bool reading_key_sequence;
+
+#endif /* HAVE_TEXT_CONVERSION */
+
 /* Minimum allowed size of the recent_keys vector.  */
 #define MIN_NUM_RECENT_KEYS (100)
 
@@ -348,6 +368,10 @@ static struct timespec timer_last_idleness_start_time;
 static Lisp_Object virtual_core_pointer_name;
 static Lisp_Object virtual_core_keyboard_name;
 
+/* If not nil, ID of the last TOUCHSCREEN_END_EVENT to land on the
+   menu bar.  */
+static Lisp_Object menu_bar_touch_id;
+
 
 /* Global variable declarations.  */
 
@@ -1270,7 +1294,7 @@ some_mouse_moved (void)
 
 enum { READ_KEY_ELTS = 30 };
 static int read_key_sequence (Lisp_Object *, Lisp_Object,
-                              bool, bool, bool, bool);
+                              bool, bool, bool, bool, bool);
 static void adjust_point_for_property (ptrdiff_t, bool);
 
 static Lisp_Object
@@ -1381,7 +1405,8 @@ command_loop_1 (void)
       /* Read next key sequence; i gets its length.  */
       raw_keybuf_count = 0;
       Lisp_Object keybuf[READ_KEY_ELTS];
-      int i = read_key_sequence (keybuf, Qnil, false, true, true, false);
+      int i = read_key_sequence (keybuf, Qnil, false, true, true, false,
+                                false);
 
       /* A filter may have run while we were reading the input.  */
       if (! FRAME_LIVE_P (XFRAME (selected_frame)))
@@ -1656,7 +1681,8 @@ read_menu_command (void)
   specbind (Qecho_keystrokes, make_fixnum (0));
 
   Lisp_Object keybuf[READ_KEY_ELTS];
-  int i = read_key_sequence (keybuf, Qnil, false, true, true, true);
+  int i = read_key_sequence (keybuf, Qnil, false, true, true, true,
+                            false);
 
   unbind_to (count, Qnil);
 
@@ -3026,6 +3052,7 @@ read_char (int commandflag, Lisp_Object map,
     {
       struct buffer *prev_buffer = current_buffer;
       last_input_event = c;
+
       call4 (Qcommand_execute, tem, Qnil, Fvector (1, &last_input_event), Qt);
 
       if (CONSP (c) && !NILP (Fmemq (XCAR (c), Vwhile_no_input_ignore_events))
@@ -3588,6 +3615,11 @@ readable_events (int flags)
     return 1;
 #endif
 
+#ifdef HAVE_TEXT_CONVERSION
+  if (detect_conversion_events ())
+    return 1;
+#endif
+
   if (!(flags & READABLE_EVENTS_IGNORE_SQUEEZABLES) && some_mouse_moved ())
     return 1;
   if (single_kboard)
@@ -3920,6 +3952,11 @@ kbd_buffer_get_event (KBOARD **kbp,
 
   had_pending_selection_requests = false;
 #endif
+#ifdef HAVE_TEXT_CONVERSION
+  bool had_pending_conversion_events;
+
+  had_pending_conversion_events = false;
+#endif
 
 #ifdef subprocesses
   if (kbd_on_hold_p () && kbd_buffer_nr_stored () < KBD_BUFFER_SIZE / 4)
@@ -3983,6 +4020,13 @@ kbd_buffer_get_event (KBOARD **kbp,
          had_pending_selection_requests = true;
          break;
        }
+#endif
+#ifdef HAVE_TEXT_CONVERSION
+      if (detect_conversion_events ())
+       {
+         had_pending_conversion_events = true;
+         break;
+       }
 #endif
       if (end_time)
        {
@@ -4039,6 +4083,24 @@ kbd_buffer_get_event (KBOARD **kbp,
       return first;
     }
 
+#ifdef HAVE_TEXT_CONVERSION
+  /* There are pending text conversion operations.  Text conversion
+     events should be generated before processing any other keyboard
+     input.  */
+  if (had_pending_conversion_events)
+    {
+      handle_pending_conversion_events ();
+      obj = Qtext_conversion;
+
+      /* See the comment in handle_pending_conversion_events_1.
+         Note that in addition, text conversion events are not
+         generated if no edits were actually made.  */
+      if (conversion_disabled_p ()
+         || NILP (Vtext_conversion_edits))
+       obj = Qnil;
+    }
+  else
+#endif
   /* At this point, we know that there is a readable event available
      somewhere.  If the event queue is empty, then there must be a
      mouse movement enabled and available.  */
@@ -4771,7 +4833,8 @@ The value when Emacs is idle is a Lisp timestamp in the 
style of
 
 The value when Emacs is not idle is nil.
 
-PSEC is a multiple of the system clock resolution.  */)
+If the value is a list of four integers (HIGH LOW USEC PSEC), then PSEC
+is a multiple of the system clock resolution.  */)
   (void)
 {
   if (timespec_valid_p (timer_idleness_start_time))
@@ -4923,7 +4986,74 @@ static const char *const lispy_accent_keys[] =
   "dead-horn",
 };
 
-#ifdef HAVE_NTGUI
+#ifdef HAVE_ANDROID
+#define FUNCTION_KEY_OFFSET 0
+
+const char *const lispy_function_keys[] =
+  {
+    /* All elements in this array default to 0, except for the few
+       function keys that Emacs recognizes.  */
+    [111] = "escape",
+    [112] = "delete",
+    [120] = "sysrq",
+    [121] = "break",
+    [122] = "home",
+    [123] = "end",
+    [124] = "insert",
+    [126] = "media-play",
+    [127] = "media-pause",
+    [130] = "media-record",
+    [131] = "f1",
+    [132] = "f2",
+    [133] = "f3",
+    [134] = "f4",
+    [135] = "f5",
+    [136] = "f6",
+    [137] = "f7",
+    [138] = "f8",
+    [139] = "f9",
+    [140] = "f10",
+    [141] = "f11",
+    [142] = "f12",
+    [160] = "kp-ret",
+    [164] = "volume-mute",
+    [19]  = "up",
+    [20]  = "down",
+    [213] = "muhenkan",
+    [214] = "henkan",
+    [215] = "hiragana-katakana",
+    [218] = "kana",
+    [21]  = "left",
+    [22]  = "right",
+    [24]  = "volume-up",
+    [259] = "help",
+    [25]  = "volume-down",
+    [268] = "kp-up-left",
+    [269] = "kp-down-left",
+    [270] = "kp-up-right",
+    [271] = "kp-down-right",
+    [272] = "media-skip-forward",
+    [273] = "media-skip-backward",
+    [277] = "cut",
+    [278] = "copy",
+    [279] = "paste",
+    [28]  = "clear",
+    [4]          = "XF86Back",
+    [61]  = "tab",
+    [66]  = "return",
+    [67]  = "backspace",
+    [82]  = "menu",
+    [84]  = "find",
+    [85]  = "media-play-pause",
+    [86]  = "media-stop",
+    [87]  = "media-next",
+    [88]  = "media-previous",
+    [89]  = "media-rewind",
+    [92]  = "prior",
+    [93]  = "next",
+  };
+
+#elif defined HAVE_NTGUI
 #define FUNCTION_KEY_OFFSET 0x0
 
 const char *const lispy_function_keys[] =
@@ -5760,6 +5890,29 @@ coords_in_menu_bar_window (struct frame *f, int x, int y)
 
 #endif
 
+#ifdef HAVE_WINDOW_SYSTEM
+
+/* Return whether or not the coordinates X and Y are inside the
+   tab-bar window of the given frame F.  */
+
+static bool
+coords_in_tab_bar_window (struct frame *f, int x, int y)
+{
+  struct window *window;
+
+  if (!WINDOWP (f->tab_bar_window))
+    return false;
+
+  window = XWINDOW (f->tab_bar_window);
+
+  return (y >= WINDOW_TOP_EDGE_Y (window)
+         && x >= WINDOW_LEFT_EDGE_X (window)
+         && y <= WINDOW_BOTTOM_EDGE_Y (window)
+         && x <= WINDOW_RIGHT_EDGE_X (window));
+}
+
+#endif /* HAVE_WINDOW_SYSTEM */
+
 /* Given a struct input_event, build the lisp event which represents
    it.  If EVENT is 0, build a mouse movement event from the mouse
    movement buffer, which should have a movement event in it.
@@ -6069,6 +6222,11 @@ make_lispy_event (struct input_event *event)
                          }
                      }
 
+                   /* Don't generate a menu bar event if ITEM is
+                      nil.  */
+                   if (NILP (item))
+                     return Qnil;
+
                    /* ELisp manual 2.4b says (x y) are window
                       relative but code says they are
                       frame-relative.  */
@@ -6404,22 +6562,206 @@ make_lispy_event (struct input_event *event)
       }
 
     case TOUCHSCREEN_BEGIN_EVENT:
+      {
+       Lisp_Object x, y, id, position;
+       struct frame *f;
+#ifdef HAVE_WINDOW_SYSTEM
+       int tab_bar_item;
+       bool close;
+#endif /* HAVE_WINDOW_SYSTEM */
+
+       f = XFRAME (event->frame_or_window);
+
+       if (!FRAME_LIVE_P (f))
+         return Qnil;
+
+       id = event->arg;
+       x = event->x;
+       y = event->y;
+
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
+       if (coords_in_menu_bar_window (f, XFIXNUM (x), XFIXNUM (y)))
+         {
+           /* If the tap began in the menu bar window, then save the
+              id.  */
+           menu_bar_touch_id = id;
+           return Qnil;
+         }
+#endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */
+
+       position = make_lispy_position (f, x, y, event->timestamp);
+
+#ifdef HAVE_WINDOW_SYSTEM
+
+       /* Now check if POSITION lies on the tab bar.  If so, look up
+          the corresponding tab bar item's propertized string as the
+          OBJECT.  */
+
+       if (coords_in_tab_bar_window (f, XFIXNUM (event->x),
+                                     XFIXNUM (event->y))
+           /* `get_tab_bar_item_kbd' returns 0 if the item was
+              previously highlighted, 1 otherwise, and -1 if there is
+              no tab bar item.  */
+           && get_tab_bar_item_kbd (f, XFIXNUM (event->x),
+                                    XFIXNUM (event->y), &tab_bar_item,
+                                    &close) >= 0)
+         {
+           /* First, obtain the propertized string.  */
+           x = Fcopy_sequence (AREF (f->tab_bar_items,
+                                     (tab_bar_item
+                                      + TAB_BAR_ITEM_CAPTION)));
+
+           /* Next, add the key binding.  */
+           AUTO_LIST2 (y, Qmenu_item, list3 (AREF (f->tab_bar_items,
+                                                   (tab_bar_item
+                                                    + TAB_BAR_ITEM_KEY)),
+                                             AREF (f->tab_bar_items,
+                                                   (tab_bar_item
+                                                    + TAB_BAR_ITEM_BINDING)),
+                                             close ? Qt : Qnil));
+
+           /* And add the new properties to the propertized string.  */
+           Fadd_text_properties (make_fixnum (0),
+                                 make_fixnum (SCHARS (x)),
+                                 y, x);
+
+           /* Set the position to 0.  */
+           x = Fcons (x, make_fixnum (0));
+
+           /* Finally, add the OBJECT.  */
+           position = nconc2 (position, Fcons (x, Qnil));
+         }
+
+#endif /* HAVE_WINDOW_SYSTEM */
+
+       return list2 (Qtouchscreen_begin,
+                     Fcons (id, position));
+      }
+
     case TOUCHSCREEN_END_EVENT:
       {
        Lisp_Object x, y, id, position;
        struct frame *f = XFRAME (event->frame_or_window);
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
+       int column, row, dummy;
+#endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */
+#ifdef HAVE_WINDOW_SYSTEM
+       int tab_bar_item;
+       bool close;
+#endif /* HAVE_WINDOW_SYSTEM */
+
+       if (!FRAME_LIVE_P (f))
+         return Qnil;
 
        id = event->arg;
        x = event->x;
        y = event->y;
 
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
+       if (EQ (menu_bar_touch_id, id))
+         {
+           /* This touch should activate the menu bar.  Generate the
+              menu bar event.  */
+           menu_bar_touch_id = Qnil;
+
+           if (!NILP (f->menu_bar_window))
+             {
+               x_y_to_hpos_vpos (XWINDOW (f->menu_bar_window), XFIXNUM (x),
+                                 XFIXNUM (y), &column, &row, NULL, NULL,
+                                 &dummy);
+
+               if (row >= 0 && row < FRAME_MENU_BAR_LINES (f))
+                 {
+                   Lisp_Object items, item;
+
+                   /* Find the menu bar item under `column'.  */
+                   item = Qnil;
+                   items = FRAME_MENU_BAR_ITEMS (f);
+                   for (i = 0; i < ASIZE (items); i += 4)
+                     {
+                       Lisp_Object pos, string;
+                       string = AREF (items, i + 1);
+                       pos = AREF (items, i + 3);
+                       if (NILP (string))
+                         break;
+                       if (column >= XFIXNUM (pos)
+                           && column < XFIXNUM (pos) + SCHARS (string))
+                         {
+                           item = AREF (items, i);
+                           break;
+                         }
+                     }
+
+                   /* Don't generate a menu bar event if ITEM is
+                      nil.  */
+                   if (NILP (item))
+                     return Qnil;
+
+                   /* ELisp manual 2.4b says (x y) are window
+                      relative but code says they are
+                      frame-relative.  */
+                   position = list4 (event->frame_or_window,
+                                     Qmenu_bar,
+                                     Fcons (event->x, event->y),
+                                     INT_TO_INTEGER (event->timestamp));
+
+                   return list2 (item, position);
+                 }
+             }
+
+           return Qnil;
+         }
+#endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */
+
        position = make_lispy_position (f, x, y, event->timestamp);
 
-       return list2 (((event->kind
-                       == TOUCHSCREEN_BEGIN_EVENT)
-                      ? Qtouchscreen_begin
-                      : Qtouchscreen_end),
-                     Fcons (id, position));
+#ifdef HAVE_WINDOW_SYSTEM
+
+       /* Now check if POSITION lies on the tab bar.  If so, look up
+          the corresponding tab bar item's propertized string as the
+          OBJECT.  */
+
+       if (coords_in_tab_bar_window (f, XFIXNUM (event->x),
+                                     XFIXNUM (event->y))
+           /* `get_tab_bar_item_kbd' returns 0 if the item was
+              previously highlighted, 1 otherwise, and -1 if there is
+              no tab bar item.  */
+           && get_tab_bar_item_kbd (f, XFIXNUM (event->x),
+                                    XFIXNUM (event->y), &tab_bar_item,
+                                    &close) >= 0)
+         {
+           /* First, obtain the propertized string.  */
+           x = Fcopy_sequence (AREF (f->tab_bar_items,
+                                     (tab_bar_item
+                                      + TAB_BAR_ITEM_CAPTION)));
+
+           /* Next, add the key binding.  */
+           AUTO_LIST2 (y, Qmenu_item, list3 (AREF (f->tab_bar_items,
+                                                   (tab_bar_item
+                                                    + TAB_BAR_ITEM_KEY)),
+                                             AREF (f->tab_bar_items,
+                                                   (tab_bar_item
+                                                    + TAB_BAR_ITEM_BINDING)),
+                                             close ? Qt : Qnil));
+
+           /* And add the new properties to the propertized string.  */
+           Fadd_text_properties (make_fixnum (0),
+                                 make_fixnum (SCHARS (x)),
+                                 y, x);
+
+           /* Set the position to 0.  */
+           x = Fcons (x, make_fixnum (0));
+
+           /* Finally, add the OBJECT.  */
+           position = nconc2 (position, Fcons (x, Qnil));
+         }
+
+#endif /* HAVE_WINDOW_SYSTEM */
+
+       position = make_lispy_position (f, x, y, event->timestamp);
+
+       return list3 (Qtouchscreen_end, Fcons (id, position),
+                     event->modifiers ? Qt : Qnil);
       }
 
     case PINCH_EVENT:
@@ -6444,6 +6786,9 @@ make_lispy_event (struct input_event *event)
        struct frame *f = XFRAME (event->frame_or_window);
        evt = Qnil;
 
+       if (!FRAME_LIVE_P (f))
+         return Qnil;
+
        for (tem = event->arg; CONSP (tem); tem = XCDR (tem))
          {
            it = XCAR (tem);
@@ -6452,10 +6797,19 @@ make_lispy_event (struct input_event *event)
            y = XCAR (XCDR (it));
            id = XCAR (XCDR (XCDR (it)));
 
+           /* Don't report touches to the menu bar.  */
+           if (EQ (id, menu_bar_touch_id))
+             continue;
+
            position = make_lispy_position (f, x, y, event->timestamp);
            evt = Fcons (Fcons (id, position), evt);
          }
 
+       if (NILP (evt))
+         /* Don't return an event if the touchpoint list is
+            empty.  */
+         return Qnil;
+
        return list2 (Qtouchscreen_update, evt);
       }
 
@@ -7655,6 +8009,14 @@ tty_read_avail_input (struct terminal *terminal,
 static void
 handle_async_input (void)
 {
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* Check and respond to an ``urgent'' query from the UI thread.
+     A query becomes urgent once the UI thread has been waiting
+     for more than two seconds.  */
+
+  android_check_query_urgent ();
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY */
+
 #ifndef DOS_NT
   while (1)
     {
@@ -7723,6 +8085,16 @@ totally_unblock_input (void)
 void
 handle_input_available_signal (int sig)
 {
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  /* Make all writes from the Android UI thread visible.  If
+     `android_urgent_query' has been set, preceding writes to query
+     related variables should become observable here on as well.  */
+#if defined __aarch64__
+  asm ("dmb ishst");
+#else /* !defined __aarch64__ */
+  __atomic_thread_fence (__ATOMIC_SEQ_CST);
+#endif /* defined __aarch64__ */
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY */
   pending_signals = true;
 
   if (input_available_clear_time)
@@ -9052,7 +9424,13 @@ set_prop (ptrdiff_t idx, Lisp_Object val)
 
    - `:label LABEL-STRING'.
 
-   A text label to show with the tool bar button if labels are enabled.  */
+   A text label to show with the tool bar button if labels are
+   enabled.
+
+   - `:wrap WRAP'
+
+   WRAP specifies whether to hide this item but display subsequent
+   tool bar items on a new line.  */
 
 static bool
 parse_tool_bar_item (Lisp_Object key, Lisp_Object item)
@@ -9060,7 +9438,15 @@ parse_tool_bar_item (Lisp_Object key, Lisp_Object item)
   Lisp_Object filter = Qnil;
   Lisp_Object caption;
   int i;
-  bool have_label = false;
+  bool have_label;
+#ifndef HAVE_EXT_TOOL_BAR
+  bool is_wrap;
+#endif /* HAVE_EXT_TOOL_BAR */
+
+  have_label = false;
+#ifndef HAVE_EXT_TOOL_BAR
+  is_wrap = false;
+#endif /* HAVE_EXT_TOOL_BAR */
 
   /* Definition looks like `(menu-item CAPTION BINDING PROPS...)'.
      Rule out items that aren't lists, don't start with
@@ -9196,6 +9582,20 @@ parse_tool_bar_item (Lisp_Object key, Lisp_Object item)
       else if (EQ (ikey, QCrtl))
         /* ':rtl STRING' */
        set_prop (TOOL_BAR_ITEM_RTL_IMAGE, value);
+      else if (EQ (ikey, QCwrap))
+       {
+#ifndef HAVE_EXT_TOOL_BAR
+         /* This specifies whether the tool bar item should be hidden
+            but cause subsequent items to be displayed on a new
+            line.  */
+         set_prop (TOOL_BAR_ITEM_WRAP, value);
+         is_wrap = !NILP (value);
+#else /* HAVE_EXT_TOOL_BAR */
+         /* Line wrapping isn't supported on builds utilizing
+            external tool bars.  */
+         return false;
+#endif /* !HAVE_EXT_TOOL_BAR */
+       }
     }
 
 
@@ -9256,6 +9656,15 @@ parse_tool_bar_item (Lisp_Object key, Lisp_Object item)
   if (CONSP (get_keymap (PROP (TOOL_BAR_ITEM_BINDING), 0, 1)))
     return 0;
 
+
+#ifndef HAVE_EXT_TOOL_BAR
+  /* If the menu item is actually a line wrap, make sure it isn't
+     visible or enabled.  */
+
+  if (is_wrap)
+    set_prop (TOOL_BAR_ITEM_ENABLED_P, Qnil);
+#endif /* !HAVE_EXT_TOOL_BAR */
+
   /* If there is a key binding, add it to the help, which will be
      displayed as a tooltip for this entry. */
   Lisp_Object binding = PROP (TOOL_BAR_ITEM_BINDING);
@@ -9677,13 +10086,18 @@ typedef struct keyremap
    If the mapping is a function and DO_FUNCALL is true,
    the function is called with PROMPT as parameter and its return
    value is used as the return value of this function (after checking
-   that it is indeed a vector).  */
+   that it is indeed a vector).
+
+   START and END are the indices of the first and last key of the
+   sequence being remapped within the keyboard buffer KEYBUF.  */
 
 static Lisp_Object
 access_keymap_keyremap (Lisp_Object map, Lisp_Object key, Lisp_Object prompt,
-                       bool do_funcall)
+                       bool do_funcall, unsigned int start, unsigned int end,
+                       Lisp_Object *keybuf)
 {
   Lisp_Object next;
+  specpdl_ref count;
 
   next = access_keymap (map, key, 1, 0, 1);
 
@@ -9699,10 +10113,18 @@ access_keymap_keyremap (Lisp_Object map, Lisp_Object 
key, Lisp_Object prompt,
      its value instead.  */
   if (do_funcall && FUNCTIONP (next))
     {
-      Lisp_Object tem;
+      Lisp_Object tem, remap;
       tem = next;
 
-      next = call1 (next, prompt);
+      /* Build Vcurrent_key_remap_sequence.  */
+      remap = Fvector (end - start + 1, keybuf + start);
+
+      /* Bind `current-key-remap-sequence' to the key sequence being
+        remapped.  */
+      count = SPECPDL_INDEX ();
+      specbind (Qcurrent_key_remap_sequence, remap);
+      next = unbind_to (count, call1 (next, prompt));
+
       /* If the function returned something invalid,
         barf--don't ignore it.  */
       if (! (NILP (next) || VECTORP (next) || STRINGP (next)))
@@ -9727,11 +10149,17 @@ keyremap_step (Lisp_Object *keybuf, volatile keyremap 
*fkey,
               int input, bool doit, int *diff, Lisp_Object prompt)
 {
   Lisp_Object next, key;
+  ptrdiff_t buf_start, buf_end;
+
+  /* Save the key sequence being translated.  */
+  buf_start = fkey->start;
+  buf_end = fkey->end;
 
   key = keybuf[fkey->end++];
 
   if (KEYMAPP (fkey->parent))
-    next = access_keymap_keyremap (fkey->map, key, prompt, doit);
+    next = access_keymap_keyremap (fkey->map, key, prompt, doit,
+                                  buf_start, buf_end, keybuf);
   else
     next = Qnil;
 
@@ -9792,6 +10220,24 @@ void init_raw_keybuf_count (void)
   raw_keybuf_count = 0;
 }
 
+
+
+#ifdef HAVE_TEXT_CONVERSION
+
+static void
+restore_reading_key_sequence (int old_reading_key_sequence)
+{
+  reading_key_sequence = old_reading_key_sequence;
+
+  /* If a key sequence is no longer being read, reset input methods
+     whose state changes were postponed.  */
+
+  if (!old_reading_key_sequence)
+    check_postponed_buffers ();
+}
+
+#endif /* HAVE_TEXT_CONVERSION */
+
 /* Read a sequence of keys that ends with a non prefix character,
    storing it in KEYBUF, a buffer of size READ_KEY_ELTS.
    Prompt with PROMPT.
@@ -9830,12 +10276,16 @@ void init_raw_keybuf_count (void)
    read_char will return it.
 
    If FIX_CURRENT_BUFFER, we restore current_buffer
-   from the selected window's buffer.  */
+   from the selected window's buffer.
+
+   If DISABLE_TEXT_CONVERSION_P, disable text conversion so the input
+   method will always send key events.  */
 
 static int
 read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt,
                   bool dont_downcase_last, bool can_return_switch_frame,
-                  bool fix_current_buffer, bool prevent_redisplay)
+                  bool fix_current_buffer, bool prevent_redisplay,
+                  bool disable_text_conversion_p)
 {
   specpdl_ref count = SPECPDL_INDEX ();
 
@@ -9900,6 +10350,13 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object 
prompt,
   /* Gets around Microsoft compiler limitations.  */
   bool dummyflag = false;
 
+#ifdef HAVE_TEXT_CONVERSION
+  bool disabled_conversion;
+
+  /* Whether or not text conversion has already been disabled.  */
+  disabled_conversion = false;
+#endif /* HAVE_TEXT_CONVERSION */
+
   struct buffer *starting_buffer;
 
   /* List of events for which a fake prefix key has been generated.  */
@@ -9944,6 +10401,16 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object 
prompt,
   keys_start = this_command_key_count;
   this_single_command_key_start = keys_start;
 
+#ifdef HAVE_TEXT_CONVERSION
+  /* Set `reading_key_sequence' to true.  This variable is used by
+     Fset_text_conversion_style to determine if it should postpone
+     resetting the input method until this function completes.  */
+
+  record_unwind_protect_int (restore_reading_key_sequence,
+                            reading_key_sequence);
+  reading_key_sequence = true;
+#endif /* HAVE_TEXT_CONVERSION */
+
   /* We jump here when we need to reinitialize fkey and keytran; this
      happens if we switch keyboards between rescans.  */
  replay_entire_sequence:
@@ -9982,6 +10449,18 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object 
prompt,
   if (INTERACTIVE && t < mock_input)
     echo_truncate (echo_start);
 
+  /* If text conversion is supposed to be disabled immediately, do it
+     now.  */
+
+#ifdef HAVE_TEXT_CONVERSION
+  if (disable_text_conversion_p)
+    {
+      disable_text_conversion ();
+      record_unwind_protect_void (resume_text_conversion);
+      disabled_conversion = true;
+    }
+#endif /* HAVE_TEXT_CONVERSION */
+
   /* If the best binding for the current key sequence is a keymap, or
      we may be looking at a function key's escape sequence, keep on
      reading.  */
@@ -10049,6 +10528,43 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object 
prompt,
        echo_local_start = echo_length ();
       keys_local_start = this_command_key_count;
 
+#ifdef HAVE_TEXT_CONVERSION
+      /* When reading a key sequence while text conversion is in
+        effect, turn it off after the first actual character read.
+        This makes input methods send actual key events instead.
+
+         Make sure only to do this once.  Also, disabling text
+         conversion seems to interact badly with menus, so don't
+         disable text conversion if a menu was displayed.  */
+
+      if (!disabled_conversion && t && !used_mouse_menu
+         && !disable_inhibit_text_conversion)
+       {
+         int i;
+
+         /* used_mouse_menu isn't set if a menu bar prefix key has
+            just been stored.  It appears necessary to look for a
+            prefix key itself.  Don't look through too many keys for
+            efficiency reasons.  */
+
+         for (i = 0; i < min (t, 10); ++i)
+           {
+             if (NUMBERP (keybuf[i])
+                 || (SYMBOLP (keybuf[i])
+                     && EQ (Fget (keybuf[i], Qevent_kind),
+                            Qfunction_key)))
+               goto disable_text_conversion;
+           }
+
+         goto replay_key;
+
+       disable_text_conversion:
+         disable_text_conversion ();
+         record_unwind_protect_void (resume_text_conversion);
+         disabled_conversion = true;
+       }
+#endif
+
     replay_key:
       /* These are no-ops, unless we throw away a keystroke below and
         jumped back up to replay_key; in that case, these restore the
@@ -10276,7 +10792,7 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object 
prompt,
       if (EVENT_HAS_PARAMETERS (key))
        {
          Lisp_Object kind = EVENT_HEAD_KIND (EVENT_HEAD (key));
-         if (EQ (kind, Qmouse_click))
+         if (EQ (kind, Qmouse_click) || EQ (kind, Qtouchscreen))
            {
              Lisp_Object window = POSN_WINDOW (EVENT_START (key));
              Lisp_Object posn = POSN_POSN (EVENT_START (key));
@@ -10354,7 +10870,15 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object 
prompt,
              posn = POSN_POSN (xevent_start (key));
              /* Handle menu-bar events:
                 insert the dummy prefix event `menu-bar'.  */
-             if (EQ (posn, Qmenu_bar) || EQ (posn, Qtab_bar) || EQ (posn, 
Qtool_bar))
+             if ((EQ (posn, Qmenu_bar) || EQ (posn, Qtab_bar)
+                  || EQ (posn, Qtool_bar))
+                 /* Only insert the prefix key if the event comes
+                    directly from the keyboard buffer.  Key
+                    translation functions might return events with a
+                    `posn-area' of tool-bar or tab-bar without
+                    intending for these prefix events to be
+                    generated.  */
+                 && (mock_input <= t))
                {
                  if (READ_KEY_ELTS - t <= 1)
                    error ("Key sequence too long");
@@ -10771,7 +11295,8 @@ static Lisp_Object
 read_key_sequence_vs (Lisp_Object prompt, Lisp_Object continue_echo,
                      Lisp_Object dont_downcase_last,
                      Lisp_Object can_return_switch_frame,
-                     Lisp_Object cmd_loop, bool allow_string)
+                     Lisp_Object cmd_loop, bool allow_string,
+                     bool disable_text_conversion)
 {
   specpdl_ref count = SPECPDL_INDEX ();
 
@@ -10798,7 +11323,8 @@ read_key_sequence_vs (Lisp_Object prompt, Lisp_Object 
continue_echo,
   raw_keybuf_count = 0;
   Lisp_Object keybuf[READ_KEY_ELTS];
   int i = read_key_sequence (keybuf, prompt, ! NILP (dont_downcase_last),
-                            ! NILP (can_return_switch_frame), false, false);
+                            ! NILP (can_return_switch_frame), false, false,
+                            disable_text_conversion);
 
 #if 0  /* The following is fine for code reading a key sequence and
          then proceeding with a lengthy computation, but it's not good
@@ -10820,7 +11346,7 @@ read_key_sequence_vs (Lisp_Object prompt, Lisp_Object 
continue_echo,
                     (i, keybuf)));
 }
 
-DEFUN ("read-key-sequence", Fread_key_sequence, Sread_key_sequence, 1, 5, 0,
+DEFUN ("read-key-sequence", Fread_key_sequence, Sread_key_sequence, 1, 6, 0,
        doc: /* Read a sequence of keystrokes and return as a string or vector.
 The sequence is sufficient to specify a non-prefix command in the
 current local and global maps.
@@ -10866,20 +11392,31 @@ sequences, where they wouldn't conflict with ordinary 
bindings.  See
 The optional fifth argument CMD-LOOP, if non-nil, means
 that this key sequence is being read by something that will
 read commands one after another.  It should be nil if the caller
-will read just one key sequence.  */)
-  (Lisp_Object prompt, Lisp_Object continue_echo, Lisp_Object 
dont_downcase_last, Lisp_Object can_return_switch_frame, Lisp_Object cmd_loop)
+will read just one key sequence.
+
+The optional sixth argument DISABLE-TEXT-CONVERSION, if non-nil, means
+disable input method text conversion for the duration of reading this
+key sequence, and that keyboard input will always result in key events
+being sent.  */)
+  (Lisp_Object prompt, Lisp_Object continue_echo, Lisp_Object 
dont_downcase_last,
+   Lisp_Object can_return_switch_frame, Lisp_Object cmd_loop,
+   Lisp_Object disable_text_conversion)
 {
   return read_key_sequence_vs (prompt, continue_echo, dont_downcase_last,
-                              can_return_switch_frame, cmd_loop, true);
+                              can_return_switch_frame, cmd_loop, true,
+                              !NILP (disable_text_conversion));
 }
 
 DEFUN ("read-key-sequence-vector", Fread_key_sequence_vector,
-       Sread_key_sequence_vector, 1, 5, 0,
+       Sread_key_sequence_vector, 1, 6, 0,
        doc: /* Like `read-key-sequence' but always return a vector.  */)
-  (Lisp_Object prompt, Lisp_Object continue_echo, Lisp_Object 
dont_downcase_last, Lisp_Object can_return_switch_frame, Lisp_Object cmd_loop)
+  (Lisp_Object prompt, Lisp_Object continue_echo, Lisp_Object 
dont_downcase_last,
+   Lisp_Object can_return_switch_frame, Lisp_Object cmd_loop,
+   Lisp_Object disable_text_conversion)
 {
   return read_key_sequence_vs (prompt, continue_echo, dont_downcase_last,
-                              can_return_switch_frame, cmd_loop, false);
+                              can_return_switch_frame, cmd_loop, false,
+                              !NILP (disable_text_conversion));
 }
 
 /* Return true if input events are pending.  */
@@ -11120,8 +11657,8 @@ the command loop or by `read-key-sequence'.
 The value is always a vector.  */)
   (void)
 {
-  return Fvector (this_command_key_count
-                 - this_single_command_key_start,
+  ptrdiff_t nkeys = this_command_key_count - this_single_command_key_start;
+  return Fvector (nkeys < 0 ? 0 : nkeys,
                  (XVECTOR (this_command_keys)->contents
                   + this_single_command_key_start));
 }
@@ -11188,7 +11725,7 @@ This may include sensitive information such as 
passwords.  */)
   if (dribble)
     {
       block_input ();
-      fclose (dribble);
+      emacs_fclose (dribble);
       unblock_input ();
       dribble = 0;
     }
@@ -11201,9 +11738,9 @@ This may include sensitive information such as 
passwords.  */)
       encfile = ENCODE_FILE (file);
       fd = emacs_open (SSDATA (encfile), O_WRONLY | O_CREAT | O_EXCL, 0600);
       if (fd < 0 && errno == EEXIST
-         && (unlink (SSDATA (encfile)) == 0 || errno == ENOENT))
+         && (emacs_unlink (SSDATA (encfile)) == 0 || errno == ENOENT))
        fd = emacs_open (SSDATA (encfile), O_WRONLY | O_CREAT | O_EXCL, 0600);
-      dribble = fd < 0 ? 0 : fdopen (fd, "w");
+      dribble = fd < 0 ? 0 : emacs_fdopen (fd, "w");
       if (dribble == 0)
        report_file_error ("Opening dribble", file);
     }
@@ -12087,7 +12624,10 @@ static const struct event_head head_table[] = {
   {SYMBOL_INDEX (Qmake_frame_visible),  SYMBOL_INDEX (Qmake_frame_visible)},
   /* `select-window' should be handled just like `switch-frame'
      in read_key_sequence.  */
-  {SYMBOL_INDEX (Qselect_window),       SYMBOL_INDEX (Qswitch_frame)}
+  {SYMBOL_INDEX (Qselect_window),       SYMBOL_INDEX (Qswitch_frame)},
+  /* Touchscreen events should be prefixed by the posn.  */
+  {SYMBOL_INDEX (Qtouchscreen_begin),  SYMBOL_INDEX (Qtouchscreen)},
+  {SYMBOL_INDEX (Qtouchscreen_end),    SYMBOL_INDEX (Qtouchscreen)},
 };
 
 static Lisp_Object
@@ -12163,6 +12703,7 @@ syms_of_keyboard (void)
   DEFSYM (Qhelp_echo, "help-echo");
   DEFSYM (Qhelp_echo_inhibit_substitution, "help-echo-inhibit-substitution");
   DEFSYM (QCrtl, ":rtl");
+  DEFSYM (QCwrap, ":wrap");
 
   staticpro (&item_properties);
   item_properties = Qnil;
@@ -12433,6 +12974,9 @@ syms_of_keyboard (void)
   virtual_core_keyboard_name = Qnil;
   staticpro (&virtual_core_keyboard_name);
 
+  menu_bar_touch_id = Qnil;
+  staticpro (&menu_bar_touch_id);
+
   defsubr (&Scurrent_idle_time);
   defsubr (&Sevent_symbol_parse_modifiers);
   defsubr (&Sevent_convert_list);
@@ -12792,6 +13336,10 @@ See also `pre-command-hook'.  */);
          "display-monitors-changed-functions");
 
   DEFSYM (Qcoding, "coding");
+  DEFSYM (Qtouchscreen, "touchscreen");
+#ifdef HAVE_TEXT_CONVERSION
+  DEFSYM (Qtext_conversion, "text-conversion");
+#endif
 
   Fset (Qecho_area_clear_hook, Qnil);
 
@@ -13166,9 +13714,25 @@ which see.  */);
   DEFVAR_LISP ("post-select-region-hook", Vpost_select_region_hook,
     doc: /* Abnormal hook run after the region is selected.
 This usually happens as a result of `select-active-regions'.  The hook
-is called with one argument, the string that was selected.  */);;
+is called with one argument, the string that was selected.  */);
   Vpost_select_region_hook = Qnil;
 
+  DEFVAR_BOOL ("disable-inhibit-text-conversion",
+              disable_inhibit_text_conversion,
+    doc: /* Don't disable text conversion inside `read-key-sequence'.
+If non-nil, text conversion will continue to happen after a prefix
+key has been read inside `read-key-sequence'.  */);
+  disable_inhibit_text_conversion = false;
+
+  DEFVAR_LISP ("current-key-remap-sequence",
+              Vcurrent_key_remap_sequence,
+    doc: /* The key sequence currently being remap, or nil.
+Bound to a vector containing the sub-sequence matching a binding
+within `input-decode-map' or `local-function-key-map' when its bound
+function is called to remap that sequence.  */);
+  Vcurrent_key_remap_sequence = Qnil;
+  DEFSYM (Qcurrent_key_remap_sequence, "current-key-remap-sequence");
+
   pdumper_do_now_and_after_load (syms_of_keyboard_for_pdumper);
 }
 
diff --git a/src/keyboard.h b/src/keyboard.h
index 3f86a8e03ad..9f6e65f9a09 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -246,6 +246,14 @@ extern KBOARD *initial_kboard;
    kboard, but doing so requires throwing to wrong_kboard_jmpbuf.  */
 extern KBOARD *current_kboard;
 
+
+#ifdef HAVE_TEXT_CONVERSION
+
+/* True if a key sequence is currently being read.  */
+extern bool reading_key_sequence;
+
+#endif /* HAVE_TEXT_CONVERSION */
+
 /* Total number of times read_char has returned, modulo UINTMAX_MAX + 1.  */
 extern uintmax_t num_input_events;
 
@@ -395,8 +403,17 @@ extern void unuse_menu_items (void);
 #define EVENT_HEAD(event) \
   (EVENT_HAS_PARAMETERS (event) ? XCAR (event) : (event))
 
-/* Extract the starting and ending positions from a composite event.  */
-#define EVENT_START(event) (CAR_SAFE (CDR_SAFE (event)))
+/* Extract the starting and ending positions from a composite event. */
+
+/* Unlike Lisp `event-start', this also handles touch screen events,
+   which are not actually mouse events in the general sense.  */
+#define EVENT_START(event)                             \
+  ((EQ (EVENT_HEAD (event), Qtouchscreen_begin)                \
+    || EQ (EVENT_HEAD (event), Qtouchscreen_end))      \
+   ? CDR_SAFE (CAR_SAFE (CDR_SAFE (event)))            \
+   : CAR_SAFE (CDR_SAFE (event)))
+
+/* This does not handle touchscreen events.  */
 #define EVENT_END(event) (CAR_SAFE (CDR_SAFE (CDR_SAFE (event))))
 
 /* Extract the click count from a multi-click event.  */
diff --git a/src/keymap.c b/src/keymap.c
index da2af98c2d6..1f863885003 100644
--- a/src/keymap.c
+++ b/src/keymap.c
@@ -1065,8 +1065,12 @@ possibly_translate_key_sequence (Lisp_Object key, 
ptrdiff_t *length)
        xsignal2 (Qerror,
                  build_string ("`key-valid-p' is not defined, so this syntax 
can't be used: %s"),
                  key);
+      /* If key-valid-p is unhappy about KEY, we return it as-is.
+         This happens when menu items define as bindings strings that
+         should be inserted into the buffer, not commands.  See
+         bug#64927, for example.  */
       if (NILP (call1 (Qkey_valid_p, AREF (key, 0))))
-       xsignal2 (Qerror, build_string ("Invalid `key-parse' syntax: %S"), key);
+       return key;
       key = call1 (Qkey_parse, AREF (key, 0));
       *length = CHECK_VECTOR_OR_STRING (key);
       if (*length == 0)
diff --git a/src/lisp.h b/src/lisp.h
index ab91341603f..6b8b5c8910f 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -29,6 +29,11 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <float.h>
 #include <inttypes.h>
 #include <limits.h>
+#include <stdio.h>
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
 
 #include <attribute.h>
 #include <count-leading-zeros.h>
@@ -332,7 +337,14 @@ typedef EMACS_INT Lisp_Word;
    see these functions for commentary.  */
 
 /* Convert among the various Lisp-related types: I for EMACS_INT, L
-   for Lisp_Object, P for void *.  */
+   for Lisp_Object, P for void *.
+
+   These use the following mnemonics:
+
+   XLI: Lisp_Object to Integer;
+   XIL: Integer to Lisp_Object;
+   XLP: Lisp_Object to Pointer.  */
+
 #if !CHECK_LISP_OBJECT_TYPE
 # if LISP_WORDS_ARE_POINTERS
 #  define lisp_h_XLI(o) ((EMACS_INT) (o))
@@ -4505,7 +4517,8 @@ extern bool suffix_p (Lisp_Object, const char *);
 extern Lisp_Object save_match_data_load (Lisp_Object, Lisp_Object, Lisp_Object,
                                         Lisp_Object, Lisp_Object);
 extern int openp (Lisp_Object, Lisp_Object, Lisp_Object,
-                  Lisp_Object *, Lisp_Object, bool, bool);
+                  Lisp_Object *, Lisp_Object, bool, bool,
+                 void **);
 enum { S2N_IGNORE_TRAILING = 1 };
 extern Lisp_Object string_to_number (char const *, int, ptrdiff_t *);
 extern void map_obarray (Lisp_Object, void (*) (Lisp_Object, Lisp_Object),
@@ -4724,6 +4737,7 @@ extern void syms_of_marker (void);
 
 /* Defined in fileio.c.  */
 
+extern Lisp_Object file_name_directory (Lisp_Object);
 extern char *splice_dir_file (char *, char const *, char const *)
   ATTRIBUTE_RETURNS_NONNULL;
 extern bool file_name_absolute_p (const char *);
@@ -5063,11 +5077,34 @@ extern void init_random (void);
 extern void emacs_backtrace (int);
 extern AVOID emacs_abort (void) NO_INLINE;
 extern int emacs_fstatat (int, char const *, void *, int);
+#ifdef HAVE_SYS_STAT_H
+extern int sys_fstat (int, struct stat *);
+#endif
+extern int sys_faccessat (int, const char *, int, int);
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
 extern int emacs_openat (int, char const *, int, int);
+#endif
 extern int emacs_open (const char *, int, int);
 extern int emacs_open_noquit (const char *, int, int);
 extern int emacs_pipe (int[2]);
 extern int emacs_close (int);
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+# define emacs_fclose fclose
+#else
+extern int emacs_fclose (FILE *);
+#endif
+extern FILE *emacs_fdopen (int, const char *)
+  ATTRIBUTE_MALLOC ATTRIBUTE_DEALLOC (emacs_fclose, 1);
+extern FILE *emacs_fopen (char const *, char const *)
+  ATTRIBUTE_MALLOC ATTRIBUTE_DEALLOC (emacs_fclose, 1);
+extern int emacs_unlink (const char *);
+extern int emacs_symlink (const char *, const char *);
+extern int emacs_rmdir (const char *);
+extern int emacs_mkdir (const char *, mode_t);
+extern int emacs_renameat_noreplace (int, const char *, int,
+                                    const char *);
+extern int emacs_rename (const char *, const char *);
+extern int emacs_fchmodat (int, const char *, mode_t, int);
 extern ptrdiff_t emacs_read (int, void *, ptrdiff_t);
 extern ptrdiff_t emacs_read_quit (int, void *, ptrdiff_t);
 extern ptrdiff_t emacs_write (int, void const *, ptrdiff_t);
@@ -5101,7 +5138,9 @@ extern Lisp_Object directory_files_internal (Lisp_Object, 
Lisp_Object,
                                              bool, Lisp_Object, Lisp_Object);
 
 /* Defined in term.c.  */
+#ifndef HAVE_ANDROID
 extern int *char_ins_del_vector;
+#endif
 extern void syms_of_term (void);
 extern AVOID fatal (const char *msgid, ...) ATTRIBUTE_FORMAT_PRINTF (1, 2);
 
@@ -5211,7 +5250,13 @@ extern char *emacs_root_dir (void);
 
 #ifdef HAVE_TEXT_CONVERSION
 /* Defined in textconv.c.  */
+extern void reset_frame_state (struct frame *);
 extern void report_selected_window_change (struct frame *);
+extern void report_point_change (struct frame *, struct window *,
+                                struct buffer *);
+extern void disable_text_conversion (void);
+extern void resume_text_conversion (void);
+extern void syms_of_textconv (void);
 #endif
 
 #ifdef HAVE_NATIVE_COMP
diff --git a/src/lread.c b/src/lread.c
index e6dba3bb8c1..b6842d0208c 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -62,6 +62,24 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include <fcntl.h>
 
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY   \
+  || (__ANDROID_API__ < 9)
+
+#define lread_fd       int
+#define lread_fd_cmp(n) (fd == (n))
+#define lread_fd_p     (fd >= 0)
+#define lread_close    emacs_close
+#define lread_fstat    fstat
+#define lread_read_quit        emacs_read_quit
+#define lread_lseek    lseek
+
+#define file_stream            FILE *
+#define file_seek              fseek
+#define file_stream_valid_p(p) (p)
+#define file_stream_close      emacs_fclose
+#define file_stream_invalid    NULL
+#define file_get_char          getc
+
 #ifdef HAVE_FSEEKO
 #define file_offset off_t
 #define file_tell ftello
@@ -70,6 +88,79 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #define file_tell ftell
 #endif
 
+#else
+
+#include "android.h"
+
+/* Use an Android file descriptor under Android instead, as this
+   allows loading directly from asset files without loading each asset
+   into memory and creating a separate file descriptor every time.
+
+   Note that `struct android_fd_or_asset' as used here is different
+   from that returned from `android_open_asset'; if fd.asset is NULL,
+   then fd.fd is either a valid file descriptor or -1, meaning that
+   the file descriptor is invalid.
+
+   However, lread requires the ability to seek inside asset files,
+   which is not provided under Android 2.2.  So when building for that
+   particular system, fall back to the usual file descriptor-based
+   code.  */
+
+#define lread_fd       struct android_fd_or_asset
+#define lread_fd_cmp(n)        (!fd.asset && fd.fd == (n))
+#define lread_fd_p     (fd.asset || fd.fd >= 0)
+#define lread_close    android_close_asset
+#define lread_fstat    android_asset_fstat
+#define lread_read_quit        android_asset_read_quit
+#define lread_lseek    android_asset_lseek
+
+/* The invalid file stream.  */
+
+static struct android_fd_or_asset invalid_file_stream =
+  {
+    -1,
+    NULL,
+  };
+
+#define file_stream            struct android_fd_or_asset
+#define file_offset            off_t
+#define file_tell(n)           (android_asset_lseek ((n), 0, SEEK_CUR))
+#define file_seek              android_asset_lseek
+#define file_stream_valid_p(p) ((p).asset || (p).fd >= 0)
+#define file_stream_close      android_close_asset
+#define file_stream_invalid    invalid_file_stream
+
+/* Return a single character from the file input stream STREAM.
+   Value and errors are the same as getc.  */
+
+static int
+file_get_char (file_stream stream)
+{
+  int c;
+  char byte;
+  ssize_t rc;
+
+ retry:
+  rc = android_asset_read (stream, &byte, 1);
+
+  if (rc == 0)
+    c = EOF;
+  else if (rc == -1)
+    {
+      if (errno == EINTR)
+       goto retry;
+      else
+       c = EOF;
+    }
+  else
+    c = (unsigned char) byte;
+
+  return c;
+}
+
+#define USE_ANDROID_ASSETS
+#endif
+
 #if IEEE_FLOATING_POINT
 # include <ieee754.h>
 # ifndef INFINITY
@@ -117,7 +208,7 @@ static Lisp_Object read_objects_completed;
 static struct infile
 {
   /* The input stream.  */
-  FILE *stream;
+  file_stream stream;
 
   /* Lookahead byte count.  */
   signed char lookahead;
@@ -379,7 +470,7 @@ skip_dyn_bytes (Lisp_Object readcharfun, ptrdiff_t n)
   if (FROM_FILE_P (readcharfun))
     {
       block_input ();          /* FIXME: Not sure if it's needed.  */
-      fseek (infile->stream, n - infile->lookahead, SEEK_CUR);
+      file_seek (infile->stream, n - infile->lookahead, SEEK_CUR);
       unblock_input ();
       infile->lookahead = 0;
     }
@@ -403,7 +494,7 @@ skip_dyn_eof (Lisp_Object readcharfun)
   if (FROM_FILE_P (readcharfun))
     {
       block_input ();          /* FIXME: Not sure if it's needed.  */
-      fseek (infile->stream, 0, SEEK_END);
+      file_seek (infile->stream, 0, SEEK_END);
       unblock_input ();
       infile->lookahead = 0;
     }
@@ -484,10 +575,12 @@ readbyte_from_stdio (void)
     return infile->buf[--infile->lookahead];
 
   int c;
-  FILE *instream = infile->stream;
+  file_stream instream = infile->stream;
 
   block_input ();
 
+#if !defined USE_ANDROID_ASSETS
+
   /* Interrupted reads have been observed while reading over the network.  */
   while ((c = getc (instream)) == EOF && errno == EINTR && ferror (instream))
     {
@@ -497,6 +590,35 @@ readbyte_from_stdio (void)
       clearerr (instream);
     }
 
+#else
+
+  {
+    char byte;
+    ssize_t rc;
+
+  retry:
+    rc = android_asset_read (instream, &byte, 1);
+
+    if (rc == 0)
+      c = EOF;
+    else if (rc == -1)
+      {
+       if (errno == EINTR)
+         {
+           unblock_input ();
+           maybe_quit ();
+           block_input ();
+           goto retry;
+         }
+       else
+         c = EOF;
+      }
+    else
+      c = (unsigned char) byte;
+  }
+
+#endif
+
   unblock_input ();
 
   return (c == EOF ? -1 : c);
@@ -676,7 +798,11 @@ static void substitute_in_interval (INTERVAL, void *);
    if the character warrants that.
 
    If SECONDS is a number, wait that many seconds for input, and
-   return Qnil if no input arrives within that time.  */
+   return Qnil if no input arrives within that time.
+
+   If text conversion is enabled and ASCII_REQUIRED, temporarily
+   disable any input method which wants to perform edits, unless
+   `disable-inhibit-text-conversion'.  */
 
 static Lisp_Object
 read_filtered_event (bool no_switch_frame, bool ascii_required,
@@ -684,12 +810,28 @@ read_filtered_event (bool no_switch_frame, bool 
ascii_required,
 {
   Lisp_Object val, delayed_switch_frame;
   struct timespec end_time;
+#ifdef HAVE_TEXT_CONVERSION
+  specpdl_ref count;
+#endif
 
 #ifdef HAVE_WINDOW_SYSTEM
   if (display_hourglass_p)
     cancel_hourglass ();
 #endif
 
+#ifdef HAVE_TEXT_CONVERSION
+  count = SPECPDL_INDEX ();
+
+  /* Don't use text conversion when trying to just read a
+     character.  */
+
+  if (ascii_required && !disable_inhibit_text_conversion)
+    {
+      disable_text_conversion ();
+      record_unwind_protect_void (resume_text_conversion);
+    }
+#endif
+
   delayed_switch_frame = Qnil;
 
   /* Compute timeout.  */
@@ -765,7 +907,11 @@ read_filtered_event (bool no_switch_frame, bool 
ascii_required,
 
 #endif
 
+#ifdef HAVE_TEXT_CONVERSION
+  return unbind_to (count, val);
+#else
   return val;
+#endif
 }
 
 DEFUN ("read-char", Fread_char, Sread_char, 0, 3, 0,
@@ -1042,7 +1188,7 @@ lisp_file_lexically_bound_p (Lisp_Object readcharfun)
    safe to load.  Only files compiled with Emacs can be loaded.  */
 
 static int
-safe_to_load_version (Lisp_Object file, int fd)
+safe_to_load_version (Lisp_Object file, lread_fd fd)
 {
   struct stat st;
   char buf[512];
@@ -1051,12 +1197,12 @@ safe_to_load_version (Lisp_Object file, int fd)
 
   /* If the file is not regular, then we cannot safely seek it.
      Assume that it is not safe to load as a compiled file.  */
-  if (fstat (fd, &st) == 0 && !S_ISREG (st.st_mode))
+  if (lread_fstat (fd, &st) == 0 && !S_ISREG (st.st_mode))
     return 0;
 
   /* Read the first few bytes from the file, and look for a line
      specifying the byte compiler version used.  */
-  nbytes = emacs_read_quit (fd, buf, sizeof buf);
+  nbytes = lread_read_quit (fd, buf, sizeof buf);
   if (nbytes > 0)
     {
       /* Skip to the next newline, skipping over the initial `ELC'
@@ -1071,7 +1217,7 @@ safe_to_load_version (Lisp_Object file, int fd)
        version = 0;
     }
 
-  if (lseek (fd, 0, SEEK_SET) < 0)
+  if (lread_lseek (fd, 0, SEEK_SET) < 0)
     report_file_error ("Seeking to start of file", file);
 
   return version;
@@ -1145,7 +1291,7 @@ close_infile_unwind (void *arg)
 {
   struct infile *prev_infile = arg;
   eassert (infile && infile != prev_infile);
-  fclose (infile->stream);
+  file_stream_close (infile->stream);
   infile = prev_infile;
 }
 
@@ -1174,6 +1320,22 @@ loadhist_initialize (Lisp_Object filename)
   specbind (Qcurrent_load_list, Fcons (filename, Qnil));
 }
 
+#ifdef USE_ANDROID_ASSETS
+
+/* Like `close_file_unwind'.  However, PTR is a pointer to an Android
+   file descriptor instead of a system file descriptor.  */
+
+static void
+close_file_unwind_android_fd (void *ptr)
+{
+  struct android_fd_or_asset *fd;
+
+  fd = ptr;
+  android_close_asset (*fd);
+}
+
+#endif
+
 DEFUN ("load", Fload, Sload, 1, 5, 0,
        doc: /* Execute a file of Lisp code named FILE.
 First try FILE with `.elc' appended, then try with `.el', then try
@@ -1222,8 +1384,12 @@ Return t if the file exists and loads successfully.  */)
   (Lisp_Object file, Lisp_Object noerror, Lisp_Object nomessage,
    Lisp_Object nosuffix, Lisp_Object must_suffix)
 {
-  FILE *stream UNINIT;
-  int fd;
+  file_stream stream UNINIT;
+  lread_fd fd;
+#ifdef USE_ANDROID_ASSETS
+  int rc;
+  void *asset;
+#endif
   specpdl_ref fd_index UNINIT;
   specpdl_ref count = SPECPDL_INDEX ();
   Lisp_Object found, efound, hist_file_name;
@@ -1264,7 +1430,12 @@ Return t if the file exists and loads successfully.  */)
      since it would try to load a directory as a Lisp file.  */
   if (SCHARS (file) == 0)
     {
+#if !defined USE_ANDROID_ASSETS
       fd = -1;
+#else
+      fd.asset = NULL;
+      fd.fd = -1;
+#endif
       errno = ENOENT;
     }
   else
@@ -1303,12 +1474,22 @@ Return t if the file exists and loads successfully.  */)
            suffixes = CALLN (Fappend, suffixes, Vload_file_rep_suffixes);
        }
 
-      fd =
-       openp (Vload_path, file, suffixes, &found, Qnil, load_prefer_newer,
-              no_native);
+#if !defined USE_ANDROID_ASSETS
+      fd = openp (Vload_path, file, suffixes, &found, Qnil,
+                 load_prefer_newer, no_native, NULL);
+#else
+      asset = NULL;
+      rc = openp (Vload_path, file, suffixes, &found, Qnil,
+                 load_prefer_newer, no_native, &asset);
+      fd.fd = rc;
+      fd.asset = asset;
+
+      /* fd.asset will be non-NULL if this is actually an asset
+        file.  */
+#endif
     }
 
-  if (fd == -1)
+  if (lread_fd_cmp (-1))
     {
       if (NILP (noerror))
        report_file_error ("Cannot open load file", file);
@@ -1320,7 +1501,7 @@ Return t if the file exists and loads successfully.  */)
     Vuser_init_file = found;
 
   /* If FD is -2, that means openp found a magic file.  */
-  if (fd == -2)
+  if (lread_fd_cmp (-2))
     {
       if (NILP (Fequal (found, file)))
        /* If FOUND is a different file name from FILE,
@@ -1349,11 +1530,21 @@ Return t if the file exists and loads successfully.  */)
 #endif
     }
 
+#if !defined USE_ANDROID_ASSETS
   if (0 <= fd)
     {
       fd_index = SPECPDL_INDEX ();
       record_unwind_protect_int (close_file_unwind, fd);
     }
+#else
+  if (fd.asset || fd.fd >= 0)
+    {
+      /* Use a different kind of unwind_protect here.  */
+      fd_index = SPECPDL_INDEX ();
+      record_unwind_protect_ptr (close_file_unwind_android_fd,
+                                &fd);
+    }
+#endif
 
 #ifdef HAVE_MODULES
   bool is_module =
@@ -1419,11 +1610,12 @@ Return t if the file exists and loads successfully.  */)
   if (is_elc
       /* version = 1 means the file is empty, in which case we can
         treat it as not byte-compiled.  */
-      || (fd >= 0 && (version = safe_to_load_version (file, fd)) > 1))
+      || (lread_fd_p
+         && (version = safe_to_load_version (file, fd)) > 1))
     /* Load .elc files directly, but not when they are
        remote and have no handler!  */
     {
-      if (fd != -2)
+      if (!lread_fd_cmp (-2))
        {
          struct stat s1, s2;
          int result;
@@ -1480,9 +1672,9 @@ Return t if the file exists and loads successfully.  */)
        {
          Lisp_Object val;
 
-         if (fd >= 0)
+         if (lread_fd_p)
            {
-             emacs_close (fd);
+             lread_close (fd);
              clear_unwind_protect (fd_index);
            }
          val = call4 (Vload_source_file_function, found, hist_file_name,
@@ -1492,12 +1684,12 @@ Return t if the file exists and loads successfully.  */)
        }
     }
 
-  if (fd < 0)
+  if (!lread_fd_p)
     {
       /* We somehow got here with fd == -2, meaning the file is deemed
         to be remote.  Don't even try to reopen the file locally;
         just force a failure.  */
-      stream = NULL;
+      stream = file_stream_invalid;
       errno = EINVAL;
     }
   else if (!is_module && !is_native_elisp)
@@ -1508,7 +1700,15 @@ Return t if the file exists and loads successfully.  */)
       efound = ENCODE_FILE (found);
       stream = emacs_fopen (SSDATA (efound), fmode);
 #else
-      stream = fdopen (fd, fmode);
+#if !defined USE_ANDROID_ASSETS
+      stream = emacs_fdopen (fd, fmode);
+#else
+      /* Android systems use special file descriptors which can point
+        into compressed data and double as file streams.  FMODE is
+        unused.  */
+      ((void) fmode);
+      stream = fd;
+#endif
 #endif
     }
 
@@ -1520,15 +1720,15 @@ Return t if the file exists and loads successfully.  */)
     {
       /* `module-load' uses the file name, so we can close the stream
          now.  */
-      if (fd >= 0)
+      if (lread_fd_p)
         {
-          emacs_close (fd);
+          lread_close (fd);
           clear_unwind_protect (fd_index);
         }
     }
   else
     {
-      if (! stream)
+      if (!file_stream_valid_p (stream))
         report_file_error ("Opening stdio stream", file);
       set_unwind_protect_ptr (fd_index, close_infile_unwind, infile);
       input.stream = stream;
@@ -1664,7 +1864,8 @@ directories, make sure the PREDICATE function returns 
`dir-ok' for them.  */)
   (Lisp_Object filename, Lisp_Object path, Lisp_Object suffixes, Lisp_Object 
predicate)
 {
   Lisp_Object file;
-  int fd = openp (path, filename, suffixes, &file, predicate, false, true);
+  int fd = openp (path, filename, suffixes, &file, predicate, false, true,
+                 NULL);
   if (NILP (predicate) && fd >= 0)
     emacs_close (fd);
   return file;
@@ -1680,7 +1881,7 @@ maybe_swap_for_eln1 (Lisp_Object src_name, Lisp_Object 
eln_name,
 
   if (eln_fd > 0)
     {
-      if (fstat (eln_fd, &eln_st) || S_ISDIR (eln_st.st_mode))
+      if (sys_fstat (eln_fd, &eln_st) || S_ISDIR (eln_st.st_mode))
        emacs_close (eln_fd);
       else
        {
@@ -1805,14 +2006,20 @@ maybe_swap_for_eln (bool no_native, Lisp_Object 
*filename, int *fd,
 
    If NEWER is true, try all SUFFIXes and return the result for the
    newest file that exists.  Does not apply to remote files,
-   or if a non-nil and non-t PREDICATE is specified.
+   platform-specific files, or if a non-nil and non-t PREDICATE is
+   specified.
+
+   If NO_NATIVE is true do not try to load native code.
 
-   if NO_NATIVE is true do not try to load native code.  */
+   If PLATFORM is non-NULL and the file being loaded lies in a special
+   directory, such as the Android `/assets' directory, return a handle
+   to that directory in *PLATFORM instead of a file descriptor; in
+   that case, value is -3.  */
 
 int
 openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes,
        Lisp_Object *storeptr, Lisp_Object predicate, bool newer,
-       bool no_native)
+       bool no_native, void **platform)
 {
   ptrdiff_t fn_size = 100;
   char buf[100];
@@ -1824,6 +2031,9 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object 
suffixes,
   ptrdiff_t max_suffix_len = 0;
   int last_errno = ENOENT;
   int save_fd = -1;
+#ifdef USE_ANDROID_ASSETS
+  struct android_fd_or_asset platform_fd;
+#endif
   USE_SAFE_ALLOCA;
 
   /* The last-modified time of the newest matching file found.
@@ -1968,8 +2178,8 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object 
suffixes,
                fd = -1;
                if (INT_MAX < XFIXNAT (predicate))
                  last_errno = EINVAL;
-               else if (faccessat (AT_FDCWD, pfn, XFIXNAT (predicate),
-                                   AT_EACCESS)
+               else if (sys_faccessat (AT_FDCWD, pfn, XFIXNAT (predicate),
+                                       AT_EACCESS)
                         == 0)
                  {
                    if (file_directory_p (encoded_fn))
@@ -1989,11 +2199,34 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object 
suffixes,
                     it.  Only open the file when we are sure that it
                     exists.  */
 #ifdef WINDOWSNT
-                if (faccessat (AT_FDCWD, pfn, R_OK, AT_EACCESS))
+                if (sys_faccessat (AT_FDCWD, pfn, R_OK, AT_EACCESS))
                   fd = -1;
                 else
 #endif
-                  fd = emacs_open (pfn, O_RDONLY, 0);
+                 {
+#if !defined USE_ANDROID_ASSETS
+                   fd = emacs_open (pfn, O_RDONLY, 0);
+#else
+                   if (platform)
+                     {
+                       platform_fd = android_open_asset (pfn, O_RDONLY, 0);
+
+                       if (platform_fd.asset
+                           && platform_fd.asset != (void *) -1)
+                         {
+                           *storeptr = string;
+                           goto handle_platform_fd;
+                         }
+
+                       if (platform_fd.asset == (void *) -1)
+                         fd = -1;
+                       else
+                         fd = platform_fd.fd;
+                     }
+                   else
+                     fd = emacs_open (pfn, O_RDONLY, 0);
+#endif
+                 }
 
                if (fd < 0)
                  {
@@ -2002,7 +2235,7 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object 
suffixes,
                  }
                else
                  {
-                   int err = (fstat (fd, &st) != 0 ? errno
+                   int err = (sys_fstat (fd, &st) != 0 ? errno
                               : S_ISDIR (st.st_mode) ? EISDIR : 0);
                    if (err)
                      {
@@ -2061,6 +2294,16 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object 
suffixes,
   SAFE_FREE ();
   errno = last_errno;
   return -1;
+
+#ifdef USE_ANDROID_ASSETS
+ handle_platform_fd:
+
+  /* Here, openp found a platform specific file descriptor.  It can't
+     be a directory under Android, so return it in *PLATFORM and then
+     -3 as the file descriptor.  */
+  *platform = platform_fd.asset;
+  return -3;
+#endif
 }
 
 
@@ -2092,7 +2335,7 @@ build_load_history (Lisp_Object filename, bool entire)
        {
          foundit = 1;
 
-         /*  If we're loading the entire file, remove old data.  */
+         /* If we're loading the entire file, remove old data.  */
          if (entire)
            {
              if (NILP (prev))
@@ -2100,8 +2343,8 @@ build_load_history (Lisp_Object filename, bool entire)
              else
                Fsetcdr (prev, XCDR (tail));
            }
-
-         /*  Otherwise, cons on new symbols that are not already members.  */
+         /* Otherwise, cons on new symbols that are not already
+            members.  */
          else
            {
              tem2 = Vcurrent_load_list;
@@ -3465,7 +3708,7 @@ skip_lazy_string (Lisp_Object readcharfun)
          ss->string = xrealloc (ss->string, ss->size);
        }
 
-      FILE *instream = infile->stream;
+      file_stream instream = infile->stream;
       ss->position = (file_tell (instream) - infile->lookahead);
 
       /* Copy that many bytes into the saved string.  */
@@ -3475,7 +3718,7 @@ skip_lazy_string (Lisp_Object readcharfun)
        ss->string[i++] = c = infile->buf[--infile->lookahead];
       block_input ();
       for (; i < nskip && c >= 0; i++)
-       ss->string[i] = c = getc (instream);
+       ss->string[i] = c = file_get_char (instream);
       unblock_input ();
 
       ss->length = i;
diff --git a/src/marker.c b/src/marker.c
index e42c49a5434..7b15cd62f1e 100644
--- a/src/marker.c
+++ b/src/marker.c
@@ -23,6 +23,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "lisp.h"
 #include "character.h"
 #include "buffer.h"
+#include "window.h"
 
 /* Record one cached position found recently by
    buf_charpos_to_bytepos or buf_bytepos_to_charpos.  */
@@ -566,6 +567,31 @@ set_marker_internal (Lisp_Object marker, Lisp_Object 
position,
 
       attach_marker (m, b, charpos, bytepos);
     }
+
+#ifdef HAVE_TEXT_CONVERSION
+
+  /* If B is the buffer's mark and there is a window displaying B, and
+     text conversion is enabled while the mark is active, redisplay
+     the buffer.
+
+     propagate_window_redisplay will propagate this redisplay to the
+     window, which will eventually reach
+     mark_window_display_accurate_1.  At that point,
+     report_point_change will be told to update the mark as seen by
+     the input method.
+
+     This is done all the way in (the seemingly irrelevant) redisplay
+     because the selection reported to the input method is actually what
+     is visible on screen, namely w->last_point.  */
+
+  if (m->buffer
+      && EQ (marker, BVAR (m->buffer, mark))
+      && !NILP (BVAR (m->buffer, mark_active))
+      && buffer_window_count (m->buffer))
+    bset_redisplay (m->buffer);
+
+#endif
+
   return marker;
 }
 
diff --git a/src/menu.c b/src/menu.c
index 73d4215b94b..6ab34a16996 100644
--- a/src/menu.c
+++ b/src/menu.c
@@ -48,7 +48,7 @@ static bool
 have_boxes (void)
 {
 #if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NTGUI) || 
defined (HAVE_NS) \
-  || defined (HAVE_HAIKU)
+  || defined (HAVE_HAIKU) || defined (HAVE_ANDROID)
   if (FRAME_WINDOW_P (XFRAME (Vmenu_updating_frame)))
     return 1;
 #endif
@@ -167,7 +167,7 @@ ensure_menu_items (int items)
     }
 }
 
-#ifdef HAVE_EXT_MENU_BAR
+#if defined HAVE_EXT_MENU_BAR || defined HAVE_ANDROID
 
 /* Begin a submenu.  */
 
@@ -191,7 +191,7 @@ push_submenu_end (void)
   menu_items_submenu_depth--;
 }
 
-#endif /* HAVE_EXT_MENU_BAR */
+#endif /* HAVE_EXT_MENU_BAR || HAVE_ANDROID */
 
 /* Indicate boundary between left and right.  */
 
@@ -420,8 +420,9 @@ single_menu_item (Lisp_Object key, Lisp_Object item, 
Lisp_Object dummy, void *sk
                  AREF (item_properties, ITEM_PROPERTY_SELECTED),
                  AREF (item_properties, ITEM_PROPERTY_HELP));
 
-#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS) \
-  || defined (HAVE_NTGUI) || defined (HAVE_HAIKU) || defined (HAVE_PGTK)
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS)  \
+  || defined (HAVE_NTGUI) || defined (HAVE_HAIKU) || defined (HAVE_PGTK) \
+  || defined (HAVE_ANDROID)
   /* Display a submenu using the toolkit.  */
   if (FRAME_WINDOW_P (XFRAME (Vmenu_updating_frame))
       && ! (NILP (map) || NILP (enabled)))
@@ -1151,7 +1152,7 @@ x_popup_menu_1 (Lisp_Object position, Lisp_Object menu)
        else
          {
            menuflags |= MENU_FOR_CLICK;
-           tem = Fcar (XCDR (position));    /* EVENT_START (position) */
+           tem = EVENT_START (position);    /* EVENT_START (position) */
            window = Fcar (tem);             /* POSN_WINDOW (tem) */
            tem2 = Fcar (Fcdr (tem));        /* POSN_POSN (tem) */
            /* The MENU_KBD_NAVIGATION field is set when the menu
@@ -1465,9 +1466,10 @@ cached information about equivalent key sequences.
 If the user gets rid of the menu without making a valid choice, for
 instance by clicking the mouse away from a valid choice or by typing
 keyboard input, then this normally results in a quit and
-`x-popup-menu' does not return.  But if POSITION is a mouse button
-event (indicating that the user invoked the menu with the mouse) then
-no quit occurs and `x-popup-menu' returns nil.  */)
+`x-popup-menu' does not return.  But if POSITION is a mouse button or
+touch screen event (indicating that the user invoked the menu with the
+a pointing device) then no quit occurs and `x-popup-menu' returns
+nil.  */)
   (Lisp_Object position, Lisp_Object menu)
 {
   init_raw_keybuf_count ();
diff --git a/src/msdos.c b/src/msdos.c
index 75a39045cee..1b7f2d4ae21 100644
--- a/src/msdos.c
+++ b/src/msdos.c
@@ -979,11 +979,15 @@ tty_draw_row_with_mouse_face (struct window *w, struct 
glyph_row *row,
   if (hl == DRAW_MOUSE_FACE)
     {
       int vpos = row->y + WINDOW_TOP_EDGE_Y (w);
-      int kstart = start_hpos + WINDOW_LEFT_EDGE_X (w);
+      int kstart = (start_hpos + WINDOW_LEFT_EDGE_X (w)
+                   + row->used[LEFT_MARGIN_AREA]);
       int nglyphs = end_hpos - start_hpos;
       int offset = ScreenPrimary + 2*(vpos*screen_size_X + kstart) + 1;
       int start_offset = offset;
 
+      if (end_hpos >= row->used[TEXT_AREA])
+       nglyphs = row->used[TEXT_AREA] - start_hpos;
+
       if (tty->termscript)
        fprintf (tty->termscript, "\n<MH+ %d-%d:%d>",
                 kstart, kstart + nglyphs - 1, vpos);
@@ -1021,6 +1025,9 @@ tty_draw_row_with_mouse_face (struct window *w, struct 
glyph_row *row,
         temporarily move cursor coordinates to the beginning of
         the highlight region.  */
       new_pos_X = start_hpos + WINDOW_LEFT_EDGE_X (w);
+      /* The coordinates supplied by the caller are relative to the
+        text area, not the window itself.  */
+      new_pos_X += row->used[LEFT_MARGIN_AREA];
       new_pos_Y = row->y + WINDOW_TOP_EDGE_Y (w);
 
       if (tty->termscript)
diff --git a/src/nsfns.m b/src/nsfns.m
index 90159533128..a79892f73b6 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -685,6 +685,12 @@ ns_change_tab_bar_height (struct frame *f, int height)
   SET_FRAME_GARBAGED (f);
 }
 
+void
+ns_make_frame_key_window (struct frame *f)
+{
+  [[FRAME_NS_VIEW (f) window] makeKeyWindow];
+}
+
 /* tabbar support */
 static void
 ns_set_tab_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
@@ -3796,6 +3802,27 @@ all_nonzero_ascii (unsigned char *str, ptrdiff_t n)
   return true;
 }
 
+/* Count the number of characters in STR, NBYTES long.
+   The string must be valid UTF-8.  */
+static ptrdiff_t
+count_utf8_chars (const char *str, ptrdiff_t nbytes)
+{
+  /* This is faster than parse_str_as_multibyte, and much faster than
+     [NSString lengthOfBytesUsingEncoding: NSUTF32StringEncoding].  */
+  const char *end = str + nbytes;
+  ptrdiff_t nc = 0;
+  while (str < end)
+    {
+      nc++;
+      unsigned char c = *str;
+      str += (  c <= 0x7f ? 1    // 0xxxxxxx
+              : c <= 0xdf ? 2    // 110xxxxx 10xxxxxx
+              : c <= 0xef ? 3    // 1110xxxx 10xxxxxx 10xxxxxx
+              :             4);  // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+    }
+  return nc;
+}
+
 @implementation NSString (EmacsString)
 /* Make an NSString from a Lisp string.  STRING must not be in an
    encoded form (e.g. UTF-8).  */
@@ -3840,7 +3867,11 @@ all_nonzero_ascii (unsigned char *str, ptrdiff_t n)
 /* Make a Lisp string from an NSString.  */
 - (Lisp_Object)lispString
 {
-  return build_string ([self UTF8String]);
+  /* If the input string includes unpaired surrogates, then the result
+     will be an empty string.  */
+  const char *utf8 = [self UTF8String];
+  ptrdiff_t bytes = [self lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+  return make_multibyte_string (utf8, count_utf8_chars (utf8, bytes), bytes);
 }
 @end
 
diff --git a/src/nsimage.m b/src/nsimage.m
index af8eb629989..b33124900bb 100644
--- a/src/nsimage.m
+++ b/src/nsimage.m
@@ -77,6 +77,10 @@ ns_can_use_native_image_api (Lisp_Object type)
 #ifndef HAVE_RSVG
   else if (EQ (type, Qsvg))
     imageType = @"public.svg-image";
+#endif
+#ifndef HAVE_WEBP
+  else if (EQ (type, Qwebp))
+    imageType = @"org.webmproject.webp";
 #endif
   else if (EQ (type, Qheic))
     imageType = @"public.heic";
diff --git a/src/pdumper.c b/src/pdumper.c
index 34998549cc8..85adf4147f9 100644
--- a/src/pdumper.c
+++ b/src/pdumper.c
@@ -2178,7 +2178,7 @@ dump_interval_node (struct dump_context *ctx, struct 
itree_node *node,
 static dump_off
 dump_overlay (struct dump_context *ctx, const struct Lisp_Overlay *overlay)
 {
-#if CHECK_STRUCTS && !defined (HASH_Lisp_Overlay_EB4C05D8D2)
+#if CHECK_STRUCTS && !defined (HASH_Lisp_Overlay_5F9D7E02FC)
 # error "Lisp_Overlay changed. See CHECK_STRUCTS comment in config.h."
 #endif
   START_DUMP_PVEC (ctx, &overlay->header, struct Lisp_Overlay, out);
@@ -2747,7 +2747,7 @@ dump_hash_table (struct dump_context *ctx,
 static dump_off
 dump_buffer (struct dump_context *ctx, const struct buffer *in_buffer)
 {
-#if CHECK_STRUCTS && !defined HASH_buffer_85D317CE74
+#if CHECK_STRUCTS && !defined HASH_buffer_6C25F9C3BC
 # error "buffer changed. See CHECK_STRUCTS comment in config.h."
 #endif
   struct buffer munged_buffer = *in_buffer;
@@ -4071,10 +4071,12 @@ types.  */)
 {
   eassert (initialized);
 
+#ifndef HAVE_ANDROID
   if (! noninteractive)
     error ("Dumping Emacs currently works only in batch mode.  "
            "If you'd like it to work interactively, please consider "
            "contributing a patch to Emacs.");
+#endif
 
   if (will_dump_with_unexec_p ())
     error ("This Emacs instance was started under the assumption "
@@ -4744,7 +4746,9 @@ dump_discard_mem (void *mem, size_t size)
 # ifdef HAVE_POSIX_MADVISE
       /* Discard COWed pages.  */
       (void) posix_madvise (mem, size, POSIX_MADV_DONTNEED);
-# endif
+# elif defined HAVE_MADVISE
+      (void) madvise (mem, size, MADV_DONTNEED);
+#endif
       /* Release the commit charge for the mapping.  */
       (void) mprotect (mem, size, PROT_NONE);
 #endif
@@ -5614,7 +5618,7 @@ pdumper_load (const char *dump_filename, char *argv0)
     }
 
   err = PDUMPER_LOAD_FILE_NOT_FOUND;
-  if (fstat (dump_fd, &stat) < 0)
+  if (sys_fstat (dump_fd, &stat) < 0)
     goto out;
 
   err = PDUMPER_LOAD_BAD_FILE_TYPE;
@@ -5836,6 +5840,10 @@ void
 syms_of_pdumper (void)
 {
 #ifdef HAVE_PDUMPER
+  unsigned char desired[sizeof fingerprint];
+  int i;
+  char hexbuf[2 * sizeof fingerprint];
+
   defsubr (&Sdump_emacs_portable);
   defsubr (&Sdump_emacs_portable__sort_predicate);
   defsubr (&Sdump_emacs_portable__sort_predicate_copied);
@@ -5848,5 +5856,17 @@ syms_of_pdumper (void)
   DEFSYM (Qdump_file_name, "dump-file-name");
   DEFSYM (Qafter_pdump_load_hook, "after-pdump-load-hook");
   defsubr (&Spdumper_stats);
+
+  for (i = 0; i < sizeof fingerprint; i++)
+    desired[i] = fingerprint[i];
+
+  hexbuf_digest (hexbuf, desired, sizeof desired);
+
+  DEFVAR_LISP ("pdumper-fingerprint", Vpdumper_fingerprint,
+              doc: /* The fingerprint of this Emacs binary.
+It is a string that is supposed to be unique to each build of
+Emacs.  */);
+  Vpdumper_fingerprint = make_unibyte_string ((char *) hexbuf,
+                                             sizeof hexbuf);
 #endif /* HAVE_PDUMPER */
 }
diff --git a/src/pgtkterm.c b/src/pgtkterm.c
index dc2d6477bb5..a7c687d811d 100644
--- a/src/pgtkterm.c
+++ b/src/pgtkterm.c
@@ -1328,14 +1328,17 @@ fill_background_by_face (struct frame *f, struct face 
*face, int x, int y,
                         int width, int height)
 {
   cairo_t *cr = pgtk_begin_cr_clip (f);
+  double r, g, b, a;
 
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
   cairo_rectangle (cr, x, y, width, height);
   cairo_clip (cr);
 
-  double r = ((face->background >> 16) & 0xff) / 255.0;
-  double g = ((face->background >> 8) & 0xff) / 255.0;
-  double b = ((face->background >> 0) & 0xff) / 255.0;
-  cairo_set_source_rgb (cr, r, g, b);
+  r = ((face->background >> 16) & 0xff) / 255.0;
+  g = ((face->background >> 8) & 0xff) / 255.0;
+  b = ((face->background >> 0) & 0xff) / 255.0;
+  a = f->alpha_background;
+  cairo_set_source_rgba (cr, r, g, b, a);
   cairo_paint (cr);
 
   if (face->stipple != 0)
@@ -1343,10 +1346,10 @@ fill_background_by_face (struct frame *f, struct face 
*face, int x, int y,
       cairo_pattern_t *mask
        = FRAME_DISPLAY_INFO (f)->bitmaps[face->stipple - 1].pattern;
 
-      double r = ((face->foreground >> 16) & 0xff) / 255.0;
-      double g = ((face->foreground >> 8) & 0xff) / 255.0;
-      double b = ((face->foreground >> 0) & 0xff) / 255.0;
-      cairo_set_source_rgb (cr, r, g, b);
+      r = ((face->foreground >> 16) & 0xff) / 255.0;
+      g = ((face->foreground >> 8) & 0xff) / 255.0;
+      b = ((face->foreground >> 0) & 0xff) / 255.0;
+      cairo_set_source_rgba (cr, r, g, b, a);
       cairo_mask (cr, mask);
     }
 
@@ -3147,11 +3150,15 @@ pgtk_scroll_run (struct window *w, struct run *run)
 
 /* Icons.  */
 
-/* Make the x-window of frame F use the gnu icon bitmap.  */
-
 static bool
 pgtk_bitmap_icon (struct frame *f, Lisp_Object file)
 {
+  /* This code has never worked anyway for the reason that Wayland
+     uses icons set within desktop files, and has been disabled
+     because leaving it intact would require image.c to retain a
+     reference to a GdkPixbuf (which are no longer used) within new
+     bitmaps.  */
+#if 0
   ptrdiff_t bitmap_id;
 
   if (FRAME_GTK_WIDGET (f) == 0)
@@ -3207,12 +3214,8 @@ pgtk_bitmap_icon (struct frame *f, Lisp_Object file)
       bitmap_id = FRAME_DISPLAY_INFO (f)->icon_bitmap_id;
     }
 
-  if (FRAME_DISPLAY_INFO (f)->bitmaps[bitmap_id - 1].img != NULL)
-    gtk_window_set_icon (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
-                        FRAME_DISPLAY_INFO (f)->bitmaps[bitmap_id - 1].img);
-
   f->output_data.pgtk->icon_bitmap = bitmap_id;
-
+#endif /* 0 */
   return false;
 }
 
@@ -6685,12 +6688,12 @@ pgtk_display_x_warning (GdkDisplay *display)
   gtk_window_set_title (window, "Warning");
   gtk_window_set_screen (window, screen);
 
-  label = gtk_label_new ("You are trying to run Emacs configured with"
-                         " the \"pure-GTK\" interface under the X Window"
-                         " System.  That configuration is unsupported and"
-                         " will lead to sporadic crashes during transfer of"
-                         " large selection data.  It will also lead to"
-                         " various problems with keyboard input.");
+  label = gtk_label_new ("You are trying to run Emacs configured with\n"
+                         " the \"pure-GTK\" interface under the X Window\n"
+                         " System.  That configuration is unsupported and\n"
+                         " will lead to sporadic crashes during transfer of\n"
+                         " large selection data.  It will also lead to\n"
+                         " various problems with keyboard input.\n");
   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
   gtk_container_add (GTK_CONTAINER (content_area), label);
   gtk_widget_show (label);
diff --git a/src/pgtkterm.h b/src/pgtkterm.h
index 8f2f00efdad..069e425fd9c 100644
--- a/src/pgtkterm.h
+++ b/src/pgtkterm.h
@@ -42,7 +42,6 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 struct pgtk_bitmap_record
 {
-  void *img;
   char *file;
   int refcount;
   int height, width, depth;
diff --git a/src/print.c b/src/print.c
index 5c95aeb9a20..eb20cfb1c47 100644
--- a/src/print.c
+++ b/src/print.c
@@ -759,8 +759,7 @@ For instance:
 
   (prin1 object nil \\='((length . 100) (circle . t))).
 
-See the manual entry `(elisp)Output Overrides' for a list of possible
-values.
+See Info node `(elisp)Output Overrides' for a list of possible values.
 
 As a special case, OVERRIDES can also simply be the symbol t, which
 means "use default values for all the print-related settings".  */)
@@ -1913,12 +1912,17 @@ print_vectorlike (Lisp_Object obj, Lisp_Object 
printcharfun, bool escapeflag,
              print_c_string ("#<font-entity", printcharfun);
            for (int i = 0; i < FONT_SPEC_MAX; i++)
              {
-               printchar (' ', printcharfun);
-               if (i < FONT_WEIGHT_INDEX || i > FONT_WIDTH_INDEX)
-                 print_object (AREF (obj, i), printcharfun, escapeflag);
-               else
-                 print_object (font_style_symbolic (obj, i, 0),
-                               printcharfun, escapeflag);
+               /* FONT_EXTRA_INDEX can contain private information in
+                  font entities which isn't safe to print.  */
+               if (i != FONT_EXTRA_INDEX || !FONT_ENTITY_P (obj))
+                 {
+                   printchar (' ', printcharfun);
+                   if (i < FONT_WEIGHT_INDEX || i > FONT_WIDTH_INDEX)
+                     print_object (AREF (obj, i), printcharfun, escapeflag);
+                   else
+                     print_object (font_style_symbolic (obj, i, 0),
+                                   printcharfun, escapeflag);
+                 }
              }
          }
        else
diff --git a/src/process.c b/src/process.c
index 2d6e08f16b5..08cb810ec13 100644
--- a/src/process.c
+++ b/src/process.c
@@ -119,6 +119,11 @@ static struct rlimit nofile_limit;
 #include "gnutls.h"
 #endif
 
+#ifdef HAVE_ANDROID
+#include "android.h"
+#include "androidterm.h"
+#endif
+
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -876,7 +881,8 @@ allocate_pty (char pty_name[PTY_NAME_SIZE])
 
            /* Check to make certain that both sides are available.
               This avoids a nasty yet stupid bug in rlogins.  */
-           if (faccessat (AT_FDCWD, pty_name, R_OK | W_OK, AT_EACCESS) != 0)
+           if (sys_faccessat (AT_FDCWD, pty_name,
+                              R_OK | W_OK, AT_EACCESS) != 0)
              {
                emacs_close (fd);
                continue;
@@ -1731,6 +1737,18 @@ DEFUN ("process-list", Fprocess_list, Sprocess_list, 0, 
0, 0,
 }
 
 
+static Lisp_Object
+get_required_string_keyword_param (Lisp_Object kwargs, Lisp_Object keyword)
+{
+  Lisp_Object arg = plist_member (kwargs, keyword);
+  if (NILP (arg) || !CONSP (arg) || !CONSP (XCDR (arg)))
+    error ("Missing %s keyword parameter", SSDATA (SYMBOL_NAME (keyword)));
+  Lisp_Object val = XCAR (XCDR (arg));
+  if (!STRINGP (val))
+    error ("%s value not a string", SSDATA (SYMBOL_NAME (keyword)));
+  return val;
+}
+
 /* Starting asynchronous inferior processes.  */
 
 DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
@@ -1795,7 +1813,7 @@ such handler, proceed as if FILE-HANDLER were nil.
 usage: (make-process &rest ARGS)  */)
   (ptrdiff_t nargs, Lisp_Object *args)
 {
-  Lisp_Object buffer, name, command, program, proc, contact, current_dir, tem;
+  Lisp_Object buffer, command, program, proc, contact, current_dir, tem;
   Lisp_Object xstderr, stderrproc;
   specpdl_ref count = SPECPDL_INDEX ();
 
@@ -1824,8 +1842,7 @@ usage: (make-process &rest ARGS)  */)
      chdir, since it's in a vfork.  */
   current_dir = get_current_directory (true);
 
-  name = plist_get (contact, QCname);
-  CHECK_STRING (name);
+  Lisp_Object name = get_required_string_keyword_param (contact, QCname);
 
   command = plist_get (contact, QCcommand);
   if (CONSP (command))
@@ -2002,7 +2019,7 @@ usage: (make-process &rest ARGS)  */)
        {
          tem = Qnil;
          openp (Vexec_path, program, Vexec_suffixes, &tem,
-                make_fixnum (X_OK), false, false);
+                make_fixnum (X_OK), false, false, NULL);
          if (NILP (tem))
            report_file_error ("Searching for program", program);
          tem = Fexpand_file_name (tem, Qnil);
@@ -2402,7 +2419,7 @@ usage:  (make-pipe-process &rest ARGS)  */)
 {
   Lisp_Object proc, contact;
   struct Lisp_Process *p;
-  Lisp_Object name, buffer;
+  Lisp_Object buffer;
   Lisp_Object tem;
   int inchannel, outchannel;
 
@@ -2411,8 +2428,7 @@ usage:  (make-pipe-process &rest ARGS)  */)
 
   contact = Flist (nargs, args);
 
-  name = plist_get (contact, QCname);
-  CHECK_STRING (name);
+  Lisp_Object name = get_required_string_keyword_param (contact, QCname);
   proc = make_process (name);
   specpdl_ref specpdl_count = SPECPDL_INDEX ();
   record_unwind_protect (remove_process, proc);
@@ -3932,7 +3948,7 @@ usage: (make-network-process &rest ARGS)  */)
 #endif
   EMACS_INT port = 0;
   Lisp_Object tem;
-  Lisp_Object name, buffer, host, service, address;
+  Lisp_Object buffer, host, service, address;
   Lisp_Object filter, sentinel, use_external_socket_p;
   Lisp_Object addrinfos = Qnil;
   int socktype;
@@ -3969,7 +3985,7 @@ usage: (make-network-process &rest ARGS)  */)
   else
     error ("Unsupported connection type");
 
-  name = plist_get (contact, QCname);
+  Lisp_Object name = get_required_string_keyword_param (contact, QCname);
   buffer = plist_get (contact, QCbuffer);
   filter = plist_get (contact, QCfilter);
   sentinel = plist_get (contact, QCsentinel);
@@ -3979,7 +3995,6 @@ usage: (make-network-process &rest ARGS)  */)
 
   if (!NILP (server) && nowait)
     error ("`:server' is incompatible with `:nowait'");
-  CHECK_STRING (name);
 
   /* :local ADDRESS or :remote ADDRESS */
   if (NILP (server))
@@ -5679,7 +5694,17 @@ wait_reading_process_output (intmax_t time_limit, int 
nsecs, int read_kbd,
            timeout = short_timeout;
 #endif
 
-         /* Non-macOS HAVE_GLIB builds call thread_select in xgselect.c.  */
+         /* Android doesn't support threads and requires using a
+            replacement for pselect in android.c to poll for
+            events.  */
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+         nfds = android_select (max_desc + 1,
+                                &Available, (check_write ? &Writeok : 0),
+                                NULL, &timeout);
+#else
+
+         /* Non-macOS HAVE_GLIB builds call thread_select in
+            xgselect.c.  */
 #if defined HAVE_GLIB && !defined HAVE_NS
          nfds = xg_select (max_desc + 1,
                            &Available, (check_write ? &Writeok : 0),
@@ -5695,6 +5720,7 @@ wait_reading_process_output (intmax_t time_limit, int 
nsecs, int read_kbd,
                                (check_write ? &Writeok : 0),
                                NULL, &timeout, NULL);
 #endif /* !HAVE_GLIB */
+#endif /* HAVE_ANDROID && !ANDROID_STUBIFY */
 
 #ifdef HAVE_GNUTLS
          /* Merge tls_available into Available. */
@@ -7232,10 +7258,10 @@ process has been transmitted to the serial port.  */)
     send_process (proc, "\004", 1, Qnil);
   else if (EQ (XPROCESS (proc)->type, Qserial))
     {
-#ifndef WINDOWSNT
+#if !defined WINDOWSNT && defined HAVE_TCDRAIN
       if (tcdrain (XPROCESS (proc)->outfd) != 0)
        report_file_error ("Failed tcdrain", Qnil);
-#endif /* not WINDOWSNT */
+#endif /* not WINDOWSNT && not TCDRAIN */
       /* Do nothing on Windows because writes are blocking.  */
     }
   else
@@ -7455,6 +7481,16 @@ handle_child_signal (int sig)
            {
              changed = true;
              if (STRINGP (XCDR (head)))
+               /* handle_child_signal is called in an async signal
+                  handler but needs to unlink temporary files which
+                  might've been created in an Android content
+                  provider.
+
+                  emacs_unlink is not async signal safe because
+                  deleting files from content providers must proceed
+                  through Java code.  Consequentially, if XCDR (head)
+                  lies on a content provider it will not be removed,
+                  which is a bug.  */
                unlink (SSDATA (XCDR (head)));
              XSETCAR (tail, Qnil);
            }
diff --git a/src/regex-emacs.c b/src/regex-emacs.c
index 9e298b81ebb..7e75f0ac597 100644
--- a/src/regex-emacs.c
+++ b/src/regex-emacs.c
@@ -47,6 +47,9 @@
 /* Make syntax table lookup grant data in gl_state.  */
 #define SYNTAX(c) syntax_property (c, 1)
 
+/* Explicit syntax lookup using the buffer-local table.  */
+#define BUFFER_SYNTAX(c) syntax_property (c, 0)
+
 #define RE_MULTIBYTE_P(bufp) ((bufp)->multibyte)
 #define RE_TARGET_MULTIBYTE_P(bufp) ((bufp)->target_multibyte)
 #define RE_STRING_CHAR(p, multibyte) \
@@ -132,18 +135,22 @@
 
 #define ISLOWER(c) lowercasep (c)
 
+#define ISUPPER(c) uppercasep (c)
+
+/* The following predicates use the buffer-local syntax table and
+   ignore syntax properties, for consistency with the up-front
+   assumptions made at compile time.  */
+
 #define ISPUNCT(c) (IS_REAL_ASCII (c)                          \
                    ? ((c) > ' ' && (c) < 0177                  \
                       && !(((c) >= 'a' && (c) <= 'z')          \
                            || ((c) >= 'A' && (c) <= 'Z')       \
                            || ((c) >= '0' && (c) <= '9')))     \
-                   : SYNTAX (c) != Sword)
+                   : BUFFER_SYNTAX (c) != Sword)
 
-#define ISSPACE(c) (SYNTAX (c) == Swhitespace)
+#define ISSPACE(c) (BUFFER_SYNTAX (c) == Swhitespace)
 
-#define ISUPPER(c) uppercasep (c)
-
-#define ISWORD(c) (SYNTAX (c) == Sword)
+#define ISWORD(c) (BUFFER_SYNTAX (c) == Sword)
 
 /* Use alloca instead of malloc.  This is because using malloc in
    re_search* or re_match* could cause memory leaks when C-g is used
@@ -554,7 +561,7 @@ print_partial_compiled_pattern (re_char *start, re_char 
*end)
            fprintf (stderr, "/charset [%s",
                     (re_opcode_t) *(p - 1) == charset_not ? "^" : "");
 
-           if (p + *p >= pend)
+           if (p + (*p & 0x7f) >= pend)
              fputs (" !extends past end of pattern! ", stderr);
 
            for (c = 0; c < 256; c++)
@@ -2048,13 +2055,6 @@ regex_compile (re_char *pattern, ptrdiff_t size,
                       is_xdigit, since they can only match ASCII characters.
                       We don't need to handle them for multibyte.  */
 
-                   /* Setup the gl_state object to its buffer-defined value.
-                      This hardcodes the buffer-global syntax-table for ASCII
-                      chars, while the other chars will obey syntax-table
-                      properties.  It's not ideal, but it's the way it's been
-                      done until now.  */
-                   SETUP_BUFFER_SYNTAX_TABLE ();
-
                    for (c = 0; c < 0x80; ++c)
                      if (re_iswctype (c, cc))
                        {
diff --git a/src/scroll.c b/src/scroll.c
index eee1ad80950..1f530dc5c95 100644
--- a/src/scroll.c
+++ b/src/scroll.c
@@ -21,6 +21,11 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include <config.h>
 
+/* The entire file is defined out under Android, where there is no
+   text terminal support of any kind.  */
+
+#ifndef HAVE_ANDROID
+
 #include "lisp.h"
 #include "termchar.h"
 #include "dispextern.h"
@@ -984,3 +989,5 @@ do_line_insertion_deletion_costs (struct frame *frame,
                 FRAME_DELETE_COST (frame), FRAME_DELETEN_COST (frame),
                 coefficient);
 }
+
+#endif
diff --git a/src/search.c b/src/search.c
index 122d6166637..742a78cb0cd 100644
--- a/src/search.c
+++ b/src/search.c
@@ -162,7 +162,7 @@ clear_regexp_cache (void)
     /* It's tempting to compare with the syntax-table we've actually changed,
        but it's not sufficient because char-table inheritance means that
        modifying one syntax-table can change others at the same time.  */
-    if (!searchbufs[i].busy && !EQ (searchbufs[i].syntax_table, Qt))
+    if (!searchbufs[i].busy && !BASE_EQ (searchbufs[i].syntax_table, Qt))
       searchbufs[i].regexp = Qnil;
 }
 
@@ -214,10 +214,11 @@ compile_pattern (Lisp_Object pattern, struct re_registers 
*regp,
           && !cp->busy
          && STRING_MULTIBYTE (cp->regexp) == STRING_MULTIBYTE (pattern)
          && !NILP (Fstring_equal (cp->regexp, pattern))
-         && EQ (cp->buf.translate, translate)
+         && BASE_EQ (cp->buf.translate, translate)
          && cp->posix == posix
-         && (EQ (cp->syntax_table, Qt)
-             || EQ (cp->syntax_table, BVAR (current_buffer, syntax_table)))
+         && (BASE_EQ (cp->syntax_table, Qt)
+             || BASE_EQ (cp->syntax_table,
+                         BVAR (current_buffer, syntax_table)))
          && !NILP (Fequal (cp->f_whitespace_regexp, Vsearch_spaces_regexp))
          && cp->buf.charset_unibyte == charset_unibyte)
        break;
@@ -2362,7 +2363,9 @@ the replacement text.  Otherwise, maybe capitalize the 
whole text, or
 maybe just word initials, based on the replaced text.  If the replaced
 text has only capital letters and has at least one multiletter word,
 convert NEWTEXT to all caps.  Otherwise if all words are capitalized
-in the replaced text, capitalize each word in NEWTEXT.
+in the replaced text, capitalize each word in NEWTEXT.  Note that
+what exactly is a word is determined by the syntax tables in effect
+in the current buffer.
 
 If optional third arg LITERAL is non-nil, insert NEWTEXT literally.
 Otherwise treat `\\' as special:
@@ -2892,7 +2895,7 @@ Return value is undefined if the last search failed.  */)
       ptrdiff_t start = search_regs.start[i];
       if (start >= 0)
        {
-         if (EQ (last_thing_searched, Qt)
+         if (BASE_EQ (last_thing_searched, Qt)
              || ! NILP (integers))
            {
              XSETFASTINT (data[2 * i], start);
diff --git a/src/sfnt.c b/src/sfnt.c
new file mode 100644
index 00000000000..b66613eaa53
--- /dev/null
+++ b/src/sfnt.c
@@ -0,0 +1,19975 @@
+/* TrueType format font support for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "sfnt.h"
+
+#include <assert.h>
+#include <attribute.h>
+#include <byteswap.h>
+#include <fcntl.h>
+#include <intprops.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <errno.h>
+#include <alloca.h>
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic ignored "-Wstringop-overflow"
+#endif
+
+#ifdef TEST
+
+#include <time.h>
+#include <timespec.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+static void *
+xmalloc (size_t size)
+{
+  void *ptr;
+
+  ptr = malloc (size);
+
+  if (!ptr)
+    abort ();
+
+  return ptr;
+}
+
+static void *
+xrealloc (void *ptr, size_t size)
+{
+  void *new_ptr;
+
+  new_ptr = realloc (ptr, size);
+
+  if (!new_ptr)
+    abort ();
+
+  return new_ptr;
+}
+
+static void
+xfree (void *ptr)
+{
+  free (ptr);
+}
+
+/* Use this for functions that are static while building in test mode,
+   but are used outside as well.  */
+#define TEST_STATIC static
+
+/* Needed for tests.  */
+#define ARRAYELTS(arr) (sizeof (arr) / sizeof (arr)[0])
+
+#else
+#define TEST_STATIC
+#include "lisp.h"
+#endif
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+/* This file provides generic support for reading most TrueType fonts,
+   and some OpenType fonts with TrueType outlines, along with glyph
+   lookup, outline decomposition, and alpha mask generation from those
+   glyphs.  It is intended to be used on any platform where proper
+   libraries such as FreeType are not easily available, and the native
+   font library is too limited for Emacs to support properly.
+
+   Unlike most popular libraries for handling fonts, no ``font'' or
+   ``face'' type is provided.  Instead, routines and structure
+   definitions for accessing and making use of individual tables in a
+   font file are exported, which allows for flexibility in the rest of
+   Emacs.
+
+   Try not to keep this file too dependent on Emacs.  Everything Lisp
+   related goes in sfntfont.c.  The author wants to keep using it for
+   some other (free) software.
+
+   The source of reference is the TrueType Reference Manual, published
+   by Apple Computer, which is currently found at:
+
+     https://developer.apple.com/fonts/TrueType-Reference-Manual/
+
+   Apple's TrueType implementation is notably missing features
+   provided by Microsoft's extended OpenType scaler, such as the two
+   additional phantom points on the Y axis, and also behaves
+   differently, especially when it comes to considering phantom points
+   as anchors in compound glyphs.
+
+   As a result, do not expect this scaler to work well with Microsoft
+   fonts such as Arial.  */
+
+
+
+/* Mapping between sfnt table names and their identifiers.  */
+
+static uint32_t sfnt_table_names[] =
+  {
+    [SFNT_TABLE_CMAP] = 0x636d6170,
+    [SFNT_TABLE_GLYF] = 0x676c7966,
+    [SFNT_TABLE_HEAD] = 0x68656164,
+    [SFNT_TABLE_HHEA] = 0x68686561,
+    [SFNT_TABLE_HMTX] = 0x686d7478,
+    [SFNT_TABLE_LOCA] = 0x6c6f6361,
+    [SFNT_TABLE_MAXP] = 0x6d617870,
+    [SFNT_TABLE_NAME] = 0x6e616d65,
+    [SFNT_TABLE_META] = 0x6d657461,
+    [SFNT_TABLE_CVT ] = 0x63767420,
+    [SFNT_TABLE_FPGM] = 0x6670676d,
+    [SFNT_TABLE_PREP] = 0x70726570,
+    [SFNT_TABLE_FVAR] = 0x66766172,
+    [SFNT_TABLE_GVAR] = 0x67766172,
+    [SFNT_TABLE_CVAR] = 0x63766172,
+    [SFNT_TABLE_AVAR] = 0x61766172,
+  };
+
+/* Swap values from TrueType to system byte order.  */
+
+static void
+sfnt_swap16_1 (uint16_t *value)
+{
+#ifndef WORDS_BIGENDIAN
+  *value = bswap_16 (*value);
+#endif
+}
+
+static void
+sfnt_swap32_1 (uint32_t *value)
+{
+#ifndef WORDS_BIGENDIAN
+  *value = bswap_32 (*value);
+#endif
+}
+
+#define sfnt_swap16(what) (sfnt_swap16_1 ((uint16_t *) (what)))
+#define sfnt_swap32(what) (sfnt_swap32_1 ((uint32_t *) (what)))
+
+/* Read the table directory from the file FD.  FD must currently be at
+   the start of the file (or an offset defined in the TTC header, if
+   applicable), and must be seekable.  Return the table directory upon
+   success, else NULL.
+
+   Value is NULL upon failure, and the offset subtable upon success.
+   If FD is actually a TrueType collection file, value is -1.  */
+
+TEST_STATIC struct sfnt_offset_subtable *
+sfnt_read_table_directory (int fd)
+{
+  struct sfnt_offset_subtable *subtable;
+  ssize_t rc;
+  size_t offset, subtable_size;
+  int i;
+
+  subtable = xmalloc (sizeof *subtable);
+  offset = SFNT_ENDOF (struct sfnt_offset_subtable,
+                      range_shift, uint16_t);
+  rc = read (fd, subtable, offset);
+
+  if (rc < offset)
+    {
+      if (rc >= sizeof (uint32_t))
+       {
+         /* Detect a TTC file.  In that case, the first long will be
+            ``ttcf''.  */
+         sfnt_swap32 (&subtable->scaler_type);
+
+         if (subtable->scaler_type == SFNT_TTC_TTCF)
+           {
+             xfree (subtable);
+             return (struct sfnt_offset_subtable *) -1;
+           }
+       }
+
+      xfree (subtable);
+      return NULL;
+    }
+
+  sfnt_swap32 (&subtable->scaler_type);
+
+  /* Bail out early if this font is actually a TrueType collection
+     file.  */
+
+  if (subtable->scaler_type == SFNT_TTC_TTCF)
+    {
+      xfree (subtable);
+      return (struct sfnt_offset_subtable *) -1;
+    }
+
+  sfnt_swap16 (&subtable->num_tables);
+  sfnt_swap16 (&subtable->search_range);
+  sfnt_swap16 (&subtable->entry_selector);
+  sfnt_swap16 (&subtable->range_shift);
+
+  /* Figure out how many more tables have to be read, and read each
+     one of them.  */
+  subtable_size = (subtable->num_tables
+                  * sizeof (struct sfnt_table_directory));
+  subtable = xrealloc (subtable, sizeof *subtable + subtable_size);
+  subtable->subtables
+    = (struct sfnt_table_directory *) (subtable + 1);
+
+  rc = read (fd, subtable->subtables, subtable_size);
+
+  if (rc < offset)
+    {
+      xfree (subtable);
+      return NULL;
+    }
+
+  /* Swap each of the subtables.  */
+
+  for (i = 0; i < subtable->num_tables; ++i)
+    {
+      sfnt_swap32 (&subtable->subtables[i].tag);
+      sfnt_swap32 (&subtable->subtables[i].checksum);
+      sfnt_swap32 (&subtable->subtables[i].offset);
+      sfnt_swap32 (&subtable->subtables[i].length);
+    }
+
+  return subtable;
+}
+
+/* Return a pointer to the table directory entry for TABLE in
+   SUBTABLE, or NULL if it was not found.  */
+
+static struct sfnt_table_directory *
+sfnt_find_table (struct sfnt_offset_subtable *subtable,
+                enum sfnt_table table)
+{
+  int i;
+
+  for (i = 0; i < subtable->num_tables; ++i)
+    {
+      if (subtable->subtables[i].tag == sfnt_table_names[table])
+       return &subtable->subtables[i];
+    }
+
+  return NULL;
+}
+
+
+
+/* Character mapping routines.  */
+
+/* Read a format 0 cmap subtable from FD.  HEADER has already been
+   read.  */
+
+static struct sfnt_cmap_format_0 *
+sfnt_read_cmap_format_0 (int fd,
+                        struct sfnt_cmap_encoding_subtable_data *header)
+{
+  struct sfnt_cmap_format_0 *format0;
+  ssize_t rc;
+  size_t wanted_size;
+
+  format0 = xmalloc (sizeof *format0);
+
+  /* Fill in fields that have already been read.  */
+  format0->format = header->format;
+  format0->length = header->length;
+
+  /* Read the rest.  */
+  wanted_size = (sizeof *format0
+                - offsetof (struct sfnt_cmap_format_0,
+                            language));
+  rc = read (fd, &format0->language, wanted_size);
+
+  if (rc < wanted_size)
+    {
+      xfree (format0);
+      return (struct sfnt_cmap_format_0 *) -1;
+    }
+
+  /* Swap fields and return.  */
+  sfnt_swap16 (&format0->language);
+  return format0;
+}
+
+/* Read a format 2 cmap subtable from FD.  HEADER has already been
+   read.  */
+
+static struct sfnt_cmap_format_2 *
+sfnt_read_cmap_format_2 (int fd,
+                        struct sfnt_cmap_encoding_subtable_data *header)
+{
+  struct sfnt_cmap_format_2 *format2;
+  ssize_t rc;
+  size_t min_bytes;
+  int i, nsub;
+
+  /* Reject contents that are too small.  */
+  min_bytes = SFNT_ENDOF (struct sfnt_cmap_format_2,
+                         sub_header_keys, uint16_t[256]);
+  if (header->length < min_bytes)
+    return NULL;
+
+  /* Add enough bytes at the end to fit the two variable length
+     pointers.  */
+  format2 = xmalloc (header->length + sizeof *format2);
+  format2->format = header->format;
+  format2->length = header->length;
+
+  /* Read the part before the variable length data.  */
+  min_bytes -= offsetof (struct sfnt_cmap_format_2, language);
+  rc = read (fd, &format2->language, min_bytes);
+  if (rc < min_bytes)
+    {
+      xfree (format2);
+      return (struct sfnt_cmap_format_2 *) -1;
+    }
+
+  /* Swap the fields now.  */
+
+  sfnt_swap16 (&format2->language);
+
+  /* At the same time, look for the largest value in sub_header_keys.
+     That will be the number of subheaders and elements in the glyph
+     index array.  */
+
+  nsub = 0;
+
+  for (i = 0; i < 256; ++i)
+    {
+      sfnt_swap16 (&format2->sub_header_keys[i]);
+
+      if (format2->sub_header_keys[i] > nsub)
+       nsub = format2->sub_header_keys[i];
+    }
+
+  if (!nsub)
+    /* If there are no subheaders, then things are finished.  */
+    return format2;
+
+  /* Otherwise, read the rest of the variable length data to the end
+     of format2.  */
+  min_bytes = (format2->length
+              - SFNT_ENDOF (struct sfnt_cmap_format_2,
+                            sub_header_keys, uint16_t[256]));
+  rc = read (fd, format2 + 1, min_bytes);
+  if (rc < min_bytes)
+    {
+      xfree (format2);
+      return (struct sfnt_cmap_format_2 *) -1;
+    }
+
+  /* Check whether or not the data is of the correct size.  */
+  if (min_bytes < nsub * sizeof *format2->subheaders)
+    {
+      xfree (format2);
+      return (struct sfnt_cmap_format_2 *) -1;
+    }
+
+  /* Point the data pointers to the right location, swap everything,
+     and return.  */
+
+  format2->subheaders
+    = (struct sfnt_cmap_format_2_subheader *) (format2 + 1);
+  format2->glyph_index_array
+    = (uint16_t *) (format2->subheaders + nsub);
+
+  for (i = 0; i < nsub; ++i)
+    {
+      sfnt_swap16 (&format2->subheaders[i].first_code);
+      sfnt_swap16 (&format2->subheaders[i].entry_count);
+      sfnt_swap16 (&format2->subheaders[i].id_delta);
+      sfnt_swap16 (&format2->subheaders[i].id_range_offset);
+    }
+
+  /* Figure out how big the glyph index array is, and swap everything
+     there.  */
+  format2->num_glyphs
+    = (min_bytes - nsub * sizeof *format2->subheaders) / 2;
+
+  for (i = 0; i < format2->num_glyphs; ++i)
+    sfnt_swap16 (&format2->glyph_index_array[i]);
+
+  return format2;
+}
+
+/* Read a format 4 cmap subtable from FD.  HEADER has already been
+   read.  */
+
+static struct sfnt_cmap_format_4 *
+sfnt_read_cmap_format_4 (int fd,
+                        struct sfnt_cmap_encoding_subtable_data *header)
+{
+  struct sfnt_cmap_format_4 *format4;
+  size_t min_bytes, variable_size;
+  ssize_t rc;
+  size_t bytes_minus_format4;
+  int seg_count, i;
+
+  min_bytes = SFNT_ENDOF (struct sfnt_cmap_format_4,
+                         range_shift, uint16_t);
+
+  /* Check that the length is at least min_bytes.  */
+  if (header->length < min_bytes)
+    return NULL;
+
+  /* Allocate the format4 buffer, making it the size of the buffer
+     itself plus that of the data.  */
+  format4 = xmalloc (header->length + sizeof *format4);
+
+  /* Copy over fields that have already been read.  */
+  format4->format = header->format;
+  format4->length = header->length;
+
+  /* Read the initial data.  */
+  min_bytes -= offsetof (struct sfnt_cmap_format_4, language);
+  rc = read (fd, &format4->language, min_bytes);
+  if (rc < min_bytes)
+    {
+      xfree (format4);
+      return (struct sfnt_cmap_format_4 *) -1;
+    }
+
+  /* Swap fields that have been read.  */
+  sfnt_swap16 (&format4->language);
+  sfnt_swap16 (&format4->seg_count_x2);
+  sfnt_swap16 (&format4->search_range);
+  sfnt_swap16 (&format4->entry_selector);
+  sfnt_swap16 (&format4->range_shift);
+
+  /* Get the number of segments to read.  */
+  seg_count = format4->seg_count_x2 / 2;
+
+  /* Now calculate whether or not the size is sufficiently large.  */
+  bytes_minus_format4
+    = format4->length - SFNT_ENDOF (struct sfnt_cmap_format_4,
+                                   range_shift, uint16_t);
+  variable_size = (seg_count * sizeof *format4->end_code
+                  + sizeof *format4->reserved_pad
+                  + seg_count * sizeof *format4->start_code
+                  + seg_count * sizeof *format4->id_delta
+                  + seg_count * sizeof *format4->id_range_offset);
+
+  if (bytes_minus_format4 < variable_size)
+    {
+      /* Not enough bytes to fit the entire implied table
+        contents.  */
+      xfree (format4);
+      return NULL;
+    }
+
+  /* Read the rest of the bytes to the end of format4.  */
+  rc = read (fd, format4 + 1, bytes_minus_format4);
+  if (rc < bytes_minus_format4)
+    {
+      xfree (format4);
+      return (struct sfnt_cmap_format_4 *) -1;
+    }
+
+  /* Set data pointers to the right locations.  */
+  format4->end_code = (uint16_t *) (format4 + 1);
+  format4->reserved_pad = format4->end_code + seg_count;
+  format4->start_code = format4->reserved_pad + 1;
+  format4->id_delta = (int16_t *) (format4->start_code + seg_count);
+  format4->id_range_offset = format4->id_delta + seg_count;
+  format4->glyph_index_array = (uint16_t *) (format4->id_range_offset
+                                            + seg_count);
+
+  /* N.B. that the number of elements in glyph_index_array is
+     (bytes_minus_format4 - variable_size) / 2.  Swap all the
+     data.  */
+
+  sfnt_swap16 (format4->reserved_pad);
+
+  for (i = 0; i < seg_count; ++i)
+    {
+      sfnt_swap16 (&format4->end_code[i]);
+      sfnt_swap16 (&format4->start_code[i]);
+      sfnt_swap16 (&format4->id_delta[i]);
+      sfnt_swap16 (&format4->id_range_offset[i]);
+    }
+
+  format4->glyph_index_size
+    = (bytes_minus_format4 - variable_size) / 2;
+
+  for (i = 0; i < format4->glyph_index_size; ++i)
+    sfnt_swap16 (&format4->glyph_index_array[i]);
+
+  /* Done.  Return the format 4 character map.  */
+  return format4;
+}
+
+/* Read a format 6 cmap subtable from FD.  HEADER has already been
+   read.  */
+
+static struct sfnt_cmap_format_6 *
+sfnt_read_cmap_format_6 (int fd,
+                        struct sfnt_cmap_encoding_subtable_data *header)
+{
+  struct sfnt_cmap_format_6 *format6;
+  size_t min_size;
+  ssize_t rc;
+  uint16_t i;
+
+  min_size = SFNT_ENDOF (struct sfnt_cmap_format_6, entry_count,
+                        uint16_t);
+
+  /* See if header->length is big enough.  */
+  if (header->length < min_size)
+    return NULL;
+
+  /* Allocate the buffer to hold header->size and enough for at least
+     the glyph index array pointer.  */
+  format6 = xmalloc (header->length + sizeof *format6);
+
+  /* Fill in data that has already been read.  */
+  format6->format = header->format;
+  format6->length = header->length;
+
+  /* Read the fixed size data.  */
+  min_size -= offsetof (struct sfnt_cmap_format_6, language);
+  rc = read (fd, &format6->language, min_size);
+  if (rc < min_size)
+    {
+      xfree (format6);
+      return (struct sfnt_cmap_format_6 *) -1;
+    }
+
+  /* Swap what was read.  */
+  sfnt_swap16 (&format6->language);
+  sfnt_swap16 (&format6->first_code);
+  sfnt_swap16 (&format6->entry_count);
+
+  /* Figure out whether or not header->length is sufficient to hold
+     the variable length data.  */
+  if (header->length
+      < format6->entry_count * sizeof *format6->glyph_index_array)
+    {
+      xfree (format6);
+      return NULL;
+    }
+
+  /* Read the variable length data.  */
+  rc = read (fd, format6 + 1,
+            (format6->entry_count
+             * sizeof *format6->glyph_index_array));
+  if (rc < format6->entry_count * sizeof *format6->glyph_index_array)
+    {
+      xfree (format6);
+      return (struct sfnt_cmap_format_6 *) -1;
+    }
+
+  /* Set the data pointer and swap everything.  */
+  format6->glyph_index_array = (uint16_t *) (format6 + 1);
+  for (i = 0; i < format6->entry_count; ++i)
+    sfnt_swap16 (&format6->glyph_index_array[i]);
+
+  /* All done! */
+  return format6;
+}
+
+/* Read a format 8 cmap subtable from FD.  HEADER has already been
+   read.  */
+
+static struct sfnt_cmap_format_8 *
+sfnt_read_cmap_format_8 (int fd,
+                        struct sfnt_cmap_encoding_subtable_data *header)
+{
+  struct sfnt_cmap_format_8 *format8;
+  size_t min_size, temp;
+  ssize_t rc;
+  uint32_t length, i;
+
+  /* Read the 32-bit length field.  */
+  if (read (fd, &length, sizeof (length)) < sizeof (length))
+    return (struct sfnt_cmap_format_8 *) -1;
+
+  /* Swap the 32-bit length field.  */
+  sfnt_swap32 (&length);
+
+  min_size = SFNT_ENDOF (struct sfnt_cmap_format_8, num_groups,
+                        uint32_t);
+
+  /* Make sure the header is at least as large as min_size.  */
+  if (length < min_size)
+    return NULL;
+
+  /* Allocate a buffer of sufficient size.  */
+  format8 = xmalloc (length + sizeof *format8);
+  format8->format = header->format;
+  format8->reserved = header->length;
+  format8->length = length;
+
+  /* Read the fixed length data.  */
+  min_size -= offsetof (struct sfnt_cmap_format_8, language);
+  rc = read (fd, &format8->language, min_size);
+  if (rc < min_size)
+    {
+      xfree (format8);
+      return (struct sfnt_cmap_format_8 *) -1;
+    }
+
+  /* Swap what was read.  */
+  sfnt_swap32 (&format8->language);
+  sfnt_swap32 (&format8->num_groups);
+
+  /* See if the size is sufficient to read the variable length
+     data.  */
+  min_size = SFNT_ENDOF (struct sfnt_cmap_format_8, num_groups,
+                        uint32_t);
+
+  if (INT_MULTIPLY_WRAPV (format8->num_groups, sizeof *format8->groups,
+                         &temp))
+    {
+      xfree (format8);
+      return NULL;
+    }
+
+  if (INT_ADD_WRAPV (min_size, temp, &min_size))
+    {
+      xfree (format8);
+      return NULL;
+    }
+
+  if (length < min_size)
+    {
+      xfree (format8);
+      return NULL;
+    }
+
+  /* Now read the variable length data.  */
+  rc = read (fd, format8 + 1, temp);
+  if (rc < temp)
+    {
+      xfree (format8);
+      return (struct sfnt_cmap_format_8 *) -1;
+    }
+
+  /* Set the pointer to the variable length data.  */
+  format8->groups
+    = (struct sfnt_cmap_format_8_or_12_group *) (format8 + 1);
+
+  for (i = 0; i < format8->num_groups; ++i)
+    {
+      sfnt_swap32 (&format8->groups[i].start_char_code);
+      sfnt_swap32 (&format8->groups[i].end_char_code);
+      sfnt_swap32 (&format8->groups[i].start_glyph_code);
+    }
+
+  /* All done.  */
+  return format8;
+}
+
+/* Read a format 12 cmap subtable from FD.  HEADER has already been
+   read.  */
+
+static struct sfnt_cmap_format_12 *
+sfnt_read_cmap_format_12 (int fd,
+                         struct sfnt_cmap_encoding_subtable_data *header)
+{
+  struct sfnt_cmap_format_12 *format12;
+  size_t min_size, temp;
+  ssize_t rc;
+  uint32_t length, i;
+
+  /* Read the 32-bit length field.  */
+  if (read (fd, &length, sizeof (length)) < sizeof (length))
+    return (struct sfnt_cmap_format_12 *) -1;
+
+  /* Swap the 32-bit length field.  */
+  sfnt_swap32 (&length);
+
+  min_size = SFNT_ENDOF (struct sfnt_cmap_format_12, num_groups,
+                        uint32_t);
+
+  /* Make sure the header is at least as large as min_size.  */
+  if (length < min_size)
+    return NULL;
+
+  /* Allocate a buffer of sufficient size.  */
+  format12 = xmalloc (length + sizeof *format12);
+  format12->format = header->format;
+  format12->reserved = header->length;
+  format12->length = length;
+
+  /* Read the fixed length data.  */
+  min_size -= offsetof (struct sfnt_cmap_format_12, language);
+  rc = read (fd, &format12->language, min_size);
+  if (rc < min_size)
+    {
+      xfree (format12);
+      return (struct sfnt_cmap_format_12 *) -1;
+    }
+
+  /* Swap what was read.  */
+  sfnt_swap32 (&format12->language);
+  sfnt_swap32 (&format12->num_groups);
+
+  /* See if the size is sufficient to read the variable length
+     data.  */
+  min_size = SFNT_ENDOF (struct sfnt_cmap_format_12, num_groups,
+                        uint32_t);
+
+  if (INT_MULTIPLY_WRAPV (format12->num_groups, sizeof *format12->groups,
+                         &temp))
+    {
+      xfree (format12);
+      return NULL;
+    }
+
+  if (INT_ADD_WRAPV (min_size, temp, &min_size))
+    {
+      xfree (format12);
+      return NULL;
+    }
+
+  if (length < min_size)
+    {
+      xfree (format12);
+      return NULL;
+    }
+
+  /* Now read the variable length data.  */
+  rc = read (fd, format12 + 1, temp);
+  if (rc < temp)
+    {
+      xfree (format12);
+      return (struct sfnt_cmap_format_12 *) -1;
+    }
+
+  /* Set the pointer to the variable length data.  */
+  format12->groups
+    = (struct sfnt_cmap_format_8_or_12_group *) (format12 + 1);
+
+  for (i = 0; i < format12->num_groups; ++i)
+    {
+      sfnt_swap32 (&format12->groups[i].start_char_code);
+      sfnt_swap32 (&format12->groups[i].end_char_code);
+      sfnt_swap32 (&format12->groups[i].start_glyph_code);
+    }
+
+  /* All done.  */
+  return format12;
+}
+
+/* Read a 3-byte big endian number from BYTES.  */
+
+static unsigned int
+sfnt_read_24 (unsigned char *bytes)
+{
+  return (bytes[0] << 16u) | (bytes[1] << 8u) | bytes[2];
+}
+
+/* Read a format 14 cmap table from FD.  HEADER->format will be 14 and
+   HEADER->length will be 0; the 16-bit length field is not read.
+   OFFSET is the offset of the table's header in the font file.
+
+   Only variation selector records will be read.  UVS tables will
+   not.  */
+
+static struct sfnt_cmap_format_14 *
+sfnt_read_cmap_format_14 (int fd,
+                         struct sfnt_cmap_encoding_subtable_data *header,
+                         off_t offset)
+{
+  struct sfnt_cmap_format_14 *format14;
+  uint32_t length;
+  uint32_t num_records;
+  uint32_t buffer1[2];
+  size_t size, temp;
+  char buffer[3 + 4 + 4];
+  int i;
+
+  /* Read the length field and number of variation selector
+     records.  */
+
+  if (read (fd, buffer1, sizeof buffer1) < sizeof buffer1)
+    return NULL;
+
+  length = buffer1[0];
+  num_records = buffer1[1];
+
+  sfnt_swap32 (&length);
+  sfnt_swap32 (&num_records);
+
+  /* Now, the number of records present is known.  Allocate the format
+     14 cmap table.  */
+
+  size = sizeof *format14;
+  if (INT_MULTIPLY_WRAPV (num_records, sizeof *format14->records,
+                         &temp)
+      || INT_ADD_WRAPV (size, temp, &size))
+    return NULL;
+
+  format14 = xmalloc (size);
+
+  /* Fill in the data already read.  */
+  format14->format = header->format;
+  format14->length = length;
+  format14->num_var_selector_records = num_records;
+  format14->offset = offset;
+
+  /* Set the pointer to the remaining record data.  */
+  format14->records
+    = (struct sfnt_variation_selector_record *) (format14 + 1);
+
+  /* Read each variation selector record.  */
+
+  for (i = 0; i < num_records; ++i)
+    {
+      if (read (fd, buffer, sizeof buffer) < sizeof buffer)
+       {
+         xfree (format14);
+         return NULL;
+       }
+
+      /* First, read the 24 bit variation selector.  */
+      format14->records[i].var_selector
+       = sfnt_read_24 ((unsigned char *) buffer);
+
+      /* Next, read the two unaligned longs.  */
+      memcpy (&format14->records[i].default_uvs_offset,
+             buffer + 3,
+             sizeof format14->records[i].default_uvs_offset);
+      memcpy (&format14->records[i].nondefault_uvs_offset,
+             buffer + 7,
+             sizeof format14->records[i].nondefault_uvs_offset);
+
+      /* And swap them.  */
+      sfnt_swap32 (&format14->records[i].default_uvs_offset);
+      sfnt_swap32 (&format14->records[i].nondefault_uvs_offset);
+    }
+
+  /* Return the format 14 character mapping table.  */
+  return format14;
+}
+
+/* Read the CMAP subtable data from a given file FD at TABLE_OFFSET
+   bytes from DIRECTORY_OFFSET.  Return the subtable data if it is
+   supported.  Else, value is NULL if the format is unsupported, or -1
+   upon an IO error.  */
+
+static struct sfnt_cmap_encoding_subtable_data *
+sfnt_read_cmap_table_1 (int fd, uint32_t directory_offset,
+                       uint32_t table_offset)
+{
+  off_t offset;
+  struct sfnt_cmap_encoding_subtable_data header;
+
+  if (INT_ADD_WRAPV (directory_offset, table_offset, &offset))
+    return (struct sfnt_cmap_encoding_subtable_data *) -1;
+
+  if (lseek (fd, offset, SEEK_SET) == (off_t) -1)
+    return (struct sfnt_cmap_encoding_subtable_data *) -1;
+
+  if (read (fd, &header.format, sizeof header.format)
+      < sizeof header.format)
+    return (struct sfnt_cmap_encoding_subtable_data *) -1;
+
+  sfnt_swap16 (&header.format);
+
+  /* Format 14 tables are rather special: they do not have a 16-bit
+     `length' field.  When these tables are encountered, leave reading
+     the rest of the header to `sfnt_read_cmap_table_14'.  */
+
+  if (header.format != 14)
+    {
+      if (read (fd, &header.length, sizeof header.length)
+         < sizeof header.length)
+       return (struct sfnt_cmap_encoding_subtable_data *) -1;
+
+      sfnt_swap16 (&header.length);
+    }
+  else
+    header.length = 0;
+
+  switch (header.format)
+    {
+    case 0:
+      /* If the length changes, then something has changed to the
+        format.  */
+      if (header.length != 262)
+       return NULL;
+
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_0 (fd, &header));
+
+    case 2:
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_2 (fd, &header));
+
+    case 4:
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_4 (fd, &header));
+
+    case 6:
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_6 (fd, &header));
+
+    case 8:
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_8 (fd, &header));
+
+    case 12:
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_12 (fd, &header));
+
+    case 14:
+      return ((struct sfnt_cmap_encoding_subtable_data *)
+             sfnt_read_cmap_format_14 (fd, &header, offset));
+
+    default:
+      return NULL;
+    }
+}
+
+/* Read the CMAP table of a given font from the file FD.  Use the
+   table directory specified in SUBTABLE.
+
+   Return the CMAP table and a list of encoding subtables in
+   *SUBTABLES and *DATA upon success, else NULL.  If DATA is NULL, do
+   not read the subtable data.  */
+
+TEST_STATIC struct sfnt_cmap_table *
+sfnt_read_cmap_table (int fd, struct sfnt_offset_subtable *subtable,
+                     struct sfnt_cmap_encoding_subtable **subtables,
+                     struct sfnt_cmap_encoding_subtable_data ***data)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_cmap_table *cmap;
+  ssize_t rc;
+  int i, j;
+
+  /* Find the CMAP table in the table directory.  */
+  directory = sfnt_find_table (subtable, SFNT_TABLE_CMAP);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the start of the CMAP table.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Read the table header.  */
+  cmap = xmalloc (sizeof *cmap);
+  rc = read (fd, cmap, sizeof *cmap);
+
+  if (rc < sizeof *cmap)
+    {
+      xfree (cmap);
+      return NULL;
+    }
+
+  /* Swap the header data.  */
+  sfnt_swap16 (&cmap->version);
+  sfnt_swap16 (&cmap->num_subtables);
+
+  if (cmap->version != 0)
+    {
+      xfree (cmap);
+      return NULL;
+    }
+
+  *subtables = xmalloc (cmap->num_subtables
+                       * sizeof **subtables);
+
+
+  /* First, read the common parts of each encoding subtable.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      /* Read the common part of the new subtable.  */
+      rc = read (fd, &(*subtables)[i], sizeof (*subtables)[i]);
+
+      if (rc < sizeof (*subtables)[i])
+       {
+         xfree (cmap);
+         xfree (*subtables);
+         return NULL;
+       }
+
+      sfnt_swap16 (&(*subtables)[i].platform_id);
+      sfnt_swap16 (&(*subtables)[i].platform_specific_id);
+      sfnt_swap32 (&(*subtables)[i].offset);
+    }
+
+  /* If data is NULL, the caller only wants the table headers.  */
+
+  if (!data)
+    return cmap;
+
+  /* Second, read each encoding subtable itself.  */
+  *data = xmalloc (cmap->num_subtables * sizeof *data);
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      (*data)[i] = sfnt_read_cmap_table_1 (fd, directory->offset,
+                                          (*subtables)[i].offset);
+
+      if ((*data)[i] == (void *) -1)
+       {
+         /* An IO error occurred (as opposed to the subtable format
+            being unsupported.)  Return now.  */
+
+         for (j = 0; j < i; ++j)
+           xfree ((*data)[j]);
+
+         xfree (*data);
+         xfree (*subtables);
+         xfree (cmap);
+         return NULL;
+       }
+    }
+
+  return cmap;
+}
+
+/* Look up the glyph corresponding to CHARACTER in the format 0 cmap
+   FORMAT0.  Return 0 if no glyph was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_0 (sfnt_char character,
+                    struct sfnt_cmap_format_0 *format0)
+{
+  if (character >= 256)
+    return 0;
+
+  return format0->glyph_index_array[character];
+}
+
+/* Look up the glyph corresponding to CHARACTER in the format 2 cmap
+   FORMAT2.  Return 0 if no glyph was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_2 (sfnt_char character,
+                    struct sfnt_cmap_format_2 *format2)
+{
+  unsigned char i, k, j;
+  struct sfnt_cmap_format_2_subheader *subheader;
+  unsigned char *slice;
+  uint16_t glyph;
+
+  if (character > 65335)
+    return 0;
+
+  i = character >> 16;
+  j = character & 0xff;
+  k = format2->sub_header_keys[i] / 8;
+
+  if (k)
+    {
+      subheader = &format2->subheaders[k];
+
+      if (subheader->first_code <= j
+         && j <= ((int) subheader->first_code
+                  + (int) subheader->entry_count))
+       {
+         /* id_range_offset is actually the number of bytes past
+            itself containing the uint16_t ``slice''.  It is possibly
+            unaligned.  */
+         slice = (unsigned char *) &subheader->id_range_offset;
+         slice += subheader->id_range_offset;
+         slice += (j - subheader->first_code) * sizeof (uint16_t);
+
+         if (slice < (unsigned char *) format2->glyph_index_array
+             || (slice + 1
+                 > (unsigned char *) (format2->glyph_index_array
+                                      + format2->num_glyphs)))
+           /* The character is out of bounds.  */
+           return 0;
+
+         memcpy (&glyph, slice, sizeof glyph);
+         return (glyph + subheader->id_delta) % 65536;
+       }
+      else
+       return 0;
+    }
+
+  /* k is 0, so glyph_index_array[i] is the glyph.  */
+  return (i < format2->num_glyphs
+         ? format2->glyph_index_array[i]
+         : 0);
+}
+
+/* Like `bsearch'.  However, return the highest element above KEY if
+   it could not be found.  */
+
+static void *
+sfnt_bsearch_above (const void *key, const void *base,
+                   size_t nmemb, size_t size,
+                   int (*compar) (const void *,
+                                  const void *))
+{
+  const unsigned char *bytes, *sample;
+  size_t low, high, mid;
+
+  bytes = base;
+  low = 0;
+  high = nmemb - 1;
+
+  if (!nmemb)
+    return NULL;
+
+  while (low != high)
+    {
+      mid = low + (high - low) / 2;
+      sample = bytes + mid * size;
+
+      if (compar (key, sample) > 0)
+       low = mid + 1;
+      else
+       high = mid;
+    }
+
+  return (unsigned char *) bytes + low * size;
+}
+
+/* Compare two uint16_t's.  Used to bisect through a format 4
+   table.  */
+
+static int
+sfnt_compare_uint16 (const void *a, const void *b)
+{
+  return ((int) *((uint16_t *) a)) - ((int) *((uint16_t *) b));
+}
+
+/* Look up the glyph corresponding to CODE in the format 4 cmap
+   FORMAT4, using the table segment SEGMENT.  Value is 0 if no glyph
+   was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_4_1 (uint16_t code, uint16_t segment,
+                      struct sfnt_cmap_format_4 *format4)
+{
+  uint16_t *index;
+
+  if (format4->id_range_offset[segment])
+    {
+      /* id_range_offset is not 0, so the glyph mapping depends on
+        it.  */
+      index = (uint16_t *) (&format4->id_range_offset[segment]
+                           + format4->id_range_offset[segment] / 2
+                           + (code - format4->start_code[segment]));
+
+      /* Check that index is not out of bounds.  */
+      if (index >= (format4->glyph_index_array
+                   + format4->glyph_index_size)
+         || index < format4->glyph_index_array)
+       return 0;
+
+      /* Return what is in index.  */
+      return (*index ? (format4->id_delta[segment]
+                       + *index) % 65536 : 0);
+    }
+
+  /* Otherwise, just add id_delta.  */
+  return (format4->id_delta[segment] + code) % 65536;
+}
+
+/* Look up the glyph corresponding to CHARACTER in the format 4 cmap
+   FORMAT4.  Return 0 if no glyph was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_4 (sfnt_char character,
+                    struct sfnt_cmap_format_4 *format4)
+{
+  uint16_t *segment_address;
+  uint16_t code, segment;
+  sfnt_glyph glyph;
+
+  if (character > 65535)
+    return 0;
+
+  code = character;
+
+  /* Find the segment ending above or at CHARACTER.  */
+  segment_address = sfnt_bsearch_above (&code, format4->end_code,
+                                       format4->seg_count_x2 / 2,
+                                       sizeof code,
+                                       sfnt_compare_uint16);
+  segment = segment_address - format4->end_code;
+
+  /* If the segment starts too late, return 0.  */
+  if (!segment_address || format4->start_code[segment] > character)
+    return 0;
+
+  glyph = sfnt_lookup_glyph_4_1 (character, segment, format4);
+
+  if (glyph)
+    return glyph;
+
+  /* Fail.  */
+  return 0;
+}
+
+/* Look up the glyph corresponding to CHARACTER in the format 6 cmap
+   FORMAT6.  Return 0 if no glyph was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_6 (sfnt_char character,
+                    struct sfnt_cmap_format_6 *format6)
+{
+  if (character < format6->first_code
+      || character >= (format6->first_code
+                      + (int) format6->entry_count))
+    return 0;
+
+  return format6->glyph_index_array[character - format6->first_code];
+}
+
+/* Compare the sfnt_char A with B's end code.  Employed to bisect
+   through a format 8 or 12 table.  */
+
+static int
+sfnt_compare_char (const void *a, const void *b)
+{
+  struct sfnt_cmap_format_8_or_12_group *group;
+
+  group = (struct sfnt_cmap_format_8_or_12_group *) b;
+
+  return ((int) *((sfnt_char *) a)) - group->end_char_code;
+}
+
+/* Look up the glyph corresponding to CHARACTER in the format 8 cmap
+   FORMAT8.  Return 0 if no glyph was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_8 (sfnt_char character,
+                    struct sfnt_cmap_format_8 *format8)
+{
+  uint32_t i;
+  struct sfnt_cmap_format_8_or_12_group *group;
+
+  if (character > 0xffffffff)
+    return 0;
+
+  if (format8->num_groups > 64)
+    {
+      /* This table is large, likely supplied by a CJK or similar
+        font.  Perform a binary search.  */
+
+      /* Find the group whose END_CHAR_CODE is greater than or equal
+        to CHARACTER.  */
+
+      group = sfnt_bsearch_above (&character, format8->groups,
+                                 format8->num_groups,
+                                 sizeof format8->groups[0],
+                                 sfnt_compare_char);
+
+      if (group->start_char_code > character)
+       /* No glyph matches this group.  */
+       return 0;
+
+      /* Otherwise, use this group to map the character to a
+        glyph.  */
+      return (group->start_glyph_code
+             + character
+             - group->start_char_code);
+    }
+
+  for (i = 0; i < format8->num_groups; ++i)
+    {
+      if (format8->groups[i].start_char_code <= character
+         && format8->groups[i].end_char_code >= character)
+       return (format8->groups[i].start_glyph_code
+               + (character
+                  - format8->groups[i].start_char_code));
+    }
+
+  return 0;
+}
+
+/* Look up the glyph corresponding to CHARACTER in the format 12 cmap
+   FORMAT12.  Return 0 if no glyph was found.  */
+
+static sfnt_glyph
+sfnt_lookup_glyph_12 (sfnt_char character,
+                     struct sfnt_cmap_format_12 *format12)
+{
+  uint32_t i;
+  struct sfnt_cmap_format_8_or_12_group *group;
+
+  if (character > 0xffffffff)
+    return 0;
+
+  if (format12->num_groups > 64)
+    {
+      /* This table is large, likely supplied by a CJK or similar
+        font.  Perform a binary search.  */
+
+      /* Find the group whose END_CHAR_CODE is greater than or equal
+        to CHARACTER.  */
+
+      group = sfnt_bsearch_above (&character, format12->groups,
+                                 format12->num_groups,
+                                 sizeof format12->groups[0],
+                                 sfnt_compare_char);
+
+      if (group->start_char_code > character)
+       /* No glyph matches this group.  */
+       return 0;
+
+      /* Otherwise, use this group to map the character to a
+        glyph.  */
+      return (group->start_glyph_code
+             + character
+             - group->start_char_code);
+    }
+
+  for (i = 0; i < format12->num_groups; ++i)
+    {
+      if (format12->groups[i].start_char_code <= character
+         && format12->groups[i].end_char_code >= character)
+       return (format12->groups[i].start_glyph_code
+               + (character
+                  - format12->groups[i].start_char_code));
+    }
+
+  return 0;
+}
+
+/* Look up the glyph index corresponding to the character CHARACTER,
+   which must be in the correct encoding for the cmap table pointed to
+   by DATA.
+
+   DATA must be either a format 0, 2, 4, 6, 8 or 12 cmap table, else
+   behavior is undefined.  */
+
+TEST_STATIC sfnt_glyph
+sfnt_lookup_glyph (sfnt_char character,
+                  struct sfnt_cmap_encoding_subtable_data *data)
+{
+  switch (data->format)
+    {
+    case 0:
+      return sfnt_lookup_glyph_0 (character,
+                                 (struct sfnt_cmap_format_0 *) data);
+
+    case 2:
+      return sfnt_lookup_glyph_2 (character,
+                                 (struct sfnt_cmap_format_2 *) data);
+
+    case 4:
+      return sfnt_lookup_glyph_4 (character,
+                                 (struct sfnt_cmap_format_4 *) data);
+
+    case 6:
+      return sfnt_lookup_glyph_6 (character,
+                                 (struct sfnt_cmap_format_6 *) data);
+
+    case 8:
+      return sfnt_lookup_glyph_8 (character,
+                                 (struct sfnt_cmap_format_8 *) data);
+
+    case 12:
+      return sfnt_lookup_glyph_12 (character,
+                                  (struct sfnt_cmap_format_12 *) data);
+    }
+
+  return 0;
+}
+
+
+
+/* Header reading routines.  */
+
+/* Read the head table of a given font FD.  Use the table directory
+   specified in SUBTABLE.
+
+   Return the head table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_head_table *
+sfnt_read_head_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_head_table *head;
+  ssize_t rc;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_HEAD);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Read the entire table.  */
+  head = xmalloc (sizeof *head);
+  rc = read (fd, head, sizeof *head);
+
+  if (rc < sizeof *head)
+    {
+      xfree (head);
+      return NULL;
+    }
+
+  /* Swap the header data.  */
+  sfnt_swap32 (&head->version);
+  sfnt_swap32 (&head->revision);
+
+  if (head->version != 0x00010000)
+    {
+      xfree (head);
+      return NULL;
+    }
+
+  /* Swap the rest of the data.  */
+  sfnt_swap32 (&head->checksum_adjustment);
+  sfnt_swap32 (&head->magic);
+
+  if (head->magic != 0x5f0f3cf5)
+    {
+      xfree (head);
+      return NULL;
+    }
+
+  sfnt_swap16 (&head->flags);
+  sfnt_swap16 (&head->units_per_em);
+  sfnt_swap32 (&head->created_high);
+  sfnt_swap32 (&head->created_low);
+  sfnt_swap32 (&head->modified_high);
+  sfnt_swap32 (&head->modified_low);
+  sfnt_swap16 (&head->xmin);
+  sfnt_swap16 (&head->xmax);
+  sfnt_swap16 (&head->ymin);
+  sfnt_swap16 (&head->ymax);
+  sfnt_swap16 (&head->mac_style);
+  sfnt_swap16 (&head->lowest_rec_ppem);
+  sfnt_swap16 (&head->font_direction_hint);
+  sfnt_swap16 (&head->index_to_loc_format);
+  sfnt_swap16 (&head->glyph_data_format);
+
+  return head;
+}
+
+/* Read the hhea table of a given font FD.  Use the table directory
+   specified in SUBTABLE.
+
+   Return the head table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_hhea_table *
+sfnt_read_hhea_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_hhea_table *hhea;
+  ssize_t rc;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_HHEA);
+
+  if (!directory)
+    return NULL;
+
+  /* Check the length is right.  */
+  if (directory->length != sizeof *hhea)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Read the entire table.  */
+  hhea = xmalloc (sizeof *hhea);
+  rc = read (fd, hhea, sizeof *hhea);
+
+  if (rc < sizeof *hhea)
+    {
+      xfree (hhea);
+      return NULL;
+    }
+
+  /* Swap the header data.  */
+  sfnt_swap32 (&hhea->version);
+
+  if (hhea->version != 0x00010000)
+    {
+      xfree (hhea);
+      return NULL;
+    }
+
+  /* Swap the rest of the data.  */
+  sfnt_swap16 (&hhea->ascent);
+  sfnt_swap16 (&hhea->descent);
+  sfnt_swap16 (&hhea->line_gap);
+  sfnt_swap16 (&hhea->advance_width_max);
+  sfnt_swap16 (&hhea->min_left_side_bearing);
+  sfnt_swap16 (&hhea->min_right_side_bearing);
+  sfnt_swap16 (&hhea->x_max_extent);
+  sfnt_swap16 (&hhea->caret_slope_rise);
+  sfnt_swap16 (&hhea->caret_slope_run);
+  sfnt_swap16 (&hhea->reserved1);
+  sfnt_swap16 (&hhea->reserved2);
+  sfnt_swap16 (&hhea->reserved3);
+  sfnt_swap16 (&hhea->reserved4);
+  sfnt_swap16 (&hhea->metric_data_format);
+  sfnt_swap16 (&hhea->num_of_long_hor_metrics);
+
+  return hhea;
+}
+
+/* Read a short loca table from the given font FD.  Use the table
+   directory specified in SUBTABLE.
+
+   Return the short table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_loca_table_short *
+sfnt_read_loca_table_short (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_loca_table_short *loca;
+  ssize_t rc;
+  int i;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_LOCA);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out how many glyphs there are based on the length.  */
+  loca = xmalloc (sizeof *loca + directory->length);
+  loca->offsets = (uint16_t *) (loca + 1);
+  loca->num_offsets = directory->length / 2;
+
+  /* Read the variable-length table data.  */
+  rc = read (fd, loca->offsets, directory->length);
+  if (rc < directory->length)
+    {
+      xfree (loca);
+      return NULL;
+    }
+
+  /* Swap each of the offsets.  */
+  for (i = 0; i < loca->num_offsets; ++i)
+    sfnt_swap16 (&loca->offsets[i]);
+
+  /* Return the table.  */
+  return loca;
+}
+
+/* Read a long loca table from the given font FD.  Use the table
+   directory specified in SUBTABLE.
+
+   Return the long table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_loca_table_long *
+sfnt_read_loca_table_long (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_loca_table_long *loca;
+  ssize_t rc;
+  int i;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_LOCA);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out how many glyphs there are based on the length.  */
+  loca = xmalloc (sizeof *loca + directory->length);
+  loca->offsets = (uint32_t *) (loca + 1);
+  loca->num_offsets = directory->length / 4;
+
+  /* Read the variable-length table data.  */
+  rc = read (fd, loca->offsets, directory->length);
+  if (rc < directory->length)
+    {
+      xfree (loca);
+      return NULL;
+    }
+
+  /* Swap each of the offsets.  */
+  for (i = 0; i < loca->num_offsets; ++i)
+    sfnt_swap32 (&loca->offsets[i]);
+
+  /* Return the table.  */
+  return loca;
+}
+
+/* Read the maxp table from the given font FD.  Use the table
+   directory specified in SUBTABLE.
+
+   Return the maxp table upon success, else NULL.  If the version is
+   0.5, fields past num_glyphs will not be populated.  */
+
+TEST_STATIC struct sfnt_maxp_table *
+sfnt_read_maxp_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_maxp_table *maxp;
+  size_t size;
+  ssize_t rc;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_MAXP);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* If directory->length is not big enough for version 0.5, punt.  */
+  if (directory->length < SFNT_ENDOF (struct sfnt_maxp_table,
+                                     num_glyphs, uint16_t))
+    return NULL;
+
+  /* Allocate the buffer to hold the data.  Then, read
+     directory->length or sizeof *maxp bytes into it, whichever is
+     smaller.  */
+
+  maxp = xmalloc (sizeof *maxp);
+  size = MIN (directory->length, sizeof *maxp);
+  rc = read (fd, maxp, size);
+
+  if (rc < size)
+    {
+      xfree (maxp);
+      return NULL;
+    }
+
+  /* Now, swap version and num_glyphs.  */
+  sfnt_swap32 (&maxp->version);
+  sfnt_swap16 (&maxp->num_glyphs);
+
+  /* Reject version 1.0 tables that are too small.  */
+  if (maxp->version > 0x00005000 && size < sizeof *maxp)
+    {
+      xfree (maxp);
+      return NULL;
+    }
+
+  /* If the table is version 0.5, then this function is done.  */
+  if (maxp->version == 0x00005000)
+    return maxp;
+  else if (maxp->version != 0x00010000)
+    {
+      /* Reject invalid versions.  */
+      xfree (maxp);
+      return NULL;
+    }
+
+  /* Otherwise, swap the rest of the fields.  */
+  sfnt_swap16 (&maxp->max_points);
+  sfnt_swap16 (&maxp->max_contours);
+  sfnt_swap16 (&maxp->max_composite_points);
+  sfnt_swap16 (&maxp->max_composite_contours);
+  sfnt_swap16 (&maxp->max_zones);
+  sfnt_swap16 (&maxp->max_twilight_points);
+  sfnt_swap16 (&maxp->max_storage);
+  sfnt_swap16 (&maxp->max_function_defs);
+  sfnt_swap16 (&maxp->max_instruction_defs);
+  sfnt_swap16 (&maxp->max_stack_elements);
+  sfnt_swap16 (&maxp->max_size_of_instructions);
+  sfnt_swap16 (&maxp->max_component_elements);
+  sfnt_swap16 (&maxp->max_component_depth);
+
+  /* All done.  */
+  return maxp;
+}
+
+
+
+/* Glyph outlining generation.  */
+
+/* Read a glyf table from the given font FD.  Use the table directory
+   specified in SUBTABLE.  The glyph data is not swapped.
+
+   Return the glyf table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_glyf_table *
+sfnt_read_glyf_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_glyf_table *glyf;
+  ssize_t rc;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_GLYF);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Allocate enough to hold everything.  */
+  glyf = xmalloc (sizeof *glyf + directory->length);
+  glyf->size = directory->length;
+  glyf->glyphs = (unsigned char *) (glyf + 1);
+
+  /* Read the glyph data.  */
+  rc = read (fd, glyf->glyphs, glyf->size);
+  if (rc < glyf->size)
+    {
+      xfree (glyf);
+      return NULL;
+    }
+
+  /* Return the table.  */
+  return glyf;
+}
+
+#if defined HAVE_MMAP && !defined TEST
+
+/* Map a glyph table from the given font FD.  Use the table directory
+   specified in SUBTABLE.  The glyph data is not byte-swapped.
+
+   Value is the glyf table upon success, else NULL.
+   A mapped glyf table must be unmapped using `sfnt_unmap_glyf_table'.
+   The caller must correctly handle bus errors in between glyf->table
+   and glyf->size.  */
+
+struct sfnt_glyf_table *
+sfnt_map_glyf_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_glyf_table *glyf;
+  void *glyphs;
+  size_t offset, page, map_offset;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_GLYF);
+
+  if (!directory)
+    return NULL;
+
+  /* Now try to map the glyph data.  Make sure offset is a multiple of
+     the page size.  */
+
+  page = getpagesize ();
+  offset = directory->offset & ~(page - 1);
+
+  /* Figure out how much larger the mapping should be.  */
+  map_offset = directory->offset - offset;
+
+  /* Do the mmap.  */
+  glyphs = mmap (NULL, directory->length + map_offset,
+                PROT_READ, MAP_PRIVATE, fd, offset);
+
+  if (glyphs == MAP_FAILED)
+    return NULL;
+
+  /* An observation is that glyphs tend to be accessed in sequential
+     order and immediately after the font's glyph table is loaded.  */
+
+#ifdef HAVE_POSIX_MADVISE
+  posix_madvise (glyphs, directory->length,
+                POSIX_MADV_WILLNEED);
+#elif defined HAVE_MADVISE
+  madvise (glyphs, directory->length, MADV_WILLNEED);
+#endif
+
+  /* Allocate the glyf table.  */
+  glyf = xmalloc (sizeof *glyf);
+  glyf->size = directory->length;
+  glyf->glyphs = (unsigned char *) glyphs + map_offset;
+  glyf->start = glyphs;
+
+  return glyf;
+}
+
+/* Unmap the mmap'ed glyf table GLYF, then free its associated data.
+   Value is 0 upon success, else 1, in which case GLYF is still freed
+   all the same.  */
+
+int
+sfnt_unmap_glyf_table (struct sfnt_glyf_table *glyf)
+{
+  int rc;
+  size_t size;
+
+  /* Calculate the size of the mapping.  */
+  size = glyf->size + (glyf->glyphs - glyf->start);
+
+  rc = munmap (glyf->start, size);
+  xfree (glyf);
+
+  return rc != 0;
+}
+
+#endif /* HAVE_MMAP */
+
+/* Read the simple glyph outline from the glyph GLYPH from the
+   specified glyf table at the given offset.  Set GLYPH->simple to a
+   non-NULL value upon success, else set it to NULL.  */
+
+static void
+sfnt_read_simple_glyph (struct sfnt_glyph *glyph,
+                       struct sfnt_glyf_table *glyf,
+                       size_t offset)
+{
+  struct sfnt_simple_glyph *simple;
+  ssize_t min_size, min_size_2;
+  int i, number_of_points, repeat_count;
+  unsigned char *instructions_start;
+  unsigned char *flags_start, *flags_end;
+  unsigned char *vec_start;
+  int16_t delta, x, y;
+
+  /* Calculate the minimum size of the glyph data.  This is the size
+     of the instruction length field followed by
+     glyph->number_of_contours * sizeof (uint16_t).  */
+
+  min_size = (glyph->number_of_contours * sizeof (uint16_t)
+             + sizeof (uint16_t));
+
+  /* Check that the size is big enough.  */
+  if (glyf->size < offset + min_size)
+    {
+      glyph->simple = NULL;
+      return;
+    }
+
+  /* Allocate enough to read at least that.  */
+  simple = xmalloc (sizeof *simple + min_size);
+  simple->end_pts_of_contours = (uint16_t *) (simple + 1);
+  memcpy (simple->end_pts_of_contours, glyf->glyphs + offset,
+         min_size);
+
+  /* This is not really an index into simple->end_pts_of_contours.
+     Rather, it is reading the first word past it.  */
+  simple->instruction_length
+    = simple->end_pts_of_contours[glyph->number_of_contours];
+
+  /* Swap the contour end point indices and the instruction
+     length.  */
+
+  for (i = 0; i < glyph->number_of_contours; ++i)
+    sfnt_swap16 (&simple->end_pts_of_contours[i]);
+
+  sfnt_swap16 (&simple->instruction_length);
+
+  /* Based on those values, calculate the maximum size of the
+     following data.  This is the instruction length + the last
+     contour point + the last contour point * uint16_t * 2.  */
+
+  if (glyph->number_of_contours)
+    number_of_points
+      = simple->end_pts_of_contours[glyph->number_of_contours - 1] + 1;
+  else
+    number_of_points = 0;
+
+  min_size_2 = (simple->instruction_length
+               + number_of_points
+               + (number_of_points
+                  * sizeof (uint16_t) * 2));
+
+  /* Set simple->number_of_points.  */
+  simple->number_of_points = number_of_points;
+
+  /* Make simple big enough.  */
+  simple = xrealloc (simple, sizeof *simple + min_size + min_size_2);
+  simple->end_pts_of_contours = (uint16_t *) (simple + 1);
+
+  /* Set the instruction data pointer and other pointers.
+     simple->instructions comes one word past number_of_contours,
+     because end_pts_of_contours also contains the instruction
+     length.  */
+  simple->instructions = (uint8_t *) (simple->end_pts_of_contours
+                                     + glyph->number_of_contours + 1);
+  simple->flags = simple->instructions + simple->instruction_length;
+
+  /* Read instructions into the glyph.  */
+  instructions_start = glyf->glyphs + offset + min_size;
+
+  if (instructions_start >= glyf->glyphs + glyf->size
+      || (instructions_start + simple->instruction_length
+         >= glyf->glyphs + glyf->size))
+    {
+      glyph->simple = NULL;
+      xfree (simple);
+      return;
+    }
+
+  memcpy (simple->instructions, instructions_start,
+         simple->instruction_length);
+
+  /* Start reading flags.  */
+  flags_start = (glyf->glyphs + offset
+                + min_size + simple->instruction_length);
+  flags_end = flags_start + number_of_points;
+
+  if (flags_start >= glyf->glyphs + glyf->size)
+    {
+      glyph->simple = NULL;
+      xfree (simple);
+      return;
+    }
+
+  i = 0;
+
+  while (flags_start < flags_end)
+    {
+      if (i == number_of_points)
+       break;
+
+      if (flags_start >= glyf->glyphs + glyf->size)
+       break;
+
+      simple->flags[i++] = *flags_start;
+
+      if (*flags_start & 010) /* REPEAT_FLAG */
+       {
+         /* The next byte specifies how many times this byte is to be
+            repeated.  Check that it is in range.  */
+
+         if (flags_start + 1 >= glyf->glyphs + glyf->size)
+           {
+             glyph->simple = NULL;
+             xfree (simple);
+             return;
+           }
+
+         /* Repeat the current flag until
+            glyph->number_of_points.  */
+
+         repeat_count = *(flags_start + 1);
+
+         while (i < number_of_points && repeat_count)
+           {
+             simple->flags[i++] = *flags_start;
+             repeat_count--;
+           }
+
+         /* Skip one byte in flags_start.  */
+         flags_start++;
+       }
+
+      flags_start++;
+    }
+
+  /* If an insufficient number of flags have been read, then the
+     outline is invalid.  */
+
+  if (i != number_of_points)
+    {
+      glyph->simple = NULL;
+      xfree (simple);
+      return;
+    }
+
+  /* Now that the flags have been decoded, start decoding the
+     vectors.  */
+  simple->x_coordinates = (int16_t *) (simple->flags + number_of_points);
+  vec_start = flags_start;
+  i = 0;
+  x = 0;
+
+  /* flags_start is now repurposed to act as a pointer to the flags
+     for the current vector! */
+  flags_start = simple->flags;
+
+  while (i < number_of_points)
+    {
+      delta = 0;
+
+      if ((*flags_start) & 02) /* X_SHORT_VECTOR */
+       {
+         /* The next byte is a delta to apply to the previous
+            value.  Make sure it is in bounds.  */
+
+         if (vec_start + 1 > glyf->glyphs + glyf->size)
+           {
+             glyph->simple = NULL;
+             xfree (simple);
+             return;
+           }
+
+         delta = *vec_start++;
+
+         if (!(*flags_start & 020)) /* SAME_X */
+           delta = -delta;
+       }
+      else if (!(*flags_start & 020)) /* SAME_X */
+       {
+         /* The next word is a delta to apply to the previous value.
+            Make sure it is in bounds.  */
+
+         if (vec_start + 2 > glyf->glyphs + glyf->size)
+           {
+             glyph->simple = NULL;
+             xfree (simple);
+             return;
+           }
+
+         /* Read the unaligned word and swap it.  */
+         memcpy (&delta, vec_start, sizeof delta);
+         sfnt_swap16 (&delta);
+         vec_start += 2;
+       }
+
+      /* Apply the delta and set the X value.  */
+      x += delta;
+      simple->x_coordinates[i++] = x;
+      flags_start++;
+    }
+
+  /* Decode the Y vector.  flags_start is again repurposed to act as a
+     pointer to the flags for the current vector.  */
+  flags_start = simple->flags;
+  y = 0;
+  simple->y_coordinates = simple->x_coordinates + i;
+  i = 0;
+
+  while (i < number_of_points)
+    {
+      delta = 0;
+
+      if (*flags_start & 04) /* Y_SHORT_VECTOR */
+       {
+         /* The next byte is a delta to apply to the previous
+            value.  Make sure it is in bounds.  */
+
+         if (vec_start + 1 > glyf->glyphs + glyf->size)
+           {
+             glyph->simple = NULL;
+             xfree (simple);
+             return;
+           }
+
+         delta = *vec_start++;
+
+         if (!(*flags_start & 040)) /* SAME_Y */
+           delta = -delta;
+       }
+      else if (!(*flags_start & 040)) /* SAME_Y */
+       {
+         /* The next word is a delta to apply to the previous value.
+            Make sure it is in bounds.  */
+
+         if (vec_start + 2 > glyf->glyphs + glyf->size)
+           {
+             glyph->simple = NULL;
+             xfree (simple);
+             return;
+           }
+
+         /* Read the unaligned word and swap it.  */
+         memcpy (&delta, vec_start, sizeof delta);
+         sfnt_swap16 (&delta);
+         vec_start += 2;
+       }
+
+      /* Apply the delta and set the X value.  */
+      y += delta;
+      simple->y_coordinates[i++] = y;
+      flags_start++;
+    }
+
+  /* All done.  */
+  simple->y_coordinates_end = simple->y_coordinates + i;
+  glyph->simple = simple;
+  return;
+}
+
+/* Read the compound glyph outline from the glyph GLYPH from the
+   specified glyf table at the given offset.  Set GLYPH->compound to a
+   non-NULL value upon success, else set it to NULL.  */
+
+static void
+sfnt_read_compound_glyph (struct sfnt_glyph *glyph,
+                         struct sfnt_glyf_table *glyf,
+                         size_t offset)
+{
+  uint16_t flags, instruction_length, words[2], words4[4];
+  size_t required_bytes, num_components, i;
+  unsigned char *data, *instruction_base;
+
+  /* Assume failure for now.  Figure out how many bytes have to be
+     allocated by reading the compound data.  */
+  glyph->compound = NULL;
+  required_bytes = 0;
+  num_components = 0;
+  data = glyf->glyphs + offset;
+
+  /* Offset could be unaligned.  */
+  do
+    {
+      if (data + 2 > glyf->glyphs + glyf->size)
+       return;
+
+      memcpy (&flags, data, sizeof flags);
+      sfnt_swap16 (&flags);
+      data += sizeof flags;
+
+      /* Require at least one structure to hold this data.  */
+      required_bytes += sizeof (struct sfnt_compound_glyph_component);
+      num_components++;
+
+      /* Skip past unused data.  */
+      data += 2;
+
+      if (flags & 01) /* ARG_1_AND_2_ARE_WORDS */
+       data += sizeof (int16_t) * 2;
+      else
+       data += sizeof (int8_t) * 2;
+
+      if (flags & 010) /* WE_HAVE_A_SCALE */
+       data += sizeof (uint16_t);
+      else if (flags & 0100) /* WE_HAVE_AN_X_AND_Y_SCALE */
+       data += sizeof (uint16_t) * 2;
+      else if (flags & 0200) /* WE_HAVE_A_TWO_BY_TWO */
+       data += sizeof (uint16_t) * 4;
+    }
+  while (flags & 040); /* MORE_COMPONENTS */
+
+  if (flags & 0400) /* WE_HAVE_INSTRUCTIONS */
+    {
+      /* Figure out the instruction length.  */
+      if (data + 2 > glyf->glyphs + glyf->size)
+       return;
+
+      /* Now see how much is required to hold the instruction
+        data.  */
+      memcpy (&instruction_length, data,
+             sizeof instruction_length);
+      sfnt_swap16 (&instruction_length);
+      required_bytes += instruction_length;
+      data += sizeof data + instruction_length;
+    }
+
+  /* Now allocate the buffer to hold all the glyph data.  */
+  glyph->compound = xmalloc (sizeof *glyph->compound
+                            + required_bytes);
+  glyph->compound->components
+    = (struct sfnt_compound_glyph_component *) (glyph->compound + 1);
+  glyph->compound->num_components = num_components;
+
+  /* Figure out where instruction data starts.  It comes after
+     glyph->compound->components ends.  */
+  instruction_base
+    = (unsigned char *) (glyph->compound->components
+                        + glyph->compound->num_components);
+
+  /* Start reading.  */
+  i = 0;
+  data = glyf->glyphs + offset;
+  do
+    {
+      if (data + 4 > glyf->glyphs + glyf->size)
+       {
+         xfree (glyph->compound);
+         glyph->compound = NULL;
+         return;
+       }
+
+      memcpy (&flags, data, sizeof flags);
+      sfnt_swap16 (&flags);
+      data += sizeof flags;
+      glyph->compound->components[i].flags = flags;
+
+      memcpy (&glyph->compound->components[i].glyph_index,
+             data, sizeof glyph->compound->components[i].glyph_index);
+      sfnt_swap16 (&glyph->compound->components[i].glyph_index);
+      data += sizeof glyph->compound->components[i].glyph_index;
+
+      if (flags & 01) /* ARG_1_AND_2_ARE_WORDS.  */
+       {
+         if (data + 4 > glyf->glyphs + glyf->size)
+           {
+             xfree (glyph->compound);
+             glyph->compound = NULL;
+             return;
+           }
+
+         /* Read two words into arg1 and arg2.  */
+         memcpy (words, data, sizeof words);
+         sfnt_swap16 (&words[0]);
+         sfnt_swap16 (&words[1]);
+
+         glyph->compound->components[i].argument1.c = words[0];
+         glyph->compound->components[i].argument2.c = words[1];
+         data += sizeof words;
+       }
+      else
+       {
+         if (data + 2 > glyf->glyphs + glyf->size)
+           {
+             xfree (glyph->compound);
+             glyph->compound = NULL;
+             return;
+           }
+
+         /* Read two bytes into arg1 and arg2.  */
+         glyph->compound->components[i].argument1.a = data[0];
+         glyph->compound->components[i].argument2.a = data[1];
+         data += 2;
+       }
+
+      if (flags & 010) /* WE_HAVE_A_SCALE */
+       {
+         if (data + 2 > glyf->glyphs + glyf->size)
+           {
+             xfree (glyph->compound);
+             glyph->compound = NULL;
+             return;
+           }
+
+         /* Read one word into scale.  */
+         memcpy (&glyph->compound->components[i].u.scale, data,
+                 sizeof glyph->compound->components[i].u.scale);
+         sfnt_swap16 (&glyph->compound->components[i].u.scale);
+         data += sizeof glyph->compound->components[i].u.scale;
+       }
+      else if (flags & 0100) /* WE_HAVE_AN_X_AND_Y_SCALE.  */
+       {
+         if (data + 4 > glyf->glyphs + glyf->size)
+           {
+             xfree (glyph->compound);
+             glyph->compound = NULL;
+             return;
+           }
+
+         /* Read two words into xscale and yscale.  */
+         memcpy (words, data, sizeof words);
+         sfnt_swap16 (&words[0]);
+         sfnt_swap16 (&words[1]);
+
+         glyph->compound->components[i].u.a.xscale = words[0];
+         glyph->compound->components[i].u.a.yscale = words[1];
+         data += sizeof words;
+       }
+      else if (flags & 0200) /* WE_HAVE_A_TWO_BY_TWO */
+       {
+         if (data + 8 > glyf->glyphs + glyf->size)
+           {
+             xfree (glyph->compound);
+             glyph->compound = NULL;
+             return;
+           }
+
+         /* Read 4 words into the transformation matrix.  */
+         memcpy (words4, data, sizeof words4);
+         sfnt_swap16 (&words4[0]);
+         sfnt_swap16 (&words4[1]);
+         sfnt_swap16 (&words4[2]);
+         sfnt_swap16 (&words4[3]);
+
+         glyph->compound->components[i].u.b.xscale = words4[0];
+         glyph->compound->components[i].u.b.scale01 = words4[1];
+         glyph->compound->components[i].u.b.scale10 = words4[2];
+         glyph->compound->components[i].u.b.yscale = words4[3];
+         data += sizeof words4;
+       }
+
+      /* Record the component flags.  */
+      glyph->compound->components[i].flags = flags;
+
+      i++;
+    }
+  while (flags & 040); /* MORE_COMPONENTS */
+
+  if (flags & 0400) /* WE_HAVE_INSTR */
+    {
+      /* Figure out the instruction length.  */
+      if (data + 2 > glyf->glyphs + glyf->size)
+       {
+         xfree (glyph->compound);
+         glyph->compound = NULL;
+         return;
+       }
+
+      /* Now see how much is required to hold the instruction
+        data.  */
+      memcpy (&glyph->compound->instruction_length,
+             data,
+             sizeof glyph->compound->instruction_length);
+      sfnt_swap16 (&glyph->compound->instruction_length);
+      data += 2;
+
+      /* Read the instructions.  */
+      glyph->compound->instructions = instruction_base;
+
+      if (data + glyph->compound->instruction_length
+         > glyf->glyphs + glyf->size)
+       {
+         xfree (glyph->compound);
+         glyph->compound = NULL;
+         return;
+       }
+
+      memcpy (instruction_base, data,
+             glyph->compound->instruction_length);
+    }
+  else
+    {
+      glyph->compound->instructions = NULL;
+      glyph->compound->instruction_length = 0;
+    }
+
+  /* Data read successfully.  */
+  return;
+}
+
+/* Read the description of the glyph GLYPH_CODE from the specified
+   glyf table, using the offsets of LOCA_SHORT or LOCA_LONG, depending
+   on which is non-NULL.  */
+
+TEST_STATIC struct sfnt_glyph *
+sfnt_read_glyph (sfnt_glyph glyph_code,
+                struct sfnt_glyf_table *glyf,
+                struct sfnt_loca_table_short *loca_short,
+                struct sfnt_loca_table_long *loca_long)
+{
+  struct sfnt_glyph glyph, *memory;
+  size_t offset, next_offset;
+
+  /* Check the glyph code is within bounds.  */
+  if (glyph_code > 65535)
+    return NULL;
+
+  if (loca_short)
+    {
+      /* Check that the glyph is within bounds.  glyph_code + 1 is the
+        entry in the table which defines the length of the glyph.  */
+      if (glyph_code + 1 >= loca_short->num_offsets)
+       return NULL;
+
+      offset = loca_short->offsets[glyph_code] * 2;
+      next_offset = loca_short->offsets[glyph_code + 1] * 2;
+    }
+  else if (loca_long)
+    {
+      if (glyph_code + 1 >= loca_long->num_offsets)
+       return NULL;
+
+      offset = loca_long->offsets[glyph_code];
+      next_offset = loca_long->offsets[glyph_code + 1];
+    }
+  else
+    abort ();
+
+  /* If offset - next_offset is 0, then the glyph is empty.  Its
+     horizontal advance may still be provided by the hmtx table.  */
+
+  if (offset == next_offset)
+    {
+      glyph.number_of_contours = 0;
+      glyph.xmin = 0;
+      glyph.ymin = 0;
+      glyph.xmax = 0;
+      glyph.ymax = 0;
+      glyph.simple = xmalloc (sizeof *glyph.simple);
+      glyph.compound = NULL;
+      memset (glyph.simple, 0, sizeof *glyph.simple);
+      memory = xmalloc (sizeof *memory);
+      *memory = glyph;
+      return memory;
+    }
+
+  /* Verify that GLYF is big enough to hold a glyph at OFFSET.  */
+  if (glyf->size < offset + SFNT_ENDOF (struct sfnt_glyph,
+                                       ymax, sfnt_fword))
+    return NULL;
+
+  /* Copy over the glyph data.  */
+  memcpy (&glyph, glyf->glyphs + offset,
+         SFNT_ENDOF (struct sfnt_glyph,
+                     ymax, sfnt_fword));
+
+  /* Swap the glyph data.  */
+  sfnt_swap16 (&glyph.number_of_contours);
+  sfnt_swap16 (&glyph.xmin);
+  sfnt_swap16 (&glyph.ymin);
+  sfnt_swap16 (&glyph.xmax);
+  sfnt_swap16 (&glyph.ymax);
+
+  /* This is set later on after `sfnt_vary_X_glyph'.  */
+  glyph.advance_distortion = 0;
+  glyph.origin_distortion = 0;
+
+  /* Figure out what needs to be read based on
+     glyph.number_of_contours.  */
+  if (glyph.number_of_contours >= 0)
+    {
+      /* Read the simple glyph.  */
+
+      glyph.compound = NULL;
+      sfnt_read_simple_glyph (&glyph, glyf,
+                             offset + SFNT_ENDOF (struct sfnt_glyph,
+                                                  ymax, sfnt_fword));
+
+      if (glyph.simple)
+       {
+         memory = xmalloc (sizeof glyph);
+         *memory = glyph;
+
+         return memory;
+       }
+    }
+  else
+    {
+      /* Read the compound glyph.  */
+
+      glyph.simple = NULL;
+      sfnt_read_compound_glyph (&glyph, glyf,
+                               offset + SFNT_ENDOF (struct sfnt_glyph,
+                                                    ymax, sfnt_fword));
+
+      if (glyph.compound)
+       {
+         memory = xmalloc (sizeof glyph);
+         *memory = glyph;
+
+         return memory;
+       }
+    }
+
+  return NULL;
+}
+
+/* Free a glyph returned from sfnt_read_glyph.  GLYPH may be NULL.  */
+
+TEST_STATIC void
+sfnt_free_glyph (struct sfnt_glyph *glyph)
+{
+  if (!glyph)
+    return;
+
+  xfree (glyph->simple);
+  xfree (glyph->compound);
+  xfree (glyph);
+}
+
+
+
+/* Glyph outline decomposition.  */
+
+/* Apply the transform in the compound glyph component COMPONENT to
+   the array of points of length NUM_COORDINATES given as X and Y.
+
+   Also, apply the fixed point offsets X_OFF and Y_OFF to each X and Y
+   coordinate after transforms within COMPONENT are effected.  */
+
+static void
+sfnt_transform_coordinates (struct sfnt_compound_glyph_component *component,
+                           sfnt_fixed *restrict x, sfnt_fixed *restrict y,
+                           size_t num_coordinates,
+                           sfnt_fixed x_off, sfnt_fixed y_off)
+{
+  double m1, m2, m3;
+  double m4, m5, m6;
+  size_t i;
+
+  if (component->flags & 010) /* WE_HAVE_A_SCALE */
+    {
+      m1 = component->u.scale / 16384.0;
+      m2 = m3 = m4 = 0;
+      m5 = component->u.scale / 16384.0;
+      m6 = 0;
+    }
+  else if (component->flags & 0100) /* WE_HAVE_AN_X_AND_Y_SCALE */
+    {
+      m1 = component->u.a.xscale / 16384.0;
+      m2 = m3 = m4 = 0;
+      m5 = component->u.a.yscale / 16384.0;
+      m6 = 0;
+    }
+  else if (component->flags & 0200) /* WE_HAVE_A_TWO_BY_TWO */
+    {
+      m1 = component->u.b.xscale / 16384.0;
+      m2 = component->u.b.scale01 / 16384.0;
+      m3 = 0;
+      m4 = component->u.b.scale10 / 16384.0;
+      m5 = component->u.b.yscale / 16384.0;
+      m6 = 0;
+    }
+  else /* No scale, just apply x_off and y_off.  */
+    {
+      for (i = 0; i < num_coordinates; ++i)
+       x[i] += x_off, y[i] += y_off;
+
+      return;
+    }
+
+  m3 = x_off;
+  m6 = y_off;
+
+  /* Apply the specified affine transformation.
+     A transform looks like:
+
+     M1 M2 M3     X
+     M4 M5 M6   * Y
+
+     =
+
+     M1*X + M2*Y + M3*1 = X1
+     M4*X + M5*Y + M6*1 = Y1
+
+     (In most transforms, there is another row at the bottom for
+     mathematical reasons.  Since Z1 is always 1.0, the row is simply
+     implied to be 0 0 1, because 0 * x + 0 * y + 1 * 1 = 1.0.  See
+     the definition of matrix3x3 in image.c for some more explanations
+     about this.) */
+
+  for (i = 0; i < num_coordinates; ++i)
+    {
+      x[i] = m1 * x[i] + m2 * y[i] + m3 * 1;
+      y[i] = m4 * x[i] + m5 * y[i] + m6 * 1;
+    }
+}
+
+struct sfnt_compound_glyph_context
+{
+  /* Arrays of points.  The underlying type is actually sfnt_f26dot6
+     when instructing a compound glyph.  */
+  sfnt_fixed *x_coordinates, *y_coordinates;
+
+  /* Array of flags for the points.  */
+  unsigned char *flags;
+
+  /* Number of points in that array, and the size of that array.  */
+  size_t num_points, points_size;
+
+  /* Array of contour end points.  */
+  size_t *contour_end_points;
+
+  /* Number of elements in and the size of that array.  */
+  size_t num_end_points, end_points_size;
+};
+
+/* Extend the arrays inside the compound glyph decomposition context
+   CONTEXT.  NUMBER_OF_CONTOURS is the number of contours to add.
+   NUMBER_OF_POINTS is the number of points to add.
+
+   Return pointers to the beginning of the extension in *X_BASE,
+   *Y_BASE, *FLAGS_BASE and *CONTOUR_BASE.  Value zero upon success,
+   and something else on failure.  */
+
+static int
+sfnt_expand_compound_glyph_context (struct sfnt_compound_glyph_context 
*context,
+                                   size_t number_of_contours,
+                                   size_t number_of_points,
+                                   sfnt_fixed **x_base, sfnt_fixed **y_base,
+                                   unsigned char **flags_base,
+                                   size_t **contour_base)
+{
+  size_t size_bytes;
+
+  /* Add each field while checking for overflow.  */
+  if (INT_ADD_WRAPV (number_of_contours, context->num_end_points,
+                    &context->num_end_points))
+    return 1;
+
+  if (INT_ADD_WRAPV (number_of_points, context->num_points,
+                    &context->num_points))
+    return 1;
+
+  /* Reallocate each array to the new size if necessary.  */
+  if (context->points_size < context->num_points)
+    {
+      if (INT_MULTIPLY_WRAPV (context->num_points, 2,
+                             &context->points_size))
+       context->points_size = context->num_points;
+
+      if (INT_MULTIPLY_WRAPV (context->points_size,
+                             sizeof *context->x_coordinates,
+                             &size_bytes))
+       return 1;
+
+      context->x_coordinates = xrealloc (context->x_coordinates,
+                                        size_bytes);
+      context->y_coordinates = xrealloc (context->y_coordinates,
+                                        size_bytes);
+      context->flags = xrealloc (context->flags,
+                                context->points_size);
+    }
+
+  /* Set x_base and y_base.  */
+  *x_base = (context->x_coordinates
+            + context->num_points
+            - number_of_points);
+  *y_base = (context->y_coordinates
+            + context->num_points
+            - number_of_points);
+  *flags_base = (context->flags
+                + context->num_points
+                - number_of_points);
+
+  if (context->end_points_size < context->num_end_points)
+    {
+      if (INT_MULTIPLY_WRAPV (context->num_end_points, 2,
+                             &context->end_points_size))
+       context->end_points_size = context->num_end_points;
+
+      if (INT_MULTIPLY_WRAPV (context->end_points_size,
+                             sizeof *context->contour_end_points,
+                             &size_bytes))
+       return 1;
+
+      context->contour_end_points
+       = xrealloc (context->contour_end_points,
+                   size_bytes);
+    }
+
+  /* Set contour_base.  */
+  *contour_base = (context->contour_end_points
+                  + context->num_end_points
+                  - number_of_contours);
+  return 0;
+}
+
+/* Round the 16.16 fixed point number NUMBER to the nearest integral
+   value.  */
+
+static int32_t
+sfnt_round_fixed (int32_t number)
+{
+  /* Add 0.5... */
+  number += (1 << 15);
+
+  /* Remove the fractional.  */
+  return number & ~0xffff;
+}
+
+/* Decompose GLYPH, a compound glyph, into an array of points and
+   contours.
+
+   CONTEXT should be zeroed and put on the stack. RECURSION_COUNT
+   should be initialized to 0.  GET_GLYPH, FREE_GLYPH, and
+   GET_METRICS, along with DCONTEXT, mean the same as in
+   sfnt_decompose_glyph.
+
+   Value is 1 upon failure, else 0.  */
+
+static int
+sfnt_decompose_compound_glyph (struct sfnt_glyph *glyph,
+                              struct sfnt_compound_glyph_context *context,
+                              sfnt_get_glyph_proc get_glyph,
+                              sfnt_free_glyph_proc free_glyph,
+                              sfnt_get_metrics_proc get_metrics,
+                              int recursion_count,
+                              void *dcontext)
+{
+  struct sfnt_glyph *subglyph;
+  int i, j, rc;
+  bool need_free;
+  struct sfnt_compound_glyph_component *component;
+  sfnt_fixed x, y, xtemp, ytemp;
+  size_t point UNINIT, point2 UNINIT, index;
+  uint16_t last_point, number_of_contours;
+  sfnt_fixed *x_base, *y_base;
+  size_t *contour_base;
+  unsigned char *flags_base;
+  size_t base_index, contour_start;
+  bool defer_offsets;
+  struct sfnt_glyph_metrics sub_metrics;
+  sfnt_fixed f1, f2;
+
+  /* Set up the base index.  This is the index from where on point
+     renumbering starts.
+
+     In other words, point 0 in this glyph will be 0 + base_index,
+     point 1 will be 1 + base_index, and so on.  */
+  base_index = context->num_points;
+
+  /* Prevent infinite loops.  Simply limit the level of nesting to the
+     maximum valid value of `max_component_depth', which is 16.  */
+
+  if (recursion_count > 16)
+    return 1;
+
+  for (j = 0; j < glyph->compound->num_components; ++j)
+    {
+      /* Look up the associated subglyph.  */
+      component = &glyph->compound->components[j];
+      subglyph = get_glyph (component->glyph_index,
+                           dcontext, &need_free);
+
+      if (!subglyph)
+       return 1;
+
+      /* Don't defer offsets.  This variable is set if the component
+        glyph is a compound glyph that is anchored to a previously
+        decomposed point, and needs its coordinates adjusted after
+        decomposition completes.  */
+      defer_offsets = false;
+
+      /* Record the size of the point array before expansion.  This
+        will be the base to apply to all points coming from this
+        subglyph.  */
+      contour_start = context->num_points;
+
+      /* Compute the offset for the component.  */
+      if (component->flags & 02) /* ARGS_ARE_XY_VALUES */
+       {
+         /* Component offsets are X/Y values as opposed to points
+            GLYPH.  */
+
+         if (!(component->flags & 01)) /* ARG_1_AND_2_ARE_WORDS */
+           {
+             /* X and Y are signed bytes.  */
+             x = component->argument1.b * 65536;
+             y = component->argument2.b * 65536;
+           }
+         else
+           {
+             /* X and Y are signed words.  */
+             x = component->argument1.d * 65536;
+             y = component->argument2.d * 65536;
+           }
+
+         /* If there is some kind of scale and component offsets are
+            scaled, then apply the transform to the offset.  */
+         if (component->flags & 04000) /* SCALED_COMPONENT_OFFSET */
+           sfnt_transform_coordinates (component, &x, &y, 1,
+                                       0, 0);
+
+         if (component->flags & 04) /* ROUND_XY_TO_GRID */
+           {
+             x = sfnt_round_fixed (x);
+             y = sfnt_round_fixed (y);
+           }
+       }
+      else
+       {
+         /* The offset is determined by matching a point location in
+            a preceeding component with a point location in the
+            current component.  The index of the point in the
+            previous component can be determined by adding
+            component->argument1.a or component->argument1.c to
+            point.  argument2 contains the index of the point in the
+            current component.  */
+
+         if (!(component->flags & 01)) /* ARG_1_AND_2_ARE_WORDS */
+           {
+             point = base_index + component->argument1.a;
+             point2 = component->argument2.a;
+           }
+         else
+           {
+             point = base_index + component->argument1.c;
+             point2 = component->argument2.c;
+           }
+
+         /* Now, check that the anchor point specified lies inside
+            the glyph.  */
+
+         if (point >= contour_start)
+           {
+             if (need_free)
+               free_glyph (subglyph, dcontext);
+
+             return 1;
+           }
+
+         if (!subglyph->compound)
+           {
+             if (point2 >= subglyph->simple->number_of_points)
+               {
+                 if (point2 < subglyph->simple->number_of_points + 2)
+                   {
+                     /* POINT2 is one of SUBGLYPH's phantom points.
+                        Retrieve the glyph's metrics.  */
+
+                     if ((*get_metrics) (component->glyph_index, &sub_metrics,
+                                         dcontext))
+                       {
+                         if (need_free)
+                           free_glyph (subglyph, dcontext);
+
+                         return 1;
+                       }
+
+                     /* Derive the phantom points from those metrics.  */
+                     f1 = glyph->xmin - sub_metrics.lbearing;
+                     f2 = f1 + sub_metrics.advance;
+
+                     /* Apply the metrics distortion.  */
+                     f1 += glyph->origin_distortion;
+                     f2 += glyph->advance_distortion;
+
+                     /* Get the points and use them to compute the offsets.  */
+
+                     if (!(point2 - subglyph->simple->number_of_points))
+                       x = f1 * 65536;
+                     else
+                       x = f2 * 65536;
+
+                     x = context->x_coordinates[point] - x;
+                     y = context->y_coordinates[point];
+
+                     /* X and Y offsets have been ascertained.  */
+                     goto skip_computation;
+                   }
+
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return 1;
+               }
+
+             /* Get the points and use them to compute the offsets.  */
+             xtemp = context->x_coordinates[point];
+             ytemp = context->y_coordinates[point];
+             x = (xtemp - subglyph->simple->x_coordinates[point2] * 65536);
+             y = (ytemp - subglyph->simple->y_coordinates[point2] * 65536);
+
+           skip_computation:
+             ;
+           }
+         else
+           {
+             /* First, set offsets to 0, because it is not yet
+                possible to determine the position of the anchor
+                point in the child.  */
+             x = 0;
+             y = 0;
+
+             /* Set a flag which indicates that offsets must be
+                resolved from the child glyph after it is loaded, but
+                before it is incorporated into the parent glyph.  */
+             defer_offsets = true;
+           }
+       }
+
+      if (subglyph->simple)
+       {
+         /* Simple subglyph.  Copy over the points and contours, and
+            transform them.  */
+         if (subglyph->number_of_contours)
+           {
+             index = subglyph->number_of_contours - 1;
+             last_point
+               = subglyph->simple->end_pts_of_contours[index];
+             number_of_contours = subglyph->number_of_contours;
+
+
+             /* Grow various arrays.  */
+             rc = sfnt_expand_compound_glyph_context (context,
+                                                      /* Number of
+                                                         new contours
+                                                         required.  */
+                                                      number_of_contours,
+                                                      /* Number of new
+                                                         points
+                                                         required.  */
+                                                      last_point + 1,
+                                                      &x_base,
+                                                      &y_base,
+                                                      &flags_base,
+                                                      &contour_base);
+             if (rc)
+               {
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return 1;
+               }
+
+             for (i = 0; i <= last_point; ++i)
+               {
+                 x_base[i] = (subglyph->simple->x_coordinates[i] * 65536);
+                 y_base[i] = (subglyph->simple->y_coordinates[i] * 65536);
+                 flags_base[i] = subglyph->simple->flags[i];
+               }
+
+             /* Apply the transform to the points.  */
+             sfnt_transform_coordinates (component, x_base, y_base,
+                                         last_point + 1, x, y);
+
+             /* Copy over the contours.  */
+             for (i = 0; i < number_of_contours; ++i)
+               contour_base[i] = (contour_start
+                                  + subglyph->simple->end_pts_of_contours[i]);
+           }
+       }
+      else
+       {
+         /* Compound subglyph.  Decompose the glyph recursively, and
+            then apply the transform.  */
+         rc = sfnt_decompose_compound_glyph (subglyph,
+                                             context,
+                                             get_glyph,
+                                             free_glyph,
+                                             get_metrics,
+                                             recursion_count + 1,
+                                             dcontext);
+
+         if (rc)
+           {
+             if (need_free)
+               free_glyph (subglyph, dcontext);
+
+             return 1;
+           }
+
+         /* When an anchor point is being used to translate the
+            glyph, and the subglyph in question is actually a
+            compound glyph, it is impossible to know which offset to
+            use until the compound subglyph has actually been
+            loaded.
+
+            As a result, the offset is calculated here, using the
+            points in the loaded child compound glyph.  But first, X
+            and Y must be reset to 0, as otherwise the translation
+            might be applied twice if defer_offsets is not set.  */
+
+         x = 0;
+         y = 0;
+
+         if (defer_offsets)
+           {
+             /* Renumber the non renumbered point2 to point into the
+                decomposed component.  */
+             point2 += contour_start;
+
+             /* Next, check that the non-renumbered point being
+                anchored lies inside the glyph data that was
+                decomposed.  */
+
+             if (point2 >= context->num_points)
+               {
+                 /* POINT2 might fall within the phantom points of
+                    that glyph.  */
+
+                 if (point2 - context->num_points < 2)
+                   {
+                     if ((*get_metrics) (component->glyph_index, &sub_metrics,
+                                         dcontext))
+                       goto error_in_defer_offsets;
+
+                     /* Derive the phantom points from those metrics.  */
+                     f1 = glyph->xmin - sub_metrics.lbearing;
+                     f2 = f1 + sub_metrics.advance;
+
+                     /* Apply the metrics distortion.  */
+                     f1 += glyph->origin_distortion;
+                     f2 += glyph->advance_distortion;
+
+                     /* Get the points and use them to compute the offsets.  */
+
+                     if (!(point2 - context->num_points))
+                       x = f1 * 65536;
+                     else
+                       x = f2 * 65536;
+
+                     x = context->x_coordinates[point] - x;
+                     y = context->y_coordinates[point];
+
+                     /* X and Y offsets have been ascertained.  */
+                     goto skip_computation_from_defer_offsets;
+                   }
+
+               error_in_defer_offsets:
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return 1;
+               }
+
+             /* Get the points and use them to compute the
+                offsets.  */
+
+             xtemp = context->x_coordinates[point];
+             ytemp = context->y_coordinates[point];
+             x = (xtemp - context->x_coordinates[point2]);
+             y = (ytemp - context->y_coordinates[point2]);
+
+           skip_computation_from_defer_offsets:
+             ;
+           }
+
+         sfnt_transform_coordinates (component,
+                                     context->x_coordinates + contour_start,
+                                     context->y_coordinates + contour_start,
+                                     context->num_points - contour_start,
+                                     x, y);
+       }
+
+      if (need_free)
+       free_glyph (subglyph, dcontext);
+    }
+
+  /* Decomposition is complete.  CONTEXT now contains the adjusted
+     outlines of the entire compound glyph.  */
+  return 0;
+}
+
+/* Linear-interpolate to a point halfway between the points specified
+   by CONTROL1 and CONTROL2.  Put the result in RESULT.  */
+
+static void
+sfnt_lerp_half (struct sfnt_point *control1, struct sfnt_point *control2,
+               struct sfnt_point *result)
+{
+  result->x = control1->x + ((control2->x - control1->x) / 2);
+  result->y = control1->y + ((control2->y - control1->y) / 2);
+}
+
+/* Decompose contour data inside X, Y and FLAGS, between the indices
+   HERE and LAST.  Call LINE_TO, CURVE_TO and MOVE_TO as appropriate,
+   with DCONTEXT as an argument.  Apply SCALE to each point; SCALE
+   should be the factor necessary to turn points into 16.16 fixed
+   point.
+
+   Value is 1 upon failure, else 0.  */
+
+static int
+sfnt_decompose_glyph_1 (size_t here, size_t last,
+                       sfnt_move_to_proc move_to,
+                       sfnt_line_to_proc line_to,
+                       sfnt_curve_to_proc curve_to,
+                       void *dcontext,
+                       sfnt_fword *x,
+                       sfnt_fword *y, unsigned char *flags,
+                       int scale)
+{
+  struct sfnt_point control1, control2, start, mid;
+  size_t i;
+
+  /* The contour is empty.  */
+
+  if (here == last)
+    return 1;
+
+  /* Move the pen to the start of the contour.  Apparently some fonts
+     have off the curve points as the start of a contour, so when that
+     happens lerp between the first and last points.  */
+
+  if (flags[here] & 01) /* On Curve */
+    {
+      control1.x = x[here] * scale;
+      control1.y = y[here] * scale;
+      start = control1;
+    }
+  else if (flags[last] & 01)
+    {
+      /* Start at the last point if it is on the curve.  Here, the
+         start really becomes the middle of a spline.  */
+      control1.x = x[last] * scale;
+      control1.y = y[last] * scale;
+      start = control1;
+
+      /* Curve back one point early.  */
+      last -= 1;
+      here -= 1;
+    }
+  else
+    {
+      /* Lerp between the start and the end.  */
+      control1.x = x[here] * scale;
+      control1.y = y[here] * scale;
+      control2.x = x[last] * scale;
+      control2.y = y[last] * scale;
+      sfnt_lerp_half (&control1, &control2, &start);
+
+      /* In either of these cases, start iterating from just here as
+        opposed to here + 1, since logically the contour now starts
+        from the last curve.  */
+      here -= 1;
+    }
+
+  /* Move to the start.  */
+  move_to (start, dcontext);
+
+  /* Now handle each point between here + 1 and last.  */
+
+  i = here;
+  while (++i <= last)
+    {
+      /* If the point is on the curve, then draw a line here from the
+        last control point.  */
+
+      if (flags[i] & 01)
+       {
+         control1.x = x[i] * scale;
+         control1.y = y[i] * scale;
+
+         line_to (control1, dcontext);
+
+         /* Move to the next point.  */
+         continue;
+       }
+
+      /* Off the curve points are more interesting.  They are handled
+        one by one, with points in between being interpolated, until
+        either the last point is reached or an on-curve point is
+        processed.  First, load the initial control points.  */
+
+      control1.x = x[i] * scale;
+      control1.y = y[i] * scale;
+
+      while (++i <= last)
+       {
+         /* Load this point.  */
+         control2.x = x[i] * scale;
+         control2.y = y[i] * scale;
+
+         /* If this point is on the curve, curve directly to this
+            point.  */
+
+         if (flags[i] & 01)
+           {
+             curve_to (control1, control2, dcontext);
+             goto continue_loop;
+           }
+
+         /* Calculate the point between here and the previous
+            point.  */
+         sfnt_lerp_half (&control1, &control2, &mid);
+
+         /* Curve over there.  */
+         curve_to (control1, mid, dcontext);
+
+         /* Reload the control point.  */
+         control1 = control2;
+       }
+
+      /* Close the contour by curving back to start.  */
+      curve_to (control1, start, dcontext);
+
+      /* Don't close the contour twice.  */
+      goto exit;
+
+    continue_loop:
+      continue;
+    }
+
+  /* Close the contour with a line back to start.  */
+  line_to (start, dcontext);
+
+ exit:
+  return 0;
+}
+
+/* Decompose contour data inside X, Y and FLAGS, between the indices
+   HERE and LAST.  Call LINE_TO, CURVE_TO and MOVE_TO as appropriate,
+   with DCONTEXT as an argument.  Apply SCALE to each point; SCALE
+   should be the factor necessary to turn points into 16.16 fixed
+   point.
+
+   This is the version of sfnt_decompose_glyph_1 which takes
+   sfnt_fixed (or sfnt_f26dot6) as opposed to sfnt_fword.
+
+   Value is 1 upon failure, else 0.  */
+
+static int
+sfnt_decompose_glyph_2 (size_t here, size_t last,
+                       sfnt_move_to_proc move_to,
+                       sfnt_line_to_proc line_to,
+                       sfnt_curve_to_proc curve_to,
+                       void *dcontext,
+                       sfnt_fixed *x,
+                       sfnt_fixed *y, unsigned char *flags,
+                       int scale)
+{
+  struct sfnt_point control1, control2, start, mid;
+  size_t i;
+
+  /* The contour is empty.  */
+
+  if (here == last)
+    return 1;
+
+  /* Move the pen to the start of the contour.  Apparently some fonts
+     have off the curve points as the start of a contour, so when that
+     happens lerp between the first and last points.  */
+
+  if (flags[here] & 01) /* On Curve */
+    {
+      control1.x = x[here] * scale;
+      control1.y = y[here] * scale;
+      start = control1;
+    }
+  else if (flags[last] & 01)
+    {
+      /* Start at the last point if it is on the curve.  Here, the
+         start really becomes the middle of a spline.  */
+      control1.x = x[last] * scale;
+      control1.y = y[last] * scale;
+      start = control1;
+
+      /* Curve back one point early.  */
+      last -= 1;
+      here -= 1;
+    }
+  else
+    {
+      /* Lerp between the start and the end.  */
+      control1.x = x[here] * scale;
+      control1.y = y[here] * scale;
+      control2.x = x[last] * scale;
+      control2.y = y[last] * scale;
+      sfnt_lerp_half (&control1, &control2, &start);
+
+      /* In either of these cases, start iterating from just here as
+        opposed to here + 1, since logically the contour now starts
+        from the last curve.  */
+      here -= 1;
+    }
+
+  /* Move to the start.  */
+  move_to (start, dcontext);
+
+  /* Now handle each point between here + 1 and last.  */
+
+  i = here;
+  while (++i <= last)
+    {
+      /* If the point is on the curve, then draw a line here from the
+        last control point.  */
+
+      if (flags[i] & 01)
+       {
+         control1.x = x[i] * scale;
+         control1.y = y[i] * scale;
+
+         line_to (control1, dcontext);
+
+         /* Move to the next point.  */
+         continue;
+       }
+
+      /* Off the curve points are more interesting.  They are handled
+        one by one, with points in between being interpolated, until
+        either the last point is reached or an on-curve point is
+        processed.  First, load the initial control points.  */
+
+      control1.x = x[i] * scale;
+      control1.y = y[i] * scale;
+
+      while (++i <= last)
+       {
+         /* Load this point.  */
+         control2.x = x[i] * scale;
+         control2.y = y[i] * scale;
+
+         /* If this point is on the curve, curve directly to this
+            point.  */
+
+         if (flags[i] & 01)
+           {
+             curve_to (control1, control2, dcontext);
+             goto continue_loop;
+           }
+
+         /* Calculate the point between here and the previous
+            point.  */
+         sfnt_lerp_half (&control1, &control2, &mid);
+
+         /* Curve over there.  */
+         curve_to (control1, mid, dcontext);
+
+         /* Reload the control point.  */
+         control1 = control2;
+       }
+
+      /* Close the contour by curving back to start.  */
+      curve_to (control1, start, dcontext);
+
+      /* Don't close the contour twice.  */
+      goto exit;
+
+    continue_loop:
+      continue;
+    }
+
+  /* Close the contour with a line back to start.  */
+  line_to (start, dcontext);
+
+ exit:
+  return 0;
+}
+
+/* Decompose GLYPH into its individual components.  Call MOVE_TO to
+   move to a specific location.  For each line encountered, call
+   LINE_TO to draw a line to that location.  For each spline
+   encountered, call CURVE_TO to draw the curves comprising the
+   spline.
+
+   If GLYPH is compound, use GET_GLYPH to obtain subglyphs.  PROC must
+   return whether or not FREE_GLYPH will be called with the glyph
+   after sfnt_decompose_glyph is done with it.  If GLYPH moreover
+   incorporates components whose anchor points are phantom points, use
+   GET_METRICS to obtain glyph metrics prerequisite for establishing
+   their coordinates.
+
+   All functions will be called with DCONTEXT as an argument.
+
+   The winding rule used to fill the resulting lines is described in
+   chapter 2 of the TrueType reference manual, under the heading
+   "distinguishing the inside from the outside of a glyph."
+
+   Value is 0 upon success, or some non-zero value upon failure, which
+   can happen if the glyph is invalid.  */
+
+static int
+sfnt_decompose_glyph (struct sfnt_glyph *glyph,
+                     sfnt_move_to_proc move_to,
+                     sfnt_line_to_proc line_to,
+                     sfnt_curve_to_proc curve_to,
+                     sfnt_get_glyph_proc get_glyph,
+                     sfnt_free_glyph_proc free_glyph,
+                     sfnt_get_metrics_proc get_metrics,
+                     void *dcontext)
+{
+  size_t here, last, n;
+  struct sfnt_compound_glyph_context context;
+
+  if (glyph->simple)
+    {
+      if (!glyph->number_of_contours)
+       /* No contours.  Nothing needs to be decomposed.  */
+       return 0;
+
+      here = 0;
+
+      for (n = 0; n < glyph->number_of_contours; ++n)
+       {
+         /* here is the first index into the glyph's point arrays
+            belonging to the contour in question.  last is the index
+            of the last point in the contour.  */
+         last = glyph->simple->end_pts_of_contours[n];
+
+         /* Make sure here and last make sense.  */
+
+         if (here > last || last >= glyph->simple->number_of_points)
+           return 1;
+
+         /* Now perform the decomposition.  */
+         if (sfnt_decompose_glyph_1 (here, last, move_to,
+                                     line_to, curve_to,
+                                     dcontext,
+                                     glyph->simple->x_coordinates,
+                                     glyph->simple->y_coordinates,
+                                     glyph->simple->flags,
+                                     65536))
+           return 1;
+
+         /* Move forward to the start of the next contour.  */
+         here = last + 1;
+       }
+
+      return 0;
+    }
+
+  /* Decompose the specified compound glyph.  */
+  memset (&context, 0, sizeof context);
+
+  if (sfnt_decompose_compound_glyph (glyph, &context,
+                                    get_glyph, free_glyph,
+                                    get_metrics, 0,
+                                    dcontext))
+    {
+      xfree (context.x_coordinates);
+      xfree (context.y_coordinates);
+      xfree (context.flags);
+      xfree (context.contour_end_points);
+
+      return 1;
+    }
+
+  /* Now, generate the outlines.  */
+
+  if (!context.num_end_points)
+    /* No contours.  */
+    goto early;
+
+  here = 0;
+
+  for (n = 0; n < context.num_end_points; ++n)
+    {
+      /* here is the first index into the glyph's point arrays
+        belonging to the contour in question.  last is the index
+        of the last point in the contour.  */
+      last = context.contour_end_points[n];
+
+      /* Make sure here and last make sense.  */
+
+      if (here > last || last >= context.num_points)
+       goto fail;
+
+      /* Now perform the decomposition.  */
+      if (sfnt_decompose_glyph_2 (here, last, move_to,
+                                 line_to, curve_to,
+                                 dcontext,
+                                 context.x_coordinates,
+                                 context.y_coordinates,
+                                 context.flags, 1))
+       goto fail;
+
+      /* Move forward.  */
+      here = last + 1;
+    }
+
+ early:
+  xfree (context.x_coordinates);
+  xfree (context.y_coordinates);
+  xfree (context.flags);
+  xfree (context.contour_end_points);
+  return 0;
+
+ fail:
+  xfree (context.x_coordinates);
+  xfree (context.y_coordinates);
+  xfree (context.flags);
+  xfree (context.contour_end_points);
+  return 1;
+}
+
+struct sfnt_build_glyph_outline_context
+{
+  /* The outline being built.  */
+  struct sfnt_glyph_outline *outline;
+
+  /* Factor to multiply positions by to get the pixel width.  */
+  sfnt_fixed factor;
+
+  /* The position of the pen in 16.16 fixed point format.  */
+  sfnt_fixed x, y;
+};
+
+/* Global state for sfnt_build_glyph_outline and related
+   functions.  */
+static struct sfnt_build_glyph_outline_context build_outline_context;
+
+/* Append the given three words FLAGS, X, and Y to the outline
+   currently being built.  Value is the new pointer to outline
+   memory.  */
+
+static struct sfnt_glyph_outline *
+sfnt_build_append (int flags, sfnt_fixed x, sfnt_fixed y)
+{
+  struct sfnt_glyph_outline *outline;
+
+  if (x == build_outline_context.x
+      && y == build_outline_context.y)
+    /* Ignore redundant motion.  */
+    return build_outline_context.outline;
+
+  outline = build_outline_context.outline;
+  outline->outline_used++;
+
+  /* See if the outline has to be extended.  Checking for overflow
+     should not be necessary.  */
+
+  if (outline->outline_used > outline->outline_size)
+    {
+      outline->outline_size = outline->outline_used * 2;
+
+      /* Extend the outline to some size past the new size.  */
+      outline = xrealloc (outline, (sizeof *outline
+                                   + (outline->outline_size
+                                      * sizeof *outline->outline)));
+      outline->outline
+       = (struct sfnt_glyph_outline_command *) (outline + 1);
+    }
+
+  /* Write the outline data.  */
+  outline->outline[outline->outline_used - 1].flags = flags;
+  outline->outline[outline->outline_used - 1].x = x;
+  outline->outline[outline->outline_used - 1].y = y;
+
+  /* Extend outline bounding box.  */
+
+  if (outline->outline_used == 1)
+    {
+      /* These are the first points in the outline.  */
+      outline->xmin = outline->xmax = x;
+      outline->ymin = outline->ymax = y;
+    }
+  else
+    {
+      outline->xmin = MIN ((sfnt_fixed) x, outline->xmin);
+      outline->ymin = MIN ((sfnt_fixed) y, outline->ymin);
+      outline->xmax = MAX ((sfnt_fixed) x, outline->xmax);
+      outline->ymax = MAX ((sfnt_fixed) y, outline->ymax);
+    }
+
+  return outline;
+}
+
+#ifndef INT64_MAX
+
+/* 64 bit integer type.  */
+
+struct sfnt_large_integer
+{
+  unsigned int high, low;
+};
+
+/* Calculate (A * B), placing the result in *VALUE.  */
+
+static void
+sfnt_multiply_divide_1 (unsigned int a, unsigned int b,
+                       struct sfnt_large_integer *value)
+{
+  unsigned int lo1, hi1, lo2, hi2, lo, hi, i1, i2;
+
+  lo1 = a & 0x0000ffffu;
+  hi1 = a >> 16;
+  lo2 = b & 0x0000ffffu;
+  hi2 = b >> 16;
+
+  lo = lo1 * lo2;
+  i1 = lo1 * hi2;
+  i2 = lo2 * hi1;
+  hi = hi1 * hi2;
+
+  /* Check carry overflow of i1 + i2.  */
+  i1 += i2;
+  hi += (unsigned int) (i1 < i2) << 16;
+
+  hi += i1 >> 16;
+  i1  = i1 << 16;
+
+  /* Check carry overflow of i1 + lo.  */
+  lo += i1;
+  hi += (lo < i1);
+
+  value->low = lo;
+  value->high = hi;
+}
+
+/* Count the number of most significant zero bits in N.  */
+
+static unsigned int
+sfnt_count_leading_zero_bits (unsigned int n)
+{
+  int shift;
+
+  shift = 0;
+
+  if (n & 0xffff0000ul)
+    {
+      n >>= 16;
+      shift += 16;
+    }
+
+  if (n & 0x0000ff00ul)
+    {
+      n >>= 8;
+      shift += 8;
+    }
+
+  if (n & 0x000000f0ul)
+    {
+      n >>= 4;
+      shift += 4;
+    }
+
+  if (n & 0x0000000cul)
+    {
+      n >>= 2;
+      shift += 2;
+    }
+
+  if (n & 0x00000002ul)
+    shift += 1;
+
+  return shift;
+}
+
+/* Calculate AB / C.  Value is a 32 bit unsigned integer.  */
+
+static unsigned int
+sfnt_multiply_divide_2 (struct sfnt_large_integer *ab,
+                       unsigned int c)
+{
+  unsigned int hi, lo;
+  int i;
+  unsigned int r, q; /* Remainder and quotient.  */
+
+  hi = ab->high;
+  lo = ab->low;
+
+  i = 31 - sfnt_count_leading_zero_bits (hi);
+  r = (hi << i) | (lo >> (32 - i));
+  lo <<= i;
+  q = r / c;
+  r -= q * c;
+  i = 32 - i;
+
+  do
+    {
+      q <<= 1;
+      r = (r << 1) | (lo >> 31);
+      lo <<= 1;
+
+      if (r >= c)
+       {
+         r -= c;
+         q |= 1;
+       }
+    }
+  while (--i);
+
+  return q;
+}
+
+#endif
+
+/* Calculate (A * B) / C with no rounding and return the result, using
+   a 64 bit integer if necessary.  */
+
+static unsigned int
+sfnt_multiply_divide (unsigned int a, unsigned int b, unsigned int c)
+{
+#ifndef INT64_MAX
+  struct sfnt_large_integer temp;
+
+  sfnt_multiply_divide_1 (a, b, &temp);
+  return sfnt_multiply_divide_2 (&temp, c);
+#else
+  uint64_t temp;
+
+  temp = (uint64_t) a * (uint64_t) b;
+  return temp / c;
+#endif
+}
+
+#ifndef INT64_MAX
+
+/* Add the specified unsigned 32-bit N to the large integer
+   INTEGER.  */
+
+static void
+sfnt_large_integer_add (struct sfnt_large_integer *integer,
+                       uint32_t n)
+{
+  struct sfnt_large_integer number;
+
+  number.low = integer->low + n;
+  number.high = integer->high + (number.low
+                                < integer->low);
+
+  *integer = number;
+}
+
+/* Calculate (A * B) / C, rounding the result with a threshold of N.
+   Use a 64 bit temporary.  */
+
+static unsigned int
+sfnt_multiply_divide_round (unsigned int a, unsigned int b,
+                           unsigned int n, unsigned int c)
+{
+  struct sfnt_large_integer temp;
+
+  sfnt_multiply_divide_1 (a, b, &temp);
+  sfnt_large_integer_add (&temp, n);
+  return sfnt_multiply_divide_2 (&temp, c);
+}
+
+#endif /* INT64_MAX */
+
+/* The same as sfnt_multiply_divide, but handle signed values
+   instead.  */
+
+MAYBE_UNUSED static int
+sfnt_multiply_divide_signed (int a, int b, int c)
+{
+  int sign;
+
+  sign = 1;
+
+  if (a < 0)
+    sign = -sign;
+
+  if (b < 0)
+    sign = -sign;
+
+  if (c < 0)
+    sign = -sign;
+
+  return (sfnt_multiply_divide (abs (a), abs (b), abs (c))
+         * sign);
+}
+
+/* Multiply the two 16.16 fixed point numbers X and Y.  Return the
+   result regardless of overflow.  */
+
+static sfnt_fixed
+sfnt_mul_fixed (sfnt_fixed x, sfnt_fixed y)
+{
+#ifdef INT64_MAX
+  int64_t product;
+
+  product = (int64_t) x * (int64_t) y;
+
+  /* This can be done quickly with int64_t.  */
+  return product / (int64_t) 65536;
+#else
+  int sign;
+
+  sign = 1;
+
+  if (x < 0)
+    sign = -sign;
+
+  if (y < 0)
+    sign = -sign;
+
+  return sfnt_multiply_divide (abs (x), abs (y),
+                              65536) * sign;
+#endif
+}
+
+/* Multiply the two 16.16 fixed point numbers X and Y, with rounding
+   of the result.  */
+
+static sfnt_fixed
+sfnt_mul_fixed_round (sfnt_fixed x, sfnt_fixed y)
+{
+#ifdef INT64_MAX
+  int64_t product, round;
+
+  product = (int64_t) x * (int64_t) y;
+  round = product < 0 ? -32768 : 32768;
+
+  /* This can be done quickly with int64_t.  */
+  return (product + round) / (int64_t) 65536;
+#else
+  int sign;
+
+  sign = 1;
+
+  if (x < 0)
+    sign = -sign;
+
+  if (y < 0)
+    sign = -sign;
+
+  return sfnt_multiply_divide_round (abs (x), abs (y),
+                                    32768, 65536) * sign;
+#endif
+}
+
+/* Set the pen size to the specified point and return.  POINT will be
+   scaled up to the pixel size.  */
+
+static void
+sfnt_move_to_and_build (struct sfnt_point point, void *dcontext)
+{
+  sfnt_fixed x, y;
+
+  x = sfnt_mul_fixed (build_outline_context.factor, point.x);
+  y = sfnt_mul_fixed (build_outline_context.factor, point.y);
+
+  build_outline_context.outline = sfnt_build_append (0, x, y);
+  build_outline_context.x = x;
+  build_outline_context.y = y;
+}
+
+/* Record a line to the specified point and return.  POINT will be
+   scaled up to the pixel size.  */
+
+static void
+sfnt_line_to_and_build (struct sfnt_point point, void *dcontext)
+{
+  sfnt_fixed x, y;
+
+  x = sfnt_mul_fixed (build_outline_context.factor, point.x);
+  y = sfnt_mul_fixed (build_outline_context.factor, point.y);
+
+  build_outline_context.outline
+    = sfnt_build_append (SFNT_GLYPH_OUTLINE_LINETO,
+                        x, y);
+  build_outline_context.x = x;
+  build_outline_context.y = y;
+}
+
+/* Divide the two 16.16 fixed point numbers X and Y.  Return the
+   result regardless of overflow.  */
+
+static sfnt_fixed
+sfnt_div_fixed (sfnt_fixed x, sfnt_fixed y)
+{
+#ifdef INT64_MAX
+  int64_t result;
+
+  result = ((int64_t) x * 65536) / y;
+
+  return result;
+#else
+  int sign;
+  unsigned int a, b;
+
+  sign = 1;
+
+  if (x < 0)
+    sign = -sign;
+
+  if (y < 0)
+    sign = -sign;
+
+  a = abs (x);
+  b = abs (y);
+
+  return sfnt_multiply_divide (a, 65536, b) * sign;
+#endif
+}
+
+/* Return the ceiling value of the specified fixed point number X.  */
+
+static sfnt_fixed
+sfnt_ceil_fixed (sfnt_fixed x)
+{
+  return (x + 0177777) & 037777600000;
+}
+
+/* Return the floor value of the specified fixed point number X.  */
+
+static sfnt_fixed
+sfnt_floor_fixed (sfnt_fixed x)
+{
+  return x & 037777600000;
+}
+
+/* Given a curve consisting of three points CONTROL0, CONTROL1 and
+   ENDPOINT, return whether or not the curve is sufficiently small to
+   be approximated by a line between CONTROL0 and ENDPOINT.  */
+
+static bool
+sfnt_curve_is_flat (struct sfnt_point control0,
+                   struct sfnt_point control1,
+                   struct sfnt_point endpoint)
+{
+  struct sfnt_point g, h;
+
+  g.x = control1.x - control0.x;
+  g.y = control1.y - control0.y;
+  h.x = endpoint.x - control0.x;
+  h.y = endpoint.y - control0.y;
+
+  /* 2.0 is a constant describing the area covered at which point the
+     curve is considered "flat".  */
+  return (abs (sfnt_mul_fixed (g.x, h.y)
+              - sfnt_mul_fixed (g.y, h.x))
+         <= 0400000);
+}
+
+/* Recursively split the splines in the bezier curve formed from
+   CONTROL0, CONTROL1 and ENDPOINT until the area between the curve's
+   two ends is small enough to be considered ``flat''.  Then, turn
+   those ``flat'' curves into lines.  */
+
+static void
+sfnt_curve_to_and_build_1 (struct sfnt_point control0,
+                          struct sfnt_point control1,
+                          struct sfnt_point endpoint)
+{
+  struct sfnt_point ab, bc, abbc;
+
+  /* control0, control and endpoint make up the spline.  Figure out
+     its distance from a line.  */
+  if (sfnt_curve_is_flat (control0, control1, endpoint))
+    {
+      /* Draw a line to endpoint.  */
+      build_outline_context.outline
+       = sfnt_build_append (SFNT_GLYPH_OUTLINE_LINETO,
+                            endpoint.x, endpoint.y);
+      build_outline_context.x = endpoint.x;
+      build_outline_context.y = endpoint.y;
+    }
+  else
+    {
+      /* Calculate new control points.
+        Maybe apply a recursion limit here? */
+      sfnt_lerp_half (&control0, &control1, &ab);
+      sfnt_lerp_half (&control1, &endpoint, &bc);
+      sfnt_lerp_half (&ab, &bc, &abbc);
+
+      /* Keep splitting until a flat enough spline results.  */
+      sfnt_curve_to_and_build_1 (control0, ab, abbc);
+
+      /* Then go on with the spline between control1 and endpoint.  */
+      sfnt_curve_to_and_build_1 (abbc, bc, endpoint);
+    }
+}
+
+/* Scale and decompose the specified bezier curve into individual
+   lines.  Then, record each of those lines into the outline being
+   built.  */
+
+static void
+sfnt_curve_to_and_build (struct sfnt_point control,
+                        struct sfnt_point endpoint,
+                        void *dcontext)
+{
+  struct sfnt_point control0;
+
+  control0.x = build_outline_context.x;
+  control0.y = build_outline_context.y;
+  control.x = sfnt_mul_fixed (control.x,
+                             build_outline_context.factor);
+  control.y = sfnt_mul_fixed (control.y,
+                             build_outline_context.factor);
+  endpoint.x = sfnt_mul_fixed (endpoint.x,
+                              build_outline_context.factor);
+  endpoint.y = sfnt_mul_fixed (endpoint.y,
+                              build_outline_context.factor);
+
+  sfnt_curve_to_and_build_1 (control0, control, endpoint);
+}
+
+/* Non-reentrantly build the outline for the specified GLYPH at the
+   given scale factor.  Return the outline data with a refcount of 0
+   upon success, or NULL upon failure.
+
+   SCALE is a scale factor that converts between em space and device
+   space.
+
+   Use the unscaled glyph METRICS to determine the origin point of the
+   outline.
+
+   Call GET_GLYPH and FREE_GLYPH with the specified DCONTEXT to obtain
+   glyphs for compound glyph subcomponents, and GET_METRICS with the
+   provided DCONTEXT for unscaled glyph metrics.  */
+
+TEST_STATIC struct sfnt_glyph_outline *
+sfnt_build_glyph_outline (struct sfnt_glyph *glyph,
+                         sfnt_fixed scale,
+                         struct sfnt_glyph_metrics *metrics,
+                         sfnt_get_glyph_proc get_glyph,
+                         sfnt_free_glyph_proc free_glyph,
+                         sfnt_get_metrics_proc get_metrics,
+                         void *dcontext)
+{
+  struct sfnt_glyph_outline *outline;
+  int rc;
+  sfnt_fword origin;
+
+  memset (&build_outline_context, 0, sizeof build_outline_context);
+
+  /* Allocate the outline now with enough for 44 words at the end.  */
+  outline = xmalloc (sizeof *outline + 40 * sizeof (*outline->outline));
+  outline->outline_size = 40;
+  outline->outline_used = 0;
+  outline->refcount = 0;
+  outline->outline
+    = (struct sfnt_glyph_outline_command *) (outline + 1);
+
+  /* DCONTEXT will be passed to GET_GLYPH and FREE_GLYPH, so global
+     variables must be used to communicate with the decomposition
+     functions.  */
+  build_outline_context.outline = outline;
+
+  /* Clear outline bounding box.  */
+  outline->xmin = 0;
+  outline->ymin = 0;
+  outline->xmax = 0;
+  outline->ymax = 0;
+
+  /* Set the scale factor.  */
+  build_outline_context.factor = scale;
+
+  /* Decompose the outline.  */
+  rc = sfnt_decompose_glyph (glyph, sfnt_move_to_and_build,
+                            sfnt_line_to_and_build,
+                            sfnt_curve_to_and_build,
+                            get_glyph, free_glyph, get_metrics,
+                            dcontext);
+
+  /* Synchronize the outline object with what might have changed
+     inside sfnt_decompose_glyph.  */
+  outline = build_outline_context.outline;
+
+  if (rc)
+    {
+      xfree (outline);
+      return NULL;
+    }
+
+  /* Compute the origin position.  Note that the original glyph xmin
+     is first used to calculate the origin point, and the origin
+     distortion is applied to it to get the distorted origin.  */
+
+  origin = glyph->xmin - metrics->lbearing + glyph->origin_distortion;
+  outline->origin = sfnt_mul_fixed (origin, scale);
+
+  return outline;
+}
+
+
+
+/* Glyph rasterization.  The algorithm used here is fairly simple.
+   Each contour is decomposed into lines, which turn into a polygon.
+   Then, a bog standard edge filler is used to turn them into
+   spans.  */
+
+/* Coverage table.  This is a four dimensional array indiced by the Y,
+   then X axis fractional, shifted down to 2 bits.  */
+
+static const unsigned char sfnt_poly_coverage[8][9] =
+  {
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+    { 0, 3, 7, 11, 15, 19, 23, 27, 31, },
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+    { 0, 4, 8, 12, 16, 20, 24, 28, 32, },
+  };
+
+/* Return the nearest coordinate on the sample grid no less than
+   F.  */
+
+static sfnt_fixed
+sfnt_poly_grid_ceil (sfnt_fixed f)
+{
+  return (((f + (SFNT_POLY_START - 1))
+          & ~(SFNT_POLY_STEP - 1)) + SFNT_POLY_START);
+}
+
+enum
+  {
+    SFNT_POLY_ALIGNMENT = 4,
+  };
+
+/* Initialize the specified RASTER in preparation for displaying spans
+   for OUTLINE, and set RASTER->refcount to 0.  The caller must then
+   set RASTER->cells to a zeroed array of size RASTER->stride *
+   RASTER->height, aligned to RASTER.  */
+
+TEST_STATIC void
+sfnt_prepare_raster (struct sfnt_raster *raster,
+                    struct sfnt_glyph_outline *outline)
+{
+  raster->width
+    = (sfnt_ceil_fixed (outline->xmax)
+       - sfnt_floor_fixed (outline->xmin)) / 65536;
+  raster->height
+    = (sfnt_ceil_fixed (outline->ymax)
+       - sfnt_floor_fixed (outline->ymin)) / 65536;
+  raster->refcount = 0;
+
+  /* Align the raster to a SFNT_POLY_ALIGNMENT byte boundary.  */
+  raster->stride = ((raster->width
+                    + (SFNT_POLY_ALIGNMENT - 1))
+                   & ~(SFNT_POLY_ALIGNMENT - 1));
+
+  /* Apply outline->origin.  This is 0 by convention in most fonts.
+     However, variable fonts typically change this as variations are
+     applied.  */
+  raster->offx = sfnt_floor_fixed (outline->xmin
+                                  - outline->origin) / 65536;
+  raster->offy = sfnt_floor_fixed (outline->ymin) / 65536;
+}
+
+typedef void (*sfnt_edge_proc) (struct sfnt_edge *, size_t,
+                               void *);
+typedef void (*sfnt_span_proc) (struct sfnt_edge *, sfnt_fixed, void *);
+
+/* Move EDGE->x forward, assuming that the scanline has moved upwards
+   by SFNT_POLY_STEP.  */
+
+static void
+sfnt_step_edge (struct sfnt_edge *edge)
+{
+  /* Add step.  */
+  edge->x += edge->step_x;
+}
+
+/* Build a list of edges for each contour in OUTLINE, applying xmin
+   and ymin as the offset to each edge.  Call EDGE_PROC with DCONTEXT
+   and the resulting edges as arguments.  It is OK to modify the edges
+   given to EDGE_PROC.  Align all edges to the sub-pixel grid.  */
+
+static void
+sfnt_build_outline_edges (struct sfnt_glyph_outline *outline,
+                         sfnt_edge_proc edge_proc, void *dcontext)
+{
+  struct sfnt_edge *edges;
+  size_t i, edge, next_vertex;
+  sfnt_fixed dx, dy, bot, step_x, ymin, xmin;
+  size_t top, bottom, y;
+
+  edges = alloca (outline->outline_used * sizeof *edges);
+  edge = 0;
+
+  /* ymin and xmin must be the same as the offset used to set offy and
+     offx in rasters.  */
+  ymin = sfnt_floor_fixed (outline->ymin);
+  xmin = sfnt_floor_fixed (outline->xmin);
+
+  for (i = 0; i < outline->outline_used; ++i)
+    {
+      /* Set NEXT_VERTEX to the next point (vertex) in this contour.
+
+        If i is past the end of the contour, then don't build edges
+        for this point.  */
+      next_vertex = i + 1;
+
+      if (next_vertex == outline->outline_used
+         || !(outline->outline[next_vertex].flags
+              & SFNT_GLYPH_OUTLINE_LINETO))
+       continue;
+
+      /* Skip past horizontal vertices.  */
+      if (outline->outline[next_vertex].y == outline->outline[i].y)
+       continue;
+
+      /* Figure out the winding direction.  */
+      if (outline->outline[next_vertex].y < outline->outline[i].y)
+       /* Vector will cross imaginary ray from its bottom from the
+          left of the ray.  Winding is thus 1.  */
+       edges[edge].winding = 1;
+      else
+       /* Moving clockwise.  Winding is thus -1.  */
+       edges[edge].winding = -1;
+
+      /* Figure out the top and bottom values of this edge.  If the
+        next edge is below, top is here and bot is the edge below.
+        If the next edge is above, then top is there and this is the
+        bottom.  */
+
+      if (outline->outline[next_vertex].y < outline->outline[i].y)
+       {
+         /* End of edge is below this one (keep in mind this is a
+            cartesian coordinate system, so smaller values are below
+            larger ones.) */
+         top = i;
+         bottom = next_vertex;
+       }
+      else
+       {
+         /* End of edge is above this one.  */
+         bottom = i;
+         top = next_vertex;
+       }
+
+      bot = (outline->outline[bottom].y - ymin);
+      edges[edge].top = (outline->outline[top].y - ymin);
+
+      /* Record the edge.  Rasterization happens from bottom to
+        up, so record the X at the bottom.  */
+      edges[edge].x = (outline->outline[bottom].x - xmin);
+      dx = (outline->outline[top].x - outline->outline[bottom].x);
+      dy = abs (outline->outline[top].y
+               - outline->outline[bottom].y);
+
+      /* Step to first grid point.  */
+      y = sfnt_poly_grid_ceil (bot);
+
+      /* If rounding would make the edge not cover any area, skip this
+        edge. */
+
+      if (y >= edges[edge].top)
+       continue;
+
+      /* Compute the step X.  This is how much X changes for each
+        increase in Y.  */
+
+      step_x = sfnt_div_fixed (dx, dy);
+      edges[edge].next = NULL;
+
+      /* Compute the step X scaled to the poly step.  */
+      edges[edge].step_x
+       = sfnt_mul_fixed (step_x, SFNT_POLY_STEP);
+
+      /* Step to the grid point.  */
+      edges[edge].x += sfnt_mul_fixed (step_x, bot - y);
+
+      /* Set the bottom position.  */
+      edges[edge].bottom = y;
+
+      edge++;
+    }
+
+  if (edge)
+    edge_proc (edges, edge, dcontext);
+}
+
+/* Sort an array of SIZE edges to increase by bottom Y position, in
+   preparation for building spans.
+
+   Insertion sort is used because there are usually not very many
+   edges, and anything larger would bloat up the code.  */
+
+static void
+sfnt_edge_sort (struct sfnt_edge *edges, size_t size)
+{
+  ssize_t i, j;
+  struct sfnt_edge edge;
+
+  for (i = 1; i < size; ++i)
+    {
+      edge = edges[i];
+      j = i - 1;
+
+      while (j >= 0 && (edges[j].bottom > edge.bottom))
+       {
+         edges[j + 1] = edges[j];
+         j--;
+       }
+
+      edges[j + 1] = edge;
+    }
+}
+
+/* Draw EDGES, an unsorted array of polygon edges of size SIZE.  For
+   each scanline, call SPAN_FUNC with a list of active edges and
+   coverage information, and DCONTEXT.
+
+   Sort each edge in ascending order by the bottommost Y coordinate to
+   which it applies.  Start a loop on the Y coordinate, which starts
+   out at that of the bottommost edge.  For each iteration, add edges
+   that now overlap with Y, keeping them sorted by X.  Poly those
+   edges through SPAN_FUNC.  Then, move upwards by SFNT_POLY_STEP,
+   remove edges that no longer apply, and interpolate the remaining
+   edges' X coordinates.  Repeat until all the edges have been polyed.
+
+   Or alternatively, think of this as such: each edge is actually a
+   vector from its bottom position towards its top most position.
+   Every time Y moves upwards, the position of each edge intersecting
+   with Y is interpolated and added to a list of spans along with
+   winding information that is then given to EDGE_FUNC.
+
+   Anti-aliasing is performed using a coverage map for fractional
+   coordinates, and incrementing the Y axis by SFNT_POLY_STEP instead
+   of 1.  SFNT_POLY_STEP is chosen to always keep Y aligned to a grid
+   placed such that there are always 1 << SFNT_POLY_SHIFT positions
+   available for each integral pixel coordinate.  */
+
+static void
+sfnt_poly_edges (struct sfnt_edge *edges, size_t size,
+                sfnt_span_proc span_func, void *dcontext)
+{
+  sfnt_fixed y;
+  size_t e;
+  struct sfnt_edge *active, **prev, *a, *n;
+
+  if (!size)
+    return;
+
+  /* Sort edges to ascend by Y-order.  Once again, remember: cartesian
+     coordinates.  */
+  sfnt_edge_sort (edges, size);
+
+  /* Step down line by line.  Find active edges.  */
+
+  y = edges[0].bottom;
+  active = 0;
+  active = NULL;
+  e = 0;
+
+  for (;;)
+    {
+      /* Add in new edges keeping them sorted.  */
+      for (; e < size && edges[e].bottom <= y; ++e)
+       {
+         /* Find where to place this edge.  */
+         for (prev = &active; (a = *prev); prev = &(a->next))
+           {
+             if (a->x > edges[e].x)
+               break;
+           }
+
+         edges[e].next = *prev;
+         *prev = &edges[e];
+       }
+
+      /* Draw this span at the current position.  Y axis antialiasing
+        is expected to be handled by SPAN_FUNC.  */
+      span_func (active, y, dcontext);
+
+      /* Compute the next Y position.  */
+      y += SFNT_POLY_STEP;
+
+      /* Strip out edges that no longer have effect.  */
+
+      for (prev = &active; (a = *prev);)
+       {
+         if (a->top <= y)
+           *prev = a->next;
+         else
+           prev = &a->next;
+       }
+
+      /* Break if all is done.  */
+      if (!active && e == size)
+       break;
+
+      /* Step all edges.  */
+      for (a = active; a; a = a->next)
+       sfnt_step_edge (a);
+
+      /* Resort on X axis.  */
+      for (prev = &active; (a = *prev) && (n = a->next);)
+       {
+         if (a->x > n->x)
+           {
+             a->next = n->next;
+             n->next = a;
+             *prev = n;
+             prev = &active;
+           }
+         else
+           prev = &a->next;
+       }
+    }
+}
+
+/* Saturate and convert the given unsigned short value X to an
+   unsigned char.  */
+
+static unsigned char
+sfnt_saturate_short (unsigned short x)
+{
+  if (x > 255)
+    return 255;
+
+  return x;
+}
+
+/* Fill a single span of pixels between X0 and X1 at Y, a raster
+   coordinate, onto RASTER.  */
+
+static void
+sfnt_fill_span (struct sfnt_raster *raster, sfnt_fixed y,
+               sfnt_fixed x0, sfnt_fixed x1)
+{
+  unsigned char *start;
+  const unsigned char *coverage;
+  sfnt_fixed left, right, end;
+  unsigned short w, a;
+  int row;
+#ifndef NDEBUG
+  unsigned char *row_end;
+#endif /* NDEBUG */
+
+  /* Clip bounds to pixmap.  */
+
+  if (x0 < 0)
+    x0 = 0;
+
+  /* If x1 is greater than the raster width, make sure the last pixel
+     is filled and no more after that.  */
+
+  if (x1 > raster->width * 65536)
+    x1 = raster->width * 65536;
+
+  /* Check for empty spans.  */
+  if (x1 <= x0)
+    return;
+
+  /* Figure out coverage based on Y axis fractional.  */
+  coverage = sfnt_poly_coverage[(y >> (16 - SFNT_POLY_SHIFT))
+                               & SFNT_POLY_MASK];
+  row = y >> 16;
+
+  /* Don't fill out of bounds rows.  */
+  if (row < 0 || row >= raster->height)
+    return;
+
+  /* Set start, then start filling according to coverage.  left and
+     right are now .3.  */
+  left = x0 >> (16 - SFNT_POLY_SHIFT);
+  right = x1 >> (16 - SFNT_POLY_SHIFT);
+  start = raster->cells + row * raster->stride;
+#ifndef NDEBUG
+  row_end = start + raster->width;
+#endif /* NDEBUG */
+  start += left >> SFNT_POLY_SHIFT;
+
+  /* If left and right actually lie in the same pixel, just fill with
+     the coverage of both and return.  */
+
+  if ((left & ~SFNT_POLY_MASK) == (right & ~SFNT_POLY_MASK))
+    {
+      /* Assert that start does not exceed the end of the row.  */
+      assert (start <= row_end);
+
+      w = coverage[right - left];
+      a = *start + w;
+
+      *start = sfnt_saturate_short (a);
+      return;
+    }
+
+  /* Compute coverage for first pixel, then poly.  The code from here
+     onwards assumes that left and right are on two different
+     pixels.  */
+
+  if (left & SFNT_POLY_MASK)
+    {
+      /* Assert that start does not exceed the end of the row.  */
+      assert (start <= row_end);
+
+      /* Compute the coverage for the first pixel, and move left past
+        it.  The coverage is a number from 1 to 7 describing how
+        ``partially'' covered this pixel is.  */
+
+      end = (left + SFNT_POLY_SAMPLE - 1) & ~SFNT_POLY_MASK;
+      end = MIN (right, end);
+
+      w = coverage[end - left];
+      a = *start + w;
+
+      /* Now move left past.  */
+      left = end;
+      *start++ = sfnt_saturate_short (a);
+    }
+
+  /* Clear coverage info for first pixel.  Compute coverage for center
+     pixels.  Note that SFNT_POLY_SAMPLE is used and not
+     SFNT_POLY_MASK, because coverage has a blank column at the
+     start.  */
+  w = coverage[SFNT_POLY_SAMPLE];
+
+  /* Fill pixels between left and right.  */
+  while (left + SFNT_POLY_MASK < right)
+    {
+      /* Assert that start does not exceed the end of the row.  */
+      assert (start <= row_end);
+
+      a = *start + w;
+      *start++ = sfnt_saturate_short (a);
+      left += SFNT_POLY_SAMPLE;
+    }
+
+  /* Fill rightmost pixel with any partial coverage.  */
+
+  if (right & SFNT_POLY_MASK)
+    {
+      /* Assert that start does not exceed the end of the row.  */
+      assert (start <= row_end);
+
+      w = coverage[right - left];
+      a = *start + w;
+      *start = sfnt_saturate_short (a);
+    }
+}
+
+/* Poly each span starting from START onto RASTER, at position Y.  Y
+   here is still a cartesian coordinate, where the bottom of the
+   raster is 0.  But that is no longer true by the time sfnt_span_fill
+   is called.  */
+
+static void
+sfnt_poly_span (struct sfnt_edge *start, sfnt_fixed y,
+               struct sfnt_raster *raster)
+{
+  struct sfnt_edge *edge;
+  int winding;
+  sfnt_fixed x0, x1;
+
+  /* Pacify -Wmaybe-uninitialized; x1 and x0 are only used when edge
+     != start, at which point x0 has already been set.  */
+  x0 = x1 = 0;
+
+  /* Generate the X axis coverage map.  Then poly it onto RASTER.
+     winding on each edge determines the winding direction: when it is
+     positive, winding is 1.  When it is negative, winding is -1.
+
+     Fill each consecutive stretch of spans that are inside the glyph;
+     otherwise, coverage will overlap for some spans, but not
+     others.
+
+     The spans must be terminated with an edge that causes an
+     off-transition, or some spans will not be filled.  */
+
+  winding = 0;
+
+  for (edge = start; edge; edge = edge->next)
+    {
+      if (!winding)
+       {
+         if (edge != start && x0 != x1)
+           /* Draw this section of spans that are on.  */
+           sfnt_fill_span (raster, (raster->height << 16) - y,
+                           x0, x1);
+
+         x0 = x1 = edge->x;
+       }
+      else
+       x1 = edge->x;
+
+      winding += edge->winding;
+    }
+
+  /* Draw the last span following the last off-transition.  */
+
+  if (!winding && edge != start && x0 != x1)
+    sfnt_fill_span (raster, (raster->height << 16) - y,
+                   x0, x1);
+}
+
+
+
+/* Main entry point for outline rasterization.  */
+
+/* Raster the spans between START and its end to the raster specified
+   as DCONTEXT.  The span's position is Y.  */
+
+static void
+sfnt_raster_span (struct sfnt_edge *start, sfnt_fixed y,
+                 void *dcontext)
+{
+  sfnt_poly_span (start, y, dcontext);
+}
+
+/* Generate and poly each span in EDGES onto the raster specified as
+   DCONTEXT.  */
+
+static void
+sfnt_raster_edge (struct sfnt_edge *edges, size_t num_edges,
+                 void *dcontext)
+{
+  sfnt_poly_edges (edges, num_edges, sfnt_raster_span,
+                  dcontext);
+}
+
+/* Generate an alpha mask for the glyph outline OUTLINE.  Value is the
+   alpha mask upon success, NULL upon failure.  */
+
+TEST_STATIC struct sfnt_raster *
+sfnt_raster_glyph_outline (struct sfnt_glyph_outline *outline)
+{
+  struct sfnt_raster raster, *data;
+
+  /* Get the raster parameters.  */
+  sfnt_prepare_raster (&raster, outline);
+
+  /* Allocate the raster data.  */
+  data = xmalloc (sizeof *data + raster.stride * raster.height);
+  *data = raster;
+  data->cells = (unsigned char *) (data + 1);
+  memset (data->cells, 0, raster.stride * raster.height);
+
+  /* Generate edges for the outline, polying each array of edges to
+     the raster.  */
+  sfnt_build_outline_edges (outline, sfnt_raster_edge, data);
+
+  /* All done.  */
+  return data;
+}
+
+
+
+/* Glyph metrics computation.  */
+
+/* Read an hmtx table from the font FD, using the table directory
+   specified as SUBTABLE, the maxp table MAXP, and the hhea table
+   HHEA.
+
+   Return NULL upon failure, and the hmtx table otherwise.
+   HHEA->num_of_long_hor_metrics determines the number of horizontal
+   metrics present, and MAXP->num_glyphs -
+   HHEA->num_of_long_hor_metrics determines the number of left-side
+   bearings present.  */
+
+TEST_STATIC struct sfnt_hmtx_table *
+sfnt_read_hmtx_table (int fd, struct sfnt_offset_subtable *subtable,
+                     struct sfnt_hhea_table *hhea,
+                     struct sfnt_maxp_table *maxp)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_hmtx_table *hmtx;
+  size_t size;
+  ssize_t rc;
+  int i;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_HMTX);
+
+  if (!directory)
+    return NULL;
+
+  /* Figure out how many bytes are required.  */
+  size = ((hhea->num_of_long_hor_metrics
+          * sizeof (struct sfnt_long_hor_metric))
+         + (MAX (0, ((int) maxp->num_glyphs
+                     - hhea->num_of_long_hor_metrics))
+            * sizeof (int16_t)));
+
+  /* Check the length matches exactly.  */
+  if (directory->length != size)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Now allocate enough to hold all of that along with the table
+     directory structure.  */
+
+  hmtx = xmalloc (sizeof *hmtx + size);
+
+  /* Read into hmtx + 1.  */
+  rc = read (fd, hmtx + 1, size);
+  if (rc < size)
+    {
+      xfree (hmtx);
+      return NULL;
+    }
+
+  /* Set pointers to data.  */
+  hmtx->h_metrics = (struct sfnt_long_hor_metric *) (hmtx + 1);
+  hmtx->left_side_bearing
+    = (int16_t *) (hmtx->h_metrics
+                  + hhea->num_of_long_hor_metrics);
+
+  /* Swap what was read.  */
+
+  for (i = 0; i < hhea->num_of_long_hor_metrics; ++i)
+    {
+      sfnt_swap16 (&hmtx->h_metrics[i].advance_width);
+      sfnt_swap16 (&hmtx->h_metrics[i].left_side_bearing);
+    }
+
+  for (; i < maxp->num_glyphs; ++i)
+    sfnt_swap16 (&hmtx->left_side_bearing[i - hhea->num_of_long_hor_metrics]);
+
+  /* All done.  */
+  return hmtx;
+}
+
+/* Obtain glyph metrics for the glyph indiced by GLYPH at the
+   specified PIXEL_SIZE.  Return 0 and the metrics in *METRICS if
+   metrics could be found, else 1.
+
+   If PIXEL_SIZE is -1, do not perform any scaling on the glyph
+   metrics; HEAD need not be specified in that case.
+
+   HMTX, HHEA, HEAD and MAXP should be the hmtx, hhea, head, and maxp
+   tables of the font respectively.  */
+
+TEST_STATIC int
+sfnt_lookup_glyph_metrics (sfnt_glyph glyph, int pixel_size,
+                          struct sfnt_glyph_metrics *metrics,
+                          struct sfnt_hmtx_table *hmtx,
+                          struct sfnt_hhea_table *hhea,
+                          struct sfnt_head_table *head,
+                          struct sfnt_maxp_table *maxp)
+{
+  short lbearing;
+  unsigned short advance;
+  sfnt_fixed factor;
+
+  if (glyph < hhea->num_of_long_hor_metrics)
+    {
+      /* There is a long entry in the hmtx table.  */
+      lbearing = hmtx->h_metrics[glyph].left_side_bearing;
+      advance = hmtx->h_metrics[glyph].advance_width;
+    }
+  else if (hhea->num_of_long_hor_metrics
+          && glyph < maxp->num_glyphs)
+    {
+      /* There is a short entry in the hmtx table.  */
+      lbearing
+       = hmtx->left_side_bearing[glyph
+                                 - hhea->num_of_long_hor_metrics];
+      advance
+       = hmtx->h_metrics[hhea->num_of_long_hor_metrics - 1].advance_width;
+    }
+  else
+    /* No entry corresponds to the glyph.  */
+    return 1;
+
+  if (pixel_size == -1)
+    {
+      /* Return unscaled metrics in this case.  */
+      metrics->lbearing = lbearing;
+      metrics->advance = advance;
+      return 0;
+    }
+
+  /* Now scale lbearing and advance up to the pixel size.  */
+  factor = sfnt_div_fixed (pixel_size, head->units_per_em);
+
+  /* Save them.  */
+  metrics->lbearing = sfnt_mul_fixed (lbearing * 65536, factor);
+  metrics->advance = sfnt_mul_fixed (advance * 65536, factor);
+
+  /* All done.  */
+  return 0;
+}
+
+/* Scale the specified glyph metrics by FACTOR.
+   Set METRICS->lbearing and METRICS->advance to their current
+   values times factor.  */
+
+MAYBE_UNUSED TEST_STATIC void
+sfnt_scale_metrics (struct sfnt_glyph_metrics *metrics,
+                   sfnt_fixed factor)
+{
+  metrics->lbearing
+    = sfnt_mul_fixed (metrics->lbearing * 65536, factor);
+  metrics->advance
+    = sfnt_mul_fixed (metrics->advance * 65536, factor);
+}
+
+/* Calculate the factor used to convert em space to device space for a
+   font with the specified HEAD table and PPEM value.  */
+
+MAYBE_UNUSED TEST_STATIC sfnt_fixed
+sfnt_get_scale (struct sfnt_head_table *head, int ppem)
+{
+  /* Figure out how to convert from font unit-space to pixel space.
+     To turn one unit to its corresponding pixel size given a ppem of
+     1, the unit must be divided by head->units_per_em.  Then, it must
+     be multipled by the ppem.  So,
+
+       PIXEL = UNIT / UPEM * PPEM
+
+     which means:
+
+       PIXEL = UNIT * PPEM / UPEM  */
+
+  return sfnt_div_fixed (ppem, head->units_per_em);
+}
+
+
+
+/* Font style parsing.  */
+
+/* Read the name table from the given font FD, using the table
+   directory specified as SUBTABLE.  Perform validation on the offsets
+   in the name records.  Return NULL upon failure, else the name
+   table.  */
+
+TEST_STATIC struct sfnt_name_table *
+sfnt_read_name_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_name_table *name;
+  size_t required;
+  ssize_t rc;
+  int i;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_NAME);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out the minimum that has to be read.  */
+  required = SFNT_ENDOF (struct sfnt_name_table,
+                        string_offset, uint16_t);
+
+  if (directory->length < required)
+    return NULL;
+
+  /* Allocate enough to hold the name table and variable length
+     data.  */
+  name = xmalloc (sizeof *name + directory->length);
+
+  /* Read the fixed length data.  */
+  rc = read (fd, name, required);
+  if (rc < required)
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  /* Swap what was read.  */
+  sfnt_swap16 (&name->format);
+  sfnt_swap16 (&name->count);
+  sfnt_swap16 (&name->string_offset);
+
+  /* Reject unsupported formats.  */
+  if (name->format)
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  /* Set the pointer to the start of the variable length data.  */
+  name->name_records
+    = (struct sfnt_name_record *) (name + 1);
+
+  /* Check there is enough for the name records.  */
+  required = directory->length - required;
+  if (required < name->count * sizeof *name->name_records)
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  /* Read the variable length data.  First, read the name records.  */
+  rc = read (fd, name->name_records,
+            (name->count
+             * sizeof *name->name_records));
+  if (rc < (name->count
+           * sizeof *name->name_records))
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  /* Swap each of the name records.  */
+  for (i = 0; i < name->count; ++i)
+    {
+      sfnt_swap16 (&name->name_records[i].platform_id);
+      sfnt_swap16 (&name->name_records[i].platform_specific_id);
+      sfnt_swap16 (&name->name_records[i].language_id);
+      sfnt_swap16 (&name->name_records[i].name_id);
+      sfnt_swap16 (&name->name_records[i].length);
+      sfnt_swap16 (&name->name_records[i].offset);
+    }
+
+  /* Now, read the name data.  */
+
+  if (name->string_offset > directory->length)
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  required = directory->length - name->string_offset;
+
+  /* It can happen that the string offset comes before the name
+     records, and as a result exceeds the number of bytes
+     previously allocated.  Extend name if that is the case.  */
+
+  if (required > (directory->length
+                 - (name->count
+                    * sizeof *name->name_records)))
+    {
+      name = xrealloc (name, (sizeof *name
+                             + (name->count
+                                * sizeof *name->name_records)
+                             + required));
+      name->name_records = (struct sfnt_name_record *) (name + 1);
+    }
+
+  /* There is enough space past name->name_records to hold REQUIRED
+     bytes.  Seek to the right offset.  */
+
+  if (lseek (fd, directory->offset + name->string_offset,
+            SEEK_SET) == (off_t) -1)
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  /* Read REQURIED bytes into the string data.  */
+  name->data = (unsigned char *) (name->name_records
+                                 + name->count);
+  rc = read (fd, name->data, required);
+  if (rc < required)
+    {
+      xfree (name);
+      return NULL;
+    }
+
+  /* Now validate each of the name records.  */
+  for (i = 0; i < name->count; ++i)
+    {
+      if (((int) name->name_records[i].offset
+          + name->name_records[i].length) > required)
+       {
+         /* The name is out of bounds! */
+         xfree (name);
+         return NULL;
+       }
+    }
+
+  /* Return the name table.  */
+  return name;
+}
+
+/* Return a pointer to the name data corresponding with CODE under the
+   name table NAME.  Return the start of the data and the name record
+   under *RECORD upon success, and NULL otherwise.  */
+
+TEST_STATIC unsigned char *
+sfnt_find_name (struct sfnt_name_table *name,
+               enum sfnt_name_identifier_code code,
+               struct sfnt_name_record *record)
+{
+  int i;
+
+  for (i = 0; i < name->count; ++i)
+    {
+      if (name->name_records[i].name_id == code)
+       {
+         /* The offsets within have already been validated.  */
+         *record = name->name_records[i];
+         return name->data + record->offset;
+       }
+    }
+
+  return NULL;
+}
+
+/* Read the meta table from the given font FD, using the table
+   directory specified as SUBTABLE.  Perform validation on the offsets
+   in each metadata record.  Return NULL upon failure, else the meta
+   table.  */
+
+TEST_STATIC struct sfnt_meta_table *
+sfnt_read_meta_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_meta_table *meta;
+  size_t required, i, data_size, map_size, offset;
+  ssize_t rc;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_META);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out the minimum that has to be read.  */
+  required = SFNT_ENDOF (struct sfnt_meta_table,
+                        num_data_maps, uint32_t);
+
+  if (directory->length < required)
+    return NULL;
+
+  /* Allocate enough to hold it.  */
+  meta = xmalloc (sizeof *meta);
+
+  /* Read the header.  */
+  rc = read (fd, meta, required);
+  if (rc < required)
+    {
+      xfree (meta);
+      return NULL;
+    }
+
+  /* Swap what has been read so far.  */
+  sfnt_swap32 (&meta->version);
+  sfnt_swap32 (&meta->flags);
+  sfnt_swap32 (&meta->data_offset);
+  sfnt_swap32 (&meta->num_data_maps);
+
+  /* Make sure the meta is supported.  */
+  if (meta->version != 1)
+    {
+      xfree (meta);
+      return NULL;
+    }
+
+  /* Reallocate the table to hold sizeof *meta + meta->num_data_maps
+     times sizeof meta->data_maps + directory->length bytes.  This is
+     because it is ok for metadata to point into the data map itself,
+     so an unswapped copy of the whole meta contents must be
+     retained.  */
+
+  if (INT_MULTIPLY_WRAPV (sizeof *meta->data_maps, meta->num_data_maps,
+                         &map_size)
+      /* Do so while checking for overflow from bad sfnt files.  */
+      || INT_ADD_WRAPV (map_size, sizeof *meta, &data_size)
+      || INT_ADD_WRAPV (data_size, directory->length, &data_size))
+    {
+      xfree (meta);
+      return NULL;
+    }
+
+  /* Do the reallocation.  */
+  meta = xrealloc (meta, data_size);
+
+  /* Check that the remaining data is big enough to hold the data
+     maps.  */
+  if (directory->length - required < map_size)
+    {
+      xfree (meta);
+      return NULL;
+    }
+
+  /* Set pointers to data_maps and data.  */
+  meta->data_maps = (struct sfnt_meta_data_map *) (meta + 1);
+  meta->data = (unsigned char *) (meta->data_maps
+                                 + meta->num_data_maps);
+
+  /* Now, seek back.  Read the entire table into meta->data.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    {
+      xfree (meta);
+      return NULL;
+    }
+
+  rc = read (fd, meta->data, directory->length);
+  if (rc < directory->length)
+    {
+      xfree (meta);
+      return NULL;
+    }
+
+  /* Copy the data maps into meta->data_maps and swap them one by
+     one.  */
+  memcpy (meta->data_maps, meta->data + required,
+         map_size);
+
+  for (i = 0; i < meta->num_data_maps; ++i)
+    {
+      sfnt_swap32 (&meta->data_maps[i].tag);
+      sfnt_swap32 (&meta->data_maps[i].data_offset);
+      sfnt_swap32 (&meta->data_maps[i].data_length);
+
+      /* Verify the data offsets.  Overflow checking is particularly
+        important here.  */
+
+      if (INT_ADD_WRAPV (meta->data_maps[i].data_offset,
+                        meta->data_maps[i].data_length,
+                        &offset))
+       {
+         xfree (meta);
+         return NULL;
+       }
+
+      if (offset > directory->length)
+       {
+         xfree (meta);
+         return NULL;
+       }
+    }
+
+  /* All done.  */
+  return meta;
+}
+
+/* Return a pointer to the metadata corresponding to TAG under the
+   meta table META.  Return the start of the data and the metadata map
+   under *MAP upon success, and NULL otherwise.  */
+
+MAYBE_UNUSED TEST_STATIC char *
+sfnt_find_metadata (struct sfnt_meta_table *meta,
+                   enum sfnt_meta_data_tag tag,
+                   struct sfnt_meta_data_map *map)
+{
+  int i;
+
+  for (i = 0; i < meta->num_data_maps; ++i)
+    {
+      if (meta->data_maps[i].tag == tag)
+       {
+         *map = meta->data_maps[i];
+         return (char *) meta->data + map->data_offset;
+       }
+    }
+
+  return NULL;
+}
+
+
+
+/* TrueType collection format support.  */
+
+/* Read a TrueType collection header from the font file FD.
+   FD must currently at the start of the file.
+
+   Value is the header upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_ttc_header *
+sfnt_read_ttc_header (int fd)
+{
+  struct sfnt_ttc_header *ttc;
+  size_t size, i;
+  ssize_t rc;
+
+  /* First, allocate only as much as required.  */
+
+  ttc = xmalloc (sizeof *ttc);
+
+  /* Read the version 1.0 data.  */
+
+  size = SFNT_ENDOF (struct sfnt_ttc_header, num_fonts,
+                    uint32_t);
+  rc = read (fd, ttc, size);
+  if (rc < size)
+    {
+      xfree (ttc);
+      return NULL;
+    }
+
+  /* Now swap what was read.  */
+  sfnt_swap32 (&ttc->ttctag);
+  sfnt_swap32 (&ttc->version);
+  sfnt_swap32 (&ttc->num_fonts);
+
+  /* Verify that the tag is as expected.  */
+  if (ttc->ttctag != SFNT_TTC_TTCF)
+    {
+      xfree (ttc);
+      return NULL;
+    }
+
+  /* Now, read the variable length data.  Make sure to check for
+     overflow.  */
+
+  if (INT_MULTIPLY_WRAPV (ttc->num_fonts,
+                         sizeof *ttc->offset_table,
+                         &size))
+    {
+      xfree (ttc);
+      return NULL;
+    }
+
+  ttc = xrealloc (ttc, sizeof *ttc + size);
+  ttc->offset_table = (uint32_t *) (ttc + 1);
+  rc = read (fd, ttc->offset_table, size);
+  if (rc < size)
+    {
+      xfree (ttc);
+      return NULL;
+    }
+
+  /* Swap each of the offsets read.  */
+  for (i = 0; i < ttc->num_fonts; ++i)
+    sfnt_swap32 (&ttc->offset_table[i]);
+
+  /* Now, look at the version.  If it is earlier than 2.0, then
+     reading is finished.  */
+
+  if (ttc->version < 0x00020000)
+    return ttc;
+
+  /* If it is 2.0 or later, then continue to read ul_dsig_tag to
+     ul_dsig_offset.  */
+
+  size = (SFNT_ENDOF (struct sfnt_ttc_header, ul_dsig_offset,
+                     uint32_t)
+         - offsetof (struct sfnt_ttc_header, ul_dsig_tag));
+  rc = read (fd, &ttc->ul_dsig_offset, size);
+  if (rc < size)
+    {
+      xfree (ttc);
+      return NULL;
+    }
+
+  /* Swap what was read.  */
+  sfnt_swap32 (&ttc->ul_dsig_tag);
+  sfnt_swap32 (&ttc->ul_dsig_length);
+  sfnt_swap32 (&ttc->ul_dsig_offset);
+
+  /* All done.  */
+  return ttc;
+}
+
+
+
+/* TrueType hinting support.
+
+   If you do not read the code in this section in conjunction with
+   Apple's TrueType Reference Manual, you will get very confused!
+
+   TrueType fonts don't provide simple hinting meta data, unlike Type
+   2 or CFF fonts.
+
+   Instead, they come with a ``font program'', a bytecode program
+   which is executed upon loading the font, a ``control value
+   program'', executed upon font metrics changing, and then a ``glyph
+   program'' for each glyph, which is run to fit its glyph after
+   scaling.
+
+   The virtual machine which runs this bytecode is arranged as such:
+
+   Firstly, there is a set of registers known as the ``graphics
+   state''.  Each time the point size of a font changes, the ``control
+   value program'' is run to establish the default values of the
+   ``graphics state''.  Then, before each glyph program is run, the
+   ``graphics state'' is set back to the default values.
+
+   Secondly, there is an address space which contains all instructions
+   being run for the current program, which is addressed by the
+   interpreter through its program counter and also by the
+   instructions which push data on to the stack.
+
+   Thirdly, there is a single stack, from which most instructions take
+   their operands and store data.
+
+   Then, there is some memory set aside for each font, the ``storage
+   area'', which is addressed through the RS[] and WS[] instructions,
+   and a ``control value table'', which is the `cvt ' table of the
+   font.
+
+   And finally, there is a ``glyph zone'' which holds points from a
+   scaled glyph outline, and a ``twilight zone'', which holds points
+   used by the font program itself.  Both are addressed indirectly
+   through one of three ``zone pointer'' registers, and are accessible
+   only when a program is being run on behalf of a glyph.  */
+
+
+
+/* Functions for reading tables used by the TrueType interpreter.  */
+
+/* Read the cvt table (control value table) from the given font FD,
+   using the table directory specified as SUBTABLE.  Swap all values
+   in the control value table.  Return NULL upon failure, else the cvt
+   table.  */
+
+TEST_STATIC struct sfnt_cvt_table *
+sfnt_read_cvt_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  size_t required, i;
+  ssize_t rc;
+  struct sfnt_cvt_table *cvt;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_CVT );
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out the minimum amount that has to be read.  */
+  if (INT_ADD_WRAPV (sizeof *cvt, directory->length, &required))
+    return NULL;
+
+  /* Allocate enough for that much data.  */
+  cvt = xmalloc (required);
+
+  /* Now set cvt->num_elements as appropriate, and make cvt->values
+     point into the values.  */
+  cvt->num_elements = directory->length / 2;
+  cvt->values = (sfnt_fword *) (cvt + 1);
+
+  /* Read into cvt.  */
+  rc = read (fd, cvt->values, directory->length);
+  if (rc != directory->length)
+    {
+      xfree (cvt);
+      return NULL;
+    }
+
+  /* Swap each element in the control value table.  */
+  for (i = 0; i < cvt->num_elements; ++i)
+    sfnt_swap16 (&cvt->values[i]);
+
+  /* All done.  */
+  return cvt;
+}
+
+/* Read the fpgm table from the given font FD, using the table
+   directory specified as SUBTABLE.  Value is NULL upon failure, else
+   the fpgm table.  */
+
+TEST_STATIC struct sfnt_fpgm_table *
+sfnt_read_fpgm_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  size_t required;
+  ssize_t rc;
+  struct sfnt_fpgm_table *fpgm;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_FPGM);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out the minimum amount that has to be read.  */
+  if (INT_ADD_WRAPV (sizeof *fpgm, directory->length, &required))
+    return NULL;
+
+  /* Allocate enough for that much data.  */
+  fpgm = xmalloc (sizeof *fpgm + directory->length);
+
+  /* Now set fpgm->num_instructions as appropriate, and make
+     fpgm->instructions point to the right place.  */
+
+  fpgm->num_instructions = directory->length;
+  fpgm->instructions = (unsigned char *) (fpgm + 1);
+
+  /* Read into fpgm.  */
+  rc = read (fd, fpgm->instructions, directory->length);
+  if (rc != directory->length)
+    {
+      xfree (fpgm);
+      return NULL;
+    }
+
+  /* All done.  */
+  return fpgm;
+}
+
+/* Read the prep table from the given font FD, using the table
+   directory specified as SUBTABLE.  Value is NULL upon failure, else
+   the prep table.  */
+
+TEST_STATIC struct sfnt_prep_table *
+sfnt_read_prep_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  size_t required;
+  ssize_t rc;
+  struct sfnt_prep_table *prep;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_PREP);
+
+  if (!directory)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Figure out the minimum amount that has to be read.  */
+  if (INT_ADD_WRAPV (sizeof *prep, directory->length, &required))
+    return NULL;
+
+  /* Allocate enough for that much data.  */
+  prep = xmalloc (sizeof *prep + directory->length);
+
+  /* Now set prep->num_instructions as appropriate, and make
+     prep->instructions point to the right place.  */
+
+  prep->num_instructions = directory->length;
+  prep->instructions = (unsigned char *) (prep + 1);
+
+  /* Read into prep.  */
+  rc = read (fd, prep->instructions, directory->length);
+  if (rc != directory->length)
+    {
+      xfree (prep);
+      return NULL;
+    }
+
+  /* All done.  */
+  return prep;
+}
+
+
+
+/* Interpreter execution environment.  */
+
+/* Divide the specified two 26.6 fixed point numbers X and Y.
+   Return the result.  */
+
+static sfnt_f26dot6
+sfnt_div_f26dot6 (sfnt_f26dot6 x, sfnt_f26dot6 y)
+{
+#ifdef INT64_MAX
+  int64_t result;
+
+  result = ((int64_t) x * 64) / y;
+
+  return result;
+#else
+  int sign;
+  unsigned int a, b;
+
+  sign = 1;
+
+  if (x < 0)
+    sign = -sign;
+
+  if (y < 0)
+    sign = -sign;
+
+  a = abs (x);
+  b = abs (y);
+
+  return sfnt_multiply_divide (a, 64, b) * sign;
+#endif
+}
+
+/* Multiply the specified two 26.6 fixed point numbers A and B.
+   Return the result, or an undefined value upon overflow.  */
+
+static sfnt_f26dot6
+sfnt_mul_f26dot6 (sfnt_f26dot6 a, sfnt_f26dot6 b)
+{
+#ifdef INT64_MAX
+  int64_t product;
+
+  product = (int64_t) a * (int64_t) b;
+
+  /* This can be done quickly with int64_t.  */
+  return product / (int64_t) 64;
+#else
+  int sign;
+
+  sign = 1;
+
+  if (a < 0)
+    sign = -sign;
+
+  if (b < 0)
+    sign = -sign;
+
+  return sfnt_multiply_divide (abs (a), abs (b),
+                              64) * sign;
+#endif
+}
+
+/* Multiply the specified 2.14 number with another signed 32 bit
+   number.  Return the result as a signed 32 bit number.  */
+
+static int32_t
+sfnt_mul_f2dot14 (sfnt_f2dot14 a, int32_t b)
+{
+#ifdef INT64_MAX
+  int64_t product;
+
+  product = (int64_t) a * (int64_t) b;
+
+  return product / (int64_t) 16384;
+#else
+  int sign;
+
+  sign = 1;
+
+  if (a < 0)
+    sign = -sign;
+
+  if (b < 0)
+    sign = -sign;
+
+  return sfnt_multiply_divide (abs (a), abs (b),
+                              16384) * sign;
+#endif
+}
+
+/* Multiply the specified 26.6 fixed point number X by the specified
+   16.16 fixed point number Y with symmetric rounding.
+
+   The 26.6 fixed point number must fit inside -32768 to 32767.ffff.
+   Value is otherwise undefined.  */
+
+static sfnt_f26dot6
+sfnt_mul_f26dot6_fixed (sfnt_f26dot6 x, sfnt_fixed y)
+{
+#ifdef INT64_MAX
+  uint64_t product;
+  int sign;
+
+  sign = 1;
+
+  if (x < 0)
+    {
+      x = -x;
+      sign = -sign;
+    }
+
+  if (y < 0)
+    {
+      y = -y;
+      sign = -sign;
+    }
+
+  product = (uint64_t) y * (uint64_t) x;
+
+  /* This can be done quickly with int64_t.  */
+  return ((int64_t) (product + 32676) / (int64_t) 65536) * sign;
+#else
+  struct sfnt_large_integer temp;
+  int sign;
+
+  sign = 1;
+
+  if (x < 0)
+    sign = -sign;
+
+  if (y < 0)
+    sign = -sign;
+
+  sfnt_multiply_divide_1 (abs (x), abs (y), &temp);
+  sfnt_large_integer_add (&temp, 32676);
+  return sfnt_multiply_divide_2 (&temp, 65536) * sign;
+#endif
+}
+
+/* Return the floor of the specified 26.6 fixed point value X.  */
+
+static sfnt_f26dot6
+sfnt_floor_f26dot6 (sfnt_f26dot6 x)
+{
+  return x & 037777777700;
+}
+
+/* Return the ceiling of the specified 26.6 fixed point value X.  */
+
+static sfnt_f26dot6
+sfnt_ceil_f26dot6 (sfnt_f26dot6 x)
+{
+  return (x + 077) & ~077;
+}
+
+/* Return the 26.6 fixed point value X rounded to the nearest integer
+   value.  */
+
+static sfnt_f26dot6
+sfnt_round_f26dot6 (sfnt_f26dot6 x)
+{
+  /* Add 0.5.  */
+  x += 040;
+
+  /* Remove the fractional.  */
+  return x & ~077;
+}
+
+/* Needed by sfnt_init_graphics_state.  */
+
+static void sfnt_validate_gs (struct sfnt_graphics_state *);
+
+/* Set up default values for the interpreter graphics state.  Return
+   them in STATE.  */
+
+static void
+sfnt_init_graphics_state (struct sfnt_graphics_state *state)
+{
+  state->auto_flip = true;
+  state->cvt_cut_in = 0104;
+  state->delta_base = 9;
+  state->delta_shift = 3;
+  state->dual_projection_vector.x = 040000; /* 1.0 */
+  state->dual_projection_vector.y = 0;
+  state->freedom_vector.x = 040000; /* 1.0 */
+  state->freedom_vector.y = 0;
+  state->instruct_control = 0;
+  state->loop = 1;
+  state->minimum_distance = 0100;
+  state->projection_vector.x = 040000; /* 1.0 */
+  state->projection_vector.y = 0;
+  state->round_state = 1;
+  state->rp0 = 0;
+  state->rp1 = 0;
+  state->rp2 = 0;
+  state->scan_control = 0;
+  state->sw_cut_in = 0;
+  state->single_width_value = 0;
+  state->zp0 = 1;
+  state->zp1 = 1;
+  state->zp2 = 1;
+
+  /* Validate the graphics state.  */
+  sfnt_validate_gs (state);
+}
+
+/* Set up an interpreter to be used with a font.  Use the resource
+   limits specified in the MAXP table, the values specified in the
+   CVT, HEAD and FVAR tables, the pixel size PIXEL_SIZE, and the point
+   size POINT_SIZE.  CVT may be NULL, in which case the interpreter
+   will not have access to a control value table.
+
+   POINT_SIZE should be PIXEL_SIZE, converted to 1/72ths of an inch.
+
+   Value is the interpreter, with all state initialized to default
+   values, or NULL upon failure.  */
+
+TEST_STATIC struct sfnt_interpreter *
+sfnt_make_interpreter (struct sfnt_maxp_table *maxp,
+                      struct sfnt_cvt_table *cvt,
+                      struct sfnt_head_table *head,
+                      struct sfnt_fvar_table *fvar,
+                      int pixel_size, int point_size)
+{
+  size_t size, temp, i, storage_size, pad;
+  struct sfnt_interpreter *interpreter;
+
+  /* Detect CFF maxp tables.  */
+  if (maxp->version != 0x00010000)
+    return NULL;
+
+  /* Use the contents of the MAXP table to determine the size of the
+     interpreter structure.  */
+  size = sizeof (*interpreter);
+
+  /* Add program stack.  */
+  if (INT_ADD_WRAPV ((maxp->max_stack_elements
+                     * sizeof *interpreter->stack),
+                    size, &size))
+    return NULL;
+
+  /* Add twilight zone.  */
+
+  if (INT_ADD_WRAPV ((maxp->max_twilight_points
+                     * sizeof *interpreter->twilight_x),
+                    size, &size))
+    return NULL;
+
+  if (INT_ADD_WRAPV ((maxp->max_twilight_points
+                     * sizeof *interpreter->twilight_y),
+                    size, &size))
+    return NULL;
+
+  if (INT_ADD_WRAPV ((maxp->max_twilight_points
+                     * sizeof *interpreter->twilight_y),
+                    size, &size))
+    return NULL;
+
+  if (INT_ADD_WRAPV ((maxp->max_twilight_points
+                     * sizeof *interpreter->twilight_y),
+                    size, &size))
+    return NULL;
+
+  /* Add the storage area.  */
+  storage_size = maxp->max_storage * sizeof *interpreter->storage;
+  if (INT_ADD_WRAPV (storage_size, size, &size))
+    return NULL;
+
+  /* Add padding for the storage area.  */
+  pad = alignof (struct sfnt_interpreter_definition);
+  pad -= size & (pad - 1);
+  if (INT_ADD_WRAPV (pad, size, &size))
+    return NULL;
+
+  /* Add function and instruction definitions.  */
+  if (INT_ADD_WRAPV ((((int) maxp->max_instruction_defs
+                      + maxp->max_function_defs)
+                     * sizeof *interpreter->function_defs),
+                    size, &size))
+    return NULL;
+
+  /* Add control value table.  */
+
+  if (cvt)
+    {
+      if (INT_MULTIPLY_WRAPV (cvt->num_elements,
+                             sizeof *interpreter->cvt,
+                             &temp)
+         || INT_ADD_WRAPV (temp, size, &size))
+       return NULL;
+    }
+
+  /* Allocate the interpreter.  */
+  interpreter = xmalloc (size);
+
+#ifdef TEST
+  interpreter->run_hook = NULL;
+  interpreter->push_hook = NULL;
+  interpreter->pop_hook = NULL;
+#endif /* TEST */
+
+  /* Fill in pointers and default values.  */
+  interpreter->max_stack_elements = maxp->max_stack_elements;
+  interpreter->num_instructions = 0;
+  interpreter->IP = 0;
+  interpreter->storage_size = maxp->max_storage;
+  interpreter->function_defs_size = maxp->max_function_defs;
+  interpreter->instruction_defs_size = maxp->max_instruction_defs;
+  interpreter->twilight_zone_size = maxp->max_twilight_points;
+  interpreter->scale = 0; /* This should be set later.  */
+
+  interpreter->stack = (uint32_t *) (interpreter + 1);
+  interpreter->SP = interpreter->stack;
+  interpreter->instructions = NULL;
+  interpreter->twilight_x
+    = (sfnt_f26dot6 *) (interpreter->stack
+                       + maxp->max_stack_elements);
+  interpreter->twilight_y = (interpreter->twilight_x
+                            + maxp->max_twilight_points);
+  interpreter->twilight_original_x = (interpreter->twilight_y
+                                     + maxp->max_twilight_points);
+  interpreter->twilight_original_y
+    = (interpreter->twilight_original_x
+       + maxp->max_twilight_points);
+  interpreter->glyph_zone = NULL;
+  interpreter->advance_width = 0;
+  interpreter->storage
+    = (uint32_t *) (interpreter->twilight_original_y
+                   + maxp->max_twilight_points);
+  interpreter->function_defs
+    = (struct sfnt_interpreter_definition *) (interpreter->storage
+                                             + maxp->max_storage);
+  interpreter->function_defs
+    = ((struct sfnt_interpreter_definition *)
+       ((unsigned char *) interpreter->function_defs + pad));
+  interpreter->instruction_defs = (interpreter->function_defs
+                                  + maxp->max_function_defs);
+  interpreter->cvt
+    = (sfnt_f26dot6 *) (interpreter->instruction_defs
+                       + maxp->max_instruction_defs);
+
+  if (cvt)
+    interpreter->cvt_size = cvt->num_elements;
+  else
+    interpreter->cvt_size = 0;
+
+  /* Now compute the scale.  Then, scale up the control value table
+     values.  */
+  interpreter->scale
+    = sfnt_div_fixed (pixel_size, head->units_per_em);
+
+  /* Set the PPEM.  */
+  interpreter->ppem = pixel_size;
+  interpreter->point_size = point_size;
+
+  /* Zero out the interpreter state from the stack to the end of the
+     instruction definitions.  */
+  memset (interpreter->stack, 0, size - sizeof *interpreter);
+
+  /* Initialize the interpreter graphics state.  */
+  sfnt_init_graphics_state (&interpreter->state);
+
+  /* Load the control value table.  */
+  for (i = 0; i < interpreter->cvt_size; ++i)
+    interpreter->cvt[i]
+      = sfnt_mul_f26dot6_fixed (cvt->values[i] * 64,
+                               interpreter->scale);
+
+  /* Fill in the default values for phase, period and threshold.  */
+  interpreter->period = 64;
+  interpreter->phase = 0;
+  interpreter->threshold = 0;
+
+  /* Fill in the current call depth.  */
+  interpreter->call_depth = 0;
+
+  /* Clear variation axes.  They will be set upon a call to
+     `sfnt_vary_interpreter'.  */
+  interpreter->n_axis = 0;
+  interpreter->norm_coords = NULL;
+
+  /* Set n_axis now if a fvar table was provided.  This way, GXAXIS
+     pushes the correct number of values even if no blend is
+     provided.  */
+
+  if (fvar)
+    interpreter->n_axis = fvar->axis_count;
+
+  /* Return the interpreter.  */
+  return interpreter;
+}
+
+/* These enums are used to determine why the interpreter is being
+   run.  They have the following meanings:
+
+     - The interpreter is being run to interpret a font program.
+     - The interpreter is being run to interpret the control
+       value program.
+     - The interpreter is being run to fit a glyph.
+     - The interpreter is being run as part of an automated test.  */
+
+enum sfnt_interpreter_run_context
+  {
+    SFNT_RUN_CONTEXT_FONT_PROGRAM,
+    SFNT_RUN_CONTEXT_CONTROL_VALUE_PROGRAM,
+    SFNT_RUN_CONTEXT_GLYPH_PROGRAM,
+#ifdef TEST
+    SFNT_RUN_CONTEXT_TEST,
+#endif
+  };
+
+/* Cancel execution of the program in INTERPRETER with the specified
+   error REASON and reset the loop counter to 1.
+
+   After this is called, it is probably okay to reuse INTERPRETER.
+   However, instructions must always be reloaded.  */
+
+_Noreturn static void
+sfnt_interpret_trap (struct sfnt_interpreter *interpreter,
+                    const char *reason)
+{
+  interpreter->trap_reason = reason;
+  interpreter->call_depth = 0;
+  interpreter->state.loop = 1;
+  longjmp (interpreter->trap, 1);
+}
+
+#define STACKSIZE()                            \
+  (interpreter->SP - interpreter->stack)
+
+#define TRAP(why)                              \
+  sfnt_interpret_trap (interpreter, (why))
+
+#define MOVE(a, b, n)                          \
+  memmove (a, b, (n) * sizeof (uint32_t))
+
+#define CHECK_STACK_ELEMENTS(n)                        \
+  {                                            \
+    if ((interpreter->SP                       \
+        - interpreter->stack) < n)             \
+      TRAP ("stack underflow");                        \
+  }
+
+#define CHECK_STACK_AVAILABLE(n)               \
+  {                                            \
+    char *stack_end;                           \
+                                               \
+    stack_end                                  \
+      = (char *) interpreter->twilight_x;      \
+    if (((char *) (interpreter->SP + (n))      \
+        > stack_end))                          \
+      TRAP ("stack overflow");                 \
+  }
+
+#define CHECK_PREP()                           \
+  if (!is_prep)                                        \
+    TRAP ("instruction executed not valid"     \
+         " outside control value program")     \
+
+#define sfnt_add(a, b)                         \
+  ((int) ((unsigned int) (a) + (unsigned int) (b)))
+
+#define sfnt_sub(a, b)                         \
+  ((int) ((unsigned int) (a) - (unsigned int) (b)))
+
+#define sfnt_mul(a, b)                         \
+  ((int) ((unsigned int) (a) * (unsigned int) (b)))
+
+
+
+/* Register, alu and logic instructions.  */
+
+#ifndef TEST
+
+#define POP()                                  \
+  (interpreter->SP == interpreter->stack       \
+   ? (TRAP ("stack underflow"), 0)             \
+   : (*(--interpreter->SP)))
+
+#define POP_UNCHECKED() (*(--interpreter->SP))
+
+#else
+
+#define POP()                                  \
+  (interpreter->SP == interpreter->stack       \
+   ? (TRAP ("stack underflow"), 0)             \
+   : ({uint32_t _value;                                \
+       _value = *(--interpreter->SP);          \
+       if (interpreter->pop_hook)              \
+        interpreter->pop_hook (interpreter,    \
+                               _value);        \
+       _value;}))
+
+#define POP_UNCHECKED()        POP ()
+
+#endif
+
+#define LOOK()                                 \
+  (interpreter->SP == interpreter->stack       \
+   ? (TRAP ("stack underflow"), 0)             \
+   : *(interpreter->SP - 1))
+
+#if !defined TEST || !0
+
+#define PUSH(value)                            \
+  {                                            \
+    if ((char *) (interpreter->SP + 1)         \
+       > (char *) interpreter->twilight_x)     \
+      TRAP ("stack overflow");                 \
+                                               \
+    *interpreter->SP = (value);                        \
+    interpreter->SP++;                         \
+  }
+
+#define PUSH_UNCHECKED(value)                  \
+  {                                            \
+    *interpreter->SP = (value);                        \
+    interpreter->SP++;                         \
+  }
+
+#else /* TEST && 0 */
+
+#define PUSH(value)                            \
+  {                                            \
+    if ((char *) (interpreter->SP + 1)         \
+       > (char *) interpreter->twilight_x)     \
+      TRAP ("stack overflow");                 \
+                                               \
+    if (interpreter->push_hook)                        \
+      interpreter->push_hook (interpreter,     \
+                             value);           \
+                                               \
+    *interpreter->SP = value;                  \
+    interpreter->SP++;                         \
+  }
+
+#define PUSH_UNCHECKED(value) PUSH (value)
+
+#endif /* TEST && 0 */
+
+#define PUSH2_UNCHECKED(high, low)             \
+  {                                            \
+    int16_t word;                              \
+                                               \
+    word = (((int8_t) high) << 8 | low);       \
+    PUSH_UNCHECKED (word);                     \
+  }                                            \
+
+#define SRP0()                                 \
+  {                                            \
+    uint32_t p;                                        \
+                                               \
+    p = POP ();                                        \
+    interpreter->state.rp0 = p;                        \
+  }
+
+#define SRP1()                                 \
+  {                                            \
+    uint32_t p;                                        \
+                                               \
+    p = POP ();                                        \
+    interpreter->state.rp1 = p;                        \
+  }
+
+#define SRP2()                                 \
+  {                                            \
+    uint32_t p;                                        \
+                                               \
+    p = POP ();                                        \
+    interpreter->state.rp2 = p;                        \
+  }
+
+#define SZP0()                                 \
+  {                                            \
+    uint32_t zone;                             \
+                                               \
+    zone = POP ();                             \
+                                               \
+    if (zone > 1)                              \
+      TRAP ("invalid zone");                   \
+                                               \
+    interpreter->state.zp0 = zone;             \
+  }
+
+#define SZP1()                                 \
+  {                                            \
+    uint32_t zone;                             \
+                                               \
+    zone = POP ();                             \
+                                               \
+    if (zone > 1)                              \
+      TRAP ("invalid zone");                   \
+                                               \
+    interpreter->state.zp1 = zone;             \
+  }
+
+#define SZP2()                                 \
+  {                                            \
+    uint32_t zone;                             \
+                                               \
+    zone = POP ();                             \
+                                               \
+    if (zone > 1)                              \
+      TRAP ("invalid zone");                   \
+                                               \
+    interpreter->state.zp2 = zone;             \
+  }
+
+#define SZPS()                                 \
+  {                                            \
+    uint32_t zone;                             \
+                                               \
+    zone = POP ();                             \
+                                               \
+    if (zone > 1)                              \
+      TRAP ("invalid zone");                   \
+                                               \
+    interpreter->state.zp0 = zone;             \
+    interpreter->state.zp1 = zone;             \
+    interpreter->state.zp2 = zone;             \
+  }
+
+#define SLOOP()                                        \
+  {                                            \
+    uint32_t loop;                             \
+                                               \
+    loop = POP ();                             \
+                                               \
+    if (!loop)                                 \
+      TRAP ("loop set to 0");                  \
+                                               \
+    interpreter->state.loop = loop;            \
+  }
+
+#define SMD()                                  \
+  {                                            \
+    sfnt_f26dot6 md;                           \
+                                               \
+    md = POP ();                               \
+                                               \
+    interpreter->state.minimum_distance = md;  \
+  }
+
+#define ELSE()                                 \
+  {                                            \
+    sfnt_interpret_else (interpreter);         \
+    goto skip_step;                            \
+  }
+
+#define JMPR()                                 \
+  {                                            \
+    int32_t offset;                            \
+                                               \
+    offset = POP ();                           \
+                                               \
+    if (interpreter->IP + offset < 0           \
+       || (interpreter->IP + offset            \
+           > interpreter->num_instructions))   \
+      TRAP ("JMPR out of bounds");             \
+                                               \
+    interpreter->IP += offset;                 \
+    goto skip_step;                            \
+  }
+
+#define SCVTCI()                               \
+  {                                            \
+    sfnt_f26dot6 cutin;                                \
+                                               \
+    cutin = POP ();                            \
+                                               \
+    interpreter->state.cvt_cut_in = cutin;     \
+  }
+
+#define SSWCI()                                        \
+  {                                            \
+    sfnt_f26dot6 cutin;                                \
+                                               \
+    cutin = POP ();                            \
+                                               \
+    interpreter->state.sw_cut_in = cutin;      \
+  }
+
+#define SSW()                                  \
+  {                                            \
+    int32_t single_width;                      \
+                                               \
+    single_width = POP ();                     \
+                                               \
+    interpreter->state.single_width_value      \
+      = (interpreter->scale * single_width     \
+        / 1024);                               \
+  }
+
+#define DUP()                                  \
+  {                                            \
+    uint32_t value;                            \
+                                               \
+    value = LOOK ();                           \
+    PUSH (value);                              \
+  }
+
+#define CLEAR()                                        \
+  {                                            \
+    interpreter->SP = interpreter->stack;      \
+  }
+
+#define SWAP()                                 \
+  {                                            \
+    uint32_t a, b;                             \
+                                               \
+    a = POP ();                                        \
+    b = POP ();                                        \
+                                               \
+    PUSH_UNCHECKED (a);                                \
+    PUSH_UNCHECKED (b);                                \
+  }
+
+#define DEPTH()                                        \
+  {                                            \
+    ptrdiff_t diff;                            \
+                                               \
+    diff = (interpreter->SP                    \
+           - interpreter->stack);              \
+    PUSH (diff);                               \
+  }
+
+#define CINDEX()                               \
+  {                                            \
+    int32_t index;                             \
+                                               \
+    index = POP ();                            \
+                                               \
+    if (index <= 0 || index > STACKSIZE ())    \
+      TRAP ("stack overflow");                 \
+                                               \
+    PUSH_UNCHECKED (*(interpreter->SP          \
+                     - index));                \
+  }
+
+#define MINDEX()                               \
+  {                                            \
+    int32_t index, what;                       \
+                                               \
+    index = POP ();                            \
+                                               \
+    if (index <= 0 || index > STACKSIZE ())    \
+      TRAP ("stack overflow");                 \
+                                               \
+    what = *(interpreter->SP - index);         \
+    MOVE (interpreter->SP - index,             \
+         interpreter->SP - index + 1,          \
+         index - 1);                           \
+    *(interpreter->SP - 1) = what;             \
+  }
+
+#define RAW()                                  \
+  {                                            \
+    if (why != SFNT_RUN_CONTEXT_GLYPH_PROGRAM) \
+      TRAP ("Read Advance Width without loaded"        \
+           " glyph");                          \
+    PUSH (interpreter->advance_width);         \
+  }
+
+#define CALL()                                 \
+  {                                            \
+    uint32_t id, i;                            \
+    struct sfnt_interpreter_definition *def;   \
+                                               \
+    id = POP ();                               \
+                                               \
+    for (i = 0;                                        \
+        i < interpreter->function_defs_size;   \
+        ++i)                                   \
+      {                                                \
+       def = &interpreter->function_defs[i];   \
+                                               \
+       if (!def->instructions)                 \
+         TRAP ("invalid function");            \
+                                               \
+       if (def->opcode == id)                  \
+         {                                     \
+           sfnt_interpret_call (def,           \
+                                interpreter,   \
+                                why);          \
+           goto next_instruction;              \
+         }                                     \
+      }                                                \
+                                               \
+    TRAP ("invalid function");                 \
+  }
+
+#define LOOPCALL()                             \
+  {                                            \
+    uint32_t id;                               \
+    int32_t n;                                 \
+    int i;                                     \
+    struct sfnt_interpreter_definition *def;   \
+                                               \
+    id = POP ();                               \
+    n = POP ();                                        \
+                                               \
+    if (n > 65535)                             \
+      TRAP ("invalid LOOPCALL count");         \
+                                               \
+    for (i = 0;                                        \
+        i < interpreter->function_defs_size;   \
+        ++i)                                   \
+      {                                                \
+       def = &interpreter->function_defs[i];   \
+                                               \
+       if (!def->instructions)                 \
+         TRAP ("invalid function");            \
+                                               \
+       if (def->opcode == id)                  \
+         goto loopcall_begin;                  \
+      }                                                \
+                                               \
+    TRAP ("invalid function");                 \
+                                               \
+  loopcall_begin:                              \
+    if (n-- <= 0)                              \
+      break;                                   \
+                                               \
+    sfnt_interpret_call (def, interpreter,     \
+                        why);                  \
+    goto loopcall_begin;                       \
+  }
+
+#define FDEF()                                 \
+  {                                            \
+    if (why == SFNT_RUN_CONTEXT_GLYPH_PROGRAM) \
+      TRAP ("FDEF inside glyph program");      \
+                                               \
+    sfnt_interpret_fdef (interpreter, POP ()); \
+    goto skip_step;                            \
+  }
+
+#define ENDF()                                 \
+  {                                            \
+    TRAP ("stray ENDF");                       \
+  }
+
+#define NPUSHB()                               \
+  {                                            \
+    int b, nbytes, IP;                         \
+    unsigned char *ip;                         \
+                                               \
+    if ((IP = interpreter->IP + 1)             \
+       >= interpreter->num_instructions)       \
+      TRAP ("Missing arg to NPUSHB");          \
+                                               \
+    nbytes                                     \
+      = interpreter->instructions[IP];         \
+                                               \
+    if (IP + 1 + nbytes                                \
+       > interpreter->num_instructions)        \
+      TRAP ("args to NPUSHB lie outside IS");  \
+                                               \
+    CHECK_STACK_AVAILABLE (nbytes);            \
+    ip = interpreter->instructions;            \
+    for (b = IP + 1; b < IP + 1 + nbytes; ++b) \
+      PUSH_UNCHECKED (ip[b]);                  \
+                                               \
+    interpreter->IP += nbytes + 1;             \
+  }
+
+#define NPUSHW()                               \
+  {                                            \
+    int b, nbytes, IP;                         \
+    unsigned char *ip;                         \
+                                               \
+    if ((IP = interpreter->IP + 1)             \
+       >= interpreter->num_instructions)       \
+      TRAP ("Missing arg to NPUSHW");          \
+                                               \
+    nbytes                                     \
+      = interpreter->instructions[IP] * 2;     \
+                                               \
+    if (IP + 1 + nbytes                                \
+       > interpreter->num_instructions)        \
+      TRAP ("args to NPUSHW lie outside IS");  \
+                                               \
+    CHECK_STACK_AVAILABLE (nbytes / 2);                \
+    ip = interpreter->instructions;            \
+    for (b = IP + 1; b < IP + 1 + nbytes;      \
+        b += 2)                                \
+      PUSH2_UNCHECKED (ip[b], ip[b + 1]);      \
+                                               \
+    interpreter->IP += nbytes + 1;             \
+  }
+
+#define WS()                                   \
+  {                                            \
+    uint32_t address, value;                   \
+                                               \
+    value = POP ();                            \
+    address = POP ();                          \
+                                               \
+    if (address >= interpreter->storage_size)  \
+      TRAP ("invalid WS");                     \
+                                               \
+    interpreter->storage[address] = value;     \
+  }
+
+#define RS()                                   \
+  {                                            \
+    uint32_t address, value;                   \
+                                               \
+    address = POP ();                          \
+                                               \
+    if (address >= interpreter->storage_size)  \
+      TRAP ("invalid RS");                     \
+                                               \
+    value = interpreter->storage[address];     \
+    PUSH_UNCHECKED (value);                    \
+  }
+
+#define WCVTP()                                        \
+  {                                            \
+    sfnt_f26dot6 value;                                \
+    uint32_t location;                         \
+                                               \
+    value = POP ();                            \
+    location = POP ();                         \
+                                               \
+    if (location >= interpreter->cvt_size)     \
+      TRAP ("WCVTP out of bounds");            \
+                                               \
+    interpreter->cvt[location] = value;                \
+  }
+
+#define RCVT()                                 \
+  {                                            \
+    sfnt_f26dot6 value;                                \
+    uint32_t location;                         \
+                                               \
+    location = POP ();                         \
+                                               \
+    if (location >= interpreter->cvt_size)     \
+      TRAP ("out of bounds RCVT");             \
+                                               \
+    value = interpreter->cvt[location];                \
+    PUSH_UNCHECKED (value);                    \
+  }
+
+#define MPPEM()                                        \
+  {                                            \
+    PUSH (interpreter->ppem);                  \
+  }
+
+#define MPS()                                  \
+  {                                            \
+    PUSH (interpreter->point_size);            \
+  }
+
+#define FLIPON()                               \
+  {                                            \
+    interpreter->state.auto_flip = true;       \
+  }
+
+#define FLIPOFF()                              \
+  {                                            \
+    interpreter->state.auto_flip = false;      \
+  }
+
+#define DEBUG()                                        \
+  {                                            \
+    POP (); /* Value is ignored.  */           \
+  }
+
+#define LT()                                   \
+  {                                            \
+    int32_t e1, e2;                            \
+                                               \
+    e2 = POP ();                               \
+    e1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 < e2 ? 1 : 0);          \
+  }
+
+#define LTEQ()                                 \
+  {                                            \
+    int32_t e1, e2;                            \
+                                               \
+    e2 = POP ();                               \
+    e1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 <= e2 ? 1 : 0);         \
+  }
+
+#define GT()                                   \
+  {                                            \
+    int32_t e1, e2;                            \
+                                               \
+    e2 = POP ();                               \
+    e1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 > e2 ? 1 : 0);          \
+  }
+
+#define GTEQ()                                 \
+  {                                            \
+    int32_t e1, e2;                            \
+                                               \
+    e2 = POP ();                               \
+    e1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 >= e2 ? 1 : 0);         \
+  }
+
+#define EQ()                                   \
+  {                                            \
+    uint32_t e1, e2;                           \
+                                               \
+    e1 = POP ();                               \
+    e2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 == e2 ? 1 : 0);         \
+  }
+
+#define NEQ()                                  \
+  {                                            \
+    uint32_t e1, e2;                           \
+                                               \
+    e1 = POP ();                               \
+    e2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 != e2 ? 1 : 0);         \
+  }
+
+#define ODD()                                  \
+  {                                            \
+    sfnt_f26dot6 e1, result;                   \
+                                               \
+    e1 = POP ();                               \
+    result = abs (e1);                         \
+                                               \
+    result                                     \
+      = interpreter->state.round (result,      \
+                                 interpreter); \
+    PUSH_UNCHECKED (((result & 127)            \
+                    == 64) ? 1 : 0);           \
+  }
+
+#define EVEN()                                 \
+  {                                            \
+    sfnt_f26dot6 e1, result;                   \
+    uint32_t value;                            \
+                                               \
+    e1 = POP ();                               \
+    result = abs (e1);                         \
+                                               \
+    result                                     \
+      = interpreter->state.round (result,      \
+                                 interpreter); \
+    value = ((result & 127) == 64) ? 0 : 1;    \
+    PUSH_UNCHECKED (value);                    \
+  }
+
+#define IF()                                   \
+  {                                            \
+    uint32_t condition;                                \
+                                               \
+    condition = POP ();                                \
+    sfnt_interpret_if (interpreter, condition);        \
+    goto skip_step;                            \
+  }
+
+#define EIF()                                  \
+  {                                            \
+                                               \
+  }
+
+#define AND()                                  \
+  {                                            \
+    uint32_t e1, e2;                           \
+                                               \
+    e1 = POP ();                               \
+    e2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 && e2 ? 1 : 0);         \
+  }
+
+#define OR()                                   \
+  {                                            \
+    uint32_t e1, e2;                           \
+                                               \
+    e1 = POP ();                               \
+    e2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (e1 || e2 ? 1 : 0);         \
+  }
+
+#define NOT()                                  \
+  {                                            \
+    uint32_t e1;                               \
+                                               \
+    e1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (!e1 ? 1 : 0);              \
+  }
+
+#define SDB()                                  \
+  {                                            \
+    uint32_t base;                             \
+                                               \
+    base = POP ();                             \
+                                               \
+    interpreter->state.delta_base = base;      \
+  }
+
+#define SDS()                                  \
+  {                                            \
+    uint32_t shift;                            \
+                                               \
+    shift = POP ();                            \
+                                               \
+    if (shift > 6)                             \
+      TRAP ("invalid delta shift");            \
+                                               \
+    interpreter->state.delta_shift = shift;    \
+  }
+
+#define ADD()                                  \
+  {                                            \
+    sfnt_f26dot6 n1, n2;                       \
+                                               \
+    n1 = POP ();                               \
+    n2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (sfnt_add (n1, n2));                \
+  }
+
+#define SUB()                                  \
+  {                                            \
+    sfnt_f26dot6 n2, n1;                       \
+                                               \
+    n2 = POP ();                               \
+    n1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (sfnt_sub (n1, n2));                \
+  }
+
+#define DIV()                                  \
+  {                                            \
+    sfnt_f26dot6 n2, n1;                       \
+                                               \
+    n2 = POP ();                               \
+    n1 = POP ();                               \
+                                               \
+    if (!n2)                                   \
+      TRAP ("DIV by 0");                       \
+                                               \
+    PUSH_UNCHECKED (sfnt_div_f26dot6 (n1, n2));        \
+  }
+
+#define MUL()                                  \
+  {                                            \
+    sfnt_f26dot6 n2, n1;                       \
+                                               \
+    n2 = POP ();                               \
+    n1 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (sfnt_mul_f26dot6 (n2, n1));        \
+  }
+
+#define ABS()                                  \
+  {                                            \
+    sfnt_f26dot6 n;                            \
+                                               \
+    n = POP ();                                        \
+                                               \
+    if (n == INT32_MIN)                                \
+      PUSH_UNCHECKED (0)                       \
+    else                                       \
+      PUSH_UNCHECKED (n < 0 ? -n : n)          \
+  }
+
+#define NEG()                                  \
+  {                                            \
+    sfnt_f26dot6 n;                            \
+                                               \
+    n = POP ();                                        \
+                                               \
+    if (n == INT32_MIN)                                \
+      PUSH_UNCHECKED (0)                       \
+    else                                       \
+      PUSH_UNCHECKED (-n)                      \
+  }
+
+#define FLOOR()                                        \
+  {                                            \
+    sfnt_f26dot6 n;                            \
+                                               \
+    n = POP ();                                        \
+    PUSH_UNCHECKED (sfnt_floor_f26dot6 (n));   \
+  }
+
+#define CEILING()                              \
+  {                                            \
+    sfnt_f26dot6 n;                            \
+                                               \
+    n = POP ();                                        \
+    PUSH_UNCHECKED (sfnt_ceil_f26dot6 (n));    \
+  }
+
+#define WCVTF()                                        \
+  {                                            \
+    int32_t value;                             \
+    uint32_t location;                         \
+                                               \
+    value = POP ();                            \
+    location = POP ();                         \
+                                               \
+    if (location >= interpreter->cvt_size)     \
+      TRAP ("WCVTF out of bounds");            \
+                                               \
+    interpreter->cvt[location]                 \
+      = (interpreter->scale * value            \
+        / 1024);                               \
+  }
+
+#define JROT()                                 \
+  {                                            \
+    uint32_t e;                                        \
+    int32_t offset;                            \
+                                               \
+    e = POP ();                                        \
+    offset = POP ();                           \
+                                               \
+    if (!e)                                    \
+      break;                                   \
+                                               \
+    if (interpreter->IP + offset < 0           \
+       || (interpreter->IP + offset            \
+           > interpreter->num_instructions))   \
+      TRAP ("JMPR out of bounds");             \
+                                               \
+    interpreter->IP += offset;                 \
+    goto skip_step;                            \
+  }
+
+#define JROF()                                 \
+  {                                            \
+    uint32_t e;                                        \
+    int32_t offset;                            \
+                                               \
+    e = POP ();                                        \
+    offset = POP ();                           \
+                                               \
+    if (e)                                     \
+      break;                                   \
+                                               \
+    if (interpreter->IP + offset < 0           \
+       || (interpreter->IP + offset            \
+           > interpreter->num_instructions))   \
+      TRAP ("JMPR out of bounds");             \
+                                               \
+    interpreter->IP += offset;                 \
+    goto skip_step;                            \
+  }
+
+#define ILLEGAL_INSTRUCTION()                  \
+  {                                            \
+    TRAP ("MS reserved illegal instruction");  \
+  }
+
+#define SCANCTRL()                             \
+  {                                            \
+    uint32_t value;                            \
+                                               \
+    value = POP ();                            \
+    interpreter->state.scan_control = value;   \
+  }
+
+/* Selector bit 8 is undocumented, but present in the Macintosh
+   rasterizer.  02000 is returned if there is a variation axis in
+   use.  */
+
+#define GETINFO()                              \
+  {                                            \
+    uint32_t selector, k;                      \
+                                               \
+    selector = POP ();                         \
+                                               \
+    k = 0;                                     \
+                                               \
+    if (selector & 1)                          \
+      k |= 02;                                 \
+                                               \
+    if (selector & 8                           \
+       && interpreter->norm_coords)            \
+      k |= 02000;                              \
+                                               \
+    PUSH_UNCHECKED (k);                                \
+  }
+
+#define IDEF()                                 \
+  {                                            \
+    if (why == SFNT_RUN_CONTEXT_GLYPH_PROGRAM) \
+      TRAP ("IDEF inside glyph program");      \
+                                               \
+    sfnt_interpret_idef (interpreter, POP ()); \
+    goto skip_step;                            \
+  }
+
+#define ROLL()                                 \
+  {                                            \
+    uint32_t a, b, c;                          \
+                                               \
+    CHECK_STACK_ELEMENTS (3);                  \
+                                               \
+    a = POP_UNCHECKED ();                      \
+    b = POP_UNCHECKED ();                      \
+    c = POP_UNCHECKED ();                      \
+                                               \
+    PUSH_UNCHECKED (b);                                \
+    PUSH_UNCHECKED (a);                                \
+    PUSH_UNCHECKED (c);                                \
+  }
+
+#define _MAX()                                 \
+  {                                            \
+    int32_t e1, e2;                            \
+                                               \
+    e1 = POP ();                               \
+    e2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (MAX (e1, e2));             \
+  }
+
+#define _MIN()                                 \
+  {                                            \
+    int32_t e1, e2;                            \
+                                               \
+    e1 = POP ();                               \
+    e2 = POP ();                               \
+                                               \
+    PUSH_UNCHECKED (MIN (e1, e2));             \
+  }
+
+#define SCANTYPE()                             \
+  {                                            \
+    POP ();                                    \
+  }
+
+#define INSTCTRL()                             \
+  {                                            \
+    uint32_t s, v;                             \
+                                               \
+    CHECK_PREP ();                             \
+    s = POP ();                                        \
+    v = POP ();                                        \
+                                               \
+    if (!s || s > 2)                           \
+      break;                                   \
+                                               \
+    interpreter->state.instruct_control                \
+      &= ~(1 << s);                            \
+                                               \
+    if (v)                                     \
+      interpreter->state.instruct_control      \
+       |= (1 << s);                            \
+  }
+
+/* GXAXIS is undocumented.  It seems to return each axis in shortFrac
+   format.  */
+
+#define GXAXIS()                               \
+  {                                            \
+    uint32_t v;                                        \
+    int i, naxis;                              \
+                                               \
+    naxis = interpreter->n_axis;               \
+    CHECK_STACK_AVAILABLE (naxis);             \
+                                               \
+    for (i = 0; i < naxis; ++i)                        \
+      {                                                \
+       if (interpreter->norm_coords)           \
+         v = interpreter->norm_coords[i] / 4;  \
+       else                                    \
+         v = 0;                                \
+                                               \
+       PUSH_UNCHECKED (v);                     \
+      }                                                \
+  }
+
+#define PUSHB()                                        \
+  {                                            \
+    int b, nbytes, IP;                         \
+    unsigned char *ip;                         \
+                                               \
+    IP = interpreter->IP;                      \
+    nbytes = opcode - 0xb0 + 1;                        \
+                                               \
+    if (IP + nbytes + 1                                \
+       > interpreter->num_instructions)        \
+      TRAP ("args to PUSHB lie outside IS");   \
+                                               \
+    CHECK_STACK_AVAILABLE (nbytes);            \
+    ip = interpreter->instructions;            \
+    for (b = IP + 1; b < IP + nbytes + 1; ++b) \
+      PUSH_UNCHECKED (ip[b]);                  \
+                                               \
+    interpreter->IP += nbytes;                 \
+  }
+
+#define PUSHW()                                        \
+  {                                            \
+    int b, nbytes, IP;                         \
+    unsigned char *ip;                         \
+                                               \
+    IP = interpreter->IP;                      \
+    nbytes = (opcode - 0xb8 + 1) * 2;          \
+                                               \
+    if (IP + 1 + nbytes                                \
+       > interpreter->num_instructions)        \
+      TRAP ("args to PUSHW lie outside IS");   \
+                                               \
+    CHECK_STACK_AVAILABLE (nbytes / 2);                \
+    ip = interpreter->instructions;            \
+    for (b = IP + 1; b < IP + nbytes + 1;      \
+        b += 2)                                \
+      PUSH2_UNCHECKED (ip[b], ip[b + 1]);      \
+                                               \
+    interpreter->IP += nbytes;                 \
+  }
+
+
+
+/* Rounding instructions.  */
+
+#define ROUND()                                        \
+  {                                            \
+    sfnt_f26dot6 n, result;                    \
+                                               \
+    n = POP ();                                        \
+    result = abs (n);                          \
+                                               \
+    result                                     \
+      = interpreter->state.round (result,      \
+                                 interpreter); \
+    PUSH_UNCHECKED (n < 0 ? -result : result); \
+  }
+
+#define NROUND()                               \
+  {                                            \
+    sfnt_f26dot6 n;                            \
+                                               \
+    n = POP ();                                        \
+    PUSH_UNCHECKED (n);                                \
+  }
+
+#define ROFF()                                 \
+  {                                            \
+    interpreter->state.round_state = 5;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define RUTG()                                 \
+  {                                            \
+    interpreter->state.round_state = 4;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define RDTG()                                 \
+  {                                            \
+    interpreter->state.round_state = 3;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define RTG()                                  \
+  {                                            \
+    interpreter->state.round_state = 1;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define RTHG()                                 \
+  {                                            \
+    interpreter->state.round_state = 0;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define RTDG()                                 \
+  {                                            \
+    interpreter->state.round_state = 2;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define SROUND()                               \
+  {                                            \
+    uint32_t operand;                          \
+                                               \
+    operand = POP ();                          \
+    sfnt_set_srounding_state (interpreter,     \
+                             operand,          \
+                             0x4000);          \
+    interpreter->state.round_state = 6;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define S45ROUND()                             \
+  {                                            \
+    uint32_t operand;                          \
+                                               \
+    operand = POP ();                          \
+    sfnt_set_srounding_state (interpreter,     \
+                             operand,          \
+                             0x5a82);          \
+    interpreter->state.round_state = 7;                \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+
+
+/* CVT and point delta exception instructions.
+
+   ``Exceptions'' can be placed directly inside the control value
+   table, as it is reloaded every time the point size changes.  */
+
+#define DELTAC1()                              \
+  {                                            \
+    uint32_t operand1, operand2, n;            \
+                                               \
+    n = POP ();                                        \
+                                               \
+  deltac1_start:                               \
+    if (!n)                                    \
+      break;                                   \
+                                               \
+    operand1 = POP ();                         \
+    operand2 = POP ();                         \
+    sfnt_deltac (1, interpreter, operand1,     \
+                operand2);                     \
+    n--;                                       \
+    goto deltac1_start;                                \
+  }
+
+#define DELTAC2()                              \
+  {                                            \
+    uint32_t operand1, operand2, n;            \
+                                               \
+    n = POP ();                                        \
+                                               \
+  deltac2_start:                               \
+    if (!n)                                    \
+      break;                                   \
+                                               \
+    operand1 = POP ();                         \
+    operand2 = POP ();                         \
+    sfnt_deltac (2, interpreter, operand1,     \
+                operand2);                     \
+    n--;                                       \
+    goto deltac2_start;                                \
+  }
+
+#define DELTAC3()                              \
+  {                                            \
+    uint32_t operand1, operand2, n;            \
+                                               \
+    n = POP ();                                        \
+                                               \
+  deltac3_start:                               \
+    if (!n)                                    \
+      break;                                   \
+                                               \
+    operand1 = POP ();                         \
+    operand2 = POP ();                         \
+    sfnt_deltac (3, interpreter, operand1,     \
+                operand2);                     \
+    n--;                                       \
+    goto deltac3_start;                                \
+  }
+
+#define DELTAP1()                              \
+  {                                            \
+    uint32_t n, argn, pn;                      \
+                                               \
+    n = POP ();                                        \
+                                               \
+  deltap1_start:                               \
+    if (!n)                                    \
+      break;                                   \
+                                               \
+    pn = POP ();                               \
+    argn = POP ();                             \
+    sfnt_deltap (1, interpreter, argn, pn);    \
+    n--;                                       \
+    goto deltap1_start;                                \
+  }
+
+#define DELTAP2()                              \
+  {                                            \
+    uint32_t n, argn, pn;                      \
+                                               \
+    n = POP ();                                        \
+                                               \
+  deltap2_start:                               \
+    if (!n)                                    \
+      break;                                   \
+                                               \
+    pn = POP ();                               \
+    argn = POP ();                             \
+    sfnt_deltap (2, interpreter, argn, pn);    \
+    n--;                                       \
+    goto deltap2_start;                                \
+  }
+
+#define DELTAP3()                              \
+  {                                            \
+    uint32_t n, argn, pn;                      \
+                                               \
+    n = POP ();                                        \
+                                               \
+  deltap3_start:                               \
+    if (!n)                                    \
+      break;                                   \
+                                               \
+    pn = POP ();                               \
+    argn = POP ();                             \
+    sfnt_deltap (3, interpreter, argn, pn);    \
+    n--;                                       \
+    goto deltap3_start;                                \
+  }
+
+
+
+/* Anachronistic angle instructions.  */
+
+#define AA()                                   \
+  {                                            \
+    POP ();                                    \
+  }
+
+#define SANGW()                                        \
+  {                                            \
+    POP ();                                    \
+  }
+
+
+
+/* Projection and freedom vector operations.  */
+
+#define PROJECT(x, y)                          \
+  sfnt_project_vector (interpreter, x, y)
+
+#define DUAL_PROJECT(x, y)                     \
+  sfnt_dual_project_vector (interpreter, x, y)
+
+#define SVTCAy()                               \
+  {                                            \
+    sfnt_set_freedom_vector (interpreter,      \
+                            0, 040000);        \
+    sfnt_set_projection_vector (interpreter,   \
+                               0, 040000);     \
+  }
+
+#define SVTCAx()                               \
+  {                                            \
+    sfnt_set_freedom_vector (interpreter,      \
+                            040000, 0);        \
+    sfnt_set_projection_vector (interpreter,   \
+                               040000, 0);     \
+  }
+
+#define SPvTCAy()                              \
+  {                                            \
+    sfnt_set_projection_vector (interpreter,   \
+                               0, 040000);     \
+  }
+
+#define SPvTCAx()                              \
+  {                                            \
+    sfnt_set_projection_vector (interpreter,   \
+                               040000, 0);     \
+  }
+
+#define SFvTCAy()                              \
+  {                                            \
+    sfnt_set_freedom_vector (interpreter,      \
+                            0, 040000);        \
+  }
+
+#define SFvTCAx()                              \
+  {                                            \
+    sfnt_set_freedom_vector (interpreter,      \
+                            040000, 0);        \
+  }
+
+#define SPVTL()                                        \
+  {                                            \
+    struct sfnt_unit_vector vector;            \
+    uint32_t p2, p1;                           \
+                                               \
+    p2 = POP ();                               \
+    p1 = POP ();                               \
+                                               \
+    sfnt_line_to_vector (interpreter,          \
+                        p2, p1, &vector,       \
+                        opcode == 0x07,        \
+                        false);                \
+                                               \
+    sfnt_save_projection_vector (interpreter,   \
+                                &vector,       \
+                                false);        \
+  }
+
+#define SFVTL()                                        \
+  {                                            \
+    struct sfnt_unit_vector vector;            \
+    uint32_t p2, p1;                           \
+                                               \
+    p2 = POP ();                               \
+    p1 = POP ();                               \
+                                               \
+    sfnt_line_to_vector (interpreter,          \
+                        p2, p1, &vector,       \
+                        opcode == 0x09,        \
+                        false);                \
+                                               \
+    sfnt_save_freedom_vector (interpreter,     \
+                             &vector);         \
+  }
+
+#define SPVFS()                                        \
+  {                                            \
+    uint32_t y, x;                             \
+                                               \
+    y = POP ();                                        \
+    x = POP ();                                        \
+                                               \
+    sfnt_set_projection_vector (interpreter, x,        \
+                               y);             \
+  }
+
+#define SFVFS()                                        \
+  {                                            \
+    uint16_t y, x;                             \
+                                               \
+    y = POP ();                                        \
+    x = POP ();                                        \
+                                               \
+    sfnt_set_freedom_vector (interpreter, x,   \
+                            y);                \
+  }
+
+#define GPV()                                  \
+  {                                            \
+    struct sfnt_unit_vector vector;            \
+                                               \
+    vector                                     \
+      = interpreter->state.projection_vector;  \
+                                               \
+    PUSH ((uint16_t) vector.x);                        \
+    PUSH ((uint16_t) vector.y);                        \
+  }
+
+#define GFV()                                  \
+  {                                            \
+    struct sfnt_unit_vector vector;            \
+                                               \
+    vector                                     \
+      = interpreter->state.freedom_vector;     \
+                                               \
+    PUSH ((uint16_t) vector.x);                        \
+    PUSH ((uint16_t) vector.y);                        \
+  }
+
+#define SFVTPV()                               \
+  {                                            \
+    interpreter->state.freedom_vector          \
+      = interpreter->state.projection_vector;  \
+                                               \
+    sfnt_validate_gs (&interpreter->state);    \
+  }
+
+#define ISECT()                                        \
+  {                                            \
+    uint32_t a0, a1, b0, b1, p;                        \
+                                               \
+    CHECK_STACK_ELEMENTS (5);                  \
+                                               \
+    a0 = POP_UNCHECKED ();                     \
+    a1 = POP_UNCHECKED ();                     \
+    b0 = POP_UNCHECKED ();                     \
+    b1 = POP_UNCHECKED ();                     \
+    p = POP_UNCHECKED ();                      \
+                                               \
+    sfnt_interpret_isect (interpreter,         \
+                         a0, a1, b0, b1, p);   \
+  }
+
+#define ALIGNPTS()                             \
+  {                                            \
+    uint32_t p1, p2;                           \
+                                               \
+    p1 = POP ();                               \
+    p2 = POP ();                               \
+                                               \
+    sfnt_interpret_alignpts (interpreter, p1,  \
+                            p2);               \
+  }
+
+#define UTP()                                  \
+  {                                            \
+    uint32_t p;                                        \
+                                               \
+    p = POP ();                                        \
+    sfnt_interpret_utp (interpreter, p);       \
+  }
+
+#define MDAP()                                 \
+  {                                            \
+    uint32_t p;                                        \
+                                               \
+    p = POP ();                                        \
+    sfnt_interpret_mdap (interpreter, p,       \
+                        opcode);               \
+  }
+
+#define IUP()                                  \
+  {                                            \
+    sfnt_interpret_iup (interpreter, opcode);  \
+  }
+
+#define SHP()                                  \
+  {                                            \
+    sfnt_interpret_shp (interpreter, opcode);  \
+  }
+
+#define SHC()                                  \
+  {                                            \
+    uint32_t contour;                          \
+                                               \
+    contour = POP ();                          \
+                                               \
+    sfnt_interpret_shc (interpreter, contour,  \
+                       opcode);                \
+  }
+
+#define SHZ()                                  \
+  {                                            \
+    uint32_t e;                                        \
+                                               \
+    e = POP ();                                        \
+                                               \
+    if (e > 1)                                 \
+      TRAP ("invalid zone!");                  \
+                                               \
+    sfnt_interpret_shz (interpreter, e,                \
+                       opcode);                \
+  }
+
+#define SHPIX()                                        \
+  {                                            \
+    sfnt_f26dot6 pixels, dx, dy;               \
+    uint32_t p;                                        \
+                                               \
+    pixels = POP ();                           \
+    sfnt_scale_by_freedom_vector (interpreter, \
+                                 pixels, &dx,  \
+                                 &dy);         \
+                                               \
+    while (interpreter->state.loop--)          \
+      {                                                \
+       p = POP ();                             \
+       sfnt_direct_move_zp2 (interpreter,      \
+                             p, dx, dy);       \
+      }                                                \
+                                               \
+    interpreter->state.loop = 1;               \
+  }
+
+#define IP()                                   \
+  {                                            \
+    sfnt_interpret_ip (interpreter);           \
+  }
+
+#define MSIRP()                                        \
+  {                                            \
+    sfnt_f26dot6 d;                            \
+    uint32_t p;                                        \
+                                               \
+    d = POP ();                                        \
+    p = POP ();                                        \
+                                               \
+    sfnt_interpret_msirp (interpreter, d, p,   \
+                         opcode);              \
+  }
+
+#define ALIGNRP()                              \
+  {                                            \
+    sfnt_interpret_alignrp (interpreter);      \
+  }
+
+#define MIAP()                                 \
+  {                                            \
+    uint32_t cvt;                              \
+    uint32_t p;                                        \
+                                               \
+    cvt = POP ();                              \
+    p = POP ();                                        \
+                                               \
+    sfnt_interpret_miap (interpreter, cvt, p,  \
+                        opcode);               \
+  }
+
+#define GC()                                   \
+  {                                            \
+    uint32_t p;                                        \
+    sfnt_f26dot6 x, y, value;                  \
+    sfnt_f26dot6 org_x, org_y;                 \
+                                               \
+    p = POP ();                                        \
+                                               \
+    sfnt_address_zp2 (interpreter, p, &x, &y,  \
+                     &org_x, &org_y);          \
+                                               \
+    if (opcode == 0x47)                                \
+      value = DUAL_PROJECT (org_x, org_y);     \
+    else                                       \
+      value = PROJECT (x, y);                  \
+                                               \
+    PUSH_UNCHECKED (value);                    \
+  }
+
+#define SCFS()                                 \
+  {                                            \
+    uint32_t p;                                        \
+    sfnt_f26dot6 c;                            \
+                                               \
+    c = POP ();                                        \
+    p = POP ();                                        \
+                                               \
+    sfnt_interpret_scfs (interpreter, p, c);   \
+  }
+
+#define MD()                                   \
+  {                                            \
+    uint32_t p1, p2;                           \
+    sfnt_f26dot6 distance;                     \
+                                               \
+    p2 = POP ();                               \
+    p1 = POP ();                               \
+                                               \
+    distance                                   \
+      = sfnt_measure_distance (interpreter,    \
+                              p1, p2,          \
+                              opcode);         \
+    PUSH_UNCHECKED (distance);                 \
+  }
+
+#define FLIPPT()                               \
+  {                                            \
+    sfnt_interpret_flippt (interpreter);       \
+  }
+
+#define FLIPRGOFF()                            \
+  {                                            \
+    uint32_t h, l;                             \
+                                               \
+    h = POP ();                                        \
+    l = POP ();                                        \
+                                               \
+    sfnt_interpret_fliprgoff (interpreter,     \
+                             h, l);            \
+  }
+
+#define FLIPRGON()                             \
+  {                                            \
+    uint32_t h, l;                             \
+                                               \
+    h = POP ();                                        \
+    l = POP ();                                        \
+                                               \
+    sfnt_interpret_fliprgon (interpreter,      \
+                            h, l);             \
+  }
+
+#define SDPVTL()                               \
+  {                                            \
+    struct sfnt_unit_vector vector;            \
+    uint32_t p2, p1;                           \
+                                               \
+    p2 = POP ();                               \
+    p1 = POP ();                               \
+                                               \
+    sfnt_line_to_vector (interpreter,          \
+                        p2, p1, &vector,       \
+                        opcode == 0x87,        \
+                        false);                \
+                                               \
+    sfnt_save_projection_vector (interpreter,  \
+                                &vector,       \
+                                false);        \
+                                               \
+    sfnt_line_to_vector (interpreter,          \
+                        p2, p1, &vector,       \
+                        opcode == 0x87,        \
+                        true);                 \
+                                               \
+    sfnt_save_projection_vector (interpreter,  \
+                                &vector,       \
+                                true);         \
+  }
+
+#define MIRP()                                 \
+  {                                            \
+    sfnt_interpret_mirp (interpreter, opcode); \
+  }
+
+#define MDRP()                                 \
+  {                                            \
+    sfnt_interpret_mdrp (interpreter, opcode); \
+  }
+
+
+
+#define NOT_IMPLEMENTED()                      \
+  sfnt_interpret_unimplemented (interpreter,   \
+                               opcode, why)
+
+
+
+/* Multiply the specified MAGNITUDE by the contents of INTERPRETER's
+   freedom vector and return the result in *DX and *DY.  */
+
+static void
+sfnt_scale_by_freedom_vector (struct sfnt_interpreter *interpreter,
+                             sfnt_f26dot6 magnitude, sfnt_f26dot6 *dx,
+                             sfnt_f26dot6 *dy)
+{
+  struct sfnt_unit_vector *vector;
+
+  vector = &interpreter->state.freedom_vector;
+  *dx = sfnt_mul_f2dot14 (vector->x, magnitude);
+  *dy = sfnt_mul_f2dot14 (vector->y, magnitude);
+}
+
+/* Interpret a UTP instruction with the point P in INTERPRETER.
+   Unset any ``touched'' flag inside the point P, relative to the
+   zone in INTERPRETER's ZP0 register.
+
+   Trap upon encountering an out of bounds point.  */
+
+static void
+sfnt_interpret_utp (struct sfnt_interpreter *interpreter,
+                   uint32_t p)
+{
+  if (!interpreter->state.zp0)
+    {
+      if (p >= interpreter->twilight_zone_size)
+       TRAP ("UTP[] p lies outside twilight zone");
+
+      /* There are no flags in the twilight zone.  */
+      return;
+    }
+
+  if (!interpreter->glyph_zone
+      || p >= interpreter->glyph_zone->num_points)
+    TRAP ("UTP[] p lies outside glyph zone");
+
+  interpreter->glyph_zone->flags[p] &= ~SFNT_POINT_TOUCHED_X;
+}
+
+/* Save the specified unit VECTOR into INTERPRETER's graphics state as
+   both the projection and the dual projection vectors.
+
+   If not DUAL_ONLY, set VECTOR as both the projection and dual
+   projection vectors.  Otherwise, only set VECTOR as the dual
+   projection vector.  */
+
+static void
+sfnt_save_projection_vector (struct sfnt_interpreter *interpreter,
+                            struct sfnt_unit_vector *vector,
+                            bool dual_only)
+{
+  if (!dual_only)
+    interpreter->state.projection_vector = *vector;
+
+  interpreter->state.dual_projection_vector = *vector;
+
+  sfnt_validate_gs (&interpreter->state);
+}
+
+/* Save the specified unit VECTOR into INTERPRETER's graphics
+   state.  */
+
+static void
+sfnt_save_freedom_vector (struct sfnt_interpreter *interpreter,
+                         struct sfnt_unit_vector *vector)
+{
+  interpreter->state.freedom_vector = *vector;
+
+  sfnt_validate_gs (&interpreter->state);
+}
+
+/* Return the values of the point NUMBER in the zone pointed to by
+   INTERPRETER's ZP2 register.
+
+   If X_ORG and Y_ORG are set, return the original values (prior to
+   any instruction interpretations) in those two locations.
+
+   Trap if NUMBER is out of bounds or the zone is inaccessible.  */
+
+static void
+sfnt_address_zp2 (struct sfnt_interpreter *interpreter,
+                 uint32_t number,
+                 sfnt_f26dot6 *x, sfnt_f26dot6 *y,
+                 sfnt_f26dot6 *x_org, sfnt_f26dot6 *y_org)
+{
+  if (!interpreter->state.zp2)
+    {
+      /* Address the twilight zone.  */
+      if (number >= interpreter->twilight_zone_size)
+       TRAP ("address to ZP2 (twilight zone) out of bounds");
+
+      *x = interpreter->twilight_x[number];
+      *y = interpreter->twilight_y[number];
+
+      if (!x_org || !y_org)
+       return;
+
+      /* The twilight zone is initially all zero, but initial
+        positions can still be changed.  */
+      *x_org = interpreter->twilight_original_x[number];
+      *y_org = interpreter->twilight_original_y[number];
+      return;
+    }
+
+  /* Address the glyph zone.  */
+  if (!interpreter->glyph_zone)
+    TRAP ("address to ZP2 (glyph zone) points into unset"
+         " zone");
+
+  if (number >= interpreter->glyph_zone->num_points)
+    TRAP ("address to ZP2 (glyph zone) out of bounds");
+
+  *x = interpreter->glyph_zone->x_current[number];
+  *y = interpreter->glyph_zone->y_current[number];
+
+  if (x_org && y_org)
+    {
+      *x_org = interpreter->glyph_zone->x_points[number];
+      *y_org = interpreter->glyph_zone->y_points[number];
+    }
+}
+
+/* Return the values of the point NUMBER in the zone pointed to by
+   INTERPRETER's ZP1 register.
+
+   Trap if NUMBER is out of bounds or the zone is inaccessible.  */
+
+static void
+sfnt_address_zp1 (struct sfnt_interpreter *interpreter,
+                 uint32_t number,
+                 sfnt_f26dot6 *x, sfnt_f26dot6 *y,
+                 sfnt_f26dot6 *x_org, sfnt_f26dot6 *y_org)
+{
+  if (!interpreter->state.zp1)
+    {
+      /* Address the twilight zone.  */
+      if (number >= interpreter->twilight_zone_size)
+       TRAP ("address to ZP1 (twilight zone) out of bounds");
+
+      *x = interpreter->twilight_x[number];
+      *y = interpreter->twilight_y[number];
+
+      if (!x_org || !y_org)
+       return;
+
+      /* The twilight zone is initially all zero, but initial
+        positions can still be changed.  */
+      *x_org = interpreter->twilight_original_x[number];
+      *y_org = interpreter->twilight_original_y[number];
+      return;
+    }
+
+  /* Address the glyph zone.  */
+  if (!interpreter->glyph_zone)
+    TRAP ("address to ZP1 (glyph zone) points into unset"
+         " zone");
+
+  if (number >= interpreter->glyph_zone->num_points)
+    TRAP ("address to ZP1 (glyph zone) out of bounds");
+
+  *x = interpreter->glyph_zone->x_current[number];
+  *y = interpreter->glyph_zone->y_current[number];
+
+  if (x_org && y_org)
+    {
+      *x_org = interpreter->glyph_zone->x_points[number];
+      *y_org = interpreter->glyph_zone->y_points[number];
+    }
+}
+
+/* Return the values of the point NUMBER in the zone pointed to by
+   INTERPRETER's ZP0 register.
+
+   Trap if NUMBER is out of bounds or the zone is inaccessible.  */
+
+static void
+sfnt_address_zp0 (struct sfnt_interpreter *interpreter,
+                 uint32_t number,
+                 sfnt_f26dot6 *x, sfnt_f26dot6 *y,
+                 sfnt_f26dot6 *x_org, sfnt_f26dot6 *y_org)
+{
+  if (!interpreter->state.zp0)
+    {
+      /* Address the twilight zone.  */
+      if (number >= interpreter->twilight_zone_size)
+       TRAP ("address to ZP0 (twilight zone) out of bounds");
+
+      *x = interpreter->twilight_x[number];
+      *y = interpreter->twilight_y[number];
+
+      if (!x_org || !y_org)
+       return;
+
+      /* The twilight zone is initially all zero, but initial
+        positions can still be changed.  */
+      *x_org = interpreter->twilight_original_x[number];
+      *y_org = interpreter->twilight_original_y[number];
+      return;
+    }
+
+  /* Address the glyph zone.  */
+  if (!interpreter->glyph_zone)
+    TRAP ("address to ZP0 (glyph zone) points into unset"
+         " zone");
+
+  if (number >= interpreter->glyph_zone->num_points)
+    TRAP ("address to ZP0 (glyph zone) out of bounds");
+
+  *x = interpreter->glyph_zone->x_current[number];
+  *y = interpreter->glyph_zone->y_current[number];
+
+  if (x_org && y_org)
+    {
+      *x_org = interpreter->glyph_zone->x_points[number];
+      *y_org = interpreter->glyph_zone->y_points[number];
+    }
+}
+
+/* Set the point NUMBER in the zone referenced by INTERPRETER's ZP2
+   register to the specified X and Y.
+
+   Apply FLAGS to NUMBER's flags in that zone.  Trap if NUMBER is out
+   of bounds.  */
+
+static void
+sfnt_store_zp2 (struct sfnt_interpreter *interpreter,
+               uint32_t number, sfnt_f26dot6 x, sfnt_f26dot6 y,
+               int flags)
+{
+  if (!interpreter->state.zp2)
+    {
+      /* Address the twilight zone.  */
+      if (number >= interpreter->twilight_zone_size)
+       TRAP ("address to ZP2 (twilight zone) out of bounds");
+
+      interpreter->twilight_x[number] = x;
+      interpreter->twilight_y[number] = y;
+      return;
+    }
+
+  /* Address the glyph zone.  */
+  if (!interpreter->glyph_zone)
+    TRAP ("address to ZP0 (glyph zone) points into unset"
+         " zone");
+
+  if (number >= interpreter->glyph_zone->num_points)
+    TRAP ("address to ZP0 (glyph zone) out of bounds");
+
+  interpreter->glyph_zone->x_current[number] = x;
+  interpreter->glyph_zone->y_current[number] = y;
+  interpreter->glyph_zone->flags[number] |= flags;
+}
+
+#if 0
+
+/* Convert the line between the points X1, Y1 and X2, Y2 to standard
+   form.
+
+   Return the two coefficients in *A0 and *B0, and the constant in
+   *C.  */
+
+static void
+sfnt_line_to_standard_form (sfnt_f26dot6 x1, sfnt_f26dot6 y1,
+                           sfnt_f26dot6 x2, sfnt_f26dot6 y2,
+                           sfnt_f26dot6 *a, sfnt_f26dot6 *b,
+                           sfnt_f26dot6 *c)
+{
+  sfnt_f26dot6 a_temp, b_temp, c_temp;
+
+  a_temp = sfnt_sub (y2, y1);
+  b_temp = sfnt_sub (x1, x2);
+  c_temp = sfnt_sub (sfnt_mul_f26dot6 (x1, y2),
+                    sfnt_mul_f26dot6 (x2, y1));
+
+  *a = a_temp;
+  *b = b_temp;
+  *c = c_temp;
+}
+
+#endif
+
+/* Check that the specified POINT lies within the zone addressed by
+   INTERPRETER's ZP2 register.  Trap if it does not.  */
+
+static void
+sfnt_check_zp2 (struct sfnt_interpreter *interpreter, uint32_t point)
+{
+  if (!interpreter->state.zp2)
+    {
+      if (point >= interpreter->twilight_zone_size)
+       TRAP ("point lies outside twilight zone (ZP2)");
+    }
+  else if (!interpreter->glyph_zone
+          || point >= interpreter->glyph_zone->num_points)
+    TRAP ("point lies outside glyph zone (ZP2)");
+}
+
+/* Check that the specified POINT lies within the zone addressed by
+   INTERPRETER's ZP0 register.  Trap if it does not.  */
+
+static void
+sfnt_check_zp0 (struct sfnt_interpreter *interpreter, uint32_t point)
+{
+  if (!interpreter->state.zp0)
+    {
+      if (point >= interpreter->twilight_zone_size)
+       TRAP ("point lies outside twilight zone (ZP0)");
+    }
+  else if (!interpreter->glyph_zone
+          || point >= interpreter->glyph_zone->num_points)
+    TRAP ("point lies outside glyph zone (ZP0)");
+}
+
+/* Check that the specified POINT lies within the zone addressed by
+   INTERPRETER's ZP1 register.  Trap if it does not.  */
+
+static void
+sfnt_check_zp1 (struct sfnt_interpreter *interpreter, uint32_t point)
+{
+  if (!interpreter->state.zp1)
+    {
+      if (point >= interpreter->twilight_zone_size)
+       TRAP ("point lies outside twilight zone (ZP0)");
+    }
+  else if (!interpreter->glyph_zone
+          || point >= interpreter->glyph_zone->num_points)
+    TRAP ("point lies outside glyph zone (ZP0)");
+}
+
+/* Move N points starting from the specified POINT in the zone
+   addressed by INTERPRETER's ZP0 register by the given DISTANCE along
+   the freedom vector.
+
+   No checking is done to ensure that POINT lies inside the zone, or
+   even that the zone exists at all.  */
+
+static void
+sfnt_move_zp0 (struct sfnt_interpreter *interpreter, uint32_t point,
+              size_t n, sfnt_f26dot6 distance)
+{
+  if (!interpreter->state.zp0)
+    interpreter->state.move (&interpreter->twilight_x[point],
+                            &interpreter->twilight_y[point],
+                            n, interpreter, distance, NULL);
+  else
+    interpreter->state.move (&interpreter->glyph_zone->x_current[point],
+                            &interpreter->glyph_zone->y_current[point],
+                            n, interpreter, distance,
+                            &interpreter->glyph_zone->flags[point]);
+}
+
+/* Move N points starting from the specified POINT in the zone
+   addressed by INTERPRETER's ZP1 register by the given DISTANCE along
+   the freedom vector.
+
+   No checking is done to ensure that POINT lies inside the zone, or
+   even that the zone exists at all.  */
+
+static void
+sfnt_move_zp1 (struct sfnt_interpreter *interpreter, uint32_t point,
+              size_t n, sfnt_f26dot6 distance)
+{
+  if (!interpreter->state.zp1)
+    interpreter->state.move (&interpreter->twilight_x[point],
+                            &interpreter->twilight_y[point],
+                            n, interpreter, distance, NULL);
+  else
+    interpreter->state.move (&interpreter->glyph_zone->x_current[point],
+                            &interpreter->glyph_zone->y_current[point],
+                            n, interpreter, distance,
+                            &interpreter->glyph_zone->flags[point]);
+}
+
+/* Move N points starting from the specified POINT in the zone
+   addressed by INTERPRETER's ZP2 register by the given DISTANCE along
+   the freedom vector.
+
+   No checking is done to ensure that POINT lies inside the zone, or
+   even that the zone exists at all.  */
+
+static void
+sfnt_move_zp2 (struct sfnt_interpreter *interpreter, uint32_t point,
+              size_t n, sfnt_f26dot6 distance)
+{
+  if (!interpreter->state.zp2)
+    interpreter->state.move (&interpreter->twilight_x[point],
+                            &interpreter->twilight_y[point],
+                            n, interpreter, distance, NULL);
+  else
+    interpreter->state.move (&interpreter->glyph_zone->x_current[point],
+                            &interpreter->glyph_zone->y_current[point],
+                            n, interpreter, distance,
+                            &interpreter->glyph_zone->flags[point]);
+}
+
+/* Move N points from the specified POINT in INTERPRETER's glyph zone
+   by the given DISTANCE along the freedom vector.
+
+   Do not touch the points that are moved.
+
+   No checking is done to ensure that POINT lies inside the zone, or
+   even that the zone exists at all.  */
+
+static void
+sfnt_move_glyph_zone (struct sfnt_interpreter *interpreter, uint32_t point,
+                     size_t n, sfnt_f26dot6 distance)
+{
+  interpreter->state.move (&interpreter->glyph_zone->x_current[point],
+                          &interpreter->glyph_zone->y_current[point],
+                          n, interpreter, distance, NULL);
+}
+
+/* Move N points from the specified POINT in INTERPRETER's twilight
+   zone by the given DISTANCE along the freedom vector.
+
+   Do not touch the points that are moved.
+
+   No checking is done to ensure that POINT lies inside the zone, or
+   even that the zone exists at all.  */
+
+static void
+sfnt_move_twilight_zone (struct sfnt_interpreter *interpreter, uint32_t point,
+                        size_t n, sfnt_f26dot6 distance)
+{
+  interpreter->state.move (&interpreter->twilight_x[point],
+                          &interpreter->twilight_y[point],
+                          n, interpreter, distance, NULL);
+}
+
+/* Move the point P in the zone pointed to by the ZP2 register in
+   INTERPRETER's graphics state by DX, and DY.
+
+   Touch the point P in the directions of the movement.
+
+   Check that P is valid; if not, trap.  Else, perform the move
+   directly without converting it from the projection vector or to the
+   freedom vector.  */
+
+static void
+sfnt_direct_move_zp2 (struct sfnt_interpreter *interpreter, uint32_t p,
+                     sfnt_f26dot6 dx, sfnt_f26dot6 dy)
+{
+  if (!interpreter->state.zp2)
+    {
+      if (p >= interpreter->twilight_zone_size)
+       TRAP ("point out of bounds");
+
+      interpreter->twilight_x[p]
+       = sfnt_add (interpreter->twilight_x[p], dx);
+      interpreter->twilight_y[p]
+       = sfnt_add (interpreter->twilight_y[p], dy);
+    }
+  else
+    {
+      if (!interpreter->glyph_zone
+         || p >= interpreter->glyph_zone->num_points)
+       TRAP ("point out of bounds");
+
+      interpreter->glyph_zone->x_current[p]
+       = sfnt_add (interpreter->glyph_zone->x_current[p], dx);
+      interpreter->glyph_zone->y_current[p]
+       = sfnt_add (interpreter->glyph_zone->y_current[p], dy);
+
+      if (dx)
+       interpreter->glyph_zone->flags[p] |= SFNT_POINT_TOUCHED_X;
+
+      if (dy)
+       interpreter->glyph_zone->flags[p] |= SFNT_POINT_TOUCHED_Y;
+    }
+}
+
+/* Project the vector VX, VY onto INTERPRETER's projection vector.
+   Return the magnitude of the projection.  */
+
+static sfnt_f26dot6
+sfnt_project_vector (struct sfnt_interpreter *interpreter,
+                    sfnt_f26dot6 vx, sfnt_f26dot6 vy)
+{
+  return interpreter->state.project (vx, vy, interpreter);
+}
+
+/* Project the vector VX, VY onto INTERPRETER's dual projection
+   vector.  Return the magnitude of the projection.  */
+
+static sfnt_f26dot6
+sfnt_dual_project_vector (struct sfnt_interpreter *interpreter,
+                         sfnt_f26dot6 vx, sfnt_f26dot6 vy)
+{
+  return interpreter->state.dual_project (vx, vy, interpreter);
+}
+
+/* Interpret a FLIPRGOFF instruction in INTERPRTER.  Make each point
+   in ZP0 between L and H an off-curve point.  */
+
+static void
+sfnt_interpret_fliprgoff (struct sfnt_interpreter *interpreter,
+                         uint32_t l, uint32_t h)
+{
+  uint32_t i;
+
+  sfnt_check_zp0 (interpreter, l);
+  sfnt_check_zp0 (interpreter, h);
+
+  if (!interpreter->state.zp0)
+    return;
+
+  for (i = l; i < h; ++i)
+    interpreter->glyph_zone->flags[i] &= ~01;
+}
+
+/* Interpret a FLIPRGON instruction in INTERPRTER.  Make each point in
+   ZP0 between L and H an on-curve point.  */
+
+static void
+sfnt_interpret_fliprgon (struct sfnt_interpreter *interpreter,
+                        uint32_t l, uint32_t h)
+{
+  uint32_t i;
+
+  sfnt_check_zp0 (interpreter, l);
+  sfnt_check_zp0 (interpreter, h);
+
+  if (!interpreter->state.zp0)
+    return;
+
+  for (i = l; i < h; ++i)
+    interpreter->glyph_zone->flags[i] |= ~01;
+}
+
+/* Interpret a FLIPPT instruction in INTERPRETER.  For loop times, pop
+   a point in ZP0.  If it is an on-curve point, make it an off-curve
+   one, and vice versa.  */
+
+static void
+sfnt_interpret_flippt (struct sfnt_interpreter *interpreter)
+{
+  uint32_t point;
+
+  while (interpreter->state.loop--)
+    {
+      point = POP ();
+
+      /* There are no flags in the twilight zone.
+         But first check that the point is within bounds.  */
+
+      sfnt_check_zp0 (interpreter, point);
+
+      if (!interpreter->state.zp0)
+       continue;
+
+      /* If POINT is on the curve, make it off the curve and vice
+        versa.  */
+
+      if (interpreter->glyph_zone->flags[point] & 01)
+       interpreter->glyph_zone->flags[point] &= ~01;
+      else
+       interpreter->glyph_zone->flags[point] |= 01;
+    }
+
+  /* Restore loop.  */
+  interpreter->state.loop = 1;
+}
+
+/* Interpret an SCFS instruction.
+   Move P in ZP2 along the freedom vector until its projection is
+   equal to C.
+
+   If ZP2 is the twilight zone, ``create'' P by setting its original
+   position to the projection.  */
+
+static void
+sfnt_interpret_scfs (struct sfnt_interpreter *interpreter,
+                    uint32_t p, sfnt_f26dot6 c)
+{
+  sfnt_f26dot6 x, y, distance;
+
+  sfnt_address_zp2 (interpreter, p, &x, &y, NULL, NULL);
+  distance = PROJECT (x, y);
+  sfnt_move_zp2 (interpreter, p, 1, sfnt_sub (c, distance));
+
+  if (!interpreter->state.zp2)
+    {
+      interpreter->twilight_original_x[p] = interpreter->twilight_x[p];
+      interpreter->twilight_original_y[p] = interpreter->twilight_y[p];
+    }
+}
+
+/* Symmetrically round the 26.6 fixed point value X using the rounding
+   mode in INTERPRETER.  Return the result.  */
+
+static sfnt_f26dot6
+sfnt_round_symmetric (struct sfnt_interpreter *interpreter, sfnt_f26dot6 x)
+{
+  int sign;
+
+  sign = 1;
+
+  if (x < 0)
+    {
+      sign = -1;
+      x = -x;
+    }
+
+  return interpreter->state.round (x, interpreter) * sign;
+}
+
+/* Interpret an MIAP (``Move Indirect Absolute Point'') instruction
+   using INTERPRETER.
+
+   Move P in ZP0 along the freedom vector until its projection on the
+   projection vector is equal to CVT units in the projection vector.
+
+   Finally, set RP0 and RP1 to P.
+
+   If ZP0 is the twilight zone, then first create that point in the
+   twilight zone by setting its ``original position'' to the
+   projection of the value.
+
+   If OPCODE is 0x3f, then in addition check the CVT value against the
+   control value cut-in, and round the magnitudes of the movement.  */
+
+static void
+sfnt_interpret_miap (struct sfnt_interpreter *interpreter,
+                    uint32_t cvt, uint32_t p, unsigned char opcode)
+{
+  sfnt_f26dot6 x, y, distance, value, delta;
+
+  /* Read the cvt value.  */
+
+  if (cvt >= interpreter->cvt_size)
+    TRAP ("out of bounds read to cvt");
+
+  value = interpreter->cvt[cvt];
+
+  /* Now load the point.  */
+  sfnt_address_zp0 (interpreter, p, &x, &y, NULL, NULL);
+
+  /* Create the twilight zone point if necessary.
+     Note that the value used is not rounded.  */
+
+  if (!interpreter->state.zp0)
+    {
+      x = interpreter->twilight_x[p]
+       = interpreter->twilight_original_x[p]
+       = sfnt_mul_f2dot14 (interpreter->state.projection_vector.x,
+                           value);
+
+      y = interpreter->twilight_y[p]
+       = interpreter->twilight_original_y[p]
+       = sfnt_mul_f2dot14 (interpreter->state.projection_vector.y,
+                           value);
+    }
+
+  /* Obtain the original distance.  */
+  distance = sfnt_project_vector (interpreter, x, y);
+
+  /* Round the distance and apply the cvt cut in if necessary.  */
+
+  if (opcode == 0x3f)
+    {
+      delta = sfnt_sub (value, distance);
+
+      if (delta < 0)
+       delta = -delta;
+
+      /* If delta is more than the cvt cut in (more aptly named ``cut
+        out''), use the original distance.  */
+
+      if (delta > interpreter->state.cvt_cut_in)
+       value = distance;
+
+      /* Round value.  */
+      value = sfnt_round_symmetric (interpreter, value);
+    }
+
+  /* Move the point by the distance.  */
+  sfnt_move_zp0 (interpreter, p, 1, sfnt_sub (value, distance));
+
+  /* Set reference points.  */
+  interpreter->state.rp0 = p;
+  interpreter->state.rp1 = p;
+}
+
+/* Perform a single iteration of sfnt_interpret_alignrp.  RP0X and
+   RP0Y should be the position of the reference point RP0 in ZP0.  */
+
+static void
+sfnt_interpret_alignrp_1 (struct sfnt_interpreter *interpreter,
+                         sfnt_f26dot6 rp0x, sfnt_f26dot6 rp0y)
+{
+  sfnt_f26dot6 distance, x, y;
+  uint32_t point;
+
+  point = POP ();
+
+  /* Load this point.  */
+  sfnt_address_zp1 (interpreter, point, &x, &y, NULL, NULL);
+
+  /* Measure the distance from here to rp0.  */
+  distance = sfnt_project_vector (interpreter, sfnt_sub (x, rp0x),
+                                 sfnt_sub (y, rp0y));
+
+  /* Move by the opposite.  */
+  sfnt_move_zp1 (interpreter, point, 1, -distance);
+}
+
+/* For loop times, pop a point in ZP1 and align it to RP0 in ZP0 by
+   moving it along the freedom vector until its projected distance
+   from RP0 becomes 0.  */
+
+static void
+sfnt_interpret_alignrp (struct sfnt_interpreter *interpreter)
+{
+  sfnt_f26dot6 rp0x, rp0y;
+
+  sfnt_address_zp0 (interpreter, interpreter->state.rp0,
+                   &rp0x, &rp0y, NULL, NULL);
+
+  while (interpreter->state.loop--)
+    {
+      sfnt_interpret_alignrp_1 (interpreter, rp0x, rp0y);
+
+      /* Reload RP0 if it is in the same zone as ZP1.  */
+      if (interpreter->state.zp0 == interpreter->state.zp1)
+       sfnt_address_zp0 (interpreter, interpreter->state.rp0,
+                         &rp0x, &rp0y, NULL, NULL);
+    }
+
+  interpreter->state.loop = 1;
+}
+
+/* Align the two points P1 and P2 relative to the projection vector.
+   P1 is addressed relative to ZP0, and P2 is addressed relative to
+   ZP1.
+
+   Move both points along the freedom vector by half the magnitude of
+   the the projection of a vector formed by P1.x - P2.x, P1.y - P2.y,
+   upon the projection vector.  */
+
+static void
+sfnt_interpret_alignpts (struct sfnt_interpreter *interpreter,
+                        uint32_t p1, uint32_t p2)
+{
+  sfnt_f26dot6 p1x, p1y, p2x, p2y;
+  sfnt_f26dot6 magnitude;
+
+  sfnt_address_zp0 (interpreter, p1, &p1x, &p1y, NULL, NULL);
+  sfnt_address_zp1 (interpreter, p2, &p2x, &p2y, NULL, NULL);
+
+  magnitude = sfnt_project_vector (interpreter,
+                                  sfnt_sub (p1x, p2x),
+                                  sfnt_sub (p1y, p2y));
+  magnitude = magnitude / 2;
+
+  /* Now move both points along the freedom vector.  */
+  sfnt_move_zp0 (interpreter, p1, 1, magnitude);
+  sfnt_move_zp1 (interpreter, p2, 1, -magnitude);
+}
+
+/* Set the point P in the zone referenced in INTERPRETER's ZP2
+   register to the intersection between the line formed by the points
+   POINT_A0 to POINT_A1 in ZP0 and another line formed by POINT_B0 to
+   POINT_B1 in ZP1.
+
+   Touch the point P.  */
+
+static void
+sfnt_interpret_isect (struct sfnt_interpreter *interpreter,
+                     uint32_t point_a0, uint32_t point_a1,
+                     uint32_t point_b0, uint32_t point_b1,
+                     uint32_t p)
+{
+  sfnt_f26dot6 a0x, a0y, a1x, a1y;
+  sfnt_f26dot6 b0x, b0y, b1x, b1y;
+#if 0
+  sfnt_f26dot6 determinant, dx, dy;
+  sfnt_f26dot6 a0, b0, a1, b1;
+  sfnt_f26dot6 c0, c1, px, py;
+#else
+  sfnt_f26dot6 dx, dy, dax, day, dbx, dby;
+  sfnt_f26dot6 discriminant, val, dot_product;
+  sfnt_f26dot6 px, py;
+#endif
+
+  /* Load points.  */
+  sfnt_address_zp0 (interpreter, point_a0, &a0x, &a0y, NULL, NULL);
+  sfnt_address_zp0 (interpreter, point_a1, &a1x, &a1y, NULL, NULL);
+  sfnt_address_zp1 (interpreter, point_b0, &b0x, &b0y, NULL, NULL);
+  sfnt_address_zp1 (interpreter, point_b1, &b1x, &b1y, NULL, NULL);
+
+#if 0
+  /* The system is determined from the standard form (look this up) of
+     both lines.
+
+     (the variables below have no relation to C identifiers
+      unless otherwise specified.)
+
+       a0*x + b0*y = c0
+       a1*x + b1*y = c1
+
+     The coefficient matrix is thus
+
+       [ a0 b0
+         a1 b1 ]
+
+     the vector of constants (also just dubbed the ``column vector''
+     by some people)
+
+       [ c0
+         c1 ]
+
+     and the solution vector becomes
+
+       [ x
+         y ]
+
+     Since there are exactly two equations and two unknowns, Cramer's
+     rule applies, and there is no need for any Gaussian elimination.
+
+     The determinant for the coefficient matrix is:
+
+       D = a0*b1 - b0*a1
+
+     the first and second determinants are:
+
+       Dx = c0*b1 - a0*c1
+       Dy = a1*c1 - c0*b1
+
+     and x = Dx / D, y = Dy / D.
+
+     If the system is indeterminate, D will be 0.  */
+
+  sfnt_line_to_standard_form (a0x, a0y, a1x, a1y,
+                             &a0, &b0, &c0);
+  sfnt_line_to_standard_form (b0x, b0y, b1x, b1y,
+                             &a1, &b1, &c1);
+
+
+  /* Compute determinants.  */
+  determinant = sfnt_sub (sfnt_mul_fixed (a0, b1),
+                         sfnt_mul_fixed (b0, a1));
+  dx = sfnt_sub (sfnt_mul_fixed (c0, b1),
+                sfnt_mul_fixed (a1, c1));
+  dy = sfnt_sub (sfnt_mul_fixed (a0, c1),
+                sfnt_mul_fixed (c0, b0));
+
+  /* Detect degenerate cases.  */
+
+  if (determinant == 0)
+    goto degenerate_case;
+#else
+  /* The algorithm above would work with floating point, but overflows
+     too easily with fixed point numbers.
+
+     Instead, use the modified vector projection algorithm found in
+     FreeType.  */
+
+  dbx = sfnt_sub (b1x, b0x);
+  dby = sfnt_sub (b1y, b0y);
+  dax = sfnt_sub (a1x, a0x);
+  day = sfnt_sub (a1y, a0y);
+
+  /* Compute vector cross product.  */
+  discriminant = sfnt_add (sfnt_mul_f26dot6 (dax, -dby),
+                          sfnt_mul_f26dot6 (day, dbx));
+  dot_product = sfnt_add (sfnt_mul_f26dot6 (dax, dbx),
+                         sfnt_mul_f26dot6 (day, dby));
+
+  /* Reject any non-intersections and grazing intersections.  */
+  if (!(sfnt_mul (19, abs (discriminant)) > abs (dot_product)))
+    return;
+
+  /* Reject any non-intersections.  */
+  if (!discriminant)
+    goto degenerate_case;
+
+  dx = sfnt_sub (b0x, a0x);
+  dy = sfnt_sub (b0y, a0y);
+  val = sfnt_add (sfnt_mul_f26dot6 (dx, -dby),
+                 sfnt_mul_f26dot6 (dy, dbx));
+
+  /* Project according to these values.  */
+  dx = sfnt_add (a0x, sfnt_multiply_divide_signed (val, dax,
+                                                  discriminant));
+  dy = sfnt_add (a0y, sfnt_multiply_divide_signed (val, day,
+                                                  discriminant));
+#endif
+
+  sfnt_store_zp2 (interpreter, p,
+#if 0
+                 sfnt_div_fixed (dx, determinant),
+                 sfnt_div_fixed (dy, determinant),
+#else
+                 dx, dy,
+#endif
+                 SFNT_POINT_TOUCHED_BOTH);
+  return;
+
+ degenerate_case:
+
+  /* Apple says that in this case:
+
+     Px = (a0x + a1x) / 2 + (b0x + b1x) / 2
+          ---------------------------------
+                         2
+     Py = (a0y + a1y) / 2 + (b0y + b1y) / 2
+          ---------------------------------
+                         2  */
+
+  px = (sfnt_add (a0x, a1x) / 2 + sfnt_add (b0x, b1x) / 2) / 2;
+  py = (sfnt_add (a0y, a1y) / 2 + sfnt_add (b0y, b1y) / 2) / 2;
+  sfnt_store_zp2 (interpreter, p, px, py,
+                 SFNT_POINT_TOUCHED_BOTH);
+}
+
+/* Compute the square root of the 16.16 fixed point number N.  */
+
+static sfnt_fixed
+sfnt_sqrt_fixed (sfnt_fixed n)
+{
+  int count;
+  unsigned int root, rem_hi, rem_lo, possible;
+
+  root = 0;
+
+  if (n > 0)
+    {
+      rem_hi = 0;
+      rem_lo = n;
+      count = 24;
+
+      do
+       {
+         rem_hi = (rem_hi << 2) | (rem_lo >> 30);
+         rem_lo <<= 2;
+         root <<= 1;
+         possible = (root << 1) + 1;
+
+         if (rem_hi >= possible)
+           {
+             rem_hi -= possible;
+             root += 1;
+           }
+       }
+      while (--count);
+    }
+
+  return root;
+}
+
+/* Compute a unit vector describing a vector VX, VY.  Return the value
+   in *VECTOR.  */
+
+static void
+sfnt_normalize_vector (sfnt_f26dot6 vx, sfnt_f26dot6 vy,
+                      struct sfnt_unit_vector *vector)
+{
+  sfnt_f26dot6 x_squared, y_squared;
+  sfnt_fixed n, magnitude;
+
+  if (!vx && !vy)
+    {
+      /* If vx and vy are both zero, then just project
+        horizontally.  */
+
+    fail:
+      vector->x = 04000;
+      vector->y = 0;
+      return;
+    }
+
+  /* Scale vx and vy up if they won't at least make 1.  */
+
+  while (!(vx < -32 || vx > 32) && !(vy < -32 || vy > 32))
+    {
+      vx = vx * 2;
+      vy = vy * 2;
+    }
+
+  /* Compute the magnitude of this vector.  */
+  x_squared = sfnt_mul_f26dot6 (vx, vx);
+  y_squared = sfnt_mul_f26dot6 (vy, vy);
+
+  /* x_squared and y_squared can end up too large to fit in a 16.16
+     fixed.  Scale both values down until they fit.  */
+
+  while (x_squared > 0x200000 || y_squared > 0x200000
+        || x_squared < -0x200000 || y_squared < -0x200000)
+    {
+      x_squared /= 2;
+      y_squared /= 2;
+    }
+
+  /* Convert to 16.16 for greater precision.  */
+  n = sfnt_add (x_squared, y_squared) * 1024;
+
+  /* Get hypotenuse of the triangle from vx, 0, to 0, vy.  */
+  magnitude = sfnt_sqrt_fixed (n);
+
+  /* Avoid division by zero.  */
+  if (!magnitude)
+    goto fail;
+
+  /* Long division.. eek! */
+  vector->x = (sfnt_div_fixed (vx * 1024, magnitude) / 4);
+  vector->y = (sfnt_div_fixed (vy * 1024, magnitude) / 4);
+}
+
+/* Compute a unit vector describing the direction of a line from the
+   point P2 to the point P1.  Save the result in *VECTOR.
+
+   P2 is the address of a point in the zone specified in the ZP2
+   register.  P1 is the address of a point in the zone specified in
+   the ZP1 register.  Take the values of both registers from the
+   specified INTERPRETER's graphics state.
+
+   If PERPENDICULAR, then *VECTOR will be rotated 90 degrees
+   counter-clockwise.  Else, *VECTOR will be parallel to the line.
+
+   If ORIGINAL, then the coordinates used to calculate the line will
+   be those prior to instructing.  Otherwise, the current coordinates
+   will be used.  */
+
+static void
+sfnt_line_to_vector (struct sfnt_interpreter *interpreter,
+                    uint32_t p2, uint32_t p1,
+                    struct sfnt_unit_vector *vector,
+                    bool perpendicular, bool original)
+{
+  sfnt_f26dot6 x2, y2, original_x2, original_y2;
+  sfnt_f26dot6 x1, y1, original_x1, original_y1;
+  sfnt_f26dot6 a, b, temp;
+
+  sfnt_address_zp2 (interpreter, p2, &x2, &y2, &original_x2,
+                   &original_y2);
+  sfnt_address_zp1 (interpreter, p1, &x1, &y1, &original_x1,
+                   &original_y1);
+
+  /* Use original coordinates if specified.  */
+
+  if (original)
+    {
+      x2 = original_x2;
+      y2 = original_y2;
+      x1 = original_x1;
+      y1 = original_y1;
+    }
+
+  /* Calculate the vector between X2, Y2, and X1, Y1.  */
+  a = sfnt_sub (x1, x2);
+  b = sfnt_sub (y1, y2);
+
+  /* Rotate counterclockwise if necessary.  */
+
+  if (perpendicular)
+    {
+      temp = b;
+      b = a;
+      a = -temp;
+    }
+
+  /* Normalize this vector, turning it into a unit vector.  */
+  sfnt_normalize_vector (a, b, vector);
+}
+
+/* Measure the distance between P1 in ZP0 and P2 in ZP1,
+   relative to the projection or dual projection vector.
+
+   Return the distance of P1 and P2 relative to their original
+   un-instructed positions should OPCODE be 0x4A, and to their
+   instructed positions should OPCODE be 0x49.  */
+
+static sfnt_f26dot6
+sfnt_measure_distance (struct sfnt_interpreter *interpreter,
+                      uint32_t p1, uint32_t p2,
+                      unsigned char opcode)
+{
+  sfnt_f26dot6 p1x, p1y, p1_original_x, p1_original_y;
+  sfnt_f26dot6 p2x, p2y, p2_original_x, p2_original_y;
+
+  /* P1 is relative to ZP0 and P2 is relative to ZP1.
+     Apple's manual says this, Microsoft's does not.  */
+
+  sfnt_address_zp0 (interpreter, p1, &p1x, &p1y,
+                   &p1_original_x, &p1_original_y);
+  sfnt_address_zp1 (interpreter, p2, &p2x, &p2y,
+                   &p2_original_x, &p2_original_y);
+
+  if (opcode == 0x4A)
+    return DUAL_PROJECT (sfnt_sub (p1_original_x, p2_original_x),
+                        sfnt_sub (p1_original_y, p2_original_y));
+
+  return PROJECT (sfnt_sub (p1x, p2x),
+                 sfnt_sub (p1y, p2y));
+}
+
+/* Interpret an MSIRP instruction in INTERPRETER.
+   Take a point P, and make the distance between P in ZP1 and the
+   current position of RP0 in ZP0 equal to D.
+
+   If ZP1 is the twilight zone, then create the point P by setting its
+   position and relative positions.
+
+   Then, if OPCODE is equal to 0x3b, make P RP0.  */
+
+static void
+sfnt_interpret_msirp (struct sfnt_interpreter *interpreter,
+                     sfnt_f26dot6 d, uint32_t p, unsigned char opcode)
+{
+  sfnt_f26dot6 rp0x, rp0y, rp0_original_x, rp0_original_y;
+  sfnt_f26dot6 x, y;
+  sfnt_f26dot6 old_distance, temp;
+
+  sfnt_address_zp0 (interpreter, interpreter->state.rp0,
+                   &rp0x, &rp0y, &rp0_original_x,
+                   &rp0_original_y);
+  sfnt_address_zp1 (interpreter, p, &x, &y, NULL, NULL);
+
+  if (!interpreter->state.zp1)
+    {
+      /* Create this point in the twilight zone at RP0.  */
+
+      x = interpreter->twilight_x[p] = rp0x;
+      y = interpreter->twilight_y[p] = rp0y;
+
+      /* Now set the original positions to the projected difference
+         from rp0.  This makes sense once you think about it.  */
+      temp = sfnt_mul_f2dot14 (interpreter->state.projection_vector.x, d);
+      temp = sfnt_add (temp, rp0_original_x);
+      interpreter->twilight_original_x[p] = temp;
+
+      temp = sfnt_mul_f2dot14 (interpreter->state.projection_vector.y, d);
+      temp = sfnt_add (temp, rp0_original_y);
+      interpreter->twilight_original_y[p] = temp;
+    }
+
+  /* Compute the original distance.  */
+  old_distance = sfnt_project_vector (interpreter,
+                                     sfnt_sub (x, rp0x),
+                                     sfnt_sub (y, rp0y));
+
+  /* Move the point.  */
+  sfnt_move_zp1 (interpreter, p, 1, sfnt_sub (d, old_distance));
+
+  /* Nothing in the TrueType reference manual says directly that this
+     instruction should change rp1 and rp2.  However, it says this
+     instruction is ``very similar to the MIRP[] instruction
+     except...'', and FreeType seems to do this, so do it as well.  */
+
+  interpreter->state.rp1 = interpreter->state.rp0;
+  interpreter->state.rp2 = p;
+
+  if (opcode == 0x3b)
+    interpreter->state.rp0 = p;
+}
+
+/* Interpret an IP instruction in INTERPRETER.  For loop times, pop a
+   single point in ZP2, and interpolate it so that its original
+   relationship to the points RP1 in ZP0 and RP2 in ZP1 as measured
+   along the dual projection vector continues to hold true.  */
+
+static void
+sfnt_interpret_ip (struct sfnt_interpreter *interpreter)
+{
+  sfnt_f26dot6 rp1x, rp1y, rp1_original_x, rp1_original_y;
+  sfnt_f26dot6 rp2x, rp2y, rp2_original_x, rp2_original_y;
+  sfnt_f26dot6 range, new_range, org_distance, cur_distance;
+  sfnt_f26dot6 new_distance;
+  uint32_t p;
+  sfnt_f26dot6 x, y, original_x, original_y;
+
+  /* First load both reference points.  */
+  sfnt_address_zp0 (interpreter, interpreter->state.rp1,
+                   &rp1x, &rp1y, &rp1_original_x,
+                   &rp1_original_y);
+  sfnt_address_zp1 (interpreter, interpreter->state.rp2,
+                   &rp2x, &rp2y, &rp2_original_x,
+                   &rp2_original_y);
+
+  /* Get the original distance between of RP1 and RP2 measured
+     relative to the dual projection vector.  */
+  range = sfnt_dual_project_vector (interpreter,
+                                   sfnt_sub (rp2_original_x,
+                                             rp1_original_x),
+                                   sfnt_sub (rp2_original_y,
+                                             rp1_original_y));
+
+  /* Get the new distance.  */
+  new_range = sfnt_dual_project_vector (interpreter,
+                                       sfnt_sub (rp2x, rp1x),
+                                       sfnt_sub (rp2y, rp1y));
+
+  while (interpreter->state.loop--)
+    {
+      p = POP ();
+
+      /* Load this point relative to zp2.  */
+      sfnt_address_zp2 (interpreter, p, &x, &y, &original_x,
+                       &original_y);
+
+      /* Now compute the old distance from this point to rp1.  */
+      org_distance
+       = sfnt_dual_project_vector (interpreter,
+                                   sfnt_sub (original_x,
+                                             rp1_original_x),
+                                   sfnt_sub (original_y,
+                                             rp1_original_y));
+
+      /* And the current distance from this point to rp1, so
+         how much to move can be determined.  */
+      cur_distance
+       = sfnt_project_vector (interpreter,
+                              sfnt_sub (x, rp1x),
+                              sfnt_sub (y, rp1y));
+
+      /* Finally, apply the ratio of the new distance between RP1 and
+        RP2 to that of the old distance between the two reference
+        points to org_distance, making new_distance.
+
+         If both reference points occupy the same position on the dual
+         projection vector, then simply use the old distance.  */
+
+      if (org_distance)
+       {
+         if (range)
+           new_distance
+             = sfnt_multiply_divide_signed (org_distance,
+                                            new_range, range);
+         else
+           new_distance = org_distance;
+       }
+      else
+       new_distance = 0;
+
+      /* And move the point along the freedom vector to reflect the
+        change in distance.  */
+      sfnt_move_zp2 (interpreter, p, 1,
+                    sfnt_sub (new_distance, cur_distance));
+    }
+
+  interpreter->state.loop = 1;
+}
+
+/* Apply the delta specified by OPERAND to the control value table
+   entry at INDEX currently loaded inside INTERPRETER.
+
+   Trap if INDEX is out of bounds.
+
+   NUMBER is the number of the specific DELTAC instruction this delta
+   is being applied on behalf of.  It must be between 1 and 3.  */
+
+static void
+sfnt_deltac (int number, struct sfnt_interpreter *interpreter,
+            unsigned int index, unsigned char operand)
+{
+  int ppem, delta;
+
+  /* Make sure INDEX is a valid cvt entry.  */
+
+  if (index >= interpreter->cvt_size)
+    TRAP ("DELTACn instruction out of bounds");
+
+  /* operand is an 8 bit number.  The most significant 4 bits
+     represent a specific PPEM size at which to apply the delta
+     specified in the low 4 bits, summed with an instruction specific
+     delta, and the current delta base.  */
+
+  ppem = (operand >> 4) + interpreter->state.delta_base;
+
+  switch (number)
+    {
+    case 1:
+      break;
+
+    case 2:
+      ppem += 16;
+      break;
+
+    case 3:
+      ppem += 32;
+      break;
+    }
+
+  /* Don't apply the delta if the ppem size doesn't match.  */
+
+  if (interpreter->ppem != ppem)
+    return;
+
+  /* Now, determine the delta using the low 4 bits.  The low 4 bits
+     actually specify a ``magnitude'' to apply to the delta, and do
+     not have an encoding for the delta 0.  */
+
+  switch (operand & 0xf)
+    {
+    case 0:
+      delta = -8;
+      break;
+
+    case 1:
+      delta = -7;
+      break;
+
+    case 2:
+      delta = -6;
+      break;
+
+    case 3:
+      delta = -5;
+      break;
+
+    case 4:
+      delta = -4;
+      break;
+
+    case 5:
+      delta = -3;
+      break;
+
+    case 6:
+      delta = -2;
+      break;
+
+    case 7:
+      delta = -1;
+      break;
+
+    case 8:
+      delta = 1;
+      break;
+
+    case 9:
+      delta = 2;
+      break;
+
+    case 10:
+      delta = 3;
+      break;
+
+    case 11:
+      delta = 4;
+      break;
+
+    case 12:
+      delta = 5;
+      break;
+
+    case 13:
+      delta = 6;
+      break;
+
+    case 14:
+      delta = 7;
+      break;
+
+    case 15:
+      delta = 8;
+      break;
+
+      /* To pacify -fanalyzer.  */
+    default:
+      abort ();
+    }
+
+  /* Now, scale up the delta by the step size, which is determined by
+     the delta shift.  */
+  delta *= 1l << (6 - interpreter->state.delta_shift);
+
+  /* Finally, apply the delta to the CVT entry.  */
+  interpreter->cvt[index] = sfnt_add (interpreter->cvt[index],
+                                     delta);
+}
+
+/* Interpret an MDAP (Move Direct Absolute Point) instruction with the
+   opcode OPCODE and the operand P in INTERPRETER.
+
+   Touch the point P (within the zone specified in zp0) in the
+   directions specified in the freedom vector.  Then, if OPCODE is
+   0x7f, round the point and move it the rounded distance along the
+   freedom vector.
+
+   Finally, set the RP0 and RP1 registers to P.  */
+
+static void
+sfnt_interpret_mdap (struct sfnt_interpreter *interpreter,
+                    uint32_t p, uint32_t opcode)
+{
+  sfnt_f26dot6 here, distance, px, py;
+
+  sfnt_address_zp0 (interpreter, p, &px, &py, NULL, NULL);
+
+  /* Measure the current distance.  */
+  here = sfnt_project_vector (interpreter, px, py);
+
+  if (opcode == 0x7f)
+    {
+      /* Measure distance, round, then move to the distance.  */
+      distance = sfnt_project_vector (interpreter, px, py);
+      distance = sfnt_round_symmetric (interpreter, distance);
+      distance = sfnt_sub (distance, here);
+    }
+  else
+    /* Don't move.  Just touch the point.  */
+    distance = 0;
+
+  sfnt_move_zp0 (interpreter, p, 1, distance);
+
+  interpreter->state.rp0 = p;
+  interpreter->state.rp1 = p;
+}
+
+/* Apply the delta specified by OPERAND to the point P in ZP0
+   currently loaded inside INTERPRETER.
+
+   Trap if P is out of bounds.
+
+   NUMBER is the number of the specific DELTAP instruction this delta
+   is being applied on behalf of.  It must be between 1 and 3.  */
+
+static void
+sfnt_deltap (int number, struct sfnt_interpreter *interpreter,
+            unsigned char operand, unsigned int index)
+{
+  int ppem, delta;
+
+  return;
+
+  /* Extract the ppem from OPERAND.  The format is the same as in
+     sfnt_deltac.  */
+
+  ppem = (operand >> 4) + interpreter->state.delta_base;
+
+  switch (number)
+    {
+    case 1:
+      break;
+
+    case 2:
+      ppem += 16;
+      break;
+
+    case 3:
+      ppem += 32;
+      break;
+    }
+
+  /* Don't apply the delta if the ppem size doesn't match.  */
+
+  if (interpreter->ppem != ppem)
+    return;
+
+  /* Now, determine the magnitude of the movement and find the
+     delta.  */
+
+  switch (operand & 0xf)
+    {
+    case 0:
+      delta = -8;
+      break;
+
+    case 1:
+      delta = -7;
+      break;
+
+    case 2:
+      delta = -6;
+      break;
+
+    case 3:
+      delta = -5;
+      break;
+
+    case 4:
+      delta = -4;
+      break;
+
+    case 5:
+      delta = -3;
+      break;
+
+    case 6:
+      delta = -2;
+      break;
+
+    case 7:
+      delta = -1;
+      break;
+
+    case 8:
+      delta = 1;
+      break;
+
+    case 9:
+      delta = 2;
+      break;
+
+    case 10:
+      delta = 3;
+      break;
+
+    case 11:
+      delta = 4;
+      break;
+
+    case 12:
+      delta = 5;
+      break;
+
+    case 13:
+      delta = 6;
+      break;
+
+    case 14:
+      delta = 7;
+      break;
+
+    case 15:
+      delta = 8;
+      break;
+
+      /* To pacify -fanalyzer.  */
+    default:
+      abort ();
+    }
+
+  /* Now, scale up the delta by the step size, which is determined by
+     the delta shift.  */
+  delta *= 1l << (6 - interpreter->state.delta_shift);
+
+  /* Move the point.  */
+  sfnt_check_zp0 (interpreter, index);
+  sfnt_move_zp0 (interpreter, index, 1, delta);
+}
+
+/* Needed by sfnt_interpret_call.  */
+static void sfnt_interpret_run (struct sfnt_interpreter *,
+                               enum sfnt_interpreter_run_context);
+
+/* Call DEFINITION inside INTERPRETER.
+
+   Save INTERPRETER->IP, INTERPRETER->instructions, and
+   INTERPRETER->num_instructions onto the C stack.
+
+   Then, load the instructions in DEFINITION, and run the interpreter
+   again with the context CONTEXT.
+
+   Finally, restore all values.  */
+
+static void
+sfnt_interpret_call (struct sfnt_interpreter_definition *definition,
+                    struct sfnt_interpreter *interpreter,
+                    enum sfnt_interpreter_run_context context)
+{
+  uint16_t num_instructions;
+  int IP;
+  unsigned char *instructions;
+
+  /* Check that no recursion is going on.  */
+  if (interpreter->call_depth++ >= 128)
+    TRAP ("CALL called CALL more than 127 times");
+
+  /* Save the old IP, instructions and number of instructions.  */
+  num_instructions = interpreter->num_instructions;
+  IP = interpreter->IP;
+  instructions = interpreter->instructions;
+
+  /* Load and run the definition.  */
+  interpreter->num_instructions = definition->instruction_count;
+  interpreter->instructions = definition->instructions;
+  interpreter->IP = 0;
+  sfnt_interpret_run (interpreter, context);
+
+  /* Restore the old values.  */
+  interpreter->num_instructions = num_instructions;
+  interpreter->IP = IP;
+  interpreter->instructions = instructions;
+  interpreter->call_depth--;
+}
+
+/* Set the detailed rounding state in interpreter, on behalf of either
+   an SROUND or S45ROUND instruction that has been given the operand
+   OPERAND.
+
+   Use the specified GRID_PERIOD to determine the period.  It is is a
+   18.14 fixed point number, but the rounding state set will be a 26.6
+   fixed point number.  */
+
+static void
+sfnt_set_srounding_state (struct sfnt_interpreter *interpreter,
+                         uint32_t operand, sfnt_f18dot14 grid_period)
+{
+  sfnt_f18dot14 period, phase, threshold;
+
+  /* The most significant 2 bits in the 8 bit OPERAND determine the
+     period.  */
+
+  switch ((operand & 0xc0) >> 6)
+    {
+    case 0:
+      period = grid_period / 2;
+      break;
+
+    case 1:
+      period = grid_period;
+      break;
+
+    case 2:
+      period = grid_period * 2;
+      break;
+
+    case 3:
+    default:
+      TRAP ("reserved period given to SROUND");
+    }
+
+  /* The next two bits determine the phase.  */
+
+  switch ((operand & 0x30) >> 4)
+    {
+    case 0:
+      phase = 0;
+      break;
+
+    case 1:
+      phase = period / 4;
+      break;
+
+    case 2:
+      phase = period / 2;
+      break;
+
+    case 3:
+    default:
+      phase = period * 3 / 2;
+      break;
+    }
+
+  /* And the least significant 4 bits determine the threshold.  */
+
+  if (operand & 0x0f)
+    threshold = (((int) (operand & 0x0f) - 4)
+                * period / 8);
+  else
+    threshold = period - 1;
+
+  /* Now extend these values to 26.6 format and set them.  */
+  interpreter->period = period >> 8;
+  interpreter->phase = phase >> 8;
+  interpreter->threshold = threshold >> 8;
+}
+
+/* Move to the next opcode in INTERPRETER's instruction stream.
+   Value is the opcode originally at INTERPRETER->IP.  */
+
+static unsigned char
+sfnt_skip_code (struct sfnt_interpreter *interpreter)
+{
+  unsigned char opcode;
+  int nbytes;
+
+  if (interpreter->IP == interpreter->num_instructions)
+    TRAP ("IP at end of instruction stream");
+
+  /* Load opcode at IP.  */
+  opcode = interpreter->instructions[interpreter->IP];
+
+  if (opcode == 0x40 || opcode == 0x41)
+    {
+      if (interpreter->IP + 1 >= interpreter->num_instructions)
+       TRAP ("Missing arg to NPUSHB or NPUSHW");
+
+      /* Figure out how many bytes or words to push.  */
+
+      nbytes = interpreter->instructions[interpreter->IP + 1];
+
+      if (opcode == 0x41)
+       nbytes *= 2;
+
+      if (interpreter->IP + 2 + nbytes > interpreter->num_instructions)
+       TRAP ("args to NPUSH instruction lie outside IS");
+
+      /* Increment IP by so much.  */
+      interpreter->IP += 2 + nbytes;
+    }
+  else if (opcode >= 0xb0 && opcode <= 0xb7)
+    {
+      nbytes = opcode - 0xb0 + 1;
+
+      if (interpreter->IP + 1 + nbytes > interpreter->num_instructions)
+       TRAP ("args to PUSHB instruction lie outide IS");
+
+      interpreter->IP += 1 + nbytes;
+    }
+  else if (opcode >= 0xb8 && opcode <= 0xbf)
+    {
+      nbytes = (opcode - 0xb8 + 1) * 2;
+
+      if (interpreter->IP + 1 + nbytes > interpreter->num_instructions)
+       TRAP ("args to PUSHW instruction lie outide IS");
+
+      interpreter->IP += 1 + nbytes;
+    }
+  else
+    interpreter->IP++;
+
+  return opcode;
+}
+
+/* Interpret the unimplemented operation OPCODE using INTERPRETER, and
+   the context WHY.  If there is no instruction definition named
+   OPCODE, trap.  */
+
+static void
+sfnt_interpret_unimplemented (struct sfnt_interpreter *interpreter,
+                             unsigned char opcode,
+                             enum sfnt_interpreter_run_context why)
+{
+  uint32_t i;
+  struct sfnt_interpreter_definition *def;
+
+  for (i = 0; i < interpreter->instruction_defs_size; ++i)
+    {
+      def = &interpreter->instruction_defs[i];
+
+      if (def->opcode == opcode)
+       {
+         if (!def->instructions)
+           TRAP ("** ERROR ** malformed internal instruction"
+                 " definition");
+
+         sfnt_interpret_call (def, interpreter, why);
+         return;
+       }
+    }
+
+  TRAP ("invalid instruction");
+}
+
+/* Start a function definition in INTERPRETER, with the function
+   opcode OPCODE.  */
+
+static void
+sfnt_interpret_fdef (struct sfnt_interpreter *interpreter,
+                    uint32_t opcode)
+{
+  size_t i, num_fdefs;
+  int IP;
+  unsigned char instruction;
+
+  IP = interpreter->IP + 1;
+  num_fdefs = 0;
+
+  /* Now find an ENDF.  */
+
+  while ((instruction = sfnt_skip_code (interpreter)) != 0x2d)
+    {
+      if (interpreter->IP >= interpreter->num_instructions)
+       TRAP ("missing ENDF");
+
+      /* If this is an FDEF or IDEF instruction, increment num_fdefs.
+        Prohibit nested FDEFs or IDEFS.  */
+      if (instruction == 0x2c || instruction == 0x89)
+       ++num_fdefs;
+
+      if (num_fdefs > 1)
+       TRAP ("IDEF or FDEF before ENDF");
+    }
+
+  /* ENDF has been found.  Now save the function definition.  Try to
+     find an existing function definition with this opcode.  If that
+     fails, make i the first available function definition.  */
+
+  for (i = 0; i < interpreter->function_defs_size; ++i)
+    {
+      if (interpreter->function_defs[i].opcode == opcode
+         || !interpreter->function_defs[i].instructions)
+       break;
+    }
+
+  if (i == interpreter->function_defs_size)
+    TRAP ("number of fdefs exceeded maxp->max_function_defs");
+
+  /* Save the opcode of this function definition.  */
+  interpreter->function_defs[i].opcode = opcode;
+
+  /* Make sure to ignore the trailing ENDF instruction.  */
+  interpreter->function_defs[i].instruction_count
+    = interpreter->IP - IP - 1;
+
+  /* Now save a pointer to the instructions.  */
+  interpreter->function_defs[i].instructions = interpreter->instructions + IP;
+}
+
+/* Start an instruction definition in INTERPRETER, with the
+   instruction opcode OPCODE.  */
+
+static void
+sfnt_interpret_idef (struct sfnt_interpreter *interpreter,
+                    uint32_t opcode)
+{
+  size_t i, num_fdefs;
+  int IP;
+  unsigned char instruction;
+
+  IP = interpreter->IP + 1;
+  num_fdefs = 0;
+
+  /* Now find an ENDF.  */
+
+  while ((instruction = sfnt_skip_code (interpreter)) != 0x2d)
+    {
+      if (interpreter->IP >= interpreter->num_instructions)
+       TRAP ("missing ENDF");
+
+      /* If this is an FDEF or IDEF instruction, increment num_fdefs.
+        Prohibit nested FDEFs or IDEFS.  */
+      if (instruction == 0x2c || instruction == 0x89)
+       ++num_fdefs;
+
+      if (num_fdefs > 1)
+       TRAP ("IDEF or FDEF before ENDF");
+    }
+
+  /* ENDF has been found.  Now save the instruction definition.  Try to
+     find an existing instruction definition with this opcode.  If that
+     fails, make i the first available instruction definition.  */
+
+  for (i = 0; i < interpreter->instruction_defs_size; ++i)
+    {
+      if (interpreter->instruction_defs[i].opcode == opcode
+         || !interpreter->instruction_defs[i].instructions)
+       break;
+    }
+
+  if (i == interpreter->instruction_defs_size)
+    TRAP ("number of defs exceeded maxp->max_instruction_defs");
+
+  /* Save the opcode of this instruction definition.  */
+  interpreter->instruction_defs[i].opcode = opcode;
+
+  /* Make sure to ignore the trailing ENDF instruction.  */
+  interpreter->instruction_defs[i].instruction_count
+    = interpreter->IP - IP - 1;
+
+  /* Now save a pointer to the instructions.  */
+  interpreter->instruction_defs[i].instructions
+    = interpreter->instructions + IP;
+}
+
+/* Interpret the specified conditional at INTERPRETER->IP.
+   If CONDITION, evaluate this branch up until the next ELSE or ENDIF.
+   Else, evaluate the branch from a matching ELSE condition, if
+   one exists.  */
+
+static void
+sfnt_interpret_if (struct sfnt_interpreter *interpreter,
+                  bool condition)
+{
+  int nifs;
+  bool need_break;
+  unsigned char opcode;
+
+  if (condition)
+    {
+      interpreter->IP++;
+      return;
+    }
+
+  /* Number of ifs.  */
+  nifs = 0;
+  need_break = false;
+
+  /* Break past the matching else condition.  */
+  do
+    {
+      /* Load the current opcode, then increase IP.  */
+      opcode = sfnt_skip_code (interpreter);
+
+      if (interpreter->IP >= interpreter->num_instructions)
+       break;
+
+      switch (opcode)
+       {
+       case 0x58: /* IF */
+         nifs++;
+         break;
+
+       case 0x1B: /* ELSE */
+         if (nifs == 1)
+           need_break = true;
+
+         break;
+
+       case 0x59: /* EIF */
+         nifs--;
+         if (nifs == 0)
+           need_break = true;
+
+         break;
+       }
+    }
+  while (!need_break);
+}
+
+/* Interpret the specified ELSE branch at INTERPRETER->IP.
+   Evaluate starting from a matching ENDIF instruction.
+
+   If IF has set INTERPRETER->IP to a code within an ELSE branch, this
+   will not be called.  */
+
+static void
+sfnt_interpret_else (struct sfnt_interpreter *interpreter)
+{
+  int nifs;
+  unsigned char opcode;
+
+  /* Number of ifs.  */
+  nifs = 1;
+
+  /* Break past the matching ENDIF condition.  */
+  do
+    {
+      /* Load the current opcode, then increase IP.  */
+      opcode = sfnt_skip_code (interpreter);
+
+      if (interpreter->IP >= interpreter->num_instructions)
+       break;
+
+      switch (opcode)
+       {
+       case 0x58: /* IF */
+         nifs++;
+         break;
+
+       case 0x59: /* EIF */
+         nifs--;
+
+         break;
+       }
+    }
+  while (nifs > 0);
+}
+
+/* ``Add engine compensation to X''.  Since engine compensation is not
+   implemented here, this simply returns X.  INTERPRETER is
+   unused.  */
+
+static sfnt_f26dot6
+sfnt_round_none (sfnt_f26dot6 x, struct sfnt_interpreter *interpreter)
+{
+  return x;
+}
+
+/* Round X to the grid after adding engine compensation.  Return the
+   result.  INTERPRETER is unused.  */
+
+static sfnt_f26dot6
+sfnt_round_to_grid (sfnt_f26dot6 x, struct sfnt_interpreter *interpreter)
+{
+  return sfnt_round_f26dot6 (x);
+}
+
+/* Round X to the nearest half integer or integer and return the
+   result.  INTERPRETER is unused.  */
+
+static sfnt_f26dot6
+sfnt_round_to_double_grid (sfnt_f26dot6 x,
+                          struct sfnt_interpreter *interpreter)
+{
+  return (x + 020) & ~037;
+}
+
+/* Take the floor of X and return the result.  INTERPRETER is
+   unused.  */
+
+static sfnt_f26dot6
+sfnt_round_down_to_grid (sfnt_f26dot6 x,
+                        struct sfnt_interpreter *interpreter)
+{
+  return sfnt_floor_f26dot6 (x);
+}
+
+/* Take the ceiling of X and return the result.  INTERPRETER is
+   unused.  */
+
+static sfnt_f26dot6
+sfnt_round_up_to_grid (sfnt_f26dot6 x,
+                      struct sfnt_interpreter *interpreter)
+{
+  return sfnt_ceil_f26dot6 (x);
+}
+
+/* Round X to only the nearest half integer and return the result.
+   INTERPRETER is unused.  */
+
+static sfnt_f26dot6
+sfnt_round_to_half_grid (sfnt_f26dot6 x,
+                        struct sfnt_interpreter *interpreter)
+{
+  return sfnt_floor_f26dot6 (x) + 32;
+}
+
+/* Round X using the detailed rounding information ``super rounding
+   state'' in INTERPRETER.  Value is the result.  */
+
+static sfnt_f26dot6
+sfnt_round_super (sfnt_f26dot6 x,
+                 struct sfnt_interpreter *interpreter)
+{
+  sfnt_f26dot6 value;
+
+  /* Compute the rounded value.  */
+  value = sfnt_add ((interpreter->threshold
+                    - interpreter->phase), x);
+  value = sfnt_add (value & -interpreter->period,
+                   interpreter->phase);
+
+  /* Remember that since the phase is specified by font instructions,
+     it is possible for the sign to be changed.  In that case, return
+     the phase itself.  */
+
+  return value < 0 ? interpreter->phase : value;
+}
+
+/* Round X using the detailed rounding information ``super rounding
+   state'' in INTERPRETER, but suitably for values that are multiples
+   of the sqrt of 2.  Value is the result.  */
+
+static sfnt_f26dot6
+sfnt_round_super45 (sfnt_f26dot6 x,
+                   struct sfnt_interpreter *interpreter)
+{
+  sfnt_f26dot6 value;
+
+  /* Compute the rounded value.  */
+
+  value = ((sfnt_add (x, (interpreter->threshold
+                         - interpreter->phase))
+           / interpreter->period)
+          * interpreter->period);
+  value = sfnt_add (value, interpreter->phase);
+
+  /* Remember that since the phase is specified by font instructions,
+     it is possible for the sign to be changed.  In that case, return
+     the phase itself.  */
+
+  return value < 0 ? interpreter->phase : value;
+}
+
+/* Project the specified vector VX and VY onto the unit vector that is
+   INTERPRETER's projection vector, assuming that INTERPRETER's
+   projection vector is on the X axis.
+
+   Value is the magnitude of the projected vector.  */
+
+static sfnt_f26dot6
+sfnt_project_onto_x_axis_vector (sfnt_f26dot6 vx, sfnt_f26dot6 vy,
+                                struct sfnt_interpreter *interpreter)
+{
+  return vx;
+}
+
+/* Project the specified vector VX and VY onto the unit vector that is
+   INTERPRETER's projection vector, assuming that INTERPRETER's
+   projection vector is on the Y axis.
+
+   Value is the magnitude of the projected vector.  */
+
+static sfnt_f26dot6
+sfnt_project_onto_y_axis_vector (sfnt_f26dot6 vx, sfnt_f26dot6 vy,
+                                struct sfnt_interpreter *interpreter)
+{
+  return vy;
+}
+
+/* Calculate AX * BX + AY * BY divided by 16384.  */
+
+static int32_t
+sfnt_dot_fix_14 (int32_t ax, int32_t ay, int bx, int by)
+{
+#ifndef INT64_MAX
+  int32_t m, s, hi1, hi2, hi;
+  uint32_t l, lo1, lo2, lo;
+
+
+  /* Compute ax*bx as 64-bit value.  */
+  l = (uint32_t) ((ax & 0xffffu) * bx);
+  m = (ax >> 16) * bx;
+
+  lo1 = l + ((uint32_t) m << 16);
+  hi1 = (m >> 16) + ((int32_t) l >> 31) + (lo1 < l);
+
+  /* Compute ay*by as 64-bit value.  */
+  l = (uint32_t) ((ay & 0xffffu) * by);
+  m = (ay >> 16) * by;
+
+  lo2 = l + ((uint32_t) m << 16);
+  hi2 = (m >> 16) + ((int32_t) l >> 31) + (lo2 < l);
+
+  /* Add them.  */
+  lo = lo1 + lo2;
+  hi = hi1 + hi2 + (lo < lo1);
+
+  /* Divide the result by 2^14 with rounding.  */
+  s = hi >> 31;
+  l = lo + (uint32_t) s;
+  hi += s + (l < lo);
+  lo = l;
+
+  l = lo + 0x2000u;
+  hi += (l < lo);
+
+  return (int32_t) (((uint32_t) hi << 18) | (l >> 14));
+#else
+  int64_t xx, yy;
+
+  xx = (int64_t) ax * bx;
+  yy = (int64_t) ay * by;
+
+  xx += yy;
+  yy = xx >> 63;
+  xx += 0x2000 + yy;
+
+  return (int32_t) (xx / (2 << 14));
+#endif
+}
+
+/* Project the specified vector VX and VY onto the unit vector that is
+   INTERPRETER's projection vector, making only the assumption that the
+   projection vector is a valid unit vector.
+
+   Value is the magnitude of the projected vector.  */
+
+static sfnt_f26dot6
+sfnt_project_onto_any_vector (sfnt_f26dot6 vx, sfnt_f26dot6 vy,
+                             struct sfnt_interpreter *interpreter)
+{
+  return sfnt_dot_fix_14 (vx, vy,
+                         interpreter->state.projection_vector.x,
+                         interpreter->state.projection_vector.y);
+}
+
+/* Project the specified vector VX and VY onto the unit vector that is
+   INTERPRETER's dual projection vector, making only the assumption
+   that the dual projection vector is a valid unit vector.
+
+   The dual projection vector is a vector that is normally the
+   projection vector, but can be set using the original unscaled
+   coordinates of two points as well.
+
+   Value is the magnitude of the projected vector.  */
+
+static sfnt_f26dot6
+sfnt_dual_project_onto_any_vector (sfnt_f26dot6 vx, sfnt_f26dot6 vy,
+                                  struct sfnt_interpreter *interpreter)
+{
+  return sfnt_dot_fix_14 (vx, vy,
+                         interpreter->state.dual_projection_vector.x,
+                         interpreter->state.dual_projection_vector.y);
+}
+
+/* Move N points at *X, *Y by DISTANCE along INTERPRETER's freedom
+   vector.  Set N flags in *FLAGS where appropriate and when non-NULL.
+
+   Assume both vectors are aligned to the X axis.  */
+
+static void
+sfnt_move_x (sfnt_f26dot6 *restrict x, sfnt_f26dot6 *restrict y,
+            size_t n, struct sfnt_interpreter *interpreter,
+            sfnt_f26dot6 distance, unsigned char *flags)
+{
+  while (n--)
+    {
+      *x = sfnt_add (*x, distance);
+      x++;
+
+      if (flags)
+       *flags++ |= SFNT_POINT_TOUCHED_X;
+    }
+}
+
+/* Move N points at *X, *Y by DISTANCE along INTERPRETER's freedom
+   vector.  Set N flags in *FLAGS where appropriate and when non-NULL.
+
+   Assume both vectors are aligned to the Y axis.  */
+
+static void
+sfnt_move_y (sfnt_f26dot6 *restrict x, sfnt_f26dot6 *restrict y,
+            size_t n, struct sfnt_interpreter *interpreter,
+            sfnt_f26dot6 distance, unsigned char *flags)
+{
+  while (n--)
+    {
+      *y = sfnt_add (*y, distance);
+      y++;
+
+      if (flags)
+       *flags++ |= SFNT_POINT_TOUCHED_Y;
+    }
+}
+
+/* Move N points at *X, *Y by DISTANCE along INTERPRETER's freedom
+   vector.  Set N flags in *FLAGS where appropriate and when
+   non-NULL.  */
+
+static void
+sfnt_move (sfnt_f26dot6 *restrict x, sfnt_f26dot6 *restrict y,
+          size_t n, struct sfnt_interpreter *interpreter,
+          sfnt_f26dot6 distance, unsigned char *flags)
+{
+  sfnt_f26dot6 versor, k;
+  sfnt_f2dot14 dot_product;
+  size_t num;
+
+  dot_product = interpreter->state.vector_dot_product;
+
+  /* If the vectors are orthogonal, it is impossible to move anywhere,
+     so simply return.  */
+  if (!dot_product)
+    return;
+
+  /* Not actually 26.6, but the multiply-divisions below cancel each
+     other out, so the result is 26.6.  */
+  versor = interpreter->state.freedom_vector.x;
+
+  if (versor)
+    {
+      /* Move along X axis, converting the distance to the freedom
+        vector.  */
+      num = n;
+      k = sfnt_multiply_divide_signed (distance,
+                                      versor,
+                                      dot_product);
+
+      while (num--)
+       {
+         *x = sfnt_add (*x, k);
+         x++;
+
+         if (flags)
+           *flags++ |= SFNT_POINT_TOUCHED_X;
+       }
+    }
+
+  versor = interpreter->state.freedom_vector.y;
+
+  if (versor)
+    {
+      /* Move along X axis, converting the distance to the freedom
+        vector.  */
+      num = n;
+      k = sfnt_multiply_divide_signed (distance,
+                                      versor,
+                                      dot_product);
+
+      while (num--)
+       {
+         *y = sfnt_add (*y, k);
+         y++;
+
+         if (flags)
+           *flags++ |= SFNT_POINT_TOUCHED_Y;
+       }
+    }
+}
+
+/* Validate the graphics state GS.
+   Establish function pointers for rounding and projection.
+   Establish dot product used to convert vector distances between
+   each other.  */
+
+static void
+sfnt_validate_gs (struct sfnt_graphics_state *gs)
+{
+  /* Establish the function used for rounding based on the round
+     state.  */
+
+  switch (gs->round_state)
+    {
+    case 5: /* Rounding off.  */
+      gs->round = sfnt_round_none;
+      break;
+
+    case 0: /* Round to half grid.  */
+      gs->round = sfnt_round_to_half_grid;
+      break;
+
+    case 1: /* Round to grid.  */
+      gs->round = sfnt_round_to_grid;
+      break;
+
+    case 2: /* Round to double grid.  */
+      gs->round = sfnt_round_to_double_grid;
+      break;
+
+    case 4: /* Round up to grid.  */
+      gs->round = sfnt_round_up_to_grid;
+      break;
+
+    case 3: /* Round down to grid.  */
+      gs->round = sfnt_round_down_to_grid;
+      break;
+
+    case 6: /* Fine grained rounding.  */
+      gs->round = sfnt_round_super;
+      break;
+
+    case 7: /* Fine grained rounding 45 degree variant.  */
+      gs->round = sfnt_round_super45;
+      break;
+    }
+
+  /* Establish the function used for vector projection.
+     When the projection vector is an axis vector, a fast
+     version can be used.  */
+
+  if (gs->projection_vector.x == 040000)
+    gs->project = sfnt_project_onto_x_axis_vector;
+  else if (gs->projection_vector.y == 040000)
+    gs->project = sfnt_project_onto_y_axis_vector;
+  else
+    gs->project = sfnt_project_onto_any_vector;
+
+  /* Do the same for the dual projection vector.  */
+
+  if (gs->dual_projection_vector.x == 040000)
+    gs->dual_project = sfnt_project_onto_x_axis_vector;
+  else if (gs->dual_projection_vector.y == 040000)
+    gs->dual_project = sfnt_project_onto_y_axis_vector;
+  else
+    gs->dual_project = sfnt_dual_project_onto_any_vector;
+
+  /* Compute dot product of the freedom and projection vectors.
+     Handle the common case where the freedom vector is aligned
+     to an axis.  */
+
+  if (gs->freedom_vector.x == 040000)
+    gs->vector_dot_product = gs->projection_vector.x;
+  else if (gs->freedom_vector.y == 040000)
+    gs->vector_dot_product = gs->projection_vector.y;
+  else
+    /* Actually calculate the dot product.  */
+    gs->vector_dot_product = ((((long) gs->projection_vector.x
+                               * gs->freedom_vector.x)
+                              + ((long) gs->projection_vector.y
+                                 * gs->freedom_vector.y))
+                             / 16384);
+
+  /* Now figure out which function to use to move distances.  Handle
+     the common case where both the freedom and projection vectors are
+     aligned to an axis.  */
+
+  if (gs->freedom_vector.x == 040000
+      && gs->projection_vector.x == 040000)
+    gs->move = sfnt_move_x;
+  else if (gs->freedom_vector.y == 040000
+          && gs->projection_vector.y == 040000)
+    gs->move = sfnt_move_y;
+  else
+    gs->move = sfnt_move;
+}
+
+/* Set the X and Y versors of the freedom vector of INTERPRETER's
+   graphics state to the specified X and Y, in 2.14 fixed point
+   format.  */
+
+static void
+sfnt_set_freedom_vector (struct sfnt_interpreter *interpreter,
+                        sfnt_f2dot14 x, sfnt_f2dot14 y)
+{
+  interpreter->state.freedom_vector.x = x;
+  interpreter->state.freedom_vector.y = y;
+
+  sfnt_validate_gs (&interpreter->state);
+}
+
+/* Set the X and Y versors of the projection vector of INTERPRETER's
+   graphics state to the specified X and Y, in 2.14 fixed point
+   format.  */
+
+static void
+sfnt_set_projection_vector (struct sfnt_interpreter *interpreter,
+                           sfnt_f2dot14 x, sfnt_f2dot14 y)
+{
+  interpreter->state.projection_vector.x = x;
+  interpreter->state.projection_vector.y = y;
+  interpreter->state.dual_projection_vector.x = x;
+  interpreter->state.dual_projection_vector.y = y;
+
+  sfnt_validate_gs (&interpreter->state);
+}
+
+/* Interpret an SHZ instruction with the specified OPCODE.  Like
+   sfnt_interpret_shc, but do the move for each point in the entire
+   specified ZONE.  */
+
+static void
+sfnt_interpret_shz (struct sfnt_interpreter *interpreter,
+                   uint32_t zone, unsigned int opcode)
+{
+  sfnt_f26dot6 x, y, original_x, original_y;
+  sfnt_f26dot6 magnitude;
+
+  if (zone != 0 && !interpreter->glyph_zone)
+    /* There are no points in the glyph zone.  */
+    return;
+
+  if (opcode == 0x37)
+    sfnt_address_zp0 (interpreter, interpreter->state.rp1,
+                     &x, &y, &original_x, &original_y);
+  else
+    sfnt_address_zp1 (interpreter, interpreter->state.rp2,
+                     &x, &y, &original_x, &original_y);
+
+  magnitude = sfnt_project_vector (interpreter,
+                                  sfnt_sub (x, original_x),
+                                  sfnt_sub (y, original_y));
+
+  if (zone == 0)
+    sfnt_move_twilight_zone (interpreter, 0,
+                            interpreter->twilight_zone_size,
+                            magnitude);
+  else
+    sfnt_move_glyph_zone (interpreter, 0,
+                         interpreter->glyph_zone->num_points,
+                         magnitude);
+}
+
+/* Interpret an SHC instruction with the specified OPCODE and CONTOUR.
+   Like sfnt_interpret_shp, but do the move for each point in the
+   specified contour.  */
+
+static void
+sfnt_interpret_shc (struct sfnt_interpreter *interpreter,
+                   uint32_t contour, unsigned int opcode)
+{
+  sfnt_f26dot6 x, y, original_x, original_y;
+  sfnt_f26dot6 magnitude;
+  uint16_t reference_point;
+  size_t start, end, start1, end1, n;
+
+  if (!interpreter->glyph_zone)
+    TRAP ("SHC without glyph zone");
+
+  /* Check that the contour is within bounds.  */
+  if (contour >= interpreter->glyph_zone->num_contours)
+    TRAP ("contour out of bounds");
+
+  /* Figure out the magnitude of the change, measured from the
+     projection vector.  */
+
+  if (opcode == 0x35)
+    sfnt_address_zp0 (interpreter,
+                     (reference_point = interpreter->state.rp1),
+                     &x, &y, &original_x, &original_y);
+  else
+    sfnt_address_zp1 (interpreter,
+                     (reference_point = interpreter->state.rp2),
+                     &x, &y, &original_x, &original_y);
+
+  magnitude = sfnt_project_vector (interpreter,
+                                  sfnt_sub (x, original_x),
+                                  sfnt_sub (y, original_y));
+
+  /* Now obtain the start and end of the contour.
+     Verify that both are valid.  */
+
+  if (contour)
+    start = interpreter->glyph_zone->contour_end_points[contour - 1] + 1;
+  else
+    start = 0;
+
+  end = interpreter->glyph_zone->contour_end_points[contour];
+
+  if (start > end || end >= interpreter->glyph_zone->num_points)
+    TRAP ("invalid contour data in glyph");
+
+  /* If the reference point falls between end and start, split the
+     range formed by end and start at the reference point and keep the
+     latter intact.  */
+
+  if (start <= reference_point && reference_point <= end)
+    {
+      /* Do the points between start and rpN.  */
+      start1 = start;
+      end1   = reference_point - 1;
+
+      if (start1 <= end1)
+       sfnt_move_glyph_zone (interpreter, start1,
+                             end1 - start1 + 1, magnitude);
+
+      /* Now the points between rpN + 1 and end.  */
+      start1 = reference_point + 1;
+      end1   = end;
+
+      if (start1 <= end1)
+       sfnt_move_glyph_zone (interpreter, start1,
+                             end1 - start1 + 1, magnitude);
+
+      return;
+    }
+
+  /* Compute the number of points to move.  */
+  n = end - start + 1;
+
+  /* Move that many points.  */
+  sfnt_move_glyph_zone (interpreter, start, n, magnitude);
+}
+
+/* Interpret an SHP instruction with the specified OPCODE.  Move a
+   popped point in ZP2 along the freedom vector by the distance
+   between a specified point from its original position, which is RP1
+   in ZP0 if OPCODE is 0x33, and RP2 in ZP1 if OPCODE is 0x32.
+
+   Repeat for the number of iterations specified by a prior SLOOP
+   instruction.  */
+
+static void
+sfnt_interpret_shp (struct sfnt_interpreter *interpreter,
+                   unsigned int opcode)
+{
+  sfnt_f26dot6 x, y, original_x, original_y;
+  sfnt_f26dot6 magnitude;
+  uint32_t point;
+
+  /* Figure out the magnitude of the change, measured from the
+     projection vector.  */
+
+  if (opcode == 0x33)
+    sfnt_address_zp0 (interpreter, interpreter->state.rp1,
+                     &x, &y, &original_x, &original_y);
+  else
+    sfnt_address_zp1 (interpreter, interpreter->state.rp2,
+                     &x, &y, &original_x, &original_y);
+
+  magnitude = sfnt_project_vector (interpreter,
+                                  sfnt_sub (x, original_x),
+                                  sfnt_sub (y, original_y));
+
+  /* Now project it onto the freedom vector and move the point that
+     much for loop variable times.  */
+
+  while (interpreter->state.loop--)
+    {
+      point = POP ();
+
+      sfnt_check_zp2 (interpreter, point);
+      sfnt_move_zp2 (interpreter, point, 1, magnitude);
+    }
+
+  /* Restore interpreter->state.loop to 1.  */
+  interpreter->state.loop = 1;
+}
+
+#define load_point(p)                          \
+  (opcode == 0x31                              \
+   ? interpreter->glyph_zone->x_current[p]     \
+   : interpreter->glyph_zone->y_current[p])
+
+#define store_point(p, val)                            \
+  (opcode == 0x31                                      \
+   ? (interpreter->glyph_zone->x_current[p] = (val))   \
+   : (interpreter->glyph_zone->y_current[p] = (val)))
+
+#define load_original(p)                       \
+  (opcode == 0x31                              \
+   ? interpreter->glyph_zone->x_points[p]      \
+   : interpreter->glyph_zone->y_points[p])
+
+#define IUP_SINGLE_PAIR()                                              \
+  /* Now make touch_start the first point before, i.e. the first       \
+     touched point in this pair.  */                                   \
+                                                                       \
+  if (touch_start == start)                                            \
+    touch_start = end;                                                 \
+  else                                                                 \
+    touch_start = touch_start - 1;                                     \
+                                                                       \
+  /* Set point_min and point_max based on which glyph is at a          \
+     lower value.  */                                                  \
+                                                                       \
+  if (load_original (touch_start) < load_original (touch_end))         \
+    {                                                                  \
+      point_min = touch_start;                                         \
+      point_max = touch_end;                                           \
+    }                                                                  \
+  else                                                                 \
+    {                                                                  \
+      point_max = touch_start;                                         \
+      point_min = touch_end;                                           \
+    }                                                                  \
+                                                                       \
+  min_pos = load_point (point_min);                                    \
+  max_pos = load_point (point_max);                                    \
+                                                                       \
+  /* This is needed for interpolation.  */                             \
+  original_max_pos = load_original (point_max);                                
\
+  original_min_pos = load_original (point_min);                                
\
+                                                                       \
+  /* Now process points between touch_start and touch_end.  */         \
+                                                                       \
+  i = touch_start + 1;                                                 \
+                                                                       \
+  /* touch_start might be the last point in the contour.  */           \
+                                                                       \
+  if (i > end)                                                         \
+    i = start;                                                         \
+                                                                       \
+  while (i != touch_end)                                               \
+    {                                                                  \
+      /* Movement is always relative to the original position of       \
+        the point.  */                                                 \
+      position = load_original (i);                                    \
+                                                                       \
+      /* If i is in between touch_start and touch_end...  */           \
+      if (position >= original_min_pos                                 \
+         && position <= original_max_pos)                              \
+       {                                                               \
+         /* Handle the degenerate case where original_min_pos and      \
+            original_max_pos have not changed by placing the point in  \
+            the middle.  */                                            \
+         if (original_min_pos == original_max_pos)                     \
+           ratio = 077777;                                             \
+         else                                                          \
+           /* ... preserve the ratio of i between min_pos and          \
+              max_pos...  */                                           \
+           ratio = sfnt_div_fixed ((sfnt_sub (position,                \
+                                              original_min_pos)        \
+                                    * 1024),                           \
+                                   (sfnt_sub (original_max_pos,        \
+                                              original_min_pos)        \
+                                    * 1024));                          \
+                                                                       \
+         delta = sfnt_sub (max_pos, min_pos);                          \
+         delta = sfnt_mul_fixed (ratio, delta);                        \
+         store_point (i, sfnt_add (min_pos, delta));                   \
+       }                                                               \
+      else                                                             \
+       {                                                               \
+         /* ... otherwise, move i by how much the nearest touched      \
+            point moved.  */                                           \
+                                                                       \
+         if (position >= original_max_pos)                             \
+           delta = sfnt_sub (max_pos, original_max_pos);               \
+         else                                                          \
+           delta = sfnt_sub (min_pos, original_min_pos);               \
+                                                                       \
+         store_point (i, sfnt_add (position, delta));                  \
+       }                                                               \
+                                                                       \
+      if (++i > end)                                                   \
+       i = start;                                                      \
+    }                                                                  \
+
+/* Interpolate untouched points in the contour between and including
+   START and END inside INTERPRETER's glyph zone according to the
+   rules specified for an IUP instruction.  Perform interpolation on
+   the axis specified by OPCODE and MASK.  */
+
+static void
+sfnt_interpret_iup_1 (struct sfnt_interpreter *interpreter,
+                     size_t start, size_t end,
+                     unsigned char opcode, int mask)
+{
+  size_t point;
+  size_t touch_start, touch_end;
+  size_t first_point;
+  size_t point_min, point_max, i;
+  sfnt_f26dot6 position, min_pos, max_pos, delta, ratio;
+  sfnt_f26dot6 original_max_pos;
+  sfnt_f26dot6 original_min_pos;
+
+  /* Find the first touched point.  If none is found, simply
+     return.  */
+
+  for (point = start; point <= end; ++point)
+    {
+      if (interpreter->glyph_zone->flags[point] & mask)
+       goto touched;
+    }
+
+  goto untouched;
+
+ touched:
+
+  point = start;
+
+  /* Find the first touched point.  */
+  while (!(interpreter->glyph_zone->flags[point] & mask))
+    {
+      point++;
+
+      /* There are no touched points.  */
+      if (point > end)
+       goto untouched;
+    }
+
+  first_point = point;
+
+  while (point <= end)
+    {
+      /* Find the next untouched point.  */
+      while (interpreter->glyph_zone->flags[point] & mask)
+       {
+         point++;
+
+         if (point > end)
+           goto wraparound;
+       }
+
+      /* touch_start is now the first untouched point.  */
+      touch_start = point;
+
+      /* Find the next touched point.  */
+      while (!(interpreter->glyph_zone->flags[point] & mask))
+       {
+         point++;
+
+         /* Move back to start if point has gone past the end of the
+            contour.  */
+         if (point > end)
+           goto wraparound_1;
+       }
+
+      /* touch_end is now the next touched point.  */
+      touch_end = point;
+
+      /* Do the interpolation.  */
+      IUP_SINGLE_PAIR ();
+    }
+
+  goto untouched;
+
+ wraparound:
+  /* This is like wraparound_1, except that no untouched points have
+     yet to be found.
+
+     This means the first untouched point is start.  */
+  touch_start = start;
+
+ wraparound_1:
+  /* If point > end, wrap around.  Here, touch_start is set
+     properly, so touch_end must be first_point.  */
+
+  touch_end = first_point;
+  IUP_SINGLE_PAIR ();
+
+ untouched:
+  /* No points were touched or all points have been considered, so
+     return immediately.  */
+  return;
+}
+
+#undef load_point
+#undef store_point
+#undef load_original
+
+/* Interpret an IUP (``interpolate untouched points'') instruction.
+   INTERPRETER is the interpreter, and OPCODE is the instruction
+   number.  See the TrueType Reference Manual for more details.  */
+
+static void
+sfnt_interpret_iup (struct sfnt_interpreter *interpreter,
+                   unsigned char opcode)
+{
+  int mask;
+  size_t i, point, end, first_point;
+
+  /* Check that the zone is the glyph zone.  */
+
+  if (!interpreter->state.zp2)
+    TRAP ("trying to iup in twilight zone");
+
+  if (!interpreter->glyph_zone)
+    TRAP ("iup without loaded glyph!");
+
+  /* Figure out what axis to interpolate in based on the opcode.  */
+  if (opcode == 0x30)
+    mask = SFNT_POINT_TOUCHED_Y;
+  else
+    mask = SFNT_POINT_TOUCHED_X;
+
+  /* Now, for each contour, interpolate untouched points.  */
+  point = 0;
+  for (i = 0; i < interpreter->glyph_zone->num_contours; ++i)
+    {
+      first_point = point;
+      end = interpreter->glyph_zone->contour_end_points[i];
+
+      if (point >= interpreter->glyph_zone->num_points
+         || end >= interpreter->glyph_zone->num_points)
+       TRAP ("glyph contains out of bounds contour end point"
+             " data!");
+
+      sfnt_interpret_iup_1 (interpreter, first_point, end,
+                           opcode, mask);
+      point = end + 1;
+
+      /* Skip the subsequent phantom points, which may end up
+        intermixed with contours inside a compound glyph.  */
+
+      while (point < interpreter->glyph_zone->num_points
+            && interpreter->glyph_zone->flags[point] & SFNT_POINT_PHANTOM)
+       point++;
+    }
+}
+
+/* Interpret an MIRP instruction with the specified OPCODE in
+   INTERPRETER.  Pop a point in ZP1 and CVT index, and move the point
+   until its distance from RP0 in ZP0 is the same as in the control
+   value.  If the point lies in the twilight zone, then ``create'' it
+   as well.
+
+   OPCODE contains a great many flags.
+   They are all described in the TrueType reference manual.  */
+
+static void
+sfnt_interpret_mirp (struct sfnt_interpreter *interpreter,
+                    uint32_t opcode)
+{
+  uint32_t n;
+  uint32_t p;
+  sfnt_f26dot6 distance, delta, temp;
+  sfnt_f26dot6 current_projection, original_projection;
+  sfnt_f26dot6 x, y, org_x, org_y;
+  sfnt_f26dot6 rx, ry, org_rx, org_ry;
+
+  /* CVT index.  */
+  n = POP ();
+
+  /* Point number.  */
+  p = POP ();
+
+  /* Now get the distance from the CVT.  */
+  if (n >= interpreter->cvt_size)
+    TRAP ("cvt index out of bounds");
+
+  distance = interpreter->cvt[n];
+
+  /* Test against the single width value.  */
+
+  delta = sfnt_sub (distance,
+                   interpreter->state.single_width_value);
+
+  if (delta < 0)
+    delta = -delta;
+
+  if (delta < interpreter->state.sw_cut_in)
+    {
+      /* Use the single width instead, as the CVT entry is too
+        small.  */
+
+      if (distance >= 0)
+       distance = interpreter->state.single_width_value;
+      else
+       distance = -interpreter->state.single_width_value;
+    }
+
+  /* Load the reference point.  */
+  sfnt_address_zp0 (interpreter, interpreter->state.rp0,
+                   &rx, &ry, &org_rx, &org_ry);
+
+  /* Create the point in the twilight zone, should that be ZP1.  */
+
+  if (!interpreter->state.zp1)
+    {
+      /* Since P hasn't been loaded yet, whether or not it is valid is
+        not known.  */
+      sfnt_check_zp1 (interpreter, p);
+
+      interpreter->twilight_x[p] = rx;
+      interpreter->twilight_y[p] = ry;
+
+      temp = sfnt_mul_f2dot14 (interpreter->state.projection_vector.x,
+                              distance);
+      temp = sfnt_add (temp, org_rx);
+      interpreter->twilight_original_x[p] = temp;
+
+      temp = sfnt_mul_f2dot14 (interpreter->state.projection_vector.y,
+                              distance);
+      temp = sfnt_add (temp, org_ry);
+      interpreter->twilight_original_y[p] = temp;
+    }
+
+  /* Load P.  */
+  sfnt_address_zp1 (interpreter, p, &x, &y, &org_x, &org_y);
+
+  /* If distance would be negative and auto_flip is on, flip it.  */
+
+  original_projection = DUAL_PROJECT (org_x - org_rx,
+                                     org_y - org_ry);
+  current_projection = PROJECT (x - rx, y - ry);
+
+  if (interpreter->state.auto_flip)
+    {
+      if ((original_projection ^ distance) < 0)
+       distance = -distance;
+    }
+
+  /* Flag B means look at the cvt cut in and round the
+     distance.  */
+
+  if (opcode & 4)
+    {
+      delta = sfnt_sub (distance, original_projection);
+
+      if (delta < 0)
+       delta = -delta;
+
+      if (delta > interpreter->state.cvt_cut_in)
+       distance = original_projection;
+
+      /* Now, round the distance.  */
+      distance = sfnt_round_symmetric (interpreter, distance);
+    }
+
+  /* Flag C means look at the minimum distance.  */
+
+  if (opcode & 8)
+    {
+      if (original_projection >= 0
+         && distance < interpreter->state.minimum_distance)
+       distance = interpreter->state.minimum_distance;
+      else if (original_projection < 0
+              && distance > -interpreter->state.minimum_distance)
+       distance = -interpreter->state.minimum_distance;
+    }
+
+  /* Finally, move the point.  */
+  sfnt_move_zp1 (interpreter, p, 1,
+                sfnt_sub (distance, current_projection));
+
+  /* Set RP1 to RP0 and RP2 to the point.  If flag 3 is set, also make
+     it RP0.  */
+  interpreter->state.rp1 = interpreter->state.rp0;
+  interpreter->state.rp2 = p;
+
+  if (opcode & 16)
+    interpreter->state.rp0 = p;
+}
+
+/* Interpret an MDRP instruction with the specified OPCODE in
+   INTERPRETER.  Pop a point in ZP1, and move the point until its
+   distance from RP0 in ZP0 is the same as in the original outline.
+
+   This is almost like MIRP[abcde].
+
+   OPCODE contains a great many flags.
+   They are all described in the TrueType reference manual.  */
+
+static void
+sfnt_interpret_mdrp (struct sfnt_interpreter *interpreter,
+                    uint32_t opcode)
+{
+  uint32_t p;
+  sfnt_f26dot6 distance, delta;
+  sfnt_f26dot6 current_projection, original_projection;
+  sfnt_f26dot6 x, y, org_x, org_y;
+  sfnt_f26dot6 rx, ry, org_rx, org_ry;
+
+  /* Point number.  */
+  p = POP ();
+
+  /* Load the points.  */
+  sfnt_address_zp1 (interpreter, p, &x, &y, &org_x, &org_y);
+  sfnt_address_zp0 (interpreter, interpreter->state.rp0,
+                   &rx, &ry, &org_rx, &org_ry);
+
+  distance = DUAL_PROJECT (org_x - org_rx,
+                          org_y - org_ry);
+  original_projection = distance;
+  current_projection = PROJECT (x - rx, y - ry);
+
+  /* Test against the single width value.  */
+
+  delta = sfnt_sub (distance,
+                   interpreter->state.single_width_value);
+
+  if (delta < 0)
+    delta = -delta;
+
+  if (delta < interpreter->state.sw_cut_in)
+    {
+      /* Use the single width instead, as the CVT entry is too
+        small.  */
+
+      if (distance >= 0)
+       distance = interpreter->state.single_width_value;
+      else
+       distance = -interpreter->state.single_width_value;
+    }
+
+  /* Flag B means look at the cvt cut in and round the
+     distance.  */
+
+  if (opcode & 4)
+    {
+      delta = sfnt_sub (distance, original_projection);
+
+      if (delta < 0)
+       delta = -delta;
+
+      if (delta > interpreter->state.cvt_cut_in)
+       distance = original_projection;
+
+      /* Now, round the distance.  */
+      distance = sfnt_round_symmetric (interpreter, distance);
+    }
+
+  /* Flag C means look at the minimum distance.  */
+
+  if (opcode & 8)
+    {
+      if (original_projection >= 0
+         && distance < interpreter->state.minimum_distance)
+       distance = interpreter->state.minimum_distance;
+      else if (original_projection < 0
+              && distance > -interpreter->state.minimum_distance)
+       distance = -interpreter->state.minimum_distance;
+    }
+
+  /* Finally, move the point.  */
+  sfnt_move_zp1 (interpreter, p, 1,
+                sfnt_sub (distance, current_projection));
+
+  /* Set RP1 to RP0 and RP2 to the point.  If flag 3 is set, also make
+     it RP0.  */
+  interpreter->state.rp1 = interpreter->state.rp0;
+  interpreter->state.rp2 = p;
+
+  if (opcode & 16)
+    interpreter->state.rp0 = p;
+}
+
+/* Execute the program now loaded into INTERPRETER.
+   WHY specifies why the interpreter is being run, and is used to
+   control the behavior of instructions such IDEF[] and FDEF[].
+
+   Transfer control to INTERPRETER->trap if interpretation is aborted
+   due to an error, and set INTERPRETER->trap_reason to a string
+   describing the error.
+
+   INTERPRETER->glyph_zone should be cleared before calling this
+   function.  */
+
+static void
+sfnt_interpret_run (struct sfnt_interpreter *interpreter,
+                   enum sfnt_interpreter_run_context why)
+{
+  unsigned char opcode;
+  bool is_prep;
+
+  /* Determine whether or not this is the control value program.  */
+  is_prep = (why == SFNT_RUN_CONTEXT_CONTROL_VALUE_PROGRAM);
+
+#ifdef TEST
+  /* Allow testing control value program instructions as well.  */
+  if (why == SFNT_RUN_CONTEXT_TEST)
+    is_prep = true;
+#endif
+
+  while (interpreter->IP < interpreter->num_instructions)
+    {
+      opcode = interpreter->instructions[interpreter->IP];
+
+#ifdef TEST
+      if (interpreter->run_hook)
+       interpreter->run_hook (interpreter);
+#endif
+
+      switch (opcode)
+       {
+       case 0x00:  /* SVTCA y  */
+         SVTCAy ();
+         break;
+
+       case 0x01:  /* SVTCA x  */
+         SVTCAx ();
+         break;
+
+       case 0x02:  /* SPvTCA y */
+         SPvTCAy ();
+         break;
+
+       case 0x03:  /* SPvTCA x */
+         SPvTCAx ();
+         break;
+
+       case 0x04:  /* SFvTCA y */
+         SFvTCAy ();
+         break;
+
+       case 0x05:  /* SFvTCA x */
+         SFvTCAx ();
+         break;
+
+       case 0x06: /* SPvTL // */
+       case 0x07: /* SPvTL +  */
+         SPVTL ();
+         break;
+
+       case 0x08:  /* SFvTL // */
+       case 0x09:  /* SFvTL +  */
+         SFVTL ();
+         break;
+
+       case 0x0A:  /* SPvFS */
+         SPVFS ();
+         break;
+
+       case 0x0B:  /* SFvFS */
+         SFVFS ();
+         break;
+
+       case 0x0C:  /* GPv */
+         GPV ();
+         break;
+
+       case 0x0D:  /* GFv */
+         GFV ();
+         break;
+
+       case 0x0E:  /* SFvTPv */
+         SFVTPV ();
+         break;
+
+       case 0x0F:  /* ISECT  */
+         ISECT ();
+         break;
+
+       case 0x10:  /* SRP0 */
+         SRP0 ();
+         break;
+
+       case 0x11:  /* SRP1 */
+         SRP1 ();
+         break;
+
+       case 0x12:  /* SRP2 */
+         SRP2 ();
+         break;
+
+       case 0x13:  /* SZP0 */
+         SZP0 ();
+         break;
+
+       case 0x14:  /* SZP1 */
+         SZP1 ();
+         break;
+
+       case 0x15:  /* SZP2 */
+         SZP2 ();
+         break;
+
+       case 0x16:  /* SZPS */
+         SZPS ();
+         break;
+
+       case 0x17:  /* SLOOP */
+         SLOOP ();
+         break;
+
+       case 0x18:  /* RTG */
+         RTG ();
+         break;
+
+       case 0x19:  /* RTHG */
+         RTHG ();
+         break;
+
+       case 0x1A:  /* SMD */
+         SMD ();
+         break;
+
+       case 0x1B:  /* ELSE */
+         ELSE ();
+         break;
+
+       case 0x1C:  /* JMPR */
+         JMPR ();
+         break;
+
+       case 0x1D:  /* SCVTCI */
+         SCVTCI ();
+         break;
+
+       case 0x1E:  /* SSWCI */
+         SSWCI ();
+         break;
+
+       case 0x1F:  /* SSW */
+         SSW ();
+         break;
+
+       case 0x20:  /* DUP */
+         DUP ();
+         break;
+
+       case 0x21:  /* POP */
+         POP ();
+         break;
+
+       case 0x22:  /* CLEAR */
+         CLEAR ();
+         break;
+
+       case 0x23:  /* SWAP */
+         SWAP ();
+         break;
+
+       case 0x24:  /* DEPTH */
+         DEPTH ();
+         break;
+
+       case 0x25:  /* CINDEX */
+         CINDEX ();
+         break;
+
+       case 0x26:  /* MINDEX */
+         MINDEX ();
+         break;
+
+       case 0x27:  /* ALIGNPTS */
+         ALIGNPTS ();
+         break;
+
+       case 0x28:  /* RAW */
+         RAW ();
+         break;
+
+       case 0x29:  /* UTP */
+         UTP ();
+         break;
+
+       case 0x2A:  /* LOOPCALL */
+         LOOPCALL ();
+         break;
+
+       case 0x2B:  /* CALL */
+         CALL ();
+         break;
+
+       case 0x2C:  /* FDEF */
+         FDEF ();
+         break;
+
+       case 0x2D:  /* ENDF */
+         ENDF ();
+         break;
+
+       case 0x2E:  /* MDAP */
+       case 0x2F:  /* MDAP */
+         MDAP ();
+         break;
+
+       case 0x30:  /* IUP */
+       case 0x31:  /* IUP */
+         IUP ();
+         break;
+
+       case 0x32:  /* SHP */
+       case 0x33:  /* SHP */
+         SHP ();
+         break;
+
+       case 0x34:  /* SHC */
+       case 0x35:  /* SHC */
+         SHC ();
+         break;
+
+       case 0x36:  /* SHZ */
+       case 0x37:  /* SHZ */
+         SHZ ();
+         break;
+
+       case 0x38:  /* SHPIX */
+         SHPIX ();
+         break;
+
+       case 0x39:  /* IP    */
+         IP ();
+         break;
+
+       case 0x3A:  /* MSIRP */
+       case 0x3B:  /* MSIRP */
+         MSIRP ();
+         break;
+
+       case 0x3C:  /* ALIGNRP */
+         ALIGNRP ();
+         break;
+
+       case 0x3D:  /* RTDG */
+         RTDG ();
+         break;
+
+       case 0x3E:  /* MIAP */
+       case 0x3F:  /* MIAP */
+         MIAP ();
+         break;
+
+       case 0x40:  /* NPUSHB */
+         NPUSHB ();
+         break;
+
+       case 0x41:  /* NPUSHW */
+         NPUSHW ();
+         break;
+
+       case 0x42:  /* WS */
+         WS ();
+         break;
+
+       case 0x43:  /* RS */
+         RS ();
+         break;
+
+       case 0x44:  /* WCVTP */
+         WCVTP ();
+         break;
+
+       case 0x45:  /* RCVT */
+         RCVT ();
+         break;
+
+       case 0x46:  /* GC */
+       case 0x47:  /* GC */
+         GC ();
+         break;
+
+       case 0x48:  /* SCFS */
+         SCFS ();
+         break;
+
+       case 0x49:  /* MD */
+       case 0x4A:  /* MD */
+         MD ();
+         break;
+
+       case 0x4B:  /* MPPEM */
+         MPPEM ();
+         break;
+
+       case 0x4C:  /* MPS */
+         MPS ();
+         break;
+
+       case 0x4D:  /* FLIPON */
+         FLIPON ();
+         break;
+
+       case 0x4E:  /* FLIPOFF */
+         FLIPOFF ();
+         break;
+
+       case 0x4F:  /* DEBUG */
+         DEBUG ();
+         break;
+
+       case 0x50:  /* LT */
+         LT ();
+         break;
+
+       case 0x51:  /* LTEQ */
+         LTEQ ();
+         break;
+
+       case 0x52:  /* GT */
+         GT ();
+         break;
+
+       case 0x53:  /* GTEQ */
+         GTEQ ();
+         break;
+
+       case 0x54:  /* EQ */
+         EQ ();
+         break;
+
+       case 0x55:  /* NEQ */
+         NEQ ();
+         break;
+
+       case 0x56:  /* ODD */
+         ODD ();
+         break;
+
+       case 0x57:  /* EVEN */
+         EVEN ();
+         break;
+
+       case 0x58:  /* IF */
+         IF ();
+         break;
+
+       case 0x59:  /* EIF */
+         EIF ();
+         break;
+
+       case 0x5A:  /* AND */
+         AND ();
+         break;
+
+       case 0x5B:  /* OR */
+         OR ();
+         break;
+
+       case 0x5C:  /* NOT */
+         NOT ();
+         break;
+
+       case 0x5D:  /* DELTAP1 */
+         DELTAP1 ();
+         break;
+
+       case 0x5E:  /* SDB */
+         SDB ();
+         break;
+
+       case 0x5F:  /* SDS */
+         SDS ();
+         break;
+
+       case 0x60:  /* ADD */
+         ADD ();
+         break;
+
+       case 0x61:  /* SUB */
+         SUB ();
+         break;
+
+       case 0x62:  /* DIV */
+         DIV ();
+         break;
+
+       case 0x63:  /* MUL */
+         MUL ();
+         break;
+
+       case 0x64:  /* ABS */
+         ABS ();
+         break;
+
+       case 0x65:  /* NEG */
+         NEG ();
+         break;
+
+       case 0x66:  /* FLOOR */
+         FLOOR ();
+         break;
+
+       case 0x67:  /* CEILING */
+         CEILING ();
+         break;
+
+       case 0x68:  /* ROUND */
+       case 0x69:  /* ROUND */
+       case 0x6A:  /* ROUND */
+       case 0x6B:  /* ROUND */
+         ROUND ();
+         break;
+
+       case 0x6C:  /* NROUND */
+       case 0x6D:  /* NROUND */
+       case 0x6E:  /* NRRUND */
+       case 0x6F:  /* NROUND */
+         NROUND ();
+         break;
+
+       case 0x70:  /* WCVTF */
+         WCVTF ();
+         break;
+
+       case 0x71:  /* DELTAP2 */
+         DELTAP2 ();
+         break;
+
+       case 0x72:  /* DELTAP3 */
+         DELTAP3 ();
+         break;
+
+       case 0x73:  /* DELTAC1 */
+         DELTAC1 ();
+         break;
+
+       case 0x74:  /* DELTAC2 */
+         DELTAC2 ();
+         break;
+
+       case 0x75:  /* DELTAC3 */
+         DELTAC3 ();
+         break;
+
+       case 0x76:  /* SROUND */
+         SROUND ();
+         break;
+
+       case 0x77:  /* S45Round */
+         S45ROUND ();
+         break;
+
+       case 0x78:  /* JROT */
+         JROT ();
+         break;
+
+       case 0x79:  /* JROF */
+         JROF ();
+         break;
+
+       case 0x7A:  /* ROFF */
+         ROFF ();
+         break;
+
+       case 0x7B:  /* ILLEGAL_INSTRUCTION */
+         ILLEGAL_INSTRUCTION ();
+         break;
+
+       case 0x7C:  /* RUTG */
+         RUTG ();
+         break;
+
+       case 0x7D:  /* RDTG */
+         RDTG ();
+         break;
+
+       case 0x7E:  /* SANGW */
+         SANGW ();
+         break;
+
+       case 0x7F:  /* AA */
+         AA ();
+         break;
+
+       case 0x80:  /* FLIPPT */
+         FLIPPT ();
+         break;
+
+       case 0x81:  /* FLIPRGON */
+         FLIPRGON ();
+         break;
+
+       case 0x82:  /* FLIPRGOFF */
+         FLIPRGOFF ();
+         break;
+
+       case 0x83:  /* RMVT */
+       case 0x84:  /* WMVT */
+         NOT_IMPLEMENTED ();
+         break;
+
+       case 0x85:  /* SCANCTRL */
+         SCANCTRL ();
+         break;
+
+       case 0x86:  /* SDPVTL */
+       case 0x87:  /* SDPVTL */
+         SDPVTL ();
+         break;
+
+       case 0x88:  /* GETINFO */
+         GETINFO ();
+         break;
+
+       case 0x89:  /* IDEF */
+         IDEF ();
+         break;
+
+       case 0x8A:  /* ROLL */
+         ROLL ();
+         break;
+
+       case 0x8B:  /* MAX */
+         _MAX ();
+         break;
+
+       case 0x8C:  /* MIN */
+         _MIN ();
+         break;
+
+         /* Scan or dropout control is not implemented.  Instead, 256
+            grays are used to display pixels which are partially
+            turned on.  */
+       case 0x8D:  /* SCANTYPE */
+         SCANTYPE ();
+         break;
+
+       case 0x8E:  /* INSTCTRL */
+         INSTCTRL ();
+         break;
+
+       case 0x8F:  /* ADJUST */
+       case 0x90:  /* ADJUST */
+         NOT_IMPLEMENTED ();
+         break;
+
+       case 0x91:  /* GXAXIS */
+         GXAXIS ();
+         break;
+
+       default:
+         if (opcode >= 0xE0) /* MIRP */
+           {
+             MIRP ();
+           }
+         else if (opcode >= 0xC0) /* MDRP */
+           {
+             MDRP ();
+           }
+         else if (opcode >= 0xB8) /* PUSHW */
+           {
+             PUSHW ();
+           }
+         else if (opcode >= 0xB0) /* PUSHB */
+           {
+             PUSHB ();
+           }
+         else
+           NOT_IMPLEMENTED ();
+       }
+
+    next_instruction:
+      /* In the case of an NPUSHB or NPUSHW instruction,
+        interpreter->IP has only been increased to skip over the
+        extra bytes, and not the byte containing the instruction
+        itself.  */
+      interpreter->IP++;
+
+      /* This label is used by instructions to continue without
+        incrementing IP.  It is used by instructions which set IP
+        themselves, such as ELSE, IF, FDEF, IDEF and JMPR.  */
+    skip_step:
+      continue;
+    }
+}
+
+/* Execute the font program FPGM using INTERPRETER.
+   This must only be called once per interpreter, else behavior is
+   undefined.
+
+   Value is NULL upon success, else it is a string describing the
+   reason for failure.
+
+   The caller must save the graphics state after interpreting the font
+   program and restore it prior to instructing each glyph.  */
+
+TEST_STATIC const char *
+sfnt_interpret_font_program (struct sfnt_interpreter *interpreter,
+                            struct sfnt_fpgm_table *fpgm)
+{
+  if (setjmp (interpreter->trap))
+    return interpreter->trap_reason;
+
+  /* Set up the interpreter to evaluate the font program.  */
+  interpreter->IP = 0;
+  interpreter->SP = interpreter->stack;
+  interpreter->instructions = fpgm->instructions;
+  interpreter->num_instructions = fpgm->num_instructions;
+  interpreter->glyph_zone = NULL;
+
+  sfnt_interpret_run (interpreter, SFNT_RUN_CONTEXT_FONT_PROGRAM);
+  return NULL;
+}
+
+/* Execute the control value program PREP using INTERPRETER.
+
+   Return NULL and the graphics state after the execution of the
+   program in *STATE, or a string describing the reason for a failure
+   to interpret the program.
+
+   The caller must save the graphics state after interpreting the
+   control value program and restore it prior to instructing each
+   glyph.  */
+
+TEST_STATIC const char *
+sfnt_interpret_control_value_program (struct sfnt_interpreter *interpreter,
+                                     struct sfnt_prep_table *prep,
+                                     struct sfnt_graphics_state *state)
+{
+  if (setjmp (interpreter->trap))
+    return interpreter->trap_reason;
+
+  /* Set up the interpreter to evaluate the control value program.  */
+  interpreter->IP = 0;
+  interpreter->SP = interpreter->stack;
+  interpreter->instructions = prep->instructions;
+  interpreter->num_instructions = prep->num_instructions;
+  interpreter->glyph_zone = NULL;
+
+  sfnt_interpret_run (interpreter,
+                     SFNT_RUN_CONTEXT_CONTROL_VALUE_PROGRAM);
+
+  /* If instruct_control & 4, then changes to the graphics state made
+     in this program should be reverted.  */
+
+  if (interpreter->state.instruct_control & 4)
+    sfnt_init_graphics_state (&interpreter->state);
+
+  /* Save the graphics state upon success.  */
+  memcpy (state, &interpreter->state, sizeof *state);
+  return NULL;
+}
+
+
+
+/* Glyph hinting.  The routines here perform hinting on simple and
+   compound glyphs.
+
+   In order to keep the hinting mechanism separate from the rest of
+   the code, the routines here perform outline decomposition and
+   scaling separately.  It might be nice to fix that in the
+   future.  */
+
+/* Instructed glyph outline decomposition.  This is separate from
+   sfnt_decompose_glyph because this file should be able to be built
+   with instructions disabled.  */
+
+/* Decompose OUTLINE, an instructed outline, into its individual
+   components.
+
+   Call MOVE_TO to move to a specific location.  For each line
+   encountered, call LINE_TO to draw a line to that location.  For
+   each spline encountered, call CURVE_TO to draw the curves
+   comprising the spline.  Call each of those functions with 16.16
+   fixed point coordinates.
+
+   Call all functions with DCONTEXT as an argument.
+
+   The winding rule used to fill the resulting lines is described in
+   chapter 2 of the TrueType reference manual, under the heading
+   "distinguishing the inside from the outside of a glyph."
+
+   Value is 0 upon success, or some non-zero value upon failure, which
+   can happen if the glyph is invalid.  */
+
+static int
+sfnt_decompose_instructed_outline (struct sfnt_instructed_outline *outline,
+                                  sfnt_move_to_proc move_to,
+                                  sfnt_line_to_proc line_to,
+                                  sfnt_curve_to_proc curve_to,
+                                  void *dcontext)
+{
+  size_t here, last, n;
+
+  if (!outline->num_contours)
+    return 0;
+
+  here = 0;
+
+  for (n = 0; n < outline->num_contours; ++n)
+    {
+      /* here is the first index into the glyph's point arrays
+        belonging to the contour in question.  last is the index
+        of the last point in the contour.  */
+      last = outline->contour_end_points[n];
+
+      /* Make sure here and last make sense.  */
+
+      if (here > last || last >= outline->num_points)
+       goto fail;
+
+      if (sfnt_decompose_glyph_2 (here, last, move_to,
+                                 line_to, curve_to, dcontext,
+                                 outline->x_points,
+                                 outline->y_points,
+                                 outline->flags, 1024))
+       goto fail;
+
+      /* Move forward to the start of the next contour.  */
+      here = last + 1;
+
+      /* here may be a phantom point when outlining a compound glyph,
+        as they can have phantom points mixed in with contours.
+
+        When that happens, skip past all the phantom points.  */
+
+      while (here < outline->num_points
+            && outline->flags[here] & SFNT_POINT_PHANTOM)
+       here++;
+    }
+
+  return 0;
+
+ fail:
+  return 1;
+}
+
+/* Decompose and build an outline for the specified instructed outline
+   INSTRUCTED.  Return the outline data with a refcount of 0 upon
+   success, or NULL upon failure.
+
+   This function is not reentrant.  */
+
+TEST_STATIC struct sfnt_glyph_outline *
+sfnt_build_instructed_outline (struct sfnt_instructed_outline *instructed)
+{
+  struct sfnt_glyph_outline *outline;
+  int rc;
+
+  memset (&build_outline_context, 0, sizeof build_outline_context);
+
+  /* Allocate the outline now with enough for 44 words at the end.  */
+  outline = xmalloc (sizeof *outline + 40 * sizeof (*outline->outline));
+  outline->outline_size = 40;
+  outline->outline_used = 0;
+  outline->refcount = 0;
+  outline->outline
+    = (struct sfnt_glyph_outline_command *) (outline + 1);
+
+  /* Clear outline bounding box.  */
+  outline->xmin = 0;
+  outline->ymin = 0;
+  outline->xmax = 0;
+  outline->ymax = 0;
+
+  /* Set up the context.  */
+  build_outline_context.outline = outline;
+  build_outline_context.factor = 0177777;
+
+  /* Start decomposing.  */
+  rc = sfnt_decompose_instructed_outline (instructed,
+                                         sfnt_move_to_and_build,
+                                         sfnt_line_to_and_build,
+                                         sfnt_curve_to_and_build,
+                                         NULL);
+
+  /* Synchronize the outline object with what might have changed
+     inside sfnt_decompose_glyph.  */
+  outline = build_outline_context.outline;
+
+  /* Finally, obtain the origin point of the glyph after it has been
+     instructed.  */
+
+  if (instructed->num_points > 1)
+    outline->origin
+      = instructed->x_points[instructed->num_points - 2];
+  else
+    outline->origin = 0;
+
+  if (rc)
+    {
+      xfree (outline);
+      return NULL;
+    }
+
+  return outline;
+}
+
+
+
+/* Compute phantom points for the specified glyph GLYPH.  Use the
+   unscaled metrics specified in METRICS, and the 16.16 fixed point
+   scale SCALE.
+
+   Place the X and Y coordinates of the first phantom point in *X1 and
+   *Y1, and those of the second phantom point in *X2 and *Y2.  */
+
+static void
+sfnt_compute_phantom_points (struct sfnt_glyph *glyph,
+                            struct sfnt_glyph_metrics *metrics,
+                            sfnt_fixed scale,
+                            sfnt_f26dot6 *x1, sfnt_f26dot6 *y1,
+                            sfnt_f26dot6 *x2, sfnt_f26dot6 *y2)
+{
+  sfnt_fword f1, f2;
+
+  /* Two ``phantom points'' are appended to each outline by the scaler
+     prior to instruction interpretation.  One of these points
+     represents the left-side bearing distance from xmin, while the
+     other represents the advance width.  Both are then used after the
+     hinting process to ensure that the reported glyph metrics are
+     consistent with the instructed outline.  */
+
+  /* First compute both values in fwords.  */
+  f1 = glyph->xmin - metrics->lbearing;
+  f2 = f1 + metrics->advance;
+
+  /* Apply the metrics distortion.  */
+  f1 += glyph->origin_distortion;
+  f2 += glyph->advance_distortion;
+
+  /* Next, scale both up.  */
+  *x1 = sfnt_mul_f26dot6_fixed (f1 * 64, scale);
+  *x2 = sfnt_mul_f26dot6_fixed (f2 * 64, scale);
+
+  /* Clear y1 and y2.  */
+  *y1 = 0;
+  *y2 = 0;
+}
+
+/* Load the simple glyph GLYPH into the specified INTERPRETER, scaling
+   it up by INTERPRETER's scale, and run its glyph program if
+   present.  Use the unscaled metrics specified in METRICS.
+
+   Upon success, return NULL and the resulting points and contours in
+   *VALUE.  Else, value is the reason interpretation failed.  */
+
+TEST_STATIC const char *
+sfnt_interpret_simple_glyph (struct sfnt_glyph *glyph,
+                            struct sfnt_interpreter *interpreter,
+                            struct sfnt_glyph_metrics *metrics,
+                            struct sfnt_instructed_outline **value)
+{
+  size_t zone_size, temp, outline_size, i;
+  struct sfnt_interpreter_zone *zone;
+  struct sfnt_interpreter_zone *volatile preserved_zone;
+  sfnt_f26dot6 phantom_point_1_x;
+  sfnt_f26dot6 phantom_point_1_y;
+  sfnt_f26dot6 phantom_point_2_x;
+  sfnt_f26dot6 phantom_point_2_y;
+  sfnt_f26dot6 tem;
+  volatile bool zone_was_allocated;
+  struct sfnt_instructed_outline *outline;
+
+  zone_size = 0;
+  zone_was_allocated = false;
+
+  /* Calculate the size of the zone structure.  */
+
+  if (INT_MULTIPLY_WRAPV (glyph->simple->number_of_points + 2,
+                         sizeof *zone->x_points * 4,
+                         &temp)
+      || INT_ADD_WRAPV (temp, zone_size, &zone_size)
+      || INT_MULTIPLY_WRAPV (glyph->number_of_contours,
+                            sizeof *zone->contour_end_points,
+                            &temp)
+      || INT_ADD_WRAPV (temp, zone_size, &zone_size)
+      || INT_MULTIPLY_WRAPV (glyph->simple->number_of_points + 2,
+                            sizeof *zone->flags,
+                            &temp)
+      || INT_ADD_WRAPV (temp, zone_size, &zone_size)
+      || INT_ADD_WRAPV (sizeof *zone, zone_size, &zone_size))
+    return "Glyph exceeded maximum permissible size";
+
+  /* Don't use malloc if possible.  */
+
+  if (zone_size <= 1024 * 16)
+    zone = alloca (zone_size);
+  else
+    {
+      zone = xmalloc (zone_size);
+      zone_was_allocated = true;
+    }
+
+  /* Now load the zone with data.  */
+  zone->num_points = glyph->simple->number_of_points + 2;
+  zone->num_contours = glyph->number_of_contours;
+  zone->contour_end_points = (size_t *) (zone + 1);
+  zone->x_points = (sfnt_f26dot6 *) (zone->contour_end_points
+                                    + zone->num_contours);
+  zone->x_current = zone->x_points + zone->num_points;
+  zone->y_points = zone->x_current + zone->num_points;
+  zone->y_current = zone->y_points + zone->num_points;
+  zone->flags = (unsigned char *) (zone->y_current
+                                  + zone->num_points);
+
+  /* Load x_points and x_current.  */
+  for (i = 0; i < glyph->simple->number_of_points; ++i)
+    {
+      /* Load the fword.  */
+      tem = glyph->simple->x_coordinates[i];
+
+      /* Scale that fword.  */
+      tem = sfnt_mul_f26dot6_fixed (tem * 64, interpreter->scale);
+
+      /* Set x_points and x_current.  */
+      zone->x_points[i] = tem;
+      zone->x_current[i] = tem;
+    }
+
+  /* Compute phantom points.  */
+  sfnt_compute_phantom_points (glyph, metrics, interpreter->scale,
+                              &phantom_point_1_x, &phantom_point_1_y,
+                              &phantom_point_2_x, &phantom_point_2_y);
+
+  /* Load phantom points.  */
+  zone->x_points[i] = phantom_point_1_x;
+  zone->x_points[i + 1] = phantom_point_2_x;
+  zone->x_current[i] = phantom_point_1_x;
+  zone->x_current[i + 1] = phantom_point_2_x;
+
+  /* Load y_points and y_current, along with flags.  */
+  for (i = 0; i < glyph->simple->number_of_points; ++i)
+    {
+      /* Load the fword.  */
+      tem = glyph->simple->y_coordinates[i];
+
+      /* Scale that fword.  Make sure not to round Y, as this could
+        lead to Y spilling over to the next line.  */
+      tem = sfnt_mul_fixed (tem * 64, interpreter->scale);
+
+      /* Set y_points and y_current.  */
+      zone->y_points[i] = tem;
+      zone->y_current[i] = tem;
+
+      /* Set flags.  */
+      zone->flags[i] = (glyph->simple->flags[i]
+                       & ~SFNT_POINT_TOUCHED_BOTH);
+
+      /* Make sure to clear the phantom points flag.  */
+      zone->flags[i] &= ~SFNT_POINT_PHANTOM;
+    }
+
+  /* Load phantom points.  */
+  zone->y_points[i] = phantom_point_1_y;
+  zone->y_points[i + 1] = phantom_point_2_y;
+  zone->y_current[i] = phantom_point_1_x;
+  zone->y_current[i + 1] = phantom_point_2_x;
+
+  /* Load phantom point flags.  */
+  zone->flags[i] = SFNT_POINT_PHANTOM;
+  zone->flags[i + 1] = SFNT_POINT_PHANTOM;
+
+  /* Load contour end points.  */
+  for (i = 0; i < zone->num_contours; ++i)
+    zone->contour_end_points[i]
+      = glyph->simple->end_pts_of_contours[i];
+
+  /* Load the glyph program.  */
+  interpreter->IP = 0;
+  interpreter->SP = interpreter->stack;
+  interpreter->instructions = glyph->simple->instructions;
+  interpreter->num_instructions = glyph->simple->instruction_length;
+  interpreter->glyph_zone = zone;
+
+  /* Copy zone over to this volatile variable.  */
+  preserved_zone = zone;
+
+  if (setjmp (interpreter->trap))
+    {
+      if (zone_was_allocated)
+       xfree (preserved_zone);
+
+      interpreter->glyph_zone = NULL;
+      return interpreter->trap_reason;
+    }
+
+  sfnt_interpret_run (interpreter, SFNT_RUN_CONTEXT_GLYPH_PROGRAM);
+  interpreter->glyph_zone = NULL;
+
+  /* Move preserved_zone back to zone.  */
+  zone = preserved_zone;
+
+  /* Now that the program has been run, build the scaled outline.  */
+
+  outline_size = sizeof (*outline);
+  outline_size += (zone->num_contours
+                  * sizeof *outline->contour_end_points);
+  outline_size += (zone->num_points
+                  * sizeof *outline->x_points * 2);
+  outline_size += zone->num_points;
+
+  /* Allocate the outline.  */
+  outline = xmalloc (outline_size);
+  outline->num_points = zone->num_points;
+  outline->num_contours = zone->num_contours;
+  outline->contour_end_points = (size_t *) (outline + 1);
+  outline->x_points = (sfnt_f26dot6 *) (outline->contour_end_points
+                                       + outline->num_contours);
+  outline->y_points = outline->x_points + outline->num_points;
+  outline->flags = (unsigned char *) (outline->y_points
+                                     + outline->num_points);
+
+  /* Copy over the contour endpoints, points, and flags.  */
+  memcpy (outline->contour_end_points, zone->contour_end_points,
+         zone->num_contours * sizeof *outline->contour_end_points);
+  memcpy (outline->x_points, zone->x_current,
+         zone->num_points * sizeof *outline->x_points);
+  memcpy (outline->y_points, zone->y_current,
+         zone->num_points * sizeof *outline->y_points);
+  memcpy (outline->flags, zone->flags, zone->num_points);
+
+  /* Free the zone if necessary.  */
+  if (zone_was_allocated)
+    xfree (zone);
+
+  /* Return the outline and NULL.  */
+  *value = outline;
+  return NULL;
+}
+
+/* Apply the transform in the compound glyph component COMPONENT to
+   the array of points of length NUM_COORDINATES given as X and Y.
+
+   Treat X and Y as arrays of 26.6 fixed point values.
+
+   Also, apply the 26.6 fixed point offsets X_OFF and Y_OFF to each X
+   and Y coordinate after the transforms in COMPONENT are
+   effected.  */
+
+static void
+sfnt_transform_f26dot6 (struct sfnt_compound_glyph_component *component,
+                       sfnt_f26dot6 *restrict x, sfnt_f26dot6 *restrict y,
+                       size_t num_coordinates,
+                       sfnt_f26dot6 x_off, sfnt_f26dot6 y_off)
+{
+  double m1, m2, m3;
+  double m4, m5, m6;
+  size_t i;
+
+  if (component->flags & 010) /* WE_HAVE_A_SCALE */
+    {
+      m1 = component->u.scale / 16384.0;
+      m2 = m3 = m4 = 0;
+      m5 = component->u.scale / 16384.0;
+      m6 = 0;
+    }
+  else if (component->flags & 0100) /* WE_HAVE_AN_X_AND_Y_SCALE */
+    {
+      m1 = component->u.a.xscale / 16384.0;
+      m2 = m3 = m4 = 0;
+      m5 = component->u.a.yscale / 16384.0;
+      m6 = 0;
+    }
+  else if (component->flags & 0200) /* WE_HAVE_A_TWO_BY_TWO */
+    {
+      m1 = component->u.b.xscale / 16384.0;
+      m2 = component->u.b.scale01 / 16384.0;
+      m3 = 0;
+      m4 = component->u.b.scale10 / 16384.0;
+      m5 = component->u.b.yscale / 16384.0;
+      m6 = 0;
+    }
+  else /* No scale, just apply x_off and y_off.  */
+    {
+      if (x_off || y_off)
+       {
+         for (i = 0; i < num_coordinates; ++i)
+           x[i] += x_off, y[i] += y_off;
+       }
+
+      return;
+    }
+
+  m3 = x_off;
+  m6 = y_off;
+
+  /* Apply the specified affine transformation.
+     A transform looks like:
+
+     M1 M2 M3     X
+     M4 M5 M6   * Y
+
+     =
+
+     M1*X + M2*Y + M3*1 = X1
+     M4*X + M5*Y + M6*1 = Y1
+
+     (In most transforms, there is another row at the bottom for
+     mathematical reasons.  Since Z1 is always 1.0, the row is simply
+     implied to be 0 0 1, because 0 * x + 0 * y + 1 * 1 = 1.0.  See
+     the definition of matrix3x3 in image.c for some more explanations
+     about this.) */
+
+  for (i = 0; i < num_coordinates; ++i)
+    {
+      x[i] = m1 * x[i] + m2 * y[i] + m3 * 1;
+      y[i] = m4 * x[i] + m5 * y[i] + m6 * 1;
+    }
+}
+
+/* Internal helper for sfnt_interpret_compound_glyph_3.
+
+   Instruct the compound glyph GLYPH using INTERPRETER after all of
+   its components have been instructed.
+
+   Use the unscaled METRICS to compute the phantom points of this
+   glyph.
+
+   CONTEXT contains the points and contours of this compound glyph,
+   numbered starting from BASE_INDEX and BASE_CONTOUR respectively.
+   In addition, CONTEXT also contains two additional ``phantom
+   points'' supplying the left and right side bearings of GLYPH.
+
+   Value is NULL upon success, or a description of the error upon
+   failure.  */
+
+static const char *
+sfnt_interpret_compound_glyph_2 (struct sfnt_glyph *glyph,
+                                struct sfnt_interpreter *interpreter,
+                                struct sfnt_compound_glyph_context *context,
+                                size_t base_index, size_t base_contour,
+                                struct sfnt_glyph_metrics *metrics)
+{
+  size_t num_points, num_contours, i;
+  size_t zone_size, temp;
+  struct sfnt_interpreter_zone *zone;
+  struct sfnt_interpreter_zone *volatile preserved_zone;
+  volatile bool zone_was_allocated;
+  int rc;
+  sfnt_f26dot6 *x_base, *y_base;
+  size_t *contour_base;
+  unsigned char *flags_base;
+
+  /* Figure out how many points and contours there are to
+     instruct.  */
+  num_points = context->num_points - base_index;
+  num_contours = context->num_end_points - base_contour;
+
+  /* Nothing to instruct! */
+  if (!num_points && !num_contours)
+    return NULL;
+
+  /* Build the zone.  First, calculate the size of the zone
+     structure.  */
+
+  zone_size = 0;
+  zone_was_allocated = false;
+
+  if (INT_MULTIPLY_WRAPV (num_points + 2,
+                         sizeof *zone->x_points * 4,
+                         &temp)
+      || INT_ADD_WRAPV (temp, zone_size, &zone_size)
+      || INT_MULTIPLY_WRAPV (num_contours,
+                            sizeof *zone->contour_end_points,
+                            &temp)
+      || INT_ADD_WRAPV (temp, zone_size, &zone_size)
+      || INT_MULTIPLY_WRAPV (num_points + 2,
+                            sizeof *zone->flags,
+                            &temp)
+      || INT_ADD_WRAPV (temp, zone_size, &zone_size)
+      || INT_ADD_WRAPV (sizeof *zone, zone_size, &zone_size))
+    return "Glyph exceeded maximum permissible size";
+
+  /* Don't use malloc if possible.  */
+
+  if (zone_size <= 1024 * 16)
+    zone = alloca (zone_size);
+  else
+    {
+      zone = xmalloc (zone_size);
+      zone_was_allocated = true;
+    }
+
+  /* Now load the zone with data.  */
+  zone->num_points = num_points;
+  zone->num_contours = num_contours;
+  zone->contour_end_points = (size_t *) (zone + 1);
+  zone->x_points = (sfnt_f26dot6 *) (zone->contour_end_points
+                                    + zone->num_contours);
+  zone->x_current = zone->x_points + zone->num_points;
+  zone->y_points = zone->x_current + zone->num_points;
+  zone->y_current = zone->y_points + zone->num_points;
+  zone->flags = (unsigned char *) (zone->y_current
+                                  + zone->num_points);
+
+  /* Copy and renumber all contour end points to start from
+     base_index.  */
+
+  for (i = 0; i < zone->num_contours; ++i)
+    zone->contour_end_points[i]
+      = (context->contour_end_points[base_contour + i]
+        - base_index);
+
+  /* Now copy over x_points, x_current, y_points and y_current.  */
+
+  for (i = 0; i < num_points; ++i)
+    {
+      zone->x_current[i] = context->x_coordinates[i + base_index];
+      zone->x_points[i] = context->x_coordinates[i + base_index];
+    }
+
+  for (i = 0; i < num_points; ++i)
+    {
+      zone->y_current[i] = context->y_coordinates[i + base_index];
+      zone->y_points[i] = context->y_coordinates[i + base_index];
+
+      /* Set flags.  */
+      zone->flags[i] = (context->flags[i + base_index]
+                       & ~SFNT_POINT_TOUCHED_BOTH);
+    }
+
+  /* Load the compound glyph program.  */
+  interpreter->IP = 0;
+  interpreter->SP = interpreter->stack;
+  interpreter->instructions = glyph->compound->instructions;
+  interpreter->num_instructions = glyph->compound->instruction_length;
+  interpreter->glyph_zone = zone;
+
+  /* Copy zone over to this volatile variable.  */
+  preserved_zone = zone;
+
+  if (setjmp (interpreter->trap))
+    {
+      if (zone_was_allocated)
+       xfree (preserved_zone);
+
+      interpreter->glyph_zone = NULL;
+      return interpreter->trap_reason;
+    }
+
+  sfnt_interpret_run (interpreter, SFNT_RUN_CONTEXT_GLYPH_PROGRAM);
+  interpreter->glyph_zone = NULL;
+
+  /* Move preserved_zone back to zone.  */
+  zone = preserved_zone;
+
+  /* Now copy the instructed points back, and add the two phantom
+     points to the end.  */
+
+  for (i = 0; i < num_points; ++i)
+    {
+      context->x_coordinates[base_index + i] = zone->x_current[i];
+      context->y_coordinates[base_index + i] = zone->y_current[i];
+    }
+
+  /* Grow various arrays to fit the phantom points.  */
+  rc = sfnt_expand_compound_glyph_context (context, 0, 2,
+                                          &x_base, &y_base,
+                                          &flags_base,
+                                          &contour_base);
+
+  if (rc)
+    {
+      if (zone_was_allocated)
+       xfree (zone);
+
+      return "Failed to expand arrays for phantom points";
+    }
+
+  /* Copy over the phantom points.  */
+  x_base[0] = zone->x_current[num_points - 2];
+  x_base[1] = zone->x_current[num_points - 1];
+  y_base[0] = zone->y_current[num_points - 2];
+  y_base[1] = zone->y_current[num_points - 1];
+  flags_base[0] = zone->flags[num_points - 2];
+  flags_base[1] = zone->flags[num_points - 1];
+
+  /* Free the zone if needed.  */
+  if (zone_was_allocated)
+    xfree (zone);
+
+  return NULL;
+}
+
+/* Internal helper for sfnt_interpret_compound_glyph.
+   RECURSION_COUNT is the number of times this function has called itself.
+
+   METRICS are the unscaled metrics of this compound glyph.
+
+   Other arguments mean the same as they do in
+   `sfnt_interpret_compound_glyph'.  Value is NULL upon success, or a
+   string describing the reason for failure.  */
+
+static const char *
+sfnt_interpret_compound_glyph_1 (struct sfnt_glyph *glyph,
+                                struct sfnt_interpreter *interpreter,
+                                struct sfnt_graphics_state *state,
+                                struct sfnt_compound_glyph_context *context,
+                                sfnt_get_glyph_proc get_glyph,
+                                sfnt_free_glyph_proc free_glyph,
+                                struct sfnt_hmtx_table *hmtx,
+                                struct sfnt_hhea_table *hhea,
+                                struct sfnt_maxp_table *maxp,
+                                struct sfnt_glyph_metrics *metrics,
+                                int recursion_count,
+                                void *dcontext)
+{
+  struct sfnt_glyph *subglyph;
+  int i, j, rc;
+  const char *error;
+  bool need_free;
+  struct sfnt_compound_glyph_component *component;
+  sfnt_f26dot6 x, y, xtemp, ytemp;
+  size_t point, point2;
+  size_t last_point, number_of_contours;
+  sfnt_f26dot6 *x_base, *y_base;
+  size_t *contour_base;
+  unsigned char *flags_base;
+  size_t base_index, contour_start, base_contour;
+  bool defer_offsets;
+  struct sfnt_instructed_outline *value;
+  struct sfnt_glyph_metrics sub_metrics;
+  sfnt_f26dot6 phantom_point_1_x;
+  sfnt_f26dot6 phantom_point_1_y;
+  sfnt_f26dot6 phantom_point_2_x;
+  sfnt_f26dot6 phantom_point_2_y;
+
+  error = NULL;
+
+  /* Set up the base index.  This is the index from where on point
+     renumbering starts.
+
+     In other words, point 0 in this glyph will be 0 + base_index,
+     point 1 will be 1 + base_index, and so on.  */
+  base_index = context->num_points;
+
+  /* And this is the index of the first contour in this glyph.  */
+  base_contour = context->num_end_points;
+
+  /* Prevent infinite loops.  Simply limit the level of nesting to the
+     maximum valid value of `max_component_depth', which is 16.  */
+
+  if (recursion_count > 16)
+    return "Excessive recursion in compound glyph data";
+
+  /* Pacify -Wmaybe-uninitialized.  */
+  point = point2 = 0;
+
+  for (j = 0; j < glyph->compound->num_components; ++j)
+    {
+      /* Look up the associated subglyph.  */
+      component = &glyph->compound->components[j];
+      subglyph = get_glyph (component->glyph_index,
+                           dcontext, &need_free);
+
+      if (!subglyph)
+       return "Failed to obtain component glyph";
+
+      /* Don't defer offsets.  This variable is set if the component
+        glyph is a compound glyph that is anchored to a previously
+        decomposed point, and needs its coordinates adjusted after
+        decomposition completes.  */
+      defer_offsets = false;
+
+      /* Record the size of the point array before expansion.  This
+        will be the base to apply to all points coming from this
+        subglyph.  */
+      contour_start = context->num_points;
+
+      /* Compute the offset for the component.  */
+      if (component->flags & 02) /* ARGS_ARE_XY_VALUES */
+       {
+         /* Component offsets are X/Y values as opposed to points
+            GLYPH.  */
+
+         if (!(component->flags & 01)) /* ARG_1_AND_2_ARE_WORDS */
+           {
+             /* X and Y are signed bytes.  */
+             x = component->argument1.b * 64;
+             y = component->argument2.b * 64;
+           }
+         else
+           {
+             /* X and Y are signed words.  */
+             x = component->argument1.d * 64;
+             y = component->argument2.d * 64;
+           }
+
+         /* Now convert X and Y into device coordinates.  */
+         x = sfnt_mul_f26dot6_fixed (x, interpreter->scale);
+         y = sfnt_mul_f26dot6_fixed (y, interpreter->scale);
+
+         /* If there is some kind of scale and component offsets are
+            scaled, then apply the transform to the offset.  */
+         if (component->flags & 04000) /* SCALED_COMPONENT_OFFSET */
+           sfnt_transform_f26dot6 (component, &x, &y, 1,
+                                   0, 0);
+
+         if (component->flags & 04) /* ROUND_XY_TO_GRID */
+           {
+             x = sfnt_round_f26dot6 (x);
+             y = sfnt_round_f26dot6 (y);
+           }
+       }
+      else
+       {
+         /* The offset is determined by matching a point location in
+            a preceeding component with a point location in the
+            current component.  The index of the point in the
+            previous component is established by adding
+            component->argument1.a or component->argument1.c to
+            point.  argument2 contains the index of the point in the
+            current component.  */
+
+         if (!(component->flags & 01)) /* ARG_1_AND_2_ARE_WORDS */
+           {
+             point = base_index + component->argument1.a;
+             point2 = component->argument2.a;
+           }
+         else
+           {
+             point = base_index + component->argument1.c;
+             point2 = component->argument2.c;
+           }
+
+         /* Now, check that the anchor point specified lies inside
+            the glyph.  */
+
+         if (point >= contour_start)
+           {
+             if (need_free)
+               free_glyph (subglyph, dcontext);
+
+             return "Invalid anchor reference point";
+           }
+
+         if (!subglyph->compound)
+           {
+             /* Detect invalid child anchor points within simple
+                glyphs in advance.  */
+
+             if (point2 >= subglyph->simple->number_of_points + 2)
+               {
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return "Invalid component anchor point";
+               }
+           }
+
+         /* First, set offsets to 0, because it is not yet possible
+            to ascertain the position of the anchor point in the
+            child.  That position cannot be established prior to the
+            completion of grid-fitting.  */
+         x = 0;
+         y = 0;
+
+         /* Set a flag which indicates that offsets must be resolved
+            from the child glyph after it is loaded, but before it is
+            incorporated into the parent glyph.  */
+         defer_offsets = true;
+       }
+
+      /* Obtain the glyph metrics.  If doing so fails, then cancel
+        decomposition.  */
+
+      if (sfnt_lookup_glyph_metrics (component->glyph_index,
+                                    -1, &sub_metrics,
+                                    hmtx, hhea, NULL, maxp))
+       {
+         if (need_free)
+           free_glyph (subglyph, dcontext);
+
+         return "Failed to obtain component metrics";
+       }
+
+      if (subglyph->simple)
+       {
+         /* Simple subglyph.  Copy over the points and contours,
+            then transform and instruct them.
+
+            Skip this step for glyphs without contours.  */
+
+         if (subglyph->number_of_contours)
+           {
+             /* Now instruct the simple glyph, and copy it over,
+                including the two phantom points at the end.  */
+             interpreter->state = *state;
+             error = sfnt_interpret_simple_glyph (subglyph, interpreter,
+                                                  &sub_metrics, &value);
+
+             /* Cancel instructing if an error occurs.  */
+
+             if (error)
+               {
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return error;
+               }
+
+             /* Figure out how many more points and contours are
+                needed.  While phantom points are not included within
+                the outline ultimately produced, they are temporarily
+                appended to the outline here, so as to enable
+                defer_offsets below to refer to them.  */
+             assert (value->num_points >= 2);
+             last_point = value->num_points - 2;
+             number_of_contours = value->num_contours;
+
+             /* Grow various arrays.  */
+             rc = sfnt_expand_compound_glyph_context (context,
+                                                      /* Number of
+                                                         new contours
+                                                         required.  */
+                                                      number_of_contours,
+                                                      /* Number of new
+                                                         points
+                                                         required.  */
+                                                      last_point,
+                                                      &x_base,
+                                                      &y_base,
+                                                      &flags_base,
+                                                      &contour_base);
+             if (rc)
+               {
+                 xfree (value);
+
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return "Failed to grow arrays";
+               }
+
+             /* Copy the values in VALUE into the context and free
+                VALUE, including phantom points.  */
+
+             for (i = 0; i < last_point; ++i)
+               {
+                 x_base[i] = value->x_points[i];
+                 y_base[i] = value->y_points[i];
+                 flags_base[i] = value->flags[i];
+               }
+
+             /* Copy over the contours.  */
+             for (i = 0; i < number_of_contours; ++i)
+               contour_base[i] = (contour_start
+                                  + value->contour_end_points[i]);
+
+             /* Establish offsets for anchor points here.  It is
+                possible for glyph anchors to be set to phantom
+                points, whose coordinates cannot be established until
+                grid fitting completes.  */
+
+             if (defer_offsets)
+               {
+                 x = 0;
+                 y = 0;
+
+                 /* Assert the child anchor is within the confines of
+                    the zone.  */
+                 assert (point2 < value->num_points);
+
+                 /* Get the points and use them to compute the
+                    offsets.  */
+
+                 xtemp = context->x_coordinates[point];
+                 ytemp = context->y_coordinates[point];
+                 x = (xtemp - value->x_points[point2]);
+                 y = (ytemp - value->y_points[point2]);
+               }
+
+             xfree (value);
+
+             /* Apply the transform to the points, excluding phantom
+                points within.  */
+             sfnt_transform_f26dot6 (component, x_base, y_base,
+                                     last_point, x, y);
+           }
+       }
+      else
+       {
+         /* Compound subglyph.  Decompose and instruct the glyph
+            recursively, and then apply the transform.  */
+
+         error = sfnt_interpret_compound_glyph_1 (subglyph, interpreter,
+                                                  state,
+                                                  context, get_glyph,
+                                                  free_glyph, hmtx, hhea,
+                                                  maxp, &sub_metrics,
+                                                  recursion_count + 1,
+                                                  dcontext);
+
+         if (error)
+           {
+             if (need_free)
+               free_glyph (subglyph, dcontext);
+
+             return error;
+           }
+
+         /* Anchor points for glyphs with instructions must be
+            computed after grid fitting completes.
+
+            As such, the offset is calculated here, using the points
+            in the loaded child compound glyph.  At present, CONTEXT
+            incorporates the two phantom points after the end of the
+            last component within SUBGLYPH.  */
+
+         if (defer_offsets)
+           {
+             x = 0;
+             y = 0;
+
+             /* Renumber the non renumbered point2 to point into the
+                decomposed component.  */
+             point2 += contour_start;
+
+             /* Next, check that the non-renumbered point being
+                anchored lies inside the glyph data that was
+                decomposed.  */
+
+             if (point2 >= context->num_points)
+               {
+                 if (need_free)
+                   free_glyph (subglyph, dcontext);
+
+                 return "Invalid anchor reference point";
+               }
+
+             /* Get the points and use them to compute the
+                offsets.  */
+
+             xtemp = context->x_coordinates[point];
+             ytemp = context->y_coordinates[point];
+             x = (xtemp - context->x_coordinates[point2]);
+             y = (ytemp - context->y_coordinates[point2]);
+           }
+
+         /* Subtract the two phantom points from context->num_points.
+            This behavior is correct, as only the subglyph's phantom
+            points may be provided as anchor points.  */
+         assert (context->num_points - contour_start >= 2);
+         context->num_points -= 2;
+
+         sfnt_transform_f26dot6 (component,
+                                 context->x_coordinates + contour_start,
+                                 context->y_coordinates + contour_start,
+                                 /* Exclude phantom points from
+                                    transformations.  */
+                                 context->num_points - contour_start,
+                                 x, y);
+       }
+
+      /* Finally, free the subglyph.  */
+      if (need_free)
+       free_glyph (subglyph, dcontext);
+    }
+
+  /* Run the program for the entire compound glyph, if any.  CONTEXT
+     should not contain phantom points by this point, so append its
+     own.  */
+
+  /* Compute phantom points.  */
+  sfnt_compute_phantom_points (glyph, metrics, interpreter->scale,
+                              &phantom_point_1_x, &phantom_point_1_y,
+                              &phantom_point_2_x, &phantom_point_2_y);
+
+  /* Grow various arrays to include those points.  */
+  rc = sfnt_expand_compound_glyph_context (context,
+                                          /* Number of new contours
+                                             required.  */
+                                          0,
+                                          /* Number of new points
+                                             required.  */
+                                          2,
+                                          &x_base, &y_base,
+                                          &flags_base, &contour_base);
+
+  /* Store the phantom points within the compound glyph.  */
+  x_base[0] = phantom_point_1_x;
+  x_base[1] = phantom_point_2_x;
+  y_base[0] = phantom_point_1_y;
+  y_base[1] = phantom_point_2_y;
+  flags_base[0] = SFNT_POINT_PHANTOM;
+  flags_base[1] = SFNT_POINT_PHANTOM;
+
+  if (glyph->compound->instruction_length)
+    {
+      interpreter->state = *state;
+      error = sfnt_interpret_compound_glyph_2 (glyph, interpreter,
+                                              context, base_index,
+                                              base_contour,
+                                              metrics);
+    }
+
+  return error;
+}
+
+/* Interpret the compound glyph GLYPH using the specified INTERPRETER.
+   Load each component of the compound glyph GLYPH.  CONTEXT should be
+   a reference to a `struct sfnt_compound_glyph_context' that is on
+   the stack.
+
+   Use glyph metrics specified in the HMTX, HHEA and MAXP tables, and
+   the unscaled metrics for GLYPH specified in METRICS.
+
+   If the component is a simple glyph, scale and instruct it
+   immediately.
+
+   If the component is a compound glyph, then load it recursively
+   until a predetermined recursion level is exceeded.
+
+   Set INTERPRETER's state to STATE prior to instructing each
+   component.
+
+   Load component glyphs using GET_GLYPH, which should return whether
+   or not FREE_GLYPH should be called with the glyph that was loaded.
+   Call both functions with DCONTEXT as an argument.
+
+   Finally, append the resulting contours, run any compound glyph
+   program, and return the instructed outline in *VALUE.
+
+   Value is NULL upon success, and the type of error upon failure.  */
+
+TEST_STATIC const char *
+sfnt_interpret_compound_glyph (struct sfnt_glyph *glyph,
+                              struct sfnt_interpreter *interpreter,
+                              struct sfnt_graphics_state *state,
+                              sfnt_get_glyph_proc get_glyph,
+                              sfnt_free_glyph_proc free_glyph,
+                              struct sfnt_hmtx_table *hmtx,
+                              struct sfnt_hhea_table *hhea,
+                              struct sfnt_maxp_table *maxp,
+                              struct sfnt_glyph_metrics *metrics,
+                              void *dcontext,
+                              struct sfnt_instructed_outline **value)
+{
+  struct sfnt_compound_glyph_context context;
+  const char *error;
+  struct sfnt_instructed_outline *outline;
+  size_t outline_size, temp;
+
+  /* Set up the glyph decomposition context.  */
+  memset (&context, 0, sizeof context);
+
+  /* Now start decomposing the glyph.  */
+  error = sfnt_interpret_compound_glyph_1 (glyph, interpreter,
+                                          state, &context,
+                                          get_glyph, free_glyph,
+                                          hmtx, hhea, maxp,
+                                          metrics, 0, dcontext);
+
+  /* If an error occurs, free the data in the context and return.  */
+
+  if (error)
+    {
+      xfree (context.x_coordinates);
+      xfree (context.y_coordinates);
+      xfree (context.flags);
+      xfree (context.contour_end_points);
+      return error;
+    }
+
+  /* Copy the compound glyph data into an instructed outline.  */
+  outline_size = sizeof (*outline);
+
+  if (INT_MULTIPLY_WRAPV (context.num_end_points,
+                         sizeof *outline->contour_end_points,
+                         &temp)
+      || INT_ADD_WRAPV (outline_size, temp, &outline_size)
+      || INT_MULTIPLY_WRAPV (context.num_points,
+                            sizeof *outline->x_points * 2,
+                            &temp)
+      || INT_ADD_WRAPV (outline_size, temp, &outline_size)
+      || INT_ADD_WRAPV (context.num_points, outline_size,
+                       &outline_size))
+    {
+      xfree (context.x_coordinates);
+      xfree (context.y_coordinates);
+      xfree (context.flags);
+      xfree (context.contour_end_points);
+      return "Glyph exceeds maximum permissible size";
+    }
+
+  /* Allocate the outline.  */
+  outline = xmalloc (outline_size);
+  outline->num_points = context.num_points;
+  outline->num_contours = context.num_end_points;
+  outline->contour_end_points = (size_t *) (outline + 1);
+  outline->x_points = (sfnt_f26dot6 *) (outline->contour_end_points
+                                       + outline->num_contours);
+  outline->y_points = outline->x_points + outline->num_points;
+  outline->flags = (unsigned char *) (outline->y_points
+                                     + outline->num_points);
+
+  /* Copy over the contour endpoints, points, and flags.  Note that
+     all arrays in `context' are NULL unless num_contours is
+     non-zero.  */
+
+  if (context.num_end_points)
+    memcpy (outline->contour_end_points, context.contour_end_points,
+           outline->num_contours * sizeof *outline->contour_end_points);
+
+  if (context.num_points)
+    {
+      memcpy (outline->x_points, context.x_coordinates,
+             outline->num_points * sizeof *outline->x_points);
+      memcpy (outline->y_points, context.y_coordinates,
+             outline->num_points * sizeof *outline->y_points);
+      memcpy (outline->flags, context.flags, context.num_points);
+    }
+
+  /* Free the context data.  */
+  xfree (context.x_coordinates);
+  xfree (context.y_coordinates);
+  xfree (context.flags);
+  xfree (context.contour_end_points);
+
+  *value = outline;
+  return NULL;
+}
+
+
+
+
+/* Unicode Variation Sequence (UVS) support.
+
+   Unicode defines a mechanism by which a two-codepoint sequence
+   consisting of a ``base character'' and ``variation selector'' is
+   able to produce a glyph that is a variant of the glyph that would
+   conventionally have been mapped to the ``base character''.
+
+   TrueType describes variation selector sequences through a type of
+   character mapping table that is given the format 14.  The character
+   mapping table consists of an array of variation selectors, each of
+   which have a corresponding ``default UVS table'', which describes
+   ranges of ``base characters'' having no special variant glyphs, and
+   a ``non-default UVS table'', which is a map of ``base characters''
+   to their corresponding variant glyphs.  */
+
+/* Read a default UVS table from the font file FD, at the specified
+   OFFSET.  Value is the default UVS table upon success, else
+   NULL.  */
+
+static struct sfnt_default_uvs_table *
+sfnt_read_default_uvs_table (int fd, off_t offset)
+{
+  struct sfnt_default_uvs_table *uvs;
+  uint32_t num_ranges, i, j;
+  size_t size, temp;
+  char data[512];
+
+  /* First, seek to the given offset.  */
+
+  if (lseek (fd, offset, SEEK_SET) != offset)
+    return NULL;
+
+  /* Next, read the number of ranges present.  */
+
+  if (read (fd, &num_ranges, sizeof num_ranges) != sizeof num_ranges)
+    return NULL;
+
+  /* Swap the number of ranges present.  */
+  sfnt_swap32 (&num_ranges);
+
+  /* Now, allocate enough to hold the UVS table.  */
+
+  size = sizeof *uvs;
+  if (INT_MULTIPLY_WRAPV (sizeof *uvs->ranges, num_ranges,
+                         &temp)
+      || INT_ADD_WRAPV (temp, size, &size))
+    return NULL;
+
+  uvs = xmalloc (size);
+
+  /* Fill in the data which was already read.  */
+  uvs->num_unicode_value_ranges = num_ranges;
+
+  /* Fill in the pointer to the ranges.  */
+  uvs->ranges = (struct sfnt_unicode_value_range *) (uvs + 1);
+  i = 0;
+
+  /* Read each default UVS range in multiples of 512 bytes.  Then,
+     fill in uvs->ranges.  */
+
+  while (num_ranges)
+    {
+      size = (num_ranges > 128 ? 512 : num_ranges * 4);
+
+      if (read (fd, data, size) != size)
+       {
+         xfree (uvs);
+         return NULL;
+       }
+
+      for (j = 0; j < size / 4; ++j)
+       {
+         uvs->ranges[i + j].start_unicode_value
+           = sfnt_read_24 ((unsigned char *) data + j * 4);
+         uvs->ranges[i + j].additional_count = data[j * 4 + 1];
+       }
+
+      i += j;
+      num_ranges -= size / 4;
+    }
+
+  /* Return the resulting default UVS table.  */
+  return uvs;
+}
+
+/* Read a non-default UVS table from the font file FD, at the
+   specified OFFSET.  Value is the non-default UVS table upon success,
+   else NULL.  */
+
+static struct sfnt_nondefault_uvs_table *
+sfnt_read_nondefault_uvs_table (int fd, off_t offset)
+{
+  struct sfnt_nondefault_uvs_table *uvs;
+  uint32_t num_mappings, i, j;
+  size_t size, temp;
+  char data[500];
+
+  /* First, seek to the given offset.  */
+
+  if (lseek (fd, offset, SEEK_SET) != offset)
+    return NULL;
+
+  /* Next, read the number of mappings present.  */
+
+  if (read (fd, &num_mappings, sizeof num_mappings)
+      != sizeof num_mappings)
+    return NULL;
+
+  /* Swap the number of mappings present.  */
+  sfnt_swap32 (&num_mappings);
+
+  /* Now, allocate enough to hold the UVS table.  */
+
+  size = sizeof *uvs;
+  if (INT_MULTIPLY_WRAPV (sizeof *uvs->mappings, num_mappings,
+                         &temp)
+      || INT_ADD_WRAPV (temp, size, &size))
+    return NULL;
+
+  uvs = xmalloc (size);
+
+  /* Fill in the data which was already read.  */
+  uvs->num_uvs_mappings = num_mappings;
+
+  /* Fill in the pointer to the mappings.  */
+  uvs->mappings = (struct sfnt_uvs_mapping *) (uvs + 1);
+
+  i = 0;
+
+  /* Read each nondefault UVS mapping in multiples of 500 bytes.
+     Then, fill in uvs->ranges.  */
+
+  while (num_mappings)
+    {
+      size = (num_mappings > 100 ? 500 : num_mappings * 5);
+
+      if (read (fd, data, size) != size)
+       {
+         xfree (uvs);
+         return NULL;
+       }
+
+      for (j = 0; j < size / 5; ++j)
+       {
+         uvs->mappings[i + j].unicode_value
+           = sfnt_read_24 ((unsigned char *) data + j * 5);
+         memcpy (&uvs->mappings[i + j].base_character_value,
+                 data + j * 5 + 3,
+                 sizeof uvs->mappings[i + j].base_character_value);
+         sfnt_swap16 (&uvs->mappings[i + j].base_character_value);
+       }
+
+      i += j;
+      num_mappings -= size / 5;
+    }
+
+  /* Return the nondefault UVS table.  */
+  return uvs;
+}
+
+/* Perform comparison of A and B, two table offsets.  */
+
+static int
+sfnt_compare_table_offsets (const void *a, const void *b)
+{
+  const struct sfnt_table_offset_rec *rec_a, *rec_b;
+
+  rec_a = a;
+  rec_b = b;
+
+  if (rec_a->offset < rec_b->offset)
+    return -1;
+  else if (rec_a->offset > rec_b->offset)
+    return 1;
+
+  return 0;
+}
+
+/* Create a variation selection context based on the format 14 cmap
+   subtable CMAP.
+
+   FD is the font file to which the table belongs.
+
+   Value is the variation selection context upon success, else NULL.
+   The context contains each variation selector record and their
+   associated default and nondefault UVS tables.  Free the context
+   with `sfnt_free_uvs_context'.  */
+
+TEST_STATIC struct sfnt_uvs_context *
+sfnt_create_uvs_context (struct sfnt_cmap_format_14 *cmap, int fd)
+{
+  struct sfnt_table_offset_rec *table_offsets, *rec, template;
+  size_t size, i, nmemb, j;
+  off_t offset;
+  struct sfnt_uvs_context *context;
+
+  if (INT_MULTIPLY_WRAPV (cmap->num_var_selector_records,
+                         sizeof *table_offsets, &size)
+      || INT_MULTIPLY_WRAPV (size, 2, &size))
+    return NULL;
+
+  context = NULL;
+
+  /* First, record and sort the UVS and nondefault UVS table offsets
+     in ascending order.  */
+
+  table_offsets = xmalloc (size);
+  memset (table_offsets, 0, size);
+  nmemb = cmap->num_var_selector_records * 2;
+  j = 0;
+
+  for (i = 0; i < cmap->num_var_selector_records; ++i)
+    {
+      /* Note that either offset may be 0, meaning there is no such
+        table.  */
+
+      if (cmap->records[i].default_uvs_offset)
+       {
+         if (INT_ADD_WRAPV (cmap->offset,
+                            cmap->records[i].default_uvs_offset,
+                            &table_offsets[j].offset))
+           goto bail;
+
+         table_offsets[j++].is_nondefault_table = false;
+       }
+
+      if (cmap->records[i].nondefault_uvs_offset)
+       {
+         if (INT_ADD_WRAPV (cmap->offset,
+                            cmap->records[i].nondefault_uvs_offset,
+                            &table_offsets[j].offset))
+           goto bail;
+
+         table_offsets[j++].is_nondefault_table = true;
+       }
+    }
+
+  /* Make nmemb the number of offsets actually looked up.  */
+  nmemb = j;
+
+  qsort (table_offsets, nmemb, sizeof *table_offsets,
+        sfnt_compare_table_offsets);
+
+  /* Now go through table_offsets, and read everything.  nmemb is the
+     number of elements in table_offsets[i]; it is kept up to date
+     when duplicate members are removed.  */
+  offset = -1;
+
+  for (i = 0; i < nmemb; ++i)
+    {
+      /* Skip past duplicate tables.  */
+
+      while (table_offsets[i].offset == offset && i < nmemb)
+       {
+         nmemb--;
+         table_offsets[i] = table_offsets[i + 1];
+       }
+
+      /* If the last element of the array is a duplicate, break out of
+        the loop.  */
+
+      if (i == nmemb)
+       break;
+
+      /* Read the correct type of table depending on
+        table_offsets[i].is_nondefault_table.  Then skip past
+        duplicate tables.  Don't handle the case where two different
+        kind of tables share the same offset, because that is not
+        possible in a valid variation selector record.  */
+
+      offset = table_offsets[i].offset;
+
+      if (table_offsets[i].is_nondefault_table)
+       table_offsets[i].table
+         = sfnt_read_nondefault_uvs_table (fd, offset);
+      else
+       table_offsets[i].table
+         = sfnt_read_default_uvs_table (fd, offset);
+    }
+
+  /* Now make the context.  */
+  context = xmalloc (sizeof *context);
+  context->num_records = cmap->num_var_selector_records;
+  context->nmemb = nmemb;
+  context->records = xmalloc (sizeof *context->records
+                             * cmap->num_var_selector_records);
+
+  for (i = 0; i < cmap->num_var_selector_records; ++i)
+    {
+      context->records[i].selector = cmap->records[i].var_selector;
+
+      /* Either offset may be 0, meaning no such table exists.  Also,
+         the code below will lose if more than one kind of table
+         shares the same offset, because that is impossible.  */
+
+      if (cmap->records[i].default_uvs_offset)
+       {
+         /* Resolve the default table.  */
+         template.offset = (cmap->records[i].default_uvs_offset
+                            + cmap->offset);
+         rec = bsearch (&template, table_offsets,
+                        nmemb, sizeof *table_offsets,
+                        sfnt_compare_table_offsets);
+
+         /* Make sure this record is the right type.  */
+         if (!rec || rec->is_nondefault_table || !rec->table)
+           goto bail;
+
+         context->records[i].default_uvs = rec->table;
+       }
+      else
+       context->records[i].default_uvs = NULL;
+
+      if (cmap->records[i].nondefault_uvs_offset)
+       {
+         /* Resolve the nondefault table.  */
+         template.offset = (cmap->records[i].nondefault_uvs_offset
+                            + cmap->offset);
+         rec = bsearch (&template, table_offsets,
+                        nmemb, sizeof *table_offsets,
+                        sfnt_compare_table_offsets);
+
+         if (!rec)
+           goto bail;
+
+         /* Make sure this record is the right type.  */
+         if (!rec || !rec->is_nondefault_table || !rec->table)
+           goto bail;
+
+         context->records[i].nondefault_uvs = rec->table;
+       }
+      else
+       context->records[i].nondefault_uvs = NULL;
+    }
+
+  context->tables = table_offsets;
+  return context;
+
+ bail:
+
+  if (context)
+    {
+      xfree (context->records);
+      xfree (context);
+    }
+
+  /* Loop through and free any tables that might have been read
+     already.  */
+
+  for (i = 0; i < nmemb; ++i)
+    xfree (table_offsets[i].table);
+
+  xfree (table_offsets);
+  return NULL;
+}
+
+/* Free the specified variation selection context C.  */
+
+TEST_STATIC void
+sfnt_free_uvs_context (struct sfnt_uvs_context *c)
+{
+  size_t i;
+
+  xfree (c->records);
+
+  for (i = 0; i < c->nmemb; ++i)
+    xfree (c->tables[i].table);
+
+  xfree (c->tables);
+  xfree (c);
+}
+
+/* Compare *(sfnt_char *) K to ((struct sfnt_uvs_mapping *)
+   V)->unicode_value appropriately for bsearch.  */
+
+static int
+sfnt_compare_uvs_mapping (const void *k, const void *v)
+{
+  const sfnt_char *key;
+  const struct sfnt_uvs_mapping *value;
+
+  key = k;
+  value = v;
+
+  if (*key < value->unicode_value)
+    return -1;
+  else if (*key == value->unicode_value)
+    return 0;
+
+  return 1;
+}
+
+/* Return the ID of a variation glyph for the character C in the
+   nondefault UVS mapping table UVS.
+
+   Value is the glyph ID upon success, or 0 if there is no variation
+   glyph for the base character C.  */
+
+TEST_STATIC sfnt_glyph
+sfnt_variation_glyph_for_char (struct sfnt_nondefault_uvs_table *uvs,
+                              sfnt_char c)
+{
+  struct sfnt_uvs_mapping *mapping;
+
+  mapping = bsearch (&c, uvs->mappings, uvs->num_uvs_mappings,
+                    sizeof *uvs->mappings,
+                    sfnt_compare_uvs_mapping);
+
+  return mapping ? mapping->base_character_value : 0;
+}
+
+
+
+#if defined HAVE_MMAP && !defined TEST
+
+/* Memory mapping support.
+   It useful to map OpenType layout tables prior to using them in
+   an external shaping engine such as HarfBuzz.  */
+
+/* Map a table identified by TAG into the structure *TABLE.
+   TAG is swapped into host byte order.
+
+   Use the table directory SUBTABLE, which corresponds to the font
+   file FD.
+
+   Return 0 upon success, and set TABLE->data to the table data,
+   TABLE->mapping to the start of the mapped area, TABLE->length to
+   the length of the table contents, and TABLE->size to the size of
+   the mapping.
+
+   Return 1 upon failure.  */
+
+int
+sfnt_map_table (int fd, struct sfnt_offset_subtable *subtable,
+               uint32_t tag, struct sfnt_mapped_table *table)
+{
+  struct sfnt_table_directory *directory;
+  size_t offset, page, map_offset;
+  void *data;
+  int i;
+
+  /* Find the table in the directory.  */
+
+  for (i = 0; i < subtable->num_tables; ++i)
+    {
+      if (subtable->subtables[i].tag == tag)
+       {
+         directory = &subtable->subtables[i];
+         break;
+       }
+    }
+
+  if (i == subtable->num_tables)
+    return 1;
+
+  /* Now try to map the glyph data.  Make sure offset is a multiple of
+     the page size.  */
+
+  page = getpagesize ();
+  offset = directory->offset & ~(page - 1);
+
+  /* Figure out how much larger the mapping should be.  */
+  map_offset = directory->offset - offset;
+
+  /* Do the mmap.  */
+  data = mmap (NULL, directory->length + map_offset,
+              PROT_READ, MAP_PRIVATE, fd, offset);
+
+  if (data == MAP_FAILED)
+    return 1;
+
+  /* Fill in *TABLE.  */
+  table->data = (unsigned char *) data + map_offset;
+  table->mapping = data;
+  table->length = directory->length;
+  table->size = directory->length + map_offset;
+  return 0;
+}
+
+/* Unmap the table inside *TABLE.
+   Value is 0 upon success, 1 otherwise.  */
+
+int
+sfnt_unmap_table (struct sfnt_mapped_table *table)
+{
+  return munmap (table->mapping, table->size) != 0;
+}
+
+#endif /* HAVE_MMAP && !TEST */
+
+
+
+#ifndef TEST
+
+/* Reading table contents.  */
+
+/* Read the table with the specified TAG from the font file FD.
+   Return its length in *LENGTH, and its data upon success, else
+   NULL.  */
+
+void *
+sfnt_read_table (int fd, struct sfnt_offset_subtable *subtable,
+                uint32_t tag, size_t *length)
+{
+  struct sfnt_table_directory *directory;
+  void *data;
+  int i;
+
+  /* Find the table in the directory.  */
+
+  for (i = 0; i < subtable->num_tables; ++i)
+    {
+      if (subtable->subtables[i].tag == tag)
+       {
+         directory = &subtable->subtables[i];
+         break;
+       }
+    }
+
+  if (i == subtable->num_tables)
+    return NULL;
+
+  /* Seek to the table.  */
+
+  if (lseek (fd, directory->offset, SEEK_SET) != directory->offset)
+    return NULL;
+
+  /* Now allocate enough to hold the data and read into it.  */
+
+  data = xmalloc (directory->length);
+  if (read (fd, data, directory->length) != directory->length)
+    {
+      xfree (data);
+      return NULL;
+    }
+
+  /* Return the length and table data.  */
+  *length = directory->length;
+  return data;
+}
+
+#endif /* !TEST */
+
+
+
+/* Glyph variations.  Instead of defining separate fonts for each
+   combination of weight, width and slant (bold, condensed, italic,
+   etc), some fonts specify a list of ``variation axes'', each of
+   which determines one delta to apply to each point in every
+   glyph.
+
+   This optional information is specified in the `fvar' (font
+   variation), `gvar' (glyph variation) and `cvar' (CVT variation)
+   tables in a font file.  */
+
+/* Read an fvar table from the given font FD.  Use the table directory
+   specified in SUBTABLE.
+
+   Return the fvar table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_fvar_table *
+sfnt_read_fvar_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_fvar_table *fvar;
+  ssize_t rc;
+  size_t min_bytes, ps_size, non_ps_size, temp, pad;
+  off_t offset;
+  int i, j;
+  char *buffer;
+  sfnt_fixed *coords;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_FVAR);
+
+  if (!directory)
+    return NULL;
+
+  min_bytes = SFNT_ENDOF (struct sfnt_fvar_table,
+                         instance_size, uint16_t);
+
+  /* Check that the length is at least min_bytes.  */
+  if (directory->length < min_bytes)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Allocate enough to hold the fvar table header.  */
+  fvar = xmalloc (sizeof *fvar);
+
+  /* Read the fvar table header.  */
+  buffer = NULL;
+  rc = read (fd, fvar, min_bytes);
+  if (rc != min_bytes)
+    goto bail;
+
+  /* Swap what was read.  */
+  sfnt_swap16 (&fvar->major_version);
+  sfnt_swap16 (&fvar->minor_version);
+  sfnt_swap16 (&fvar->offset_to_data);
+  sfnt_swap16 (&fvar->count_size_pairs);
+  sfnt_swap16 (&fvar->axis_count);
+  sfnt_swap16 (&fvar->axis_size);
+  sfnt_swap16 (&fvar->instance_count);
+  sfnt_swap16 (&fvar->instance_size);
+
+  /* major_version should be 1, and minor_version 0.  */
+
+  if (fvar->major_version != 1 || fvar->minor_version)
+    goto bail;
+
+  /* count_size_pairs should be more than 2.  */
+
+  if (fvar->count_size_pairs < 2)
+    goto bail;
+
+  /* Don't try to read tables where the axis format differs.  */
+
+  if (fvar->axis_size != 20)
+    goto bail;
+
+  /* The instance size must either be 2 * sizeof (uint16_t) +
+     axisCount * sizeof (sfnt_fixed), meaning there is no PostScript
+     name identifier, or 3 * sizeof (uint16_t) + axisCount * sizeof
+     (sfnt_fixed), meaning there is.  */
+
+  if (INT_MULTIPLY_WRAPV (fvar->axis_count, sizeof (sfnt_fixed),
+                         &temp)
+      || INT_ADD_WRAPV (2 * sizeof (uint16_t), temp, &non_ps_size))
+    goto bail;
+
+  if (INT_MULTIPLY_WRAPV (fvar->axis_count, sizeof (sfnt_fixed),
+                         &temp)
+      || INT_ADD_WRAPV (3 * sizeof (uint16_t), temp, &ps_size))
+    goto bail;
+
+  if (fvar->instance_size != non_ps_size
+      && fvar->instance_size != ps_size)
+    goto bail;
+
+  /* Now compute the offset of the axis data from the start of the
+     font file.  */
+
+  if (INT_ADD_WRAPV (fvar->offset_to_data, directory->offset,
+                    &offset))
+    goto bail;
+
+  /* Seek there.  */
+
+  if (lseek (fd, offset, SEEK_SET) != offset)
+    goto bail;
+
+  min_bytes = sizeof *fvar;
+
+  /* Now, read each axis and instance.  Calculate how much extra data
+     needs to be allocated for the axes and instances: this is
+     fvar->axis_count * sizeof (struct sfnt_variation_axis), some
+     padding, and finally fvar->instance_count * sizeof (struct
+     sfnt_instance) + sizeof (sfnt_fixed) * fvar->instance_count *
+     fvar->axis_count.  */
+
+  if (INT_MULTIPLY_WRAPV (fvar->axis_count, sizeof *fvar->axis,
+                         &temp)
+      || INT_ADD_WRAPV (min_bytes, temp, &min_bytes))
+    goto bail;
+
+  pad = alignof (struct sfnt_variation_axis);
+  pad -= min_bytes & (pad - 1);
+
+  if (INT_ADD_WRAPV (min_bytes, pad, &min_bytes))
+    goto bail;
+
+  if (INT_MULTIPLY_WRAPV (fvar->instance_count,
+                         sizeof *fvar->instance,
+                         &temp)
+      || INT_ADD_WRAPV (min_bytes, temp, &min_bytes))
+    goto bail;
+
+  if (INT_MULTIPLY_WRAPV (fvar->instance_count,
+                         sizeof *fvar->instance->coords,
+                         &temp)
+      || INT_MULTIPLY_WRAPV (temp, fvar->axis_count, &temp)
+      || INT_ADD_WRAPV (min_bytes, temp, &min_bytes))
+    goto bail;
+
+  /* Reallocate fvar.  */
+  fvar = xrealloc (fvar, min_bytes);
+
+  /* Fill in offsets.  */
+  fvar->axis = (struct sfnt_variation_axis *) (fvar + 1);
+  fvar->instance
+    = (struct sfnt_instance *) (((char *) (fvar->axis
+                                          + fvar->axis_count))
+                               + pad);
+
+  /* Read axes.  */
+
+  if (directory->length - SFNT_ENDOF (struct sfnt_fvar_table,
+                                     instance_size, uint16_t)
+      < sizeof *fvar->axis * fvar->axis_count)
+    goto bail;
+
+  rc = read (fd, fvar->axis, sizeof *fvar->axis * fvar->axis_count);
+  if (rc != sizeof *fvar->axis * fvar->axis_count)
+    goto bail;
+
+  /* Swap each axis.  */
+
+  for (i = 0; i < fvar->axis_count; ++i)
+    {
+      sfnt_swap32 (&fvar->axis[i].axis_tag);
+      sfnt_swap32 (&fvar->axis[i].min_value);
+      sfnt_swap32 (&fvar->axis[i].default_value);
+      sfnt_swap32 (&fvar->axis[i].max_value);
+      sfnt_swap16 (&fvar->axis[i].flags);
+      sfnt_swap16 (&fvar->axis[i].name_id);
+    }
+
+  /* Read each instance.  */
+
+  if (fvar->instance_size < 1024 * 16)
+    buffer = alloca (fvar->instance_size);
+  else
+    buffer = xmalloc (fvar->instance_size);
+
+  coords = (sfnt_fixed *) (fvar->instance + fvar->instance_count);
+
+  for (i = 0; i < fvar->instance_count; ++i)
+    {
+      rc = read (fd, buffer, fvar->instance_size);
+      if (rc != fvar->instance_size)
+       goto bail;
+
+      /* Fill in various fields.  */
+
+      fvar->instance[i].name_id = *((uint16_t *) buffer);
+      fvar->instance[i].flags = *((uint16_t *) buffer + 1);
+      fvar->instance[i].ps_name_id = 0;
+
+      sfnt_swap16 (&fvar->instance[i].name_id);
+      sfnt_swap16 (&fvar->instance[i].flags);
+
+      /* Read coordinates.  */
+
+      fvar->instance[i].coords = coords;
+      coords += fvar->axis_count;
+
+      memcpy (fvar->instance[i].coords, buffer + 4,
+             sizeof *fvar->instance[i].coords * fvar->axis_count);
+
+      /* Swap coordinates.  */
+
+      for (j = 0; j < fvar->axis_count; ++j)
+       sfnt_swap32 (&fvar->instance[i].coords[j]);
+
+      /* Read the PostScript name ID if necessary.  If not, set it to
+        nil.  */
+
+      if (fvar->instance_size == ps_size)
+       {
+         fvar->instance[i].ps_name_id
+           = *(uint16_t *) (buffer + 4 + (sizeof *fvar->instance[i].coords
+                                          * fvar->axis_count));
+         sfnt_swap16 (&fvar->instance[i].ps_name_id);
+       }
+    }
+
+  /* Free the temporary buffer.  */
+  if (buffer && fvar->instance_size >= 1024 * 16)
+    xfree (buffer);
+
+  /* Return the fvar table.  */
+  return fvar;
+
+ bail:
+  if (buffer && fvar->instance_size >= 1024 * 16)
+    xfree (buffer);
+
+  xfree (fvar);
+  return NULL;
+}
+
+
+
+/* Read a gvar table from the given font FD.  Use the table directory
+   specified in SUBTABLE.
+
+   Return the gvar table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_gvar_table *
+sfnt_read_gvar_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_gvar_table *gvar;
+  ssize_t rc;
+  size_t min_bytes, off_size, coordinate_size, data_size;
+  int i;
+  off_t offset;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_GVAR);
+
+  if (!directory)
+    return NULL;
+
+  min_bytes = SFNT_ENDOF (struct sfnt_gvar_table,
+                         offset_to_data, uint32_t);
+
+  /* Check that the length is at least min_bytes.  */
+  if (directory->length < min_bytes)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Allocate enough to hold the gvar table header.  */
+  gvar = xmalloc (sizeof *gvar);
+
+  /* Read the gvar table header.  */
+  rc = read (fd, gvar, min_bytes);
+  if (rc != min_bytes)
+    goto bail;
+
+  /* Swap what was read.  */
+  sfnt_swap16 (&gvar->version);
+  sfnt_swap16 (&gvar->reserved);
+  sfnt_swap16 (&gvar->axis_count);
+  sfnt_swap16 (&gvar->shared_coord_count);
+  sfnt_swap32 (&gvar->offset_to_coord);
+  sfnt_swap16 (&gvar->glyph_count);
+  sfnt_swap16 (&gvar->flags);
+  sfnt_swap32 (&gvar->offset_to_data);
+
+  if (gvar->version != 1)
+    goto bail;
+
+  if (gvar->offset_to_data > directory->length)
+    goto bail;
+
+  /* Figure out the size required for the offset array.  Note that
+     there is one extra offset at the end of the array to mark the
+     size of the last glyph.  */
+
+  if (gvar->flags & 1)
+    /* Offsets are long words.  */
+    off_size = sizeof (uint32_t) * (gvar->glyph_count + 1);
+  else
+    /* Offsets are words.  */
+    off_size = sizeof (uint16_t) * (gvar->glyph_count + 1);
+
+  /* Now figure out the size of the shared coordinates.  */
+  coordinate_size = (gvar->shared_coord_count * gvar->axis_count
+                    * sizeof (uint16_t));
+
+  /* And the size of the glyph variation data.  */
+  data_size = directory->length - gvar->offset_to_data;
+
+  /* Wraparound.  */
+  if (data_size > directory->length)
+    goto bail;
+
+  /* Figure out how big gvar needs to be.  */
+  if (INT_ADD_WRAPV (sizeof *gvar, coordinate_size, &min_bytes)
+      || INT_ADD_WRAPV (min_bytes, off_size, &min_bytes)
+      || INT_ADD_WRAPV (min_bytes, data_size, &min_bytes))
+    goto bail;
+
+  /* Now allocate enough for all of this extra data.  */
+  gvar = xrealloc (gvar, min_bytes);
+
+  /* Start reading offsets.  */
+
+  if (!(gvar->flags & 1))
+    {
+      gvar->u.offset_word = (uint16_t *) (gvar + 1);
+      rc = read (fd, gvar->u.offset_word, off_size);
+      if (rc != off_size)
+       goto bail;
+
+      for (i = 0; i <= gvar->glyph_count; ++i)
+       sfnt_swap16 (&gvar->u.offset_word[i]);
+    }
+  else
+    {
+      gvar->u.offset_long = (uint32_t *) (gvar + 1);
+      rc = read (fd, gvar->u.offset_long, off_size);
+      if (rc != off_size)
+       goto bail;
+
+      for (i = 0; i <= gvar->glyph_count; ++i)
+       sfnt_swap32 (&gvar->u.offset_long[i]);
+    }
+
+  /* Start reading shared coordinates.  */
+
+  gvar->global_coords = ((sfnt_f2dot14 *) ((char *) (gvar + 1)
+                                          + off_size));
+
+  if (gvar->shared_coord_count)
+    {
+      if (INT_ADD_WRAPV (gvar->offset_to_coord, directory->offset,
+                        &offset))
+       goto bail;
+
+      if (lseek (fd, offset, SEEK_SET) != offset)
+       goto bail;
+
+      if (read (fd, gvar->global_coords, coordinate_size)
+         != coordinate_size)
+       goto bail;
+
+      for (i = 0; i < coordinate_size / sizeof *gvar->global_coords; ++i)
+       sfnt_swap16 (&gvar->global_coords[i]);
+    }
+
+  /* Finally, read the rest of the glyph variation data.  */
+  gvar->data_size = data_size;
+  gvar->glyph_variation_data
+    = (unsigned char *) (gvar->global_coords
+                        + (coordinate_size
+                           / sizeof *gvar->global_coords));
+
+  if (gvar->data_size)
+    {
+      if (INT_ADD_WRAPV (gvar->offset_to_data, directory->offset,
+                        &offset))
+       goto bail;
+
+      if (lseek (fd, offset, SEEK_SET) != offset)
+       goto bail;
+
+      if (read (fd, gvar->glyph_variation_data,
+               gvar->data_size) != gvar->data_size)
+       goto bail;
+    }
+
+  /* Return the read gvar table.  */
+  return gvar;
+
+ bail:
+  xfree (gvar);
+  return NULL;
+}
+
+
+
+/* Read an avar table from the given font FD.  Use the table directory
+   specified in SUBTABLE.
+
+   Return the avar table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_avar_table *
+sfnt_read_avar_table (int fd, struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_avar_table *avar;
+  ssize_t rc;
+  size_t min_size, size, i, k, j;
+  uint16_t *buffer;
+  struct sfnt_short_frac_correspondence *correspondences;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_AVAR);
+
+  if (!directory)
+    return NULL;
+
+  min_size = SFNT_ENDOF (struct sfnt_avar_table, axis_count, uint32_t);
+
+  /* Check that the length is at least min_size.  */
+  if (directory->length < min_size)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Allocate enough to hold the avar table header.  */
+  avar = xmalloc (sizeof *avar);
+
+  /* Read the avar table header.  */
+  rc = read (fd, avar, min_size);
+  if (rc != min_size)
+    goto bail;
+
+  /* Swap what was read.  */
+  sfnt_swap32 (&avar->version);
+  sfnt_swap32 (&avar->axis_count);
+
+  if (avar->version != 0x00010000)
+    goto bail;
+
+  if (avar->axis_count < 0)
+    goto bail;
+
+  /* Allocate a buffer that holds the rest of the data.  */
+  size = directory->length - min_size;
+  buffer = xmalloc (size);
+  rc = read (fd, buffer, size);
+  if (rc != size)
+    goto bail1;
+
+  /* Swap each word.  */
+  for (i = 0; i < size / sizeof *buffer; ++i)
+    sfnt_swap16 (&buffer[i]);
+
+  /* Now, determine how big the resulting data needs to be.  Each
+     struct has a pointer field, and that should be its alignment.  */
+
+  k = 0;
+  min_size = sizeof *avar;
+  for (i = 0; i < avar->axis_count; ++i)
+    {
+      /* Check that k remains within bounds.  */
+      if (k >= size / sizeof *buffer)
+       goto bail1;
+
+      /* Now add one struct sfnt_short_frac_segment for each axis and
+         each of its correspondences.  */
+      if (INT_ADD_WRAPV (sizeof (struct sfnt_short_frac_segment),
+                        min_size, &min_size)
+         || INT_ADD_WRAPV (sizeof (struct sfnt_short_frac_correspondence)
+                           * buffer[k], min_size, &min_size))
+       goto bail1;
+
+      /* Verify that words from here to buffer[1 + buffer[k] * 2], the
+        next pairCount field, are within bounds.  */
+      j = k + 1 + buffer[k] * 2;
+      if (j > size / sizeof *buffer)
+       goto bail1;
+
+      /* Move to the next pairCount field.  */
+      k = j;
+    }
+
+  /* Resize avar to min_size and start filling in various
+     pointers.  */
+  avar = xrealloc (avar, min_size);
+  avar->segments = (struct sfnt_short_frac_segment *) (avar + 1);
+  correspondences
+    = ((struct sfnt_short_frac_correspondence *) (avar->segments
+                                                 + avar->axis_count));
+
+  k = 0;
+  for (i = 0; i < avar->axis_count; ++i)
+    {
+      avar->segments[i].pair_count = buffer[k++];
+      avar->segments[i].correspondence = correspondences;
+
+      for (j = 0; j < avar->segments[i].pair_count; ++j)
+       {
+         correspondences->from_coord = buffer[k++];
+         correspondences->to_coord = buffer[k++];
+         correspondences++;
+       }
+    }
+
+  /* Return the read avar table.  Free buffer.  */
+  xfree (buffer);
+  return avar;
+
+ bail1:
+  xfree (buffer);
+ bail:
+  xfree (avar);
+  return NULL;
+}
+
+
+
+/* Read a sequence of packed points starting from DATA.  Return the
+   number of points read in *NPOINTS_RETURN and the array of unpacked
+   points, or NULL upon failure.
+
+   If non-NULL, set LOCATION to DATA plus the number of bytes read
+   upon success.
+
+   Return (uint16_t *) -1 if there are no points at all.
+   In this case, deltas will apply to all points in the glyph,
+   and *NPOINTS_RETURN will be UINT16_MAX.
+
+   END is one byte past the last byte in DATA.  */
+
+static uint16_t *
+sfnt_read_packed_points (unsigned char *restrict data,
+                        uint16_t *npoints_return,
+                        unsigned char *restrict end,
+                        unsigned char *restrict *location)
+{
+  int npoints;
+  uint16_t *points;
+  int i, first, control;
+
+  points = NULL;
+  npoints = 0;
+
+  if (data >= end)
+    return NULL;
+
+  /* Load the control byte.  */
+  control = *data++;
+
+  if (!control)
+    {
+      *npoints_return = UINT16_MAX;
+      *location = data;
+      return (uint16_t *) -1;
+    }
+
+  /* Now figure out the number of points within.  */
+
+  if (control & 0x80)
+    {
+      npoints = control & 0x7f;
+      npoints <<= 8;
+
+      if (data >= end)
+       return NULL;
+
+      npoints |= *data++;
+    }
+  else
+    npoints = control;
+
+  /* Start reading points.  */
+  first = 0;
+  i = 0;
+  points = xmalloc (sizeof *points * npoints);
+
+  while (i < npoints)
+    {
+      if (data >= end)
+       goto bail;
+
+      control = *data++;
+
+      if (control & 0x80)
+       {
+         /* Next control & 0x7f words are points.  */
+
+         control &= 0x7f;
+
+         while (control != -1 && i < npoints)
+           {
+             if (data >= end || data + 1 >= end)
+               goto bail;
+
+             first += *data++ << 8u;
+             first += *data++;
+             points[i] = first;
+             control -= 1, ++i;
+           }
+       }
+      else
+       {
+         /* Next control bytes are points.  */
+
+         while (control != -1 && i < npoints)
+           {
+             if (data >= end)
+               goto bail;
+
+             first += *data++;
+             points[i] = first;
+             control -= 1, ++i;
+           }
+       }
+    }
+
+  /* Return the points read.  */
+  *npoints_return = npoints;
+  *location = data;
+  return points;
+
+ bail:
+  xfree (points);
+  return NULL;
+}
+
+/* Read and return N packed deltas from DATA.  Set *DATA_RETURN to
+   DATA plus the number of bytes read.
+
+   END is the end of the glyph variation data.  Value is an array of N
+   deltas upon success, and NULL upon failure.  */
+
+static sfnt_fword *
+sfnt_read_packed_deltas (unsigned char *restrict data,
+                        unsigned char *restrict end,
+                        int n,
+                        unsigned char *restrict *data_return)
+{
+  sfnt_fword *deltas;
+  int i, count;
+  unsigned char control;
+  uint16_t value;
+
+  if (data >= end)
+    return NULL;
+
+  deltas = xmalloc (sizeof *deltas * n);
+  i = 0;
+
+  while (i < n)
+    {
+      if (data >= end)
+       goto fail;
+
+      control = *data++;
+      count = control & 0x3f;
+
+      while (count != -1 && i < n)
+       {
+         if (control & 0x80)
+           deltas[i++] = 0;
+         else if (control & 0x40)
+           {
+             if (data + 1 >= end)
+               goto fail;
+
+             value = *data++ << 8;
+             value |= *data++;
+             deltas[i++] = value;
+           }
+         else
+           {
+             if (data >= end)
+               goto fail;
+
+             deltas[i++] = (signed char) *data++;
+           }
+
+         --count;
+       }
+    }
+
+  *data_return = data;
+  return deltas;
+
+ fail:
+  xfree (deltas);
+  return NULL;
+}
+
+/* Read a cvar table from the given font FD.  Use the table directory
+   specified in SUBTABLE, axis information provided in the fvar table
+   FVAR, and CVT information provided in the cvt table CVT.
+
+   Return the cvar table upon success, else NULL.  */
+
+TEST_STATIC struct sfnt_cvar_table *
+sfnt_read_cvar_table (int fd, struct sfnt_offset_subtable *subtable,
+                     struct sfnt_fvar_table *fvar,
+                     struct sfnt_cvt_table *cvt)
+{
+  struct sfnt_table_directory *directory;
+  struct sfnt_cvar_table *cvar;
+  ssize_t rc;
+  size_t ntuples, size;
+  int i, j;
+  sfnt_f2dot14 *coords;
+  uint16_t *local, *points, npoints, data_size, min_size, index;
+  unsigned char *buffer, *data, *end, *tuple;
+  ptrdiff_t data_offset;
+  sfnt_fword *deltas;
+
+  /* Find the table in the directory.  */
+
+  directory = sfnt_find_table (subtable, SFNT_TABLE_CVAR);
+
+  if (!directory)
+    return NULL;
+
+  min_size = SFNT_ENDOF (struct sfnt_cvar_table, data_offset,
+                        uint16_t);
+
+  /* Check that the length is at least min_size.  */
+  if (directory->length < min_size)
+    return NULL;
+
+  /* Seek to the location given in the directory.  */
+  if (lseek (fd, directory->offset, SEEK_SET) == (off_t) -1)
+    return NULL;
+
+  /* Allocate enough to hold the cvar table header.  */
+  cvar = xmalloc (sizeof *cvar);
+
+  /* Read the cvar table header.  */
+  rc = read (fd, cvar, min_size);
+  if (rc != min_size)
+    goto bail;
+
+  /* Swap what was read.  */
+  sfnt_swap32 (&cvar->version);
+  sfnt_swap16 (&cvar->tuple_count);
+  sfnt_swap16 (&cvar->data_offset);
+
+  /* Read the rest of the table.  */
+  size = directory->length - min_size;
+  buffer = xmalloc (size);
+  rc = read (fd, buffer, size);
+  if (rc != size)
+    goto bail;
+
+  /* Now figure out how large cvar must be by reading the tuples.  */
+
+  ntuples = cvar->tuple_count & 0x0fff;
+  data_offset = ((ptrdiff_t) cvar->data_offset
+                - (ptrdiff_t) min_size);
+  end = buffer + size;
+
+  if (data_offset < 0)
+    goto bail1;
+
+  /* See if there are shared points, and read them if there are.  */
+
+  data = buffer + data_offset;
+  tuple = buffer;
+  points = NULL;
+
+  /* Initialize `npoints' to zero.  The specification doesn't say what
+     should happen with tuples using shared point numbers if it is not
+     set later on; simply assume no points at all apply to such a
+     tuple.  */
+
+  npoints = 0;
+
+  /* Initialize `size' to 0.  */
+  size = 0;
+
+  if (cvar->tuple_count & 0x8000)
+    {
+      points = sfnt_read_packed_points (data, &npoints, end,
+                                       &tuple);
+      if (!points)
+       goto bail1;
+
+      /* Add npoints words to the size.  If npoints is UINT16_MAX, no
+        coordinates will actually be allocated.  */
+
+      if (npoints != UINT16_MAX)
+       size = npoints * sizeof *points;
+    }
+
+  while (ntuples--)
+    {
+      data = buffer + data_offset;
+
+      /* Read the tuple.  */
+      if (tuple + 3 >= end)
+       goto bail2;
+
+      memcpy (&data_size, tuple, sizeof data_size);
+      tuple += sizeof data_size;
+      memcpy (&index, tuple, sizeof index);
+      tuple += sizeof index;
+      sfnt_swap16 (&data_size);
+      sfnt_swap16 (&index);
+
+      /* Increment the offset to the data by the data size specified
+        here.  */
+      data_offset += data_size;
+
+      if (index & 0x8000)
+       {
+         /* Embedded coordinates are present.  Read each coordinate
+            and add it to the size.  */
+
+         if (tuple + fvar->axis_count * sizeof *coords - 1 >= end)
+           goto bail2;
+
+         tuple += sizeof *coords * fvar->axis_count;
+         if (INT_ADD_WRAPV (size, sizeof *coords * fvar->axis_count,
+                            &size))
+           goto bail2;
+       }
+      else
+       /* This table is invalid, as cvar tables don't have global
+          coordinates.  */
+       goto bail2;
+
+      /* Now read indeterminate tuples if required.  */
+      if (index & 0x4000)
+       {
+         tuple += fvar->axis_count * 4;
+         if (INT_ADD_WRAPV (size, fvar->axis_count * 4, &size))
+           goto bail2;
+       }
+
+      /* Add one point and one delta for each CVT element.  */
+      if (INT_ADD_WRAPV (size, cvt->num_elements * 4, &size))
+       goto bail2;
+
+      /* Now add the size of the tuple.  */
+      if (INT_ADD_WRAPV (size, sizeof *cvar->variation, &size))
+       goto bail2;
+    }
+
+  if (INT_ADD_WRAPV (sizeof *cvar, size, &size))
+    goto bail2;
+
+  /* Reallocate cvar.  */
+  cvar = xrealloc (cvar, size);
+  ntuples = cvar->tuple_count & 0x0fff;
+  cvar->variation = (struct sfnt_tuple_variation *) (cvar + 1);
+  coords = (sfnt_f2dot14 *) (cvar->variation + ntuples);
+  tuple = buffer;
+
+  data_offset = ((ptrdiff_t) cvar->data_offset
+                - (ptrdiff_t) min_size);
+
+  /* Start reading the tuples into cvar.  */
+  for (i = 0; i < ntuples; ++i)
+    {
+      data = buffer + data_offset;
+
+      /* Read the tuple.  */
+      if (tuple + 3 >= end)
+       goto bail2;
+
+      memcpy (&data_size, tuple, sizeof data_size);
+      tuple += sizeof data_size;
+      memcpy (&index, tuple, sizeof index);
+      tuple += sizeof index;
+      sfnt_swap16 (&data_size);
+      sfnt_swap16 (&index);
+
+      /* Increment the offset to the data by the data size specified
+        here.  */
+      data_offset += data_size;
+
+      cvar->variation[i].intermediate_start = NULL;
+      cvar->variation[i].intermediate_end = NULL;
+
+      if (index & 0x8000)
+       {
+         /* Embedded coordinates are present.  Read each
+            coordinate.  */
+         cvar->variation[i].coordinates = coords;
+
+         for (j = 0; j < fvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto bail2;
+
+             memcpy (coords++, tuple, sizeof *coords);
+             tuple += sizeof *coords;
+             sfnt_swap16 (coords);
+           }
+       }
+      else
+       goto bail2;
+
+      /* Now read indeterminate tuples if required.  */
+      if (index & 0x4000)
+       {
+         cvar->variation[i].intermediate_start = coords;
+
+         for (j = 0; j < fvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto bail2;
+
+             memcpy (coords++, tuple, sizeof *coords);
+             tuple += sizeof *coords;
+             sfnt_swap16 (coords);
+           }
+
+         cvar->variation[i].intermediate_end = coords;
+
+         for (j = 0; j < fvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto bail2;
+
+             memcpy (coords++, tuple, sizeof *coords);
+             tuple += sizeof *coords;
+             sfnt_swap16 (coords);
+           }
+       }
+
+      /* Finally, read private ``point'' numbers.  If this flag is not
+         set, use shared point numbers previously read.
+
+         Read at most CVT->num_elements points, as that is all the
+         storage allocated.  */
+
+      if (index & 0x2000)
+       {
+         local = sfnt_read_packed_points (data, &cvar->variation[i].num_points,
+                                          end, &data);
+         if (!local)
+           goto bail2;
+
+         /* If points apply to all CVT indices, skip this part.  */
+
+         if (cvar->variation[i].num_points != UINT16_MAX)
+           {
+             if (cvar->variation[i].num_points > cvt->num_elements)
+               cvar->variation[i].num_points = cvt->num_elements;
+
+             cvar->variation[i].points = (uint16_t *) coords;
+             for (j = 0; j < cvar->variation[i].num_points; ++j)
+               *coords++ = local[j];
+             xfree (local);
+           }
+         else
+           cvar->variation[i].points = NULL;
+       }
+      else
+       {
+         /* Copy in the shared point numbers instead.  */
+         cvar->variation[i].num_points = npoints;
+
+         if (npoints != UINT16_MAX)
+           {
+             if (cvar->variation[i].num_points > cvt->num_elements)
+               cvar->variation[i].num_points = cvt->num_elements;
+
+             cvar->variation[i].points = (uint16_t *) coords;
+             for (j = 0; j < cvar->variation[i].num_points; ++j)
+               *coords++ = points[j];
+           }
+         else
+           cvar->variation[i].points = NULL;
+       }
+
+      /* And read packed deltas.  If cvar->variation[i].num_points is
+        UINT16_MAX, then there is one delta for each CVT entry.
+        Otherwise, there are that many deltas.  */
+
+      if (cvar->variation[i].num_points == UINT16_MAX)
+       {
+         deltas = sfnt_read_packed_deltas (data, end, cvt->num_elements,
+                                           &data);
+
+         if (!deltas)
+           goto bail2;
+
+         cvar->variation[i].deltas = coords;
+
+         for (j = 0; j < cvt->num_elements; ++j)
+           *coords++ = deltas[j];
+         xfree (deltas);
+       }
+      else
+       {
+         deltas = sfnt_read_packed_deltas (data, end,
+                                           cvar->variation[i].num_points,
+                                           &data);
+         if (!deltas)
+           goto bail2;
+
+         cvar->variation[i].deltas = coords;
+
+         for (j = 0; j < cvar->variation[i].num_points; ++j)
+           *coords++ = deltas[j];
+         xfree (deltas);
+       }
+    }
+
+  /* Free data and return the read cvar table.  */
+  if (points != (void *) -1)
+    xfree (points);
+  xfree (buffer);
+  return cvar;
+
+ bail2:
+  if (points != (void *) -1)
+    xfree (points);
+ bail1:
+  xfree (buffer);
+ bail:
+  xfree (cvar);
+  return NULL;
+}
+
+
+
+/* Initialize the specified BLEND with the given FVAR and GVAR tables.
+   If non-NULL, adjust normalized coordinates using the axis variation
+   table AVAR; similarly, adjust interpreter CVT values using CVAR, if
+   specified.  */
+
+TEST_STATIC void
+sfnt_init_blend (struct sfnt_blend *blend, struct sfnt_fvar_table *fvar,
+                struct sfnt_gvar_table *gvar, struct sfnt_avar_table *avar,
+                struct sfnt_cvar_table *cvar)
+{
+  size_t size;
+
+  blend->fvar = fvar;
+  blend->gvar = gvar;
+  blend->avar = avar;
+  blend->cvar = cvar;
+
+  /* Allocate a single array to hold both coords and norm_coords.  */
+  size = (fvar->axis_count * sizeof *blend->coords * 2);
+  blend->coords = xmalloc (size);
+  blend->norm_coords = blend->coords + fvar->axis_count;
+}
+
+/* Free what was initialized in the specified BLEND.  */
+
+TEST_STATIC void
+sfnt_free_blend (struct sfnt_blend *blend)
+{
+  xfree (blend->coords);
+}
+
+/* Normalize BLEND->fvar->axis_count coordinates in BLEND->coords and
+   place the result in BLEND->norm_coords.  */
+
+TEST_STATIC void
+sfnt_normalize_blend (struct sfnt_blend *blend)
+{
+  struct sfnt_variation_axis *axis;
+  int i, j;
+  sfnt_fixed from, coord, j0, j1, j2;
+  sfnt_fixed from_last, coord_last;
+  struct sfnt_short_frac_segment *segment;
+
+  /* For each axis... */
+  for (i = 0; i < blend->fvar->axis_count; ++i)
+    {
+      /* Normalize based on [min, default, max], into [-1, 0, 1].  */
+      axis = &blend->fvar->axis[i];
+
+      /* Load the current design coordinate.  */
+      coord = blend->coords[i];
+
+      /* Keep it within bounds.  */
+
+      if (coord > axis->max_value)
+       coord = axis->max_value;
+      else if (coord < axis->min_value)
+       coord = axis->min_value;
+
+      if (coord > axis->default_value)
+       {
+         /* Avoid division by 0.  */
+         if (axis->max_value != axis->default_value)
+           blend->norm_coords[i]
+             = sfnt_div_fixed (sfnt_sub (coord, axis->default_value),
+                               sfnt_sub (axis->max_value,
+                                         axis->default_value));
+         else
+           blend->norm_coords[i] = 0;
+       }
+      else if (coord < axis->default_value)
+       {
+         if (axis->default_value != axis->min_value)
+           blend->norm_coords[i]
+             = sfnt_div_fixed (sfnt_sub (coord, axis->default_value),
+                               sfnt_sub (axis->default_value,
+                                         axis->min_value));
+         else
+           blend->norm_coords[i] = 0;
+       }
+      else
+       blend->norm_coords[i] = 0;
+    }
+
+  /* Now, apply axis variations, but only if the avar table has the
+     right number of axes.  */
+
+  if (blend->avar && (blend->fvar->axis_count
+                     == blend->avar->axis_count))
+    {
+      for (i = 0; i < blend->fvar->axis_count; ++i)
+       {
+         segment = &blend->avar->segments[i];
+
+         /* Search for a correspondence record above the normalized
+            coordinate of this axis.  */
+
+         for (j = 1; j < segment->pair_count; ++j)
+           {
+             from = segment->correspondence[j].from_coord * 4;
+             coord = segment->correspondence[j].to_coord * 4;
+
+             if (blend->norm_coords[i] < from)
+               {
+                 from_last
+                   = segment->correspondence[j - 1].from_coord * 4;
+                 coord_last
+                   = segment->correspondence[j - 1].to_coord * 4;
+
+                 j0 = blend->norm_coords[i] - from_last;
+                 j1 = coord - coord_last;
+                 j2 = from - from_last;
+
+                 blend->norm_coords[i]
+                   = (sfnt_multiply_divide_signed (j0, j1, j2) + coord_last);
+                 break;
+               }
+           }
+       }
+    }
+}
+
+
+
+struct sfnt_gvar_glyph_header
+{
+  /* A packed field. The high 4 bits are flags and the low 12 bits are
+     the number of tuples for this glyph.  The number of tuples can be
+     any number between 1 and 4095.  */
+  uint16_t tuple_count;
+
+  /* Offset from the start of the GlyphVariationData table to the
+     serialized data.  */
+  uint16_t data_offset;
+};
+
+/* Given a BLEND containing normalized coordinates, an array of
+   BLEND->gvar->axis_count tuple coordinates, and, if INTERMEDIATE_P,
+   a range of tuple coordinates from INTERMEDIATE_START to
+   INTERMEDIATE_END, return the scaling factor to apply to deltas for
+   each corresponding point.  */
+
+static sfnt_fixed
+sfnt_compute_tuple_scale (struct sfnt_blend *blend, bool intermediate_p,
+                         sfnt_f2dot14 *coords,
+                         sfnt_f2dot14 *intermediate_start,
+                         sfnt_f2dot14 *intermediate_end)
+{
+  int i;
+  sfnt_fixed coord, start UNINIT, end UNINIT;
+  sfnt_fixed scale;
+
+  /* scale is initially 1.0.  */
+  scale = 0200000;
+
+  for (i = 0; i < blend->gvar->axis_count; ++i)
+    {
+      /* Load values for this axis, scaled up to sfnt_fixed.  */
+      coord = coords[i] * 4;
+
+      /* GCC warns about start and end being used when uninitialized,
+        but they are used only if intermediate_p.  */
+
+      if (intermediate_p)
+       {
+         start = intermediate_start[i] * 4;
+         end = intermediate_start[i] * 4;
+       }
+
+      /* Ignore tuples that can be skipped.  */
+
+      if (!coord)
+       continue;
+
+      /* If the coordinate is set to 0, then deltas should not be
+        applied.  Return 0.  */
+
+      if (!blend->norm_coords[i])
+       return 0;
+
+      /* If no scaling need take place, continue.  */
+
+      if (blend->norm_coords[i] == coord)
+       continue;
+
+      if (!intermediate_p)
+       {
+         /* Not an intermediate tuple; if coord is less than 0 and
+            blend->norm_coords[i] < coord, or coord is more than 0
+            and blend->norm_coords[i] > coord, then it doesn't fit,
+            so return.  */
+
+         if (blend->norm_coords[i] < MIN (0, coord)
+             || blend->norm_coords[i] > MAX (0, coord))
+           return 0;
+
+         scale = sfnt_multiply_divide_signed (scale,
+                                              blend->norm_coords[i],
+                                              coord);
+       }
+      else
+       {
+         /* Otherwise, renormalize between start and end.  */
+
+         if (blend->norm_coords[i] < start
+             || blend->norm_coords[i] > end)
+           return 0;
+
+         if (blend->norm_coords[i] < coord)
+           scale = sfnt_multiply_divide (scale,
+                                         blend->norm_coords[i] - start,
+                                         coord - start);
+         else
+           scale = sfnt_multiply_divide (scale,
+                                         end - blend->norm_coords[i],
+                                         end - coord);
+       }
+    }
+
+  return scale;
+}
+
+/* Infer point positions for points that have been partially moved
+   within the contour in GLYPH denoted by START and END.  */
+
+static void
+sfnt_infer_deltas_1 (struct sfnt_glyph *glyph, size_t start,
+                    size_t end, bool *touched, sfnt_fword *x,
+                    sfnt_fword *y)
+{
+  size_t i, pair_start, pair_end, pair_first, j;
+  sfnt_fword min_pos, max_pos, position;
+  sfnt_fixed ratio, delta;
+
+  pair_start = pair_first = -1;
+
+  /* Look for pairs of touched points.  */
+
+  for (i = start; i <= end; ++i)
+    {
+      if (!touched[i])
+       continue;
+
+      if (pair_start == -1)
+       {
+         pair_first = i;
+         goto next;
+       }
+
+      pair_end = i;
+
+      /* pair_start to pair_end are now a pair of points, where points
+        in between should be interpolated.  */
+
+      for (j = pair_start + 1; j < pair_end; ++j)
+       {
+         /* Consider the X axis.  Set min_pos and max_pos to the
+            smallest and greatest values along that axis.  */
+         min_pos = MIN (x[pair_start], x[pair_end]);
+         max_pos = MAX (x[pair_start], x[pair_end]);
+
+         /* Now see if the current point lies between min and
+            max... */
+         if (x[j] >= min_pos && x[j] <= max_pos)
+           {
+             /* If min_pos and max_pos are the same, apply
+                pair_start's delta if it is identical to that of
+                pair_end, or apply nothing at all otherwise.  */
+
+             if (min_pos == max_pos)
+               {
+                 if ((glyph->simple->x_coordinates[pair_start]
+                      - x[pair_start])
+                     == (glyph->simple->x_coordinates[pair_end]
+                         - x[pair_end]))
+                   glyph->simple->x_coordinates[j]
+                     += (glyph->simple->x_coordinates[pair_start]
+                         - x[pair_start]);
+
+                 continue;
+               }
+
+             /* Interpolate between min_pos and max_pos.  */
+             ratio = sfnt_div_fixed ((sfnt_sub (x[j], min_pos)
+                                      * 65536),
+                                     (sfnt_sub (max_pos, min_pos)
+                                      * 65536));
+
+             /* Load the current positions of pair_start and pair_end
+                along this axis.  */
+             min_pos = MIN (glyph->simple->x_coordinates[pair_start],
+                            glyph->simple->x_coordinates[pair_end]);
+             max_pos = MAX (glyph->simple->x_coordinates[pair_start],
+                            glyph->simple->x_coordinates[pair_end]);
+
+             /* Lerp in between.  */
+             delta = sfnt_sub (max_pos, min_pos);
+             delta = sfnt_mul_fixed (ratio, delta);
+             glyph->simple->x_coordinates[j] = min_pos + delta;
+           }
+         else
+           {
+             /* ... otheriwse, move point j by the delta of the
+                nearest touched point.  */
+
+             if (x[j] >= max_pos)
+               {
+                 position = MAX (glyph->simple->x_coordinates[pair_start],
+                                 glyph->simple->x_coordinates[pair_end]);
+                 delta = position - max_pos;
+               }
+             else
+               {
+                 position = MIN (glyph->simple->x_coordinates[pair_start],
+                                 glyph->simple->x_coordinates[pair_end]);
+                 delta = position - min_pos;
+               }
+
+             glyph->simple->x_coordinates[j] = x[j] + delta;
+           }
+
+         /* Now, consider the Y axis.  */
+         min_pos = MIN (y[pair_start], y[pair_end]);
+         max_pos = MAX (y[pair_start], y[pair_end]);
+
+         /* Now see if the current point lies between min and
+            max... */
+         if (y[j] >= min_pos && y[j] <= max_pos)
+           {
+             /* If min_pos and max_pos are the same, apply
+                pair_start's delta if it is identical to that of
+                pair_end, or apply nothing at all otherwise.  */
+
+             if (min_pos == max_pos)
+               {
+                 if ((glyph->simple->y_coordinates[pair_start]
+                      - y[pair_start])
+                     == (glyph->simple->y_coordinates[pair_end]
+                         - y[pair_end]))
+                   glyph->simple->y_coordinates[j]
+                     += (glyph->simple->y_coordinates[pair_start]
+                         - y[pair_start]);
+
+                 continue;
+               }
+
+             /* Interpolate between min_pos and max_pos.  */
+             ratio = sfnt_div_fixed ((sfnt_sub (y[j], min_pos)
+                                      * 65536),
+                                     (sfnt_sub (max_pos, min_pos)
+                                      * 65536));
+
+             /* Load the current positions of pair_start and pair_end
+                along this axis.  */
+             min_pos = MIN (glyph->simple->y_coordinates[pair_start],
+                            glyph->simple->y_coordinates[pair_end]);
+             max_pos = MAX (glyph->simple->y_coordinates[pair_start],
+                            glyph->simple->y_coordinates[pair_end]);
+
+             /* Lerp in between.  */
+             delta = sfnt_sub (max_pos, min_pos);
+             delta = sfnt_mul_fixed (ratio, delta);
+             glyph->simple->y_coordinates[j] = min_pos + delta;
+           }
+         else
+           {
+             /* ... otheriwse, move point j by the delta of the
+                nearest touched point.  */
+
+             if (y[j] >= max_pos)
+               {
+                 position = MAX (glyph->simple->y_coordinates[pair_start],
+                                 glyph->simple->y_coordinates[pair_end]);
+                 delta = position - max_pos;
+               }
+             else
+               {
+                 position = MIN (glyph->simple->y_coordinates[pair_start],
+                                 glyph->simple->y_coordinates[pair_end]);
+                 delta = position - min_pos;
+               }
+
+             glyph->simple->y_coordinates[j] = y[j] + delta;
+           }
+       }
+
+    next:
+      pair_start = i;
+    }
+
+  /* If pair_start is set, then lerp points between it and
+     pair_first.  */
+
+  if (pair_start != (size_t) -1)
+    {
+      j = pair_start + 1;
+
+      if (j > end)
+       j = start;
+
+      pair_end = pair_first;
+
+      while (j != pair_first)
+       {
+         /* Consider the X axis.  Set min_pos and max_pos to the
+            smallest and greatest values along that axis.  */
+         min_pos = MIN (x[pair_start], x[pair_end]);
+         max_pos = MAX (x[pair_start], x[pair_end]);
+
+         /* Now see if the current point lies between min and
+            max... */
+         if (x[j] >= min_pos && x[j] <= max_pos)
+           {
+             /* If min_pos and max_pos are the same, apply
+                pair_start's delta if it is identical to that of
+                pair_end, or apply nothing at all otherwise.  */
+
+             if (min_pos == max_pos)
+               {
+                 if ((glyph->simple->x_coordinates[pair_start]
+                      - x[pair_start])
+                     == (glyph->simple->x_coordinates[pair_end]
+                         - x[pair_end]))
+                   glyph->simple->x_coordinates[j]
+                     += (glyph->simple->x_coordinates[pair_start]
+                         - x[pair_start]);
+
+                 goto next_1;
+               }
+
+             /* Interpolate between min_pos and max_pos.  */
+             ratio = sfnt_div_fixed ((sfnt_sub (x[j], min_pos)
+                                      * 65536),
+                                     (sfnt_sub (max_pos, min_pos)
+                                      * 65536));
+
+             /* Load the current positions of pair_start and pair_end
+                along this axis.  */
+             min_pos = MIN (glyph->simple->x_coordinates[pair_start],
+                            glyph->simple->x_coordinates[pair_end]);
+             max_pos = MAX (glyph->simple->x_coordinates[pair_start],
+                            glyph->simple->x_coordinates[pair_end]);
+
+             /* Lerp in between.  */
+             delta = sfnt_sub (max_pos, min_pos);
+             delta = sfnt_mul_fixed (ratio, delta);
+             glyph->simple->x_coordinates[j] = min_pos + delta;
+           }
+         else
+           {
+             /* ... otheriwse, move point j by the delta of the
+                nearest touched point.  */
+
+             if (x[j] >= max_pos)
+               {
+                 position = MAX (glyph->simple->x_coordinates[pair_start],
+                                 glyph->simple->x_coordinates[pair_end]);
+                 delta = position - max_pos;
+               }
+             else
+               {
+                 position = MIN (glyph->simple->x_coordinates[pair_start],
+                                 glyph->simple->x_coordinates[pair_end]);
+                 delta = position - min_pos;
+               }
+
+             glyph->simple->x_coordinates[j] = x[j] + delta;
+           }
+
+         /* Now, consider the Y axis.  */
+         min_pos = MIN (y[pair_start], y[pair_end]);
+         max_pos = MAX (y[pair_start], y[pair_end]);
+
+         /* Now see if the current point lies between min and
+            max... */
+         if (y[j] >= min_pos && y[j] <= max_pos)
+           {
+             /* If min_pos and max_pos are the same, apply
+                pair_start's delta if it is identical to that of
+                pair_end, or apply nothing at all otherwise.  */
+
+             if (min_pos == max_pos)
+               {
+                 if ((glyph->simple->y_coordinates[pair_start]
+                      - y[pair_start])
+                     == (glyph->simple->y_coordinates[pair_end]
+                         - y[pair_end]))
+                   glyph->simple->y_coordinates[j]
+                     += (glyph->simple->y_coordinates[pair_start]
+                         - y[pair_start]);
+
+                 goto next_1;
+               }
+
+             /* Interpolate between min_pos and max_pos.  */
+             ratio = sfnt_div_fixed ((sfnt_sub (y[j], min_pos)
+                                      * 65536),
+                                     (sfnt_sub (max_pos, min_pos)
+                                      * 65536));
+
+             /* Load the current positions of pair_start and pair_end
+                along this axis.  */
+             min_pos = MIN (glyph->simple->y_coordinates[pair_start],
+                            glyph->simple->y_coordinates[pair_end]);
+             max_pos = MAX (glyph->simple->y_coordinates[pair_start],
+                            glyph->simple->y_coordinates[pair_end]);
+
+             /* Lerp in between.  */
+             delta = sfnt_sub (max_pos, min_pos);
+             delta = sfnt_mul_fixed (ratio, delta);
+             glyph->simple->y_coordinates[j] = min_pos + delta;
+           }
+         else
+           {
+             /* ... otheriwse, move point j by the delta of the
+                nearest touched point.  */
+
+             if (y[j] >= max_pos)
+               {
+                 position = MAX (glyph->simple->y_coordinates[pair_start],
+                                 glyph->simple->y_coordinates[pair_end]);
+                 delta = position - max_pos;
+               }
+             else
+               {
+                 position = MIN (glyph->simple->y_coordinates[pair_start],
+                                 glyph->simple->y_coordinates[pair_end]);
+                 delta = position - min_pos;
+               }
+
+             glyph->simple->y_coordinates[j] = y[j] + delta;
+           }
+
+       next_1:
+         j++;
+         if (j > end)
+           j = start;
+       }
+    }
+}
+
+/* Infer point positions for contours that have been partially moved
+   by variation.  For each contour in GLYPH, find pairs of points
+   which have had deltas applied.  For each such pair, interpolate
+   points between the first point in the pair and the second by
+   considering each point along every one of the two axes (X and Y)
+   like so:
+
+     - For each point that lies between the first point and the last
+       on the axis currently being considered, interpolate its
+       position in that axis so that the ratio formed by its position
+       relative to the first and last points of the pair in the
+       original outline still holds.
+
+     - For each point that lies to the left or top of the first point
+       on the axis being considered, use the delta of the first point.
+
+     - And finally, for each point that lies to the right or bottom of
+       the last point on that axis, use the delta of the last
+       point.
+
+   X and Y contain the original positions of each point.
+   TOUCHED contains whether or not each point within GLYPH has been
+   changed through variation.
+
+   Apply the inferred deltas back to GLYPH.  */
+
+static void
+sfnt_infer_deltas (struct sfnt_glyph *glyph, bool *touched,
+                  sfnt_fword *x, sfnt_fword *y)
+{
+  size_t i;
+  int point, first, end;
+
+  point = 0;
+  for (i = 0; i < glyph->number_of_contours; ++i)
+    {
+      first = point;
+      end = glyph->simple->end_pts_of_contours[i];
+
+      /* Return if the glyph is invalid.  */
+
+      if (first >= glyph->simple->number_of_points
+         || end >= glyph->simple->number_of_points
+         || first > end)
+       return;
+
+      sfnt_infer_deltas_1 (glyph, first, end, touched, x, y);
+      point = end + 1;
+    }
+}
+
+/* Read the glyph variation data for the specified glyph ID from
+   BLEND's gvar table.  Apply the offsets to each point in the
+   specified simple GLYPH, based on the specified BLEND.
+
+   Value is 0 upon success, else 1.
+
+   The glyph variation data consists of a number of elements, each of
+   which has its own associated point numbers and deltas, and a list
+   of one or two coordinates for each axis.  Each such list is
+   referred to as a ``tuple''.
+
+   The deltas, one for each point, are multipled by the normalized
+   value of each axis and applied to those points for each tuple that
+   is found to be applicable.
+
+   Each element of the glyph variation data is applicable to an axis
+   if its list of coordinates:
+
+     - contains one element for each axis, and its axis has a value
+       between 0 and that element.
+
+     - contains two elements for each axis, and its axis has a value
+       between the first element and the second.
+
+   Return the deltas that would normally be applied to the two phantom
+   points describing horizontal bounds in *DISTORTION.  Do not
+   transform the outline to reflect adjustments to the origin
+   point.  */
+
+TEST_STATIC int
+sfnt_vary_simple_glyph (struct sfnt_blend *blend, sfnt_glyph id,
+                       struct sfnt_glyph *glyph,
+                       struct sfnt_metrics_distortion *distortion)
+{
+  uint32_t offset;
+  struct sfnt_gvar_glyph_header header;
+  uint16_t *points, npoints;
+  int i, ntuples, j, point_count;
+  unsigned char *tuple, *end, *data;
+  uint16_t data_size, index, *glyph_points;
+  sfnt_f2dot14 *restrict coords;
+  sfnt_f2dot14 *restrict intermediate_start;
+  sfnt_f2dot14 *restrict intermediate_end;
+  sfnt_fword *restrict dx, *restrict dy, fword;
+  struct sfnt_gvar_table *gvar;
+  uint16_t *local_points, n_local_points;
+  sfnt_fixed scale;
+  ptrdiff_t data_offset;
+  bool *touched;
+  sfnt_fword *restrict original_x, *restrict original_y;
+
+  gvar = blend->gvar;
+
+  if (gvar->axis_count != blend->fvar->axis_count)
+    return 1;
+
+  if (gvar->glyph_count <= id)
+    return 1;
+
+  if (gvar->flags & 1)
+    offset = gvar->u.offset_long[id];
+  else
+    offset = gvar->u.offset_word[id] * 2u;
+
+  if (offset >= gvar->data_size)
+    return 1;
+
+  end = gvar->glyph_variation_data + gvar->data_size;
+
+  /* Start reading the header.  */
+
+  if (offset + sizeof header > gvar->data_size)
+    return 1;
+
+  /* Clear the distortion.  */
+  distortion->origin = 0;
+  distortion->advance = 0;
+
+  memcpy (&header, gvar->glyph_variation_data + offset,
+         sizeof header);
+
+  /* Swap the header.  */
+  sfnt_swap16 (&header.tuple_count);
+  sfnt_swap16 (&header.data_offset);
+
+  /* Prepare to read each tuple.  */
+  ntuples = header.tuple_count & 0x0fff;
+
+  /* Initialize the data offset.  This is incremented with each tuple
+     read.  */
+  data_offset = header.data_offset;
+
+  /* If gvar->flags & tuples_share_point_numbers, read the shared
+     point numbers.  Initialize `npoints' to zero.  The specification
+     doesn't say what should happen with tuples using shared point
+     numbers if it is not set later on; simply assume no points at all
+     apply to such a tuple.  */
+
+  npoints = 0;
+
+  if (header.tuple_count & 0x8000)
+    {
+      data = gvar->glyph_variation_data + offset + data_offset;
+      points = sfnt_read_packed_points (data, &npoints, end,
+                                       &tuple);
+
+      if (!points)
+       return 1;
+
+      /* Shared point numbers are part of the data after the tuple
+         array.  Thus, increment data_offset by tuple - data.  `tuple'
+         here holds no relation to a pointer to the current part of
+         the tuple array that is being read later on.  */
+      data_offset += tuple - data;
+    }
+  else
+    points = NULL;
+
+  /* Start reading each tuple.  */
+  tuple = gvar->glyph_variation_data + offset + sizeof header;
+
+  if (gvar->axis_count * sizeof *coords * 3 >= 1024 * 16)
+    coords = xmalloc (gvar->axis_count * sizeof *coords * 3);
+  else
+    coords = alloca (gvar->axis_count * sizeof *coords * 3);
+
+  intermediate_start = coords + gvar->axis_count;
+  intermediate_end = coords + gvar->axis_count;
+
+  /* Allocate arrays of booleans and fwords to keep track of which
+     points have been touched.  */
+  touched = NULL;
+  original_x = NULL;
+  original_y = NULL;
+
+  while (ntuples--)
+    {
+      data = gvar->glyph_variation_data + offset + data_offset;
+
+      if (tuple + 3 >= end)
+       goto fail1;
+
+      memcpy (&data_size, tuple, sizeof data_size);
+      tuple += sizeof data_size;
+      memcpy (&index, tuple, sizeof index);
+      tuple += sizeof index;
+      sfnt_swap16 (&data_size);
+      sfnt_swap16 (&index);
+
+      /* Increment the offset to the data by the data size specified
+        here.  */
+      data_offset += data_size;
+
+      if (index & 0x8000)
+       {
+         /* Embedded coordinates are present.  Read each
+            coordinate and add it to the tuple.  */
+         for (j = 0; j < gvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto fail1;
+
+             memcpy (&coords[j], tuple, sizeof *coords);
+             tuple += sizeof *coords;
+             sfnt_swap16 (&coords[j]);
+           }
+       }
+      else if ((index & 0xfff) > gvar->shared_coord_count)
+       /* index exceeds the number of shared tuples present.  */
+       goto fail1;
+      else
+       /* index points into gvar->axis_count coordinates making up
+          the tuple.  */
+       memcpy (coords, (gvar->global_coords
+                        + ((index & 0xfff) * gvar->axis_count)),
+               gvar->axis_count * sizeof *coords);
+
+      /* Now read indeterminate tuples if required.  */
+      if (index & 0x4000)
+       {
+         for (j = 0; j < gvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto fail1;
+
+             memcpy (&intermediate_start[j], tuple,
+                     sizeof *intermediate_start);
+             tuple += sizeof *intermediate_start;
+             sfnt_swap16 (&intermediate_start[j]);
+           }
+
+         for (j = 0; j < gvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto fail1;
+
+             memcpy (&intermediate_end[j], tuple,
+                     sizeof *intermediate_end);
+             tuple += sizeof *intermediate_end;
+             sfnt_swap16 (&intermediate_end[j]);
+           }
+       }
+
+      /* See whether or not the tuple applies to the current variation
+        configuration, and how much to scale them by.  */
+
+      scale = sfnt_compute_tuple_scale (blend, index & 0x4000,
+                                       coords, intermediate_start,
+                                       intermediate_end);
+
+      if (!scale)
+       continue;
+
+      local_points = NULL;
+
+      /* Finally, read private point numbers.
+        Set local_points to those numbers; it will be freed
+        once the loop ends.  */
+
+      if (index & 0x2000)
+       {
+         local_points = sfnt_read_packed_points (data, &n_local_points,
+                                                 end, &data);
+         if (!local_points)
+           goto fail1;
+
+         point_count = n_local_points;
+         glyph_points = local_points;
+       }
+      else
+       {
+         /* If there are no private point numbers, use global
+            points.  */
+         point_count = npoints;
+         glyph_points = points;
+       }
+
+      /* Now, read packed deltas.  */
+
+      dx = NULL;
+      dy = NULL;
+
+      switch (point_count)
+       {
+       case UINT16_MAX:
+         /* Deltas are provided for all points in the glyph.
+            No glyph should have more than 65535 points.  */
+
+         /* Add 4 phantom points to each end.  */
+         dx = sfnt_read_packed_deltas (data, end,
+                                       glyph->simple->number_of_points + 4,
+                                       &data);
+         dy = sfnt_read_packed_deltas (data, end,
+                                       glyph->simple->number_of_points + 4,
+                                       &data);
+
+         if (!dx || !dy)
+           goto fail3;
+
+         /* Apply each delta to the simple glyph.  */
+
+         for (i = 0; i < glyph->simple->number_of_points; ++i)
+           {
+             fword = sfnt_mul_fixed_round (dx[i], scale);
+             glyph->simple->x_coordinates[i] += fword;
+             fword = sfnt_mul_fixed_round (dy[i], scale);
+             glyph->simple->y_coordinates[i] += fword;
+           }
+
+         /* Apply the deltas for the two phantom points.  */
+         distortion->origin += sfnt_mul_fixed_round (dx[i++], scale);
+         distortion->advance += sfnt_mul_fixed_round (dx[i], scale);
+         break;
+
+       default:
+         dx = sfnt_read_packed_deltas (data, end, point_count, &data);
+         dy = sfnt_read_packed_deltas (data, end, point_count, &data);
+
+         if (!dx || !dy)
+           goto fail3;
+
+         /* Deltas are only applied for each point number read.  */
+
+         if (!original_x)
+           {
+             if ((glyph->simple->number_of_points
+                  * sizeof *touched) >= 1024 * 16)
+               touched = xmalloc (sizeof *touched
+                                  * glyph->simple->number_of_points);
+             else
+               touched = alloca (sizeof *touched
+                                 * glyph->simple->number_of_points);
+
+             if ((sizeof *original_x * 2
+                  * glyph->simple->number_of_points) >= 1024 * 16)
+               original_x = xmalloc (sizeof *original_x * 2
+                                     * glyph->simple->number_of_points);
+             else
+               original_x = alloca (sizeof *original_x * 2
+                                    * glyph->simple->number_of_points);
+
+             original_y = original_x + glyph->simple->number_of_points;
+             memcpy (original_x, glyph->simple->x_coordinates,
+                     (sizeof *original_x
+                      * glyph->simple->number_of_points));
+             memcpy (original_y, glyph->simple->y_coordinates,
+                     (sizeof *original_y
+                      * glyph->simple->number_of_points));
+           }
+
+         memset (touched, 0, (sizeof *touched
+                              * glyph->simple->number_of_points));
+
+         for (i = 0; i < point_count; ++i)
+           {
+             /* Apply deltas to phantom points.  */
+
+             if (glyph_points[i] == glyph->simple->number_of_points)
+               {
+                 distortion->origin += sfnt_mul_fixed_round (dx[i], scale);
+                 continue;
+               }
+
+             if (glyph_points[i] == glyph->simple->number_of_points + 1)
+               {
+                 distortion->advance += sfnt_mul_fixed_round (dx[i], scale);
+                 continue;
+               }
+
+             /* Make sure the point doesn't end up out of bounds.  */
+             if (glyph_points[i] >= glyph->simple->number_of_points)
+               continue;
+
+             fword = sfnt_mul_fixed_round (dx[i], scale);
+             glyph->simple->x_coordinates[glyph_points[i]] += fword;
+             fword = sfnt_mul_fixed_round (dy[i], scale);
+             glyph->simple->y_coordinates[glyph_points[i]] += fword;
+             touched[glyph_points[i]] = true;
+           }
+
+         sfnt_infer_deltas (glyph, touched, original_x,
+                            original_y);
+         break;
+       }
+
+      xfree (dx);
+      xfree (dy);
+
+      if (local_points != (uint16_t *) -1)
+       xfree (local_points);
+    }
+
+  /* Return success.  */
+
+  if ((glyph->simple->number_of_points
+       * sizeof *touched) >= 1024 * 16)
+    xfree (touched);
+
+  if (gvar->axis_count * sizeof *coords * 3 >= 1024 * 16)
+    xfree (coords);
+
+  if ((sizeof *original_x * 2
+       * glyph->simple->number_of_points) >= 1024 * 16)
+    xfree (original_x);
+
+  if (points != (uint16_t *) -1)
+    xfree (points);
+
+  /* Set the glyph metrics distortion as well.  */
+  glyph->advance_distortion = distortion->advance;
+  glyph->origin_distortion = distortion->origin;
+
+  return 0;
+
+ fail3:
+  xfree (dx);
+  xfree (dy);
+  xfree (local_points);
+ fail1:
+
+  if ((glyph->simple->number_of_points
+       * sizeof *touched) >= 1024 * 16)
+    xfree (touched);
+
+  if (gvar->axis_count * sizeof *coords * 3 >= 1024 * 16)
+    xfree (coords);
+
+  if ((sizeof *original_x * 2
+       * glyph->simple->number_of_points) >= 1024 * 16)
+    xfree (original_x);
+
+  if (points != (uint16_t *) -1)
+    xfree (points);
+
+  return 1;
+}
+
+/* Read the glyph variation data for the specified glyph ID from
+   BLEND's gvar table.  Apply the deltas specified within to each
+   component with offsets in the specified compound GLYPH, based on
+   the specified BLEND.  Return distortions to phantom points in
+   *DISTORTION.
+
+   Value is 0 upon success, 1 otherwise.  */
+
+TEST_STATIC int
+sfnt_vary_compound_glyph (struct sfnt_blend *blend, sfnt_glyph id,
+                         struct sfnt_glyph *glyph,
+                         struct sfnt_metrics_distortion *distortion)
+{
+  uint32_t offset;
+  struct sfnt_gvar_glyph_header header;
+  uint16_t *points, npoints;
+  int i, ntuples, j, point_count;
+  unsigned char *tuple, *end, *data;
+  uint16_t data_size, index, *glyph_points;
+  sfnt_f2dot14 *restrict coords;
+  sfnt_f2dot14 *restrict intermediate_start;
+  sfnt_f2dot14 *restrict intermediate_end;
+  sfnt_fword *restrict dx, *restrict dy, fword, word;
+  struct sfnt_gvar_table *gvar;
+  uint16_t *local_points, n_local_points;
+  sfnt_fixed scale;
+  ptrdiff_t data_offset;
+  struct sfnt_compound_glyph_component *component;
+
+  gvar = blend->gvar;
+
+  if (gvar->axis_count != blend->fvar->axis_count)
+    return 1;
+
+  if (gvar->glyph_count <= id)
+    return 1;
+
+  if (gvar->flags & 1)
+    offset = gvar->u.offset_long[id];
+  else
+    offset = gvar->u.offset_word[id] * 2u;
+
+  if (offset >= gvar->data_size)
+    return 1;
+
+  end = gvar->glyph_variation_data + gvar->data_size;
+
+  /* Start reading the header.  */
+
+  if (offset + sizeof header > gvar->data_size)
+    return 1;
+
+  /* Clear the distortion.  */
+  distortion->origin = 0;
+  distortion->advance = 0;
+
+  memcpy (&header, gvar->glyph_variation_data + offset,
+         sizeof header);
+
+  /* Swap the header.  */
+  sfnt_swap16 (&header.tuple_count);
+  sfnt_swap16 (&header.data_offset);
+
+  /* Prepare to read each tuple.  */
+  ntuples = header.tuple_count & 0x0fff;
+
+  /* Initialize the data offset.  This is incremented with each tuple
+     read.  */
+  data_offset = header.data_offset;
+
+  /* If gvar->flags & tuples_share_point_numbers, read the shared
+     point numbers.  */
+
+  npoints = 0;
+
+  if (header.tuple_count & 0x8000)
+    {
+      data = gvar->glyph_variation_data + offset + data_offset;
+      points = sfnt_read_packed_points (data, &npoints, end,
+                                       &tuple);
+
+      if (!points)
+       return 1;
+
+      /* Shared point numbers are part of the data after the tuple
+         array.  Thus, increment data_offset by tuple - data.  `tuple'
+         here holds no relation to a pointer to the current part of
+         the tuple array that is being read later on.  */
+      data_offset += tuple - data;
+    }
+  else
+    points = NULL;
+
+  /* Start reading each tuple.  */
+  tuple = gvar->glyph_variation_data + offset + sizeof header;
+
+  if (gvar->axis_count * sizeof *coords * 3 >= 1024 * 16)
+    coords = xmalloc (gvar->axis_count * sizeof *coords * 3);
+  else
+    coords = alloca (gvar->axis_count * sizeof *coords * 3);
+
+  intermediate_start = coords + gvar->axis_count;
+  intermediate_end = coords + gvar->axis_count;
+
+  while (ntuples--)
+    {
+      data = gvar->glyph_variation_data + offset + data_offset;
+
+      if (tuple + 3 >= end)
+       goto fail1;
+
+      memcpy (&data_size, tuple, sizeof data_size);
+      tuple += sizeof data_size;
+      memcpy (&index, tuple, sizeof index);
+      tuple += sizeof index;
+      sfnt_swap16 (&data_size);
+      sfnt_swap16 (&index);
+
+      /* Increment the offset to the data by the data size specified
+        here.  */
+      data_offset += data_size;
+
+      if (index & 0x8000)
+       {
+         /* Embedded coordinates are present.  Read each
+            coordinate and add it to the tuple.  */
+         for (j = 0; j < gvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto fail1;
+
+             memcpy (&coords[j], tuple, sizeof *coords);
+             tuple += sizeof *coords;
+             sfnt_swap16 (&coords[j]);
+           }
+       }
+      else if ((index & 0xfff) > gvar->shared_coord_count)
+       /* index exceeds the number of shared tuples present.  */
+       goto fail1;
+      else
+       /* index points into gvar->axis_count coordinates making up
+          the tuple.  */
+       memcpy (coords, (gvar->global_coords
+                        + ((index & 0xfff) * gvar->axis_count)),
+               gvar->axis_count * sizeof *coords);
+
+      /* Now read indeterminate tuples if required.  */
+      if (index & 0x4000)
+       {
+         for (j = 0; j < gvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto fail1;
+
+             memcpy (&intermediate_start[j], tuple,
+                     sizeof *intermediate_start);
+             tuple += sizeof *intermediate_start;
+             sfnt_swap16 (&intermediate_start[j]);
+           }
+
+         for (j = 0; j < gvar->axis_count; ++j)
+           {
+             if (tuple + 1 >= end)
+               goto fail1;
+
+             memcpy (&intermediate_end[j], tuple,
+                     sizeof *intermediate_end);
+             tuple += sizeof *intermediate_end;
+             sfnt_swap16 (&intermediate_end[j]);
+           }
+       }
+
+      /* See whether or not the tuple applies to the current variation
+        configuration, and how much to scale them by.  */
+
+      scale = sfnt_compute_tuple_scale (blend, index & 0x4000,
+                                       coords, intermediate_start,
+                                       intermediate_end);
+
+      if (!scale)
+       continue;
+
+      local_points = NULL;
+
+      /* Finally, read private point numbers.
+        Set local_points to those numbers; it will be freed
+        once the loop ends.  */
+
+      if (index & 0x2000)
+       {
+         local_points = sfnt_read_packed_points (data, &n_local_points,
+                                                 end, &data);
+         if (!local_points)
+           goto fail1;
+
+         point_count = n_local_points;
+         glyph_points = local_points;
+       }
+      else
+       {
+         /* If there are no private point numbers, use global
+            points.  */
+         point_count = npoints;
+         glyph_points = points;
+       }
+
+      /* Now, read packed deltas.  */
+
+      dx = NULL;
+      dy = NULL;
+
+      switch (point_count)
+       {
+       case UINT16_MAX:
+         /* Deltas are provided for all components in the glyph.  */
+
+         /* Add 4 phantom points to each end.  */
+         dx = sfnt_read_packed_deltas (data, end,
+                                       glyph->compound->num_components + 4,
+                                       &data);
+         dy = sfnt_read_packed_deltas (data, end,
+                                       glyph->compound->num_components + 4,
+                                       &data);
+
+         if (!dx || !dy)
+           goto fail3;
+
+         /* Apply each delta to the compound glyph.  */
+
+         for (i = 0; i < glyph->compound->num_components; ++i)
+           {
+             component = &glyph->compound->components[i];
+
+             /* Check if the component uses deltas at all.  */
+             if (!(component->flags & 02))
+               continue;
+
+             /* Vary the X offset.  */
+
+             if (!(component->flags & 01))
+               word = component->argument1.b;
+             else
+               word = component->argument1.d;
+
+             fword = sfnt_mul_fixed_round (dx[i], scale);
+             component->argument1.d = word + fword;
+
+             /* Vary the Y offset.  */
+
+             if (!(component->flags & 01))
+               word = component->argument2.b;
+             else
+               word = component->argument2.d;
+
+             fword = sfnt_mul_fixed_round (dy[i], scale);
+
+             /* Set the flag that says offsets are words.  */
+             component->flags |= 01;
+             component->argument2.d = word + fword;
+           }
+
+         /* Apply the deltas for the two phantom points.  */
+         distortion->origin += sfnt_mul_fixed_round (dx[i++], scale);
+         distortion->advance += sfnt_mul_fixed_round (dx[i], scale);
+         break;
+
+       default:
+         dx = sfnt_read_packed_deltas (data, end, point_count, &data);
+         dy = sfnt_read_packed_deltas (data, end, point_count, &data);
+
+         if (!dx || !dy)
+           goto fail3;
+
+         /* Deltas are only applied for each point number read.  */
+
+         for (i = 0; i < point_count; ++i)
+           {
+             /* Apply deltas to phantom points.  */
+
+             if (glyph_points[i] == glyph->compound->num_components)
+               {
+                 distortion->origin += sfnt_mul_fixed_round (dx[i], scale);
+                 continue;
+               }
+
+             if (glyph_points[i] == glyph->compound->num_components + 1)
+               {
+                 distortion->advance += sfnt_mul_fixed_round (dx[i], scale);
+                 continue;
+               }
+
+             /* Make sure the point doesn't end up out of bounds.  */
+             if (glyph_points[i] >= glyph->compound->num_components)
+               continue;
+
+             component = &glyph->compound->components[glyph_points[i]];
+
+             /* Check if the component uses deltas at all.  */
+             if (!(component->flags & 02))
+               continue;
+
+             /* Vary the X offset.  */
+
+             if (!(component->flags & 01))
+               word = component->argument1.b;
+             else
+               word = component->argument1.d;
+
+             fword = sfnt_mul_fixed_round (dx[i], scale);
+             component->argument1.d = word + fword;
+
+             /* Vary the Y offset.  */
+
+             if (!(component->flags & 01))
+               word = component->argument2.b;
+             else
+               word = component->argument2.d;
+
+             fword = sfnt_mul_fixed_round (dy[i], scale);
+
+             /* Set the flag that says offsets are words.  */
+             component->flags |= 01;
+             component->argument2.d = word + fword;
+           }
+
+         break;
+       }
+
+      xfree (dx);
+      xfree (dy);
+
+      if (local_points != (uint16_t *) -1)
+       xfree (local_points);
+    }
+
+  /* Return success.  */
+
+  if (gvar->axis_count * sizeof *coords * 3 >= 1024 * 16)
+    xfree (coords);
+
+  if (points != (uint16_t *) -1)
+    xfree (points);
+
+  /* Set the glyph metrics distortion as well.  */
+  glyph->advance_distortion = distortion->advance;
+  glyph->origin_distortion = distortion->origin;
+
+  return 0;
+
+ fail3:
+  xfree (dx);
+  xfree (dy);
+  xfree (local_points);
+ fail1:
+
+  if (gvar->axis_count * sizeof *coords * 3 >= 1024 * 16)
+    xfree (coords);
+
+  if (points != (uint16_t *) -1)
+    xfree (points);
+
+  return 1;
+}
+
+/* Vary the specified INTERPRETER's control value table using the
+   variations in BLEND's CVT variations table, then record the blend's
+   normalized coordinates and axis count in the interpreter.
+
+   The CVT table used to create INTERPRETER must be the same used
+   to read BLEND->cvar.  If not, behavior is undefined.  */
+
+TEST_STATIC void
+sfnt_vary_interpreter (struct sfnt_interpreter *interpreter,
+                      struct sfnt_blend *blend)
+{
+  sfnt_fixed scale;
+  int i;
+  struct sfnt_tuple_variation *variation;
+  size_t ndeltas, j, index;
+  sfnt_f26dot6 delta;
+
+  /* Return if there's no cvar table.  */
+  if (!blend->cvar)
+    return;
+
+  /* For each tuple in the cvar table... */
+  for (i = 0; i < (blend->cvar->tuple_count & 0x0fff); ++i)
+    {
+      /* See if the tuple applies.  */
+      variation = &blend->cvar->variation[i];
+      scale = sfnt_compute_tuple_scale (blend,
+                                       variation->intermediate_start != NULL,
+                                       variation->coordinates,
+                                       variation->intermediate_start,
+                                       variation->intermediate_end);
+      if (!scale)
+       continue;
+
+      /* Figure out how many deltas there are.  If variation->points,
+         there are num_points deltas.  Otherwise, there are
+         interpreter->cvt->num_elements deltas.  */
+
+      ndeltas = (variation->points
+                ? variation->num_points
+                : interpreter->cvt_size);
+
+      for (j = 0; j < ndeltas; ++j)
+       {
+         /* Figure out which CVT entry this applies to.  */
+         index = variation->points ? variation->points[j] : j;
+
+         if (index > interpreter->cvt_size)
+           continue;
+
+         /* Multiply the delta by the interpreter scale factor and
+            then the tuple scale factor.  */
+         delta = sfnt_mul_f26dot6_fixed (variation->deltas[j] * 64,
+                                         interpreter->scale);
+         delta = sfnt_mul_fixed_round (delta, scale);
+
+         /* Apply the delta to the control value table.  */
+         interpreter->cvt[i] += delta;
+       }
+    }
+
+  interpreter->n_axis = blend->fvar->axis_count;
+  interpreter->norm_coords = blend->norm_coords;
+}
+
+
+
+#ifdef TEST
+
+struct sfnt_test_dcontext
+{
+  /* Context for sfnt_test_get_glyph.  */
+  struct sfnt_glyf_table *glyf;
+  struct sfnt_loca_table_short *loca_short;
+  struct sfnt_loca_table_long *loca_long;
+  struct sfnt_hmtx_table *hmtx;
+  struct sfnt_hhea_table *hhea;
+  struct sfnt_maxp_table *maxp;
+  struct sfnt_blend *blend;
+};
+
+/* Global context for test functions.  Height of glyph.  */
+static sfnt_fixed sfnt_test_max;
+
+static void
+sfnt_test_move_to (struct sfnt_point point, void *dcontext)
+{
+  printf ("move_to: %g, %g\n", sfnt_coerce_fixed (point.x),
+         sfnt_coerce_fixed (point.y));
+}
+
+static void
+sfnt_test_line_to (struct sfnt_point point, void *dcontext)
+{
+  printf ("line_to: %g, %g\n", sfnt_coerce_fixed (point.x),
+         sfnt_coerce_fixed (point.y));
+}
+
+static void
+sfnt_test_curve_to (struct sfnt_point control,
+                   struct sfnt_point endpoint,
+                   void *dcontext)
+{
+  printf ("curve_to: %g, %g - %g, %g\n",
+         sfnt_coerce_fixed (control.x),
+         sfnt_coerce_fixed (control.y),
+         sfnt_coerce_fixed (endpoint.x),
+         sfnt_coerce_fixed (endpoint.y));
+}
+
+static struct sfnt_glyph *
+sfnt_test_get_glyph (sfnt_glyph id, void *dcontext,
+                    bool *need_free)
+{
+  struct sfnt_test_dcontext *tables;
+  struct sfnt_glyph *glyph;
+  struct sfnt_metrics_distortion distortion;
+
+  tables = dcontext;
+  *need_free = true;
+
+  glyph = sfnt_read_glyph (id, tables->glyf,
+                          tables->loca_short,
+                          tables->loca_long);
+
+  if (tables->blend && glyph)
+    {
+      if (glyph->simple)
+       sfnt_vary_simple_glyph (tables->blend, id, glyph,
+                               &distortion);
+      else
+       sfnt_vary_compound_glyph (tables->blend, id, glyph,
+                                 &distortion);
+    }
+
+  return glyph;
+}
+
+static void
+sfnt_test_free_glyph (struct sfnt_glyph *glyph, void *dcontext)
+{
+  sfnt_free_glyph (glyph);
+}
+
+static int
+sfnt_test_get_metrics (sfnt_glyph glyph, struct sfnt_glyph_metrics *metrics,
+                      void *dcontext)
+{
+  struct sfnt_test_dcontext *tables;
+
+  tables = dcontext;
+  return sfnt_lookup_glyph_metrics (glyph, -1, metrics,
+                                   tables->hmtx, tables->hhea,
+                                   NULL, tables->maxp);
+}
+
+static void
+sfnt_test_span (struct sfnt_edge *edge, sfnt_fixed y,
+               void *dcontext)
+{
+#if 1
+  printf ("/* span at %g */\n", sfnt_coerce_fixed (y));
+  for (; edge; edge = edge->next)
+    {
+      if (y >= edge->bottom && y < edge->top)
+       printf ("ctx.fillRect (%g, %g, 1, 1); "
+               "/* %g top: %g bot: %g stepx: %g winding: %d */\n",
+               sfnt_coerce_fixed (edge->x),
+               sfnt_coerce_fixed (sfnt_test_max - y),
+               sfnt_coerce_fixed (y),
+               sfnt_coerce_fixed (edge->top),
+               sfnt_coerce_fixed (edge->bottom),
+               sfnt_coerce_fixed (edge->step_x),
+               edge->winding);
+      else
+       printf ("STRIPPED BAD SPAN!!! %g %g %"PRIi32
+               " %"PRIi32" (winding: %d)\n",
+               sfnt_coerce_fixed (edge->top),
+               sfnt_coerce_fixed (edge->bottom),
+               edge->top, y, edge->winding);
+    }
+#elif 0
+  int winding;
+  short x, dx;
+
+  winding = 0;
+  x = 0;
+
+  for (; edge; edge = edge->next)
+    {
+      dx = (edge->x >> 16) - x;
+      x = edge->x >> 16;
+
+      for (; dx > 0; --dx)
+       putc (winding ? '.' : ' ', stdout);
+
+      winding = !winding;
+    }
+
+  putc ('\n', stdout);
+#elif 0
+  for (; edge; edge = edge->next)
+    printf ("%g-", sfnt_coerce_fixed (edge->x));
+  puts ("");
+#endif
+}
+
+static void
+sfnt_test_edge_ignore (struct sfnt_edge *edges, size_t num_edges,
+                      void *dcontext)
+{
+
+}
+
+/* The same debugger stuff is used here.  */
+static void sfnt_setup_debugger (void);
+
+/* The debugger's X display.  */
+static Display *display;
+
+/* The debugger window.  */
+static Window window;
+
+/* The GC.  */
+static GC point_gc, background_gc;
+
+static void
+sfnt_test_edges (struct sfnt_edge *edges, size_t num_edges)
+{
+  static sfnt_fixed y;
+  size_t i;
+
+  for (i = 0; i < num_edges; ++i)
+    {
+      if (y >= edges[i].bottom && y < edges[i].top)
+       {
+         XDrawPoint (display, window, point_gc,
+                     edges[i].x / 65536, 100 - (y / 65536));
+         printf ("sfnt_test_edges: %d %d\n",
+                 edges[i].x / 65536, 100 - (y / 65536));
+       }
+    }
+
+  y += SFNT_POLY_STEP;
+
+  for (i = 0; i < num_edges; ++i)
+    sfnt_step_edge (&edges[i]);
+}
+
+static void
+sfnt_debug_edges (struct sfnt_edge *edges, size_t num_edges)
+{
+  XEvent event;
+
+  sfnt_setup_debugger ();
+
+  while (true)
+    {
+      XNextEvent (display, &event);
+
+      switch (event.type)
+       {
+       case KeyPress:
+         XDestroyWindow (display, window);
+         XCloseDisplay (display);
+         exit (0);
+         break;
+
+       case Expose:
+
+         while (true)
+           {
+             sfnt_test_edges (edges, num_edges);
+             XFlush (display);
+             usleep (50000);
+           }
+
+         break;
+       }
+    }
+}
+
+static void
+sfnt_test_edge (struct sfnt_edge *edges, size_t num_edges,
+               void *dcontext)
+{
+  size_t i;
+
+  printf ("built %zu edges\n", num_edges);
+
+  for (i = 0; i < num_edges; ++i)
+    {
+      printf ("/* edge x, top, bot: %g, %g - %g.  winding: %d */\n"
+             "/* edge step_x: %g */\n",
+             sfnt_coerce_fixed (edges[i].x),
+             sfnt_coerce_fixed (edges[i].top),
+             sfnt_coerce_fixed (edges[i].bottom),
+             edges[i].winding,
+             sfnt_coerce_fixed (edges[i].step_x));
+#ifdef TEST_VERTEX
+      printf ("ctx.fillRect (%g, %g, 1, 1);\n",
+             sfnt_coerce_fixed (edges[i].x),
+             sfnt_coerce_fixed (sfnt_test_max
+                                - edges[i].y));
+#else
+      printf ("ctx.fillRect (%g, %g, 1, 1);\n",
+             sfnt_coerce_fixed (edges[i].x),
+             sfnt_coerce_fixed (sfnt_test_max
+                                - edges[i].bottom));
+#endif
+    }
+
+  if (getenv ("SFNT_DEBUG_STEP"))
+    {
+      if (!fork ())
+       sfnt_debug_edges (edges, num_edges);
+    }
+
+  printf ("==end of edges==\n");
+
+  sfnt_poly_edges (edges, num_edges, sfnt_test_span, NULL);
+}
+
+static void
+sfnt_x_raster (struct sfnt_raster **rasters,
+              int *advances,
+              int nrasters,
+              struct sfnt_hhea_table *hhea,
+              sfnt_fixed scale)
+{
+  Display *display;
+  Window window;
+  Pixmap *pixmaps;
+  Picture *glyphs, drawable, solid;
+  int event_base, error_base;
+  int major, minor, *depths, count;
+  XRenderPictFormat *format, *glyph_format;
+  Visual *visual;
+  XImage image;
+  GC gc;
+  XGCValues gcvalues;
+  XEvent event;
+  XRenderColor white, black;
+  int i, ascent, origin, x, y;
+  Font font;
+
+  if (!nrasters)
+    exit (0);
+
+  display = XOpenDisplay (NULL);
+
+  if (!display)
+    exit (0);
+
+  if (!XRenderQueryExtension (display, &event_base, &error_base)
+      || !XRenderQueryVersion (display, &major, &minor))
+    exit (0);
+
+  if (major == 0 && minor < 10)
+    exit (0);
+
+  window = XCreateSimpleWindow (display, DefaultRootWindow (display),
+                               0, 0, 100, 150, 0, 0,
+                               WhitePixel (display,
+                                           DefaultScreen (display)));
+  XSelectInput (display, window, ExposureMask);
+  XMapWindow (display, window);
+
+  visual = DefaultVisual (display, DefaultScreen (display));
+  format = XRenderFindVisualFormat (display, visual);
+
+  if (!format)
+    exit (0);
+
+  glyph_format = XRenderFindStandardFormat (display, PictStandardA8);
+  depths = XListDepths (display, DefaultScreen (display), &count);
+
+  for (i = 0; i < count; ++i)
+    {
+      if (depths[i] == 8)
+       goto depth_found;
+    }
+
+  exit (0);
+
+ depth_found:
+
+  XFree (depths);
+  pixmaps = alloca (sizeof *pixmaps * nrasters);
+  glyphs = alloca (sizeof *glyphs * nrasters);
+  gc = None;
+
+  for (i = 0; i < nrasters; ++i)
+    {
+      pixmaps[i] = XCreatePixmap (display, DefaultRootWindow (display),
+                                 rasters[i]->width, rasters[i]->height, 8);
+      if (!gc)
+       gc = XCreateGC (display, pixmaps[i], 0, &gcvalues);
+
+      /* Upload the raster.  */
+      image.width = rasters[i]->width;
+      image.height = rasters[i]->height;
+      image.xoffset = 0;
+      image.format = ZPixmap;
+      image.data = (char *) rasters[i]->cells;
+      image.byte_order = MSBFirst;
+      image.bitmap_unit = 8;
+      image.bitmap_bit_order = LSBFirst;
+      image.bitmap_pad = SFNT_POLY_ALIGNMENT * 8;
+      image.depth = 8;
+      image.bytes_per_line = rasters[i]->stride;
+      image.bits_per_pixel = 8;
+      image.red_mask = 0;
+      image.green_mask = 0;
+      image.blue_mask = 0;
+
+      if (!XInitImage (&image))
+       abort ();
+
+      XPutImage (display, pixmaps[i], gc, &image,
+                0, 0, 0, 0, image.width, image.height);
+
+      glyphs[i] = XRenderCreatePicture (display, pixmaps[i],
+                                       glyph_format, 0, NULL);
+    }
+
+  XFreeGC (display, gc);
+
+  font = XLoadFont (display, "6x13");
+
+  if (!font)
+    exit (1);
+
+  gcvalues.font = font;
+  gcvalues.foreground = BlackPixel (display, DefaultScreen (display));
+  gc = XCreateGC (display, window, GCForeground | GCFont, &gcvalues);
+
+  drawable = XRenderCreatePicture (display, window, format,
+                                  0, NULL);
+  memset (&black, 0, sizeof black);
+  black.alpha = 65535;
+
+  solid = XRenderCreateSolidFill (display, &black);
+
+  while (true)
+    {
+      XNextEvent (display, &event);
+
+      if (event.type == Expose)
+       {
+         white.red = 65535;
+         white.green = 65535;
+         white.blue = 65535;
+         white.alpha = 65535;
+
+         /* Clear the background.  */
+         XRenderFillRectangle (display, PictOpSrc, drawable,
+                               &white, 0, 0, 65535, 65535);
+
+         /* Compute ascent line.  */
+         ascent = sfnt_mul_fixed (hhea->ascent * 65536,
+                                  scale) / 65536;
+
+         origin = 0;
+
+         for (i = 0; i < nrasters; ++i)
+           {
+             /* Compute the base position.  */
+             x = origin + rasters[i]->offx;
+             y = ascent - rasters[i]->height - rasters[i]->offy;
+
+             /* Draw the solid fill with the glyph as clip mask.  */
+             XRenderComposite (display, PictOpOver, solid, glyphs[i],
+                               drawable, 0, 0, 0, 0, x, y,
+                               rasters[i]->width, rasters[i]->height);
+
+             origin += advances[i];
+           }
+       }
+    }
+}
+
+static void
+sfnt_test_raster (struct sfnt_raster *raster,
+                 struct sfnt_hhea_table *hhea,
+                 sfnt_fixed scale)
+{
+  int x, y, i;
+
+  for (y = 0; y < raster->height; ++y)
+    {
+      for (x = 0; x < raster->width; ++x)
+       printf ("%3d ", (int) raster->cells[y * raster->stride + x]);
+      puts ("");
+    }
+
+  if (hhea && getenv ("SFNT_X"))
+    {
+      i = 0;
+
+      if (!fork ())
+       sfnt_x_raster (&raster, &i, 1, hhea, scale);
+    }
+}
+
+
+
+/* Instruction execution tests.  */
+
+static struct sfnt_maxp_table test_interpreter_profile =
+  {
+    0x00010000,
+    650,
+    100,
+    100,
+    100,
+    100,
+    2,
+    100,
+    255,
+    12,
+    12,
+    100,
+    5000,
+    100,
+    1,
+  };
+
+static sfnt_fword test_cvt_values[] =
+  {
+    100, 100, -100, -100, 50, 50, 50, 50, 0, 0,
+  };
+
+static struct sfnt_cvt_table test_interpreter_cvt =
+  {
+    10,
+    test_cvt_values,
+  };
+
+static struct sfnt_head_table test_interpreter_head =
+  {
+    0x00010000,
+    0x00010000,
+    0,
+    0x5f0f3cf5,
+    0,
+    800,
+    0,
+    0,
+    0,
+    0,
+    -312,
+    -555,
+    1315,
+    2163,
+    0,
+    12,
+    0,
+    0,
+    0,
+  };
+
+static struct sfnt_interpreter *
+sfnt_make_test_interpreter (void)
+{
+  return sfnt_make_interpreter (&test_interpreter_profile,
+                               &test_interpreter_cvt,
+                               &test_interpreter_head,
+                               NULL, 17, 17);
+}
+
+struct sfnt_interpreter_test
+{
+  const char *name;
+  unsigned char *instructions;
+  int num_instructions;
+  void *arg;
+  void (*check) (struct sfnt_interpreter *, void *, bool);
+};
+
+static void
+sfnt_run_interpreter_test (struct sfnt_interpreter_test *test,
+                          struct sfnt_interpreter *interpreter)
+{
+  fprintf (stderr, "Testing %s: ", test->name);
+
+  if (setjmp (interpreter->trap))
+    test->check (interpreter, test->arg, true);
+  else
+    {
+      interpreter->IP = 0;
+      interpreter->SP = interpreter->stack;
+      interpreter->instructions = test->instructions;
+      interpreter->num_instructions = test->num_instructions;
+
+      sfnt_interpret_run (interpreter, SFNT_RUN_CONTEXT_TEST);
+      test->check (interpreter, test->arg, false);
+    }
+}
+
+struct sfnt_generic_test_args
+{
+  uint32_t *expected_stack;
+  int expected_stack_elements;
+  bool expected_trap;
+  int expected_IP;
+};
+
+static void
+sfnt_generic_check (struct sfnt_interpreter *interpreter,
+                   void *arg, bool trap)
+{
+  struct sfnt_generic_test_args *args;
+  int i;
+
+  args = arg;
+
+  if (((interpreter->SP - interpreter->stack)
+       != args->expected_stack_elements))
+    {
+      fprintf (stderr,
+              "failed at IP %d:%d (expected %d stack elements,"
+              " got %td); last trap string: %s\n",
+              interpreter->call_depth, interpreter->IP,
+              args->expected_stack_elements,
+              interpreter->SP - interpreter->stack,
+              ((trap && interpreter->trap_reason)
+               ? interpreter->trap_reason
+               : "NULL"));
+
+      for (i = 0; i < interpreter->SP - interpreter->stack; ++i)
+       fprintf (stderr, "%8d ", (int) interpreter->stack[i]);
+      fprintf (stderr, "\n");
+      return;
+    }
+
+  if (memcmp (interpreter->stack, args->expected_stack,
+             ((char *) interpreter->SP
+              - (char *) interpreter->stack)))
+    {
+      fprintf (stderr, "failed (inconsistent stack elements)\n"
+              "machine stack ------------------------->\n");
+
+      for (i = 0; i < args->expected_stack_elements; ++i)
+       fprintf (stderr, "%8d ", (int) interpreter->stack[i]);
+
+      fprintf (stderr,
+              "\nexpected stack ------------------------>\n");
+
+      for (i = 0; i < args->expected_stack_elements; ++i)
+       fprintf (stderr, "%8d ", (int) args->expected_stack[i]);
+
+      fprintf (stderr, "\n");
+      return;
+    }
+
+  if (args->expected_IP != -1
+      && interpreter->IP != args->expected_IP)
+    {
+      fprintf (stderr, "failed (IP is %d, not %d)\n",
+              interpreter->IP, args->expected_IP);
+      return;
+    }
+
+  if (trap)
+    {
+      if (args->expected_trap)
+       fprintf (stderr, "passed (with trap %s)\n",
+                interpreter->trap_reason);
+      else
+       fprintf (stderr, "failed (unexpected trap %s)\n",
+                interpreter->trap_reason);
+
+      return;
+    }
+
+  if (args->expected_trap)
+    fprintf (stderr, "failed, trap not encountered\n");
+  else
+    fprintf (stderr, "passed\n");
+
+  return;
+}
+
+static void
+sfnt_check_srp0 (struct sfnt_interpreter *interpreter,
+                void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed (unexpected trap %s)\n",
+              interpreter->trap_reason);
+      return;
+    }
+
+  if (interpreter->state.rp0 != 0)
+    {
+      fprintf (stderr, "failed, rp0 is not 0, but %d\n",
+              interpreter->state.rp0);
+      return;
+    }
+
+  if (interpreter->state.rp1 != 1)
+    {
+      fprintf (stderr, "failed, rp1 is not 1, but %d\n",
+              interpreter->state.rp1);
+      return;
+    }
+
+  if (interpreter->state.rp2 != 2)
+    {
+      fprintf (stderr, "failed, rp2 is not 2, but %d\n",
+              interpreter->state.rp2);
+      return;
+    }
+
+  if (interpreter->SP != interpreter->stack)
+    {
+      fprintf (stderr, "failed, stack not empty\n");
+      return;
+    }
+
+  fprintf (stderr, "passed\n");
+  return;
+}
+
+static void
+sfnt_check_szp0 (struct sfnt_interpreter *interpreter,
+                void *arg, bool trap)
+{
+  if (!trap)
+    {
+      fprintf (stderr, "failed, expected trap\n");
+      return;
+    }
+
+  if (interpreter->state.zp0 != 1
+      || interpreter->state.zp1 != 1
+      || interpreter->state.zp2 != 0)
+    {
+      fprintf (stderr,
+              "failed, unexpected values of zone pointers: %d %d %d\n",
+              interpreter->state.zp0, interpreter->state.zp1,
+              interpreter->state.zp2);
+      return;
+    }
+
+  if (interpreter->SP != interpreter->stack)
+    {
+      fprintf (stderr, "failed, stack not empty\n");
+      return;
+    }
+
+  fprintf (stderr, "passed with expected trap %s\n",
+          interpreter->trap_reason);
+  return;
+}
+
+static void
+sfnt_check_sloop (struct sfnt_interpreter *interpreter,
+                 void *arg, bool trap)
+{
+  if (interpreter->state.loop != 1)
+    {
+      /* The trap should've restored GS->loop to 1.  */
+      fprintf (stderr, "failed, GS->loop should be 1, not %d\n",
+              interpreter->state.loop);
+      return;
+    }
+
+  if (!trap)
+    {
+      fprintf (stderr, "failed, expected trap\n");
+      return;
+    }
+
+  if (interpreter->SP != interpreter->stack)
+    {
+      fprintf (stderr, "failed, stack not empty\n");
+      return;
+    }
+
+  fprintf (stderr, "passed with expected trap %s\n",
+          interpreter->trap_reason);
+  return;
+}
+
+struct sfnt_rounding_test_args
+{
+  sfnt_f26dot6 value;
+};
+
+static void
+sfnt_check_rounding (struct sfnt_interpreter *interpreter,
+                    void *arg, bool trap)
+{
+  sfnt_f26dot6 value;
+  struct sfnt_rounding_test_args *args;
+
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap: %s\n",
+              interpreter->trap_reason);
+      return;
+    }
+
+  if (interpreter->SP == interpreter->stack)
+    {
+      fprintf (stderr, "failed, empty stack\n");
+      return;
+    }
+
+  value = *(interpreter->SP - 1);
+  args = arg;
+
+  if (value != args->value)
+    {
+      fprintf (stderr, "failed.  value is: %d %d, but wanted: %d %d\n",
+              value >> 6, value & 63, args->value >> 6,
+              args->value & 63);
+      return;
+    }
+
+  fprintf (stderr, "passed, expected value %d\n", value);
+  return;
+}
+
+static void
+sfnt_check_smd (struct sfnt_interpreter *interpreter,
+               void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap\n");
+      return;
+    }
+
+  if (interpreter->state.minimum_distance != 32)
+    {
+      fprintf (stderr, "failed, expected minimum distance"
+              " of 32, got %d\n",
+              interpreter->state.minimum_distance);
+      return;
+    }
+
+  fprintf (stderr, "passed\n");
+  return;
+}
+
+static void
+sfnt_check_scvtci (struct sfnt_interpreter *interpreter,
+                  void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap\n");
+      return;
+    }
+
+  if (interpreter->state.cvt_cut_in != 128)
+    {
+      fprintf (stderr, "failed, expected 128, got %d\n",
+              interpreter->state.cvt_cut_in);
+      return;
+    }
+
+  fprintf (stderr, "passed\n");
+  return;
+}
+
+static void
+sfnt_check_sswci (struct sfnt_interpreter *interpreter,
+                 void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap\n");
+      return;
+    }
+
+  if (interpreter->state.sw_cut_in != 512)
+    {
+      fprintf (stderr, "failed, expected 512, got %d\n",
+              interpreter->state.sw_cut_in);
+      return;
+    }
+
+  fprintf (stderr, "passed\n");
+  return;
+}
+
+static void
+sfnt_check_ssw (struct sfnt_interpreter *interpreter,
+               void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap\n");
+      return;
+    }
+
+  if (interpreter->state.single_width_value
+      != sfnt_mul_f26dot6_fixed (-64, interpreter->scale))
+    {
+      fprintf (stderr, "failed, got %d at scale %d,"
+              " expected %d\n",
+              interpreter->state.single_width_value,
+              interpreter->scale,
+              sfnt_mul_f26dot6_fixed (-64, interpreter->scale));
+      return;
+    }
+
+  fprintf (stderr, "passed\n");
+  return;
+}
+
+static void
+sfnt_check_flipon (struct sfnt_interpreter *interpreter,
+                  void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap\n");
+      return;
+    }
+
+  if (!interpreter->state.auto_flip)
+    fprintf (stderr, "failed, auto flip not enabled\n");
+  else
+    fprintf (stderr, "pass\n");
+
+  return;
+}
+
+static void
+sfnt_check_flipoff (struct sfnt_interpreter *interpreter,
+                   void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap\n");
+      return;
+    }
+
+  if (interpreter->state.auto_flip)
+    fprintf (stderr, "failed, auto flip not disabled\n");
+  else
+    fprintf (stderr, "pass\n");
+
+  return;
+}
+
+static void
+sfnt_check_sdb (struct sfnt_interpreter *interpreter,
+               void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap %s\n",
+              interpreter->trap_reason);
+      return;
+    }
+
+  if (interpreter->state.delta_base != 8)
+    fprintf (stderr, "failed, delta base is %d, not 8\n",
+            interpreter->state.delta_base);
+  else
+    fprintf (stderr, "pass\n");
+
+  return;
+}
+
+static void
+sfnt_check_sds (struct sfnt_interpreter *interpreter,
+               void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap %s\n",
+              interpreter->trap_reason);
+      return;
+    }
+
+  if (interpreter->state.delta_shift != 1)
+    fprintf (stderr, "failed, delta shift is %d, not 1\n",
+            interpreter->state.delta_shift);
+  else
+    fprintf (stderr, "pass\n");
+
+  return;
+}
+
+static void
+sfnt_check_scanctrl (struct sfnt_interpreter *interpreter,
+                    void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap %s\n",
+              interpreter->trap_reason);
+      return;
+    }
+
+  if (interpreter->SP != interpreter->stack)
+    {
+      fprintf (stderr, "failed, expected empty stack\n");
+      return;
+    }
+
+  if (interpreter->state.scan_control != 1)
+    fprintf (stderr, "failed, scan control is %d, not 1\n",
+            interpreter->state.scan_control);
+  else
+    fprintf (stderr, "pass\n");
+
+  return;
+}
+
+static void
+sfnt_check_instctrl (struct sfnt_interpreter *interpreter,
+                    void *arg, bool trap)
+{
+  if (trap)
+    {
+      fprintf (stderr, "failed, unexpected trap %s\n",
+              interpreter->trap_reason);
+      return;
+    }
+
+  if (interpreter->SP != interpreter->stack)
+    {
+      fprintf (stderr, "failed, expected empty stack\n");
+      return;
+    }
+
+  if (interpreter->state.instruct_control != 2)
+    fprintf (stderr, "failed, inst control is %d, not 2\n",
+            interpreter->state.instruct_control);
+  else
+    fprintf (stderr, "pass\n");
+
+  return;
+}
+
+static struct sfnt_generic_test_args npushb_test_args =
+  {
+    (uint32_t []) { 1U, 2U, 3U, 4U, },
+    4,
+    true,
+    6,
+  };
+
+static struct sfnt_generic_test_args npushw_test_args =
+  {
+    (uint32_t []) { 0x101U, 0x202U, 0x303U, 0x404U, },
+    4,
+    true,
+    10,
+  };
+
+static struct sfnt_generic_test_args pushb_test_args =
+  {
+    (uint32_t []) { 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U,
+                   1U, },
+    9,
+    true,
+    11,
+  };
+
+static struct sfnt_generic_test_args pushw_test_args =
+  {
+    (uint32_t []) { 0x203U, 0x204U, 0x205U, 0x206U, 0x207U, 0x208U,
+                   0x909U, 0x909U, (uint32_t) -1, },
+    9,
+    true,
+    20,
+  };
+
+static struct sfnt_generic_test_args stack_overflow_test_args =
+  {
+    NULL,
+    0,
+    true,
+    0,
+  };
+
+static struct sfnt_generic_test_args stack_underflow_test_args =
+  {
+    NULL,
+    0,
+    true,
+    4,
+  };
+
+static struct sfnt_rounding_test_args rtg_test_args =
+  {
+    64,
+  };
+
+static struct sfnt_rounding_test_args rtg_symmetric_test_args =
+  {
+    -64,
+  };
+
+static struct sfnt_rounding_test_args rtg_1_test_args =
+  {
+    0,
+  };
+
+static struct sfnt_rounding_test_args rtg_1_symmetric_test_args =
+  {
+    0,
+  };
+
+static struct sfnt_rounding_test_args rthg_test_args =
+  {
+    32,
+  };
+
+static struct sfnt_rounding_test_args rthg_1_test_args =
+  {
+    96,
+  };
+
+static struct sfnt_rounding_test_args rtdg_test_args =
+  {
+    32,
+  };
+
+static struct sfnt_rounding_test_args rtdg_1_test_args =
+  {
+    0,
+  };
+
+static struct sfnt_rounding_test_args rtdg_2_test_args =
+  {
+    32,
+  };
+
+static struct sfnt_rounding_test_args rtdg_3_test_args =
+  {
+    64,
+  };
+
+static struct sfnt_generic_test_args else_test_args =
+  {
+    (uint32_t []) { 77U, 90U, 83U, },
+    3,
+    false,
+    40,
+  };
+
+static struct sfnt_generic_test_args jmpr_test_args =
+  {
+    /* What ends up on the stack?
+
+       First, there are the three words that the first PUSHW[2]
+       instruction has pushed:
+
+        0, 0xb2, -3
+
+       After those three words are pushed, JMPR[] is called, and pops an
+       offset:
+
+        -3
+
+       so now the stack is:
+
+        0, 0xb2
+
+       as a result of the relative jump, IP is now at the least
+       significant byte of the word inside what was originally a
+       PUSHW[2] instruction, 0xb2, which itself is PUSHB[2]!
+
+       As a result of that instruction, three more bytes, including
+       JMPR[] itself are pushed onto the stack, making it:
+
+        0, 0xb2, 255, 253, 0x1c
+
+       Then, execution continues as usual.  4 is pushed on to the
+       stack, making it:
+
+        0, 0xb2, 255, 253, 0x1c, 4
+
+       Another JMPR[] pops:
+
+        4
+
+       making the stack:
+
+        0, 0xb2, 255, 253, 0x1c
+
+       And skips the next three padding bytes, finally reaching a
+       PUSHW[0] instruction which pushes -30 onto the stack:
+
+        0, 0xb2, 255, 253, 0x1c, -30
+
+       and a JMPR[] instruction, which pops:
+
+        -30
+
+       making:
+
+        0, 0xb2, 255, 253,
+
+       and subsequently traps, as -30 would underflow the instruction
+       stream.  */
+    (uint32_t []) { 0, 0xb2, 255, 253, 0x1c, },
+    5,
+    true,
+    17,
+  };
+
+static struct sfnt_generic_test_args dup_test_args =
+  {
+    NULL,
+    0,
+    true,
+    5,
+  };
+
+static struct sfnt_generic_test_args pop_test_args =
+  {
+    (uint32_t []) { 70, 70, },
+    2,
+    false,
+    5,
+  };
+
+static struct sfnt_generic_test_args clear_test_args =
+  {
+    NULL,
+    0,
+    false,
+    10,
+  };
+
+static struct sfnt_generic_test_args swap_test_args =
+  {
+    (uint32_t []) { 2, 1, },
+    2,
+    false,
+    4,
+  };
+
+static struct sfnt_generic_test_args depth_test_args =
+  {
+    (uint32_t []) { 3, 3, 3, 3, },
+    4,
+    false,
+    5,
+  };
+
+static struct sfnt_generic_test_args cindex_test_args =
+  {
+    (uint32_t []) { 0, 3, 3, 4, 0, },
+    5,
+    true,
+    10,
+  };
+
+static struct sfnt_generic_test_args mindex_test_args =
+  {
+    (uint32_t []) { 0, 3, 7, 4, 4, },
+    5,
+    false,
+    10,
+  };
+
+static struct sfnt_generic_test_args raw_test_args =
+  {
+    NULL,
+    0,
+    true,
+    0,
+  };
+
+static struct sfnt_generic_test_args loopcall_test_args =
+  {
+    (uint32_t []) { 10, },
+    1,
+    false,
+    12,
+  };
+
+static struct sfnt_generic_test_args call_test_args =
+  {
+    (uint32_t []) { 11, },
+    1,
+    true,
+    2,
+  };
+
+static struct sfnt_generic_test_args fdef_test_args =
+  {
+    NULL,
+    0,
+    true,
+    4,
+  };
+
+static struct sfnt_generic_test_args fdef_1_test_args =
+  {
+    NULL,
+    0,
+    true,
+    9,
+  };
+
+static struct sfnt_generic_test_args endf_test_args =
+  {
+    NULL,
+    0,
+    true,
+    0,
+  };
+
+static struct sfnt_generic_test_args ws_test_args =
+  {
+    (uint32_t []) { 40, },
+    1,
+    true,
+    10,
+  };
+
+static struct sfnt_generic_test_args rs_test_args =
+  {
+    NULL,
+    0,
+    true,
+    2,
+  };
+
+static struct sfnt_generic_test_args wcvtp_test_args =
+  {
+    (uint32_t []) { 32, },
+    1,
+    true,
+    10,
+  };
+
+static struct sfnt_generic_test_args rcvt_test_args =
+  {
+    (uint32_t []) { 136, },
+    1,
+    true,
+    5,
+  };
+
+static struct sfnt_generic_test_args mppem_test_args =
+  {
+    (uint32_t []) { 17, },
+    1,
+    false,
+    1,
+  };
+
+static struct sfnt_generic_test_args mps_test_args =
+  {
+    (uint32_t []) { 17, },
+    1,
+    false,
+    1,
+  };
+
+static struct sfnt_generic_test_args debug_test_args =
+  {
+    NULL,
+    0,
+    false,
+    3,
+  };
+
+static struct sfnt_generic_test_args lt_test_args =
+  {
+    (uint32_t []) { 1, 0, 0, },
+    3,
+    false,
+    12,
+  };
+
+static struct sfnt_generic_test_args lteq_test_args =
+  {
+    (uint32_t []) { 1, 0, 1, },
+    3,
+    false,
+    12,
+  };
+
+static struct sfnt_generic_test_args gt_test_args =
+  {
+    (uint32_t []) { 0, 1, 0, },
+    3,
+    false,
+    12,
+  };
+
+static struct sfnt_generic_test_args gteq_test_args =
+  {
+    (uint32_t []) { 0, 1, 1, },
+    3,
+    false,
+    12,
+  };
+
+static struct sfnt_generic_test_args eq_test_args =
+  {
+    (uint32_t []) { 0, 1, 0, },
+    3,
+    false,
+    18,
+  };
+
+static struct sfnt_generic_test_args neq_test_args =
+  {
+    (uint32_t []) { 1, 0, 1, },
+    3,
+    false,
+    18,
+  };
+
+static struct sfnt_generic_test_args odd_test_args =
+  {
+    (uint32_t []) { 1, 0, },
+    2,
+    false,
+    9,
+  };
+
+static struct sfnt_generic_test_args even_test_args =
+  {
+    (uint32_t []) { 0, 1, },
+    2,
+    false,
+    9,
+  };
+
+static struct sfnt_generic_test_args if_test_args =
+  {
+    (uint32_t []) { 17, 24, 1, 2, 3, 4, 5, -1, -1,
+                   88, 1, 3, },
+    12,
+    false,
+    185,
+  };
+
+static struct sfnt_generic_test_args eif_test_args =
+  {
+    NULL,
+    0,
+    false,
+    3,
+  };
+
+static struct sfnt_generic_test_args and_test_args =
+  {
+    (uint32_t []) { 0, 0, 1, 0, },
+    4,
+    false,
+    16,
+  };
+
+static struct sfnt_generic_test_args or_test_args =
+  {
+    (uint32_t []) { 1, 1, 1, 0, },
+    4,
+    false,
+    16,
+  };
+
+static struct sfnt_generic_test_args not_test_args =
+  {
+    (uint32_t []) { 0, 1, },
+    2,
+    false,
+    6,
+  };
+
+static struct sfnt_generic_test_args sds_test_args =
+  {
+    NULL,
+    0,
+    true,
+    5,
+  };
+
+static struct sfnt_generic_test_args add_test_args =
+  {
+    (uint32_t []) { 96, -1, },
+    2,
+    false,
+    10,
+  };
+
+static struct sfnt_generic_test_args sub_test_args =
+  {
+    (uint32_t []) { 64, -64, 431, },
+    3,
+    false,
+    14,
+  };
+
+static struct sfnt_generic_test_args div_test_args =
+  {
+    (uint32_t []) { 32, -64, },
+    2,
+    true,
+    15,
+  };
+
+static struct sfnt_generic_test_args mul_test_args =
+  {
+    (uint32_t []) { 255, -255, 255, },
+    3,
+    false,
+    16,
+  };
+
+static struct sfnt_generic_test_args abs_test_args =
+  {
+    (uint32_t []) { 1, 1, },
+    2,
+    false,
+    7,
+  };
+
+static struct sfnt_generic_test_args neg_test_args =
+  {
+    (uint32_t []) { 1, -1, },
+    2,
+    false,
+    7,
+  };
+
+static struct sfnt_generic_test_args floor_test_args =
+  {
+    (uint32_t []) { -128, -64, 0, 64, 128, },
+    5,
+    false,
+    17,
+  };
+
+static struct sfnt_generic_test_args ceiling_test_args =
+  {
+    (uint32_t []) { -128, -128, -64, 0, 64, 128, 128, },
+    7,
+    false,
+    25,
+  };
+
+static struct sfnt_generic_test_args round_test_args =
+  {
+    NULL,
+    0,
+    true,
+    0,
+  };
+
+static struct sfnt_generic_test_args nround_test_args =
+  {
+    (uint32_t []) { 63, },
+    1,
+    false,
+    3,
+  };
+
+static struct sfnt_generic_test_args wcvtf_test_args =
+  {
+    (uint32_t []) { (63 * 17 * 65535 / 800) >> 10, },
+    1,
+    false,
+    7,
+  };
+
+static struct sfnt_generic_test_args jrot_test_args =
+  {
+    (uint32_t []) { 40, 40, },
+    2,
+    false,
+    13,
+  };
+
+static struct sfnt_generic_test_args jrof_test_args =
+  {
+    (uint32_t []) { 4, },
+    1,
+    false,
+    13,
+  };
+
+static struct sfnt_generic_test_args deltac1_test_args =
+  {
+    (uint32_t []) { ((((50 * 17 * 65535) + 32767) / 800) >> 10) + 8,
+                   ((((50 * 17 * 65535) + 32767) / 800) >> 10) + 8, },
+    2,
+    false,
+    22,
+  };
+
+static struct sfnt_generic_test_args deltac2_test_args =
+  {
+    (uint32_t []) { ((((50 * 17 * 65535) + 32767) / 800) >> 10) + 8,
+                   ((((50 * 17 * 65535) + 32767) / 800) >> 10) + 8, },
+    2,
+    false,
+    22,
+  };
+
+static struct sfnt_generic_test_args deltac3_test_args =
+  {
+    (uint32_t []) { ((((50 * 17 * 65535) + 32767) / 800) >> 10) + 8,
+                   ((((50 * 17 * 65535) + 32767) / 800) >> 10) + 8, },
+    2,
+    false,
+    22,
+  };
+
+/* Macros and instructions for detailed rounding tests.  */
+
+/* PUSHB[0] period:phase:threshold
+   SROUND[] */
+#define SFNT_ROUNDING_OPERAND(period, phase, threshold)        \
+  0xb0, (((unsigned char) period << 6)                 \
+        | ((unsigned char) phase & 3) << 4             \
+        | ((unsigned char) threshold & 15)), 0x76
+
+/* PUSHB[0] period:phase:threshold
+   S45ROUND[] */
+#define SFNT_ROUNDING_OPERAND_45(period, phase, threshold)     \
+  0xb0, (((unsigned char) period << 6)                         \
+        | ((unsigned char) phase & 3) << 4                     \
+        | ((unsigned char) threshold & 15)), 0x77
+
+/* PUSHB[0] value
+   ROUND[] */
+#define SFNT_ROUND_VALUE(value) 0xb0, value, 0x68
+
+static unsigned char sfnt_sround_instructions[] =
+  {
+    SFNT_ROUNDING_OPERAND (0, 0, 8),
+    SFNT_ROUND_VALUE (15),
+    SFNT_ROUND_VALUE (17),
+    SFNT_ROUNDING_OPERAND (1, 0, 8),
+    SFNT_ROUND_VALUE (32),
+    SFNT_ROUND_VALUE (16),
+    SFNT_ROUNDING_OPERAND (2, 0, 8),
+    SFNT_ROUND_VALUE (64),
+    SFNT_ROUND_VALUE (63),
+    SFNT_ROUNDING_OPERAND (0, 1, 8),
+    SFNT_ROUND_VALUE (16),
+    SFNT_ROUND_VALUE (24),
+    SFNT_ROUNDING_OPERAND (0, 2, 8),
+    SFNT_ROUND_VALUE (20),
+    SFNT_ROUND_VALUE (48),
+    SFNT_ROUNDING_OPERAND (0, 3, 8),
+    SFNT_ROUND_VALUE (7),
+    SFNT_ROUND_VALUE (70),
+  };
+
+static uint32_t sfnt_sround_values[] =
+  {
+    /* 0, 0, 8 = RTDG; 15 rounded to the double grid and becomes 0, 17
+       is 32.  */
+    0, 32,
+    /* 1, 0, 8 = RTG; 32 rounded to the grid is 64, 16 is 0.  */
+    64, 0,
+    /* 2, 0, 8 = round to a grid separated by 128s.  64 is 128, 63 is
+       0.  */
+    128, 0,
+    /* 0, 1, 8 = round to a double grid with a phase of 8.  16 rounds
+       down to 8, 24 rounds up to 40.  */
+    8, 40,
+    /* 0, 2, 8 = round to a double grid with a phase of 16.  20 rounds
+       down to 16, 40 rounds up to 48.  */
+    16, 48,
+    /* 0, 3, 8 = round to a double grid with a phase of 48.  7 rounds
+       up to 16, 70 rounds up to 80.  */
+    16, 80,
+  };
+
+static struct sfnt_generic_test_args sround_test_args =
+  {
+    sfnt_sround_values,
+    ARRAYELTS (sfnt_sround_values),
+    false,
+    ARRAYELTS (sfnt_sround_instructions),
+  };
+
+static unsigned char sfnt_s45round_instructions[] =
+  {
+    SFNT_ROUNDING_OPERAND_45 (0, 0, 0),
+    SFNT_ROUND_VALUE (1),
+    SFNT_ROUND_VALUE (45),
+  };
+
+static uint32_t sfnt_s45round_values[] =
+  {
+    /* 0, 0, 0: 1 rounded to the double cubic grid becomes 45, and 46
+       rounded to the double cubic grid becomes 90.  */
+    45, 90,
+  };
+
+static struct sfnt_generic_test_args s45round_test_args =
+  {
+    sfnt_s45round_values,
+    ARRAYELTS (sfnt_s45round_values),
+    false,
+    ARRAYELTS (sfnt_s45round_instructions),
+  };
+
+static struct sfnt_generic_test_args rutg_test_args =
+  {
+    (uint32_t []) { 64, 64, 0, },
+    3,
+    false,
+    10,
+  };
+
+static struct sfnt_generic_test_args rdtg_test_args =
+  {
+    (uint32_t []) { 0, 0, 64, },
+    3,
+    false,
+    10,
+  };
+
+static struct sfnt_generic_test_args sangw_test_args =
+  {
+    NULL,
+    0,
+    false,
+    3,
+  };
+
+static struct sfnt_generic_test_args aa_test_args =
+  {
+    NULL,
+    0,
+    false,
+    3,
+  };
+
+static struct sfnt_generic_test_args getinfo_test_args =
+  {
+    /* Pretend to be the Macintosh System 7 scaler.
+
+       This lets the interpreter get away with only two phantom
+       points, as specified in Apple's TrueType reference manual.  */
+    (uint32_t []) { 2, 0, },
+    2,
+    false,
+    6,
+  };
+
+static struct sfnt_generic_test_args idef_test_args =
+  {
+    (uint32_t []) { 1, 2, 3, },
+    3,
+    false,
+    11,
+  };
+
+static struct sfnt_generic_test_args roll_test_args =
+  {
+    (uint32_t []) { 1, 2, 4, 5, 3, },
+    5,
+    false,
+    7,
+  };
+
+static struct sfnt_generic_test_args roll_1_test_args =
+  {
+    (uint32_t []) { 1, 2, },
+    2,
+    true,
+    3,
+  };
+
+static struct sfnt_generic_test_args max_test_args =
+  {
+    (uint32_t []) { 70, },
+    1,
+    false,
+    6,
+  };
+
+static struct sfnt_generic_test_args min_test_args =
+  {
+    (uint32_t []) { -70, },
+    1,
+    false,
+    6,
+  };
+
+static struct sfnt_generic_test_args scantype_test_args =
+  {
+    NULL,
+    0,
+    false,
+    3,
+  };
+
+static struct sfnt_interpreter_test all_tests[] =
+  {
+    {
+      "NPUSHB",
+      /* NPUSHB[] 4 1 2 3 4
+        NPUSHB[] 5 1 2 3 4 */
+      (unsigned char []) { 0x40, 4, 1, 2, 3, 4,
+                          0x40, 5, 1, 2, 3, 4, },
+      10,
+      &npushb_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "NPUSHW",
+      /* NPUSHW[] 4 0x101 0x202 0x303 0x404
+        NPUSHW[] 4 0x101 0x202 0x303 0x4?? */
+      (unsigned char []) { 0x41, 4, 1, 1, 2, 2, 3, 3, 4, 4,
+                          0x41, 4, 1, 1, 2, 2, 3, 3, 4, },
+      19,
+      &npushw_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "PUSHB",
+      /* PUSHB[7] 1 2 3 4 5 6 7 8
+        PUSHB[0] 1
+        PUSHB[5] 1 2 3 4 5 ? */
+      (unsigned char []) { 0xb7, 1, 2, 3, 4, 5, 6, 7, 8,
+                          0xb0, 1,
+                          0xb5, 1, 2, 3, 4, 5, },
+      17,
+      &pushb_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "PUSHW",
+      /* PUSHW[7] 2 3 2 4 2 5 2 6 2 7 2 8 9 9 9 9
+        PUSHW[0] 255 255 -- this should get sign-extended
+        PUSHW[5] 1 1 2 2 3 3 4 4 5 5 6 ? */
+      (unsigned char []) { 0xbf, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 8, 9, 9, 9, 
9,
+                          0xb8, 255, 255,
+                          0xbc, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, },
+      28,
+      &pushw_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "that stack overflow is handled correctly",
+      /* NPUSHB[] 101 0... */
+      (unsigned char [103]) { 0x40, 101, },
+      103,
+      &stack_overflow_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "that stack underflow is handled correctly",
+      /* PUSHW[0] 100 100
+        POP[]
+        POP[] */
+      (unsigned char []) { 0xb8, 100, 100,
+                          0x21,
+                          0x21, },
+      5,
+      &stack_underflow_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SRP0, SRP1, SRP2",
+      /* PUSHB[0] 0
+        SRP0[]
+        PUSHB[0] 1
+        SRP1[]
+        PUSHB[0] 2
+        SRP2[] */
+      (unsigned char []) { 0xb0, 0,
+                          0x10,
+                          0xb0, 1,
+                          0x11,
+                          0xb0, 2,
+                          0x12, },
+      9,
+      NULL,
+      sfnt_check_srp0,
+    },
+    {
+      "SZP0, SZP1, SZP2, SZPS",
+      /* PUSHB[0] 1
+        SZP0[]
+        PUSHB[0] 1
+        SZP1[]
+        PUSHB[0] 0
+        SZP2[]
+        PUSHB[0] 5
+        SZPS[]  */
+      (unsigned char []) { 0xb0, 1,
+                          0x13,
+                          0xb0, 1,
+                          0x14,
+                          0xb0, 0,
+                          0x15,
+                          0xb0, 5,
+                          0x16, },
+      12,
+      NULL,
+      sfnt_check_szp0,
+    },
+    {
+      "SLOOP",
+      /* PUSHB[0] 2
+        SLOOP[]
+        PUSHB[0] 0
+        SLOOP[] */
+      (unsigned char []) { 0xb0, 2,
+                          0x17,
+                          0xb0, 0,
+                          0x17, },
+      6,
+      NULL,
+      sfnt_check_sloop,
+    },
+    {
+      "RTG",
+      /* RTG[]
+        PUSHB[0] 32
+        ROUND[] */
+      (unsigned char []) { 0x18,
+                          0xb0, 32,
+                          0x68, },
+      4,
+      &rtg_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "rounding symmetry",
+      /* RTG[]
+        PUSHW[0] 255 -32
+        ROUND[] */
+      (unsigned char []) { 0x18,
+                          0xb8, 255, - (signed char) 32,
+                          0x68, },
+      5,
+      &rtg_symmetric_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "RTG to 0",
+      /* RTG[]
+        PUSHB[0] 31
+        ROUND[] */
+      (unsigned char []) { 0x18,
+                          0xb0, 31,
+                          0x68, },
+      4,
+      &rtg_1_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "rounding symmetry to 0",
+      /* RTG[]
+        PUSHB[0] 255 -31
+        ROUND[] */
+      (unsigned char []) { 0x18,
+                          0xb8, 255, - (signed char) 31,
+                          0x68, },
+      5,
+      &rtg_1_symmetric_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "RTHG",
+      /* RTHG[]
+        PUSHB[0] 0
+        ROUND[] */
+      (unsigned char []) { 0x19,
+                          0xb0, 0,
+                          0x68, },
+      4,
+      &rthg_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "RTHG to 96",
+      /* RTHG[]
+        PUSHB[0] 64
+        ROUND[] */
+      (unsigned char []) { 0x19,
+                          0xb0, 64,
+                          0x68, },
+      4,
+      &rthg_1_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "SMD",
+      /* PUSHB[0] 32
+        SMD[] */
+      (unsigned char []) { 0xb0, 32,
+                          0x1a, },
+      3,
+      NULL,
+      sfnt_check_smd,
+    },
+    {
+      "ELSE",
+      /* ELSE[]
+        ;; Lots of variable length instructions
+        ;; which will not be executed, like:
+        NPUSHW[] 3 11 22 33 44 55 66
+        NPUSHB[] 1 3
+        PUSHW[2] 1 1 2 2 3 3
+        PUSHB[2] 1 2 3
+        ;; Also test nested ifs.
+        PUSHW[0] 1 1
+        IF[]
+        PUSHW[0] 1 1
+        ELSE[]
+        PUSHW[0] 1 1
+        EIF[]
+        EIF[]
+        PUSHW[0] 1 1
+        ;; the actual contents of the stack.
+        PUSHB[2] 77 90 83 */
+      (unsigned char []) { 0x1b,
+                          0x41, 3, 11, 22, 33, 44, 55, 66,
+                          0x40, 1, 3,
+                          0xba, 1, 1, 2, 2, 3, 3,
+                          0xb2, 1, 2, 3,
+                          0xb8, 1, 1,
+                          0x58,
+                          0xb8, 1, 1,
+                          0x1b,
+                          0xb8, 1, 1,
+                          0x59,
+                          0x59,
+                          0xb2, 77, 90, 83, },
+      40,
+      &else_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "JMPR",
+      /* PUSHW[2] 00 00 00 PUSHB[2] 255 253 JMPR[]
+        PUSHB[0] 4
+        JMPR[]
+        255 255 255
+        PUSHW[0] 255 -30
+        JMPR[] */
+      (unsigned char []) { 0xba, 00, 00, 00, 0xb2, 255, 253, 0x1c,
+                          0xb0, 4,
+                          0x1c,
+                          255, 255, 255,
+                          0xb8, 255, -30,
+                          0x1c, },
+      18,
+      &jmpr_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SCVTCI",
+      /* PUSHB[0] 128
+        SCVTCI[] */
+      (unsigned char []) { 0xb0, 128,
+                          0x1d, },
+      3,
+      NULL,
+      sfnt_check_scvtci,
+    },
+    {
+      "SSWCI",
+      /* PUSHW[0] 2 0 ;; 512
+        SSWCI[] */
+      (unsigned char []) { 0xb8, 2, 0,
+                          0x1e, },
+      4,
+      NULL,
+      sfnt_check_sswci,
+    },
+    {
+      "SSW",
+      /* PUSHW[0] 255 255 ; -1
+        SSW[] ; this should be converted to device-space */
+      (unsigned char []) { 0xb8, 255, 255,
+                          0x1f, },
+      4,
+      NULL,
+      sfnt_check_ssw,
+    },
+    {
+      "DUP",
+      /* PUSHB[0] 70
+        DUP[]
+        POP[]
+        POP[]
+        DUP[] */
+      (unsigned char []) { 0xb0, 70,
+                          0x20,
+                          0x21,
+                          0x21,
+                          0x70, },
+      6,
+      &dup_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "POP",
+      /* PUSHB[0] 70
+        DUP[]
+        DUP[]
+        POP[] */
+      (unsigned char []) { 0xb0, 70,
+                          0x20,
+                          0x20,
+                          0x21, },
+      5,
+      &pop_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "CLEAR",
+      /* PUSHB[7] 1 2 3 4 5 6 7 8
+        CLEAR[] */
+      (unsigned char []) { 0xb7, 1, 2, 3, 4, 5, 6, 7, 8,
+                          0x22, },
+      10,
+      &clear_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SWAP",
+      /* PUSHB[1] 1 2
+        SWAP[] */
+      (unsigned char []) { 0xb1, 1, 2,
+                          0x23, },
+      4,
+      &swap_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "DEPTH",
+      /* PUSHB[2] 3 3 3
+        DEPTH[] */
+      (unsigned char []) { 0xb2, 3, 3, 3,
+                          0x24, },
+      5,
+      &depth_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "CINDEX",
+      /* PUSHB[4] 0 3 3 4 1
+        CINDEX[] ; pops 1, indices 4
+        CINDEX[] ; pops 4, indices 0
+        PUSHB[0] 6
+        CINDEX[] ; pops 6, trap */
+      (unsigned char []) { 0xb4, 0, 3, 3, 4, 1,
+                          0x25,
+                          0x25,
+                          0xb0, 6,
+                          0x25, },
+      11,
+      &cindex_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "MINDEX",
+      /* PUSHB[6] 0 3 4 7 3 4 2
+        MINDEX[] ; pops 2, array becomes 0 3 4 7 4 3
+        MINDEX[] ; pops 3, array becomes 0 3 7 4 4 */
+      (unsigned char []) { 0xb6, 0, 3, 4, 7, 3, 4, 2,
+                          0x26,
+                          0x26, },
+      10,
+      &mindex_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "RAW",
+      /* RAW[] */
+      (unsigned char []) { 0x28, },
+      1,
+      &raw_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "LOOPCALL",
+      /* PUSHB[1] 0 2
+        FDEF[]
+        PUSHB[0] 1
+        ADD[]
+        ENDF[]
+        PUSHB[1] 10 2
+        LOOPCALL[]  */
+      (unsigned char []) { 0xb1, 0, 2,
+                          0x2c,
+                          0xb0, 1,
+                          0x60,
+                          0x2d,
+                          0xb1, 10, 2,
+                          0x2a, },
+      12,
+      &loopcall_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "CALL",
+      /* PUSHB[1] 7 2
+        FDEF[]
+        PUSHB[0] 1
+        ADD[]
+        ENDF[]
+        PUSHB[0] 2
+        CALL[]
+        PUSHB[0] 3
+        ADD[]
+        ;; Test that infinite recursion fails.
+        PUSHB[0] 3
+        FDEF[]
+        PUSHB[0] 3
+        CALL[]
+        ENDF[]
+        PUSHB[0] 3
+        CALL[] */
+      (unsigned char []) { 0xb1, 7, 2,
+                          0x2c,
+                          0xb0, 1,
+                          0x60,
+                          0x2d,
+                          0xb0, 2,
+                          0x2b,
+                          0xb0, 3,
+                          0x60,
+                          0xb0, 3,
+                          0x2c,
+                          0xb0, 3,
+                          0x2b,
+                          0x2d,
+                          0xb0, 3,
+                          0x2b, },
+      24,
+      &call_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "that FDEF traps inside nested definitions",
+      /* PUSHB[0] 1
+        FDEF[]
+        FDEF[]
+        ENDF[]
+        ENDF[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x2c,
+                          0x2c,
+                          0x2d,
+                          0x2d, },
+      6,
+      &fdef_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "that FDEF traps upon missing ENDF",
+      /* PUSHB[0] 1
+        FDEF[]
+        PUSHB[3] 1 2 3 4
+        POP[]  */
+      (unsigned char []) { 0xb0, 1,
+                          0x2c,
+                          0xb3, 1, 2, 3, 4,
+                          0x21, },
+      9,
+      &fdef_1_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "ENDF",
+      /* ENDF[] */
+      (unsigned char []) { 0x2d, },
+      1,
+      &endf_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "RTDG",
+      /* RTDG[]
+        PUSHB[0] 16
+        ROUND[] */
+      (unsigned char []) { 0x3d,
+                          0xb0, 16,
+                          0x68, },
+      4,
+      &rtdg_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "RTDG down to 0",
+      /* RTDG[]
+        PUSHB[0] 15
+        ROUND[] */
+      (unsigned char []) { 0x3d,
+                          0xb0, 15,
+                          0x68, },
+      4,
+      &rtdg_1_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "RTDG down to 32",
+      /* RTDG[]
+        PUSHB[0] 47
+        ROUND[] */
+      (unsigned char []) { 0x3d,
+                          0xb0, 47,
+                          0x68, },
+      4,
+      &rtdg_2_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "RTDG up to 64",
+      /* RTDG[]
+        PUSHB[0] 48
+        ROUND[] */
+      (unsigned char []) { 0x3d,
+                          0xb0, 48,
+                          0x68, },
+      4,
+      &rtdg_3_test_args,
+      sfnt_check_rounding,
+    },
+    {
+      "WS",
+      /* PUSHB[1] 240 40
+        WS[]
+        PUSHB[0] 240
+        RS[]
+        PUSHB[1] 255 40
+        WS[] */
+      (unsigned char []) { 0xb1, 240, 40,
+                          0x42,
+                          0xb0, 240,
+                          0x43,
+                          0xb1, 255, 40,
+                          0x42, },
+      11,
+      &ws_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "RS",
+      /* PUSHB[0] 255
+        RS[] */
+      (unsigned char []) { 0xb0, 255,
+                          0x43, },
+      3,
+      &rs_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "WCVTP",
+      /* PUSHB[1] 9 32
+        WCVTP[]
+        PUSHB[0] 9
+        RCVT[]
+        PUSHB[1] 10 10
+        WCVTP[] */
+      (unsigned char []) { 0xb1, 9, 32,
+                          0x44,
+                          0xb0, 9,
+                          0x45,
+                          0xb1, 10, 10,
+                          0x44, },
+      11,
+      &wcvtp_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "RCVT",
+      /* PUSHB[0] 1
+        RCVT[]
+        PUSHB[0] 10
+        RCVT[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x45,
+                          0xb0, 10,
+                          0x45, },
+      6,
+      &rcvt_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "MPPEM",
+      /* MPPEM[] */
+      (unsigned char []) { 0x4b, },
+      1,
+      &mppem_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "MPS",
+      /* MPS[] */
+      (unsigned char []) { 0x4c, },
+      1,
+      &mps_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "FLIPON",
+      /* FLIPON[] */
+      (unsigned char []) { 0x4d, },
+      1,
+      NULL,
+      sfnt_check_flipon,
+    },
+    {
+      "FLIPOFF",
+      /* FLIPOFF[] */
+      (unsigned char []) { 0x4e, },
+      1,
+      NULL,
+      sfnt_check_flipoff,
+    },
+    {
+      "DEBUG",
+      /* PUSHB[0] 1
+        DEBUG[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x4f, },
+      3,
+      &debug_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "LT",
+      /* PUSHB[1] 47 48
+        LT[]
+        PUSHB[1] 48 47
+        LT[]
+        PUSHB[1] 47 47
+        LT[] */
+      (unsigned char []) { 0xb1, 47, 48,
+                          0x50,
+                          0xb1, 48, 47,
+                          0x50,
+                          0xb1, 47, 47,
+                          0x50, },
+      12,
+      &lt_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "LTEQ",
+      /* PUSHB[1] 47 48
+        LTEQ[]
+        PUSHB[1] 48 47
+        LTEQ[]
+        PUSHB[1] 47 47
+        LTEQ[] */
+      (unsigned char []) { 0xb1, 47, 48,
+                          0x51,
+                          0xb1, 48, 47,
+                          0x51,
+                          0xb1, 47, 47,
+                          0x51, },
+      12,
+      &lteq_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "GT",
+      /* PUSHB[1] 47 48
+        GT[]
+        PUSHB[1] 48 47
+        GT[]
+        GT[1] 47 47
+        LTEQ[] */
+      (unsigned char []) { 0xb1, 47, 48,
+                          0x52,
+                          0xb1, 48, 47,
+                          0x52,
+                          0xb1, 47, 47,
+                          0x52, },
+      12,
+      &gt_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "GTEQ",
+      /* PUSHB[1] 47 48
+        GTEQ[]
+        PUSHB[1] 48 47
+        GTEQ[]
+        GTEQ[1] 47 47
+        LTEQ[] */
+      (unsigned char []) { 0xb1, 47, 48,
+                          0x53,
+                          0xb1, 48, 47,
+                          0x53,
+                          0xb1, 47, 47,
+                          0x53, },
+      12,
+      &gteq_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "EQ",
+      /* PUSHW[1] 255 253 255 255
+        EQ[]
+        PUSHW[1] 27 27 27 27
+        EQ[]
+        PUSHB[0] 3
+        PUSHW[0] 255 254
+        EQ[] */
+      (unsigned char []) { 0xb9, 255, 253, 255, 255,
+                          0x54,
+                          0xb9, 27, 27, 27, 27,
+                          0x54,
+                          0xb0, 3,
+                          0xb8, 255, 254,
+                          0x54, },
+      18,
+      &eq_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "NEQ",
+      /* PUSHW[1] 255 253 255 255
+        NEQ[]
+        PUSHW[1] 27 27 27 27
+        NEQ[]
+        PUSHB[0] 3
+        PUSHW[0] 255 254
+        NEQ[] */
+      (unsigned char []) { 0xb9, 255, 253, 255, 255,
+                          0x55,
+                          0xb9, 27, 27, 27, 27,
+                          0x55,
+                          0xb0, 3,
+                          0xb8, 255, 254,
+                          0x55, },
+      18,
+      &neq_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "ODD",
+      /* RTG[]
+        PUSHW[1] 255 224 ;; -32
+        ODD[] ;; Rounds symmetrically to -64, which is odd.
+        PUSHW[1] 255 159 ;; -96
+        ODD[] ;; Rounds symmetrically to -128, which is even.  */
+      (unsigned char []) { 0x18,
+                          0xb8, 255, 224,
+                          0x56,
+                          0xb8, 255, 159,
+                          0x56, },
+      9,
+      &odd_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "EVEN",
+      /* RTG[]
+        PUSHW[1] 255 224 ;; -32
+        EVEN[] ;; Rounds symmetrically to -64, which is odd.
+        PUSHW[1] 255 159 ;; -96
+        EVEN[] ;; Rounds symmetrically to -128, which is even.  */
+      (unsigned char []) { 0x18,
+                          0xb8, 255, 224,
+                          0x57,
+                          0xb8, 255, 159,
+                          0x57, },
+      9,
+      &even_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "IF",
+      /* NPUSHB[] 1 0
+        IF[]
+        PUSHW[0] 1 1
+        PUSHW[1] 1 1 2 2
+        PUSHW[2] 1 1 2 2 3 3
+        PUSHW[3] 1 1 2 2 3 3 4 4
+        PUSHW[4] 1 1 2 2 3 3 4 4 5 5
+        PUSHW[5] 1 1 2 2 3 3 4 4 5 5 6 6
+        PUSHW[6] 1 1 2 2 3 3 4 4 5 5 6 6 7 7
+        PUSHW[7] 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8
+        PUSHB[0] 1
+        PUSHB[1] 2 1
+        PUSHB[2] 3 2 1
+        PUSHB[3] 4 3 2 1
+        PUSHB[4] 5 4 3 2 1
+        PUSHB[5] 6 5 4 3 2 1
+        PUSHB[6] 7 6 5 4 3 2 1
+        PUSHB[7] 8 7 6 5 4 3 2 1
+        DEBUG[]
+        IF[]
+        PUSHB[7] 12 12 12 12 12 12 12 12
+        ELSE[]
+        EIF[]
+        ELSE[]
+        PUSHB[1] 17 24
+        NPUSHB[] 5 1 2 3 4 5
+        NPUSHW[] 2 255 255 255 255
+        EIF[]
+
+        PUSHB[0] 1
+        IF[]
+        NPUSHB[] 2 43 43
+        IF[]
+        PUSHB[0] 45
+        ELSE[]
+        PUSHB[0] 14
+        EIF[]
+        ADD[]
+        ELSE[]
+        NPUSHB[] 4 3 2 1 0
+        EIF[]
+        PUSHB[1] 1 3 */
+      (unsigned char []) { 0x40, 1, 0,
+                          0x58,
+                          0xb8, 1, 1,
+                          0xb9, 1, 1, 2, 2,
+                          0xba, 1, 1, 2, 2, 3, 3,
+                          0xbb, 1, 1, 2, 2, 3, 3, 4, 4,
+                          0xbc, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+                          0xbd, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+                          0xbe, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7,
+                          0xbf, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
+                          0xb0, 1,
+                          0xb1, 2, 1,
+                          0xb2, 3, 2, 1,
+                          0xb3, 4, 3, 2, 1,
+                          0xb4, 5, 4, 3, 2, 1,
+                          0xb5, 6, 5, 4, 3, 2, 1,
+                          0xb6, 7, 6, 5, 4, 3, 2, 1,
+                          0xb7, 8, 7, 6, 5, 4, 3, 2, 1,
+                          0x4f,
+                          0x58,
+                          0xb7, 12, 12, 12, 12, 12, 12, 12, 12,
+                          0x1b,
+                          0x59,
+                          0x1b,
+                          0xb1, 17, 24,
+                          0x40, 5, 1, 2, 3, 4, 5,
+                          0x41, 2, 255, 255, 255, 255,
+                          0x59,
+                          0xb0, 1,
+                          0x58,
+                          0x40, 2, 43, 43,
+                          0x58,
+                          0xb0, 45,
+                          0x1b,
+                          0xb0, 14,
+                          0x59,
+                          0x60,
+                          0x1b,
+                          0x40, 4, 3, 2, 1, 0,
+                          0x59,
+                          0xb1, 1, 3, },
+      185,
+      &if_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "EIF",
+      /* PUSHB[0] 1
+        IF[]
+        EIF[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x58,
+                          0x59, },
+      3,
+      &eif_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "AND",
+      /* PUSHB[1] 0 1
+        AND[]
+        PUSHB[1] 37 0
+        AND[]
+        PUSHB[1] 40 1
+        AND[]
+        PUSHB[1] 0 0
+        AND[] */
+      (unsigned char []) { 0xb1, 0, 1,
+                          0x5a,
+                          0xb1, 37, 0,
+                          0x5a,
+                          0xb1, 40, 1,
+                          0x5a,
+                          0xb1, 0, 0,
+                          0x5a, },
+      16,
+      &and_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "OR",
+      /* PUSHB[1] 0 1
+        OR[]
+        PUSHB[1] 37 0
+        OR[]
+        PUSHB[1] 40 1
+        OR[]
+        PUSHB[1] 0 0
+        OR[] */
+      (unsigned char []) { 0xb1, 0, 1,
+                          0x5b,
+                          0xb1, 37, 0,
+                          0x5b,
+                          0xb1, 40, 1,
+                          0x5b,
+                          0xb1, 0, 0,
+                          0x5b, },
+      16,
+      &or_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "NOT",
+      /* PUSHB[0] 1
+        NOT[]
+        PUSHB[0] 0
+        NOT[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x5c,
+                          0xb0, 0,
+                          0x5c, },
+      6,
+      &not_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SDB",
+      /* PUSHB[0] 8
+        SDB[] */
+      (unsigned char []) { 0xb0, 8,
+                          0x5e, },
+      3,
+      NULL,
+      sfnt_check_sdb,
+    },
+    {
+      "SDS",
+      /* PUSHB[0] 1
+        SDS[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x5f, },
+      3,
+      NULL,
+      sfnt_check_sds,
+    },
+    {
+      "that SDS rejects invalid values",
+      /* PUSHB[0] 1,
+        SDS[]
+        PUSHB[0] 7
+        SDS[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x5f,
+                          0xb0, 7,
+                          0x5f, },
+      6,
+      &sds_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "ADD",
+      /* PUSHB[1] 64 32
+        ADD[]
+        PUSHW[1] 255 40 0 215 ;; -216 + 215
+        ADD[] */
+      (unsigned char []) { 0xb1, 64, 32,
+                          0x60,
+                          0xb9, 255, 40, 0, 215,
+                          0x60, },
+      10,
+      &add_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SUB",
+      /* PUSHB[1] 96 32
+        SUB[]
+        PUSHB[1] 32 96
+        SUB[]
+        PUSHW[1] 0 215 255 40 ;; 215 - -216
+        SUB[] */
+      (unsigned char []) { 0xb1, 96, 32,
+                          0x61,
+                          0xb1, 32, 96,
+                          0x61,
+                          0xb9, 0, 215, 255, 40,
+                          0x61, },
+      14,
+      &sub_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "DIV",
+      /* PUSHB[1] 64 128
+        DIV[] ; 1 / 2 = 0.5
+        PUSHW[1] 0 32 255 224
+        DIV[] ; 0.5 / -0.5 = -1.0
+        PUSHW[1] 255 255 0 0
+        DIV[] ; -1 / 0 = trap */
+      (unsigned char []) { 0xb1, 64, 128,
+                          0x62,
+                          0xb9, 0, 32, 255, 224,
+                          0x62,
+                          0xb9, 255, 255, 0, 0,
+                          0x62, },
+      16,
+      &div_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "MUL",
+      /* PUSHB[1] 255 64
+        MUL[] ; 255 * 1 = 255
+        PUSHW[1] 0 255 255 192
+        MUL[] ; 255 * -1 = -255
+        PUSHW[1] 255 1 255 192
+        MUL[] ; -255 * -1 = 255 */
+      (unsigned char []) { 0xb1, 255, 64,
+                          0x63,
+                          0xb9, 0, 255, 255, 192,
+                          0x63,
+                          0xb9, 255, 1, 255, 192,
+                          0x63, },
+      16,
+      &mul_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "ABS",
+      /* PUSHW[0] 255 255
+        ABS[] ;; abs (-1) == 1
+        PUSHB[0] 1
+        ABS[] ;; abs (1) == 1 */
+      (unsigned char []) { 0xb8, 255, 255,
+                          0x64,
+                          0xb0, 1,
+                          0x64, },
+      7,
+      &abs_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "NEG",
+      /* PUSHW[0] 255 255
+        NEG[] ;; neg (-1) == 1
+        PUSHB[0] 1
+        NEG[] ;; neg (1) == -1 */
+      (unsigned char []) { 0xb8, 255, 255,
+                          0x65,
+                          0xb0, 1,
+                          0x65, },
+      7,
+      &neg_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "FLOOR",
+      /* PUSHW[0] 255 129 ; -127
+        FLOOR[] ; floor (-127) == -128
+        PUSHW[0] 255 193 ; -63
+        FLOOR[] ; floor (-63) == -64
+        PUSHB[0] 63
+        FLOOR[] ; floor (63) == 0
+        PUSHB[0] 127
+        FLOOR[] ; floor (127) == 64
+        PUSHB[0] 191
+        FLOOR[] ; floor (191) == 128 */
+      (unsigned char []) { 0xb8, 255, 129,
+                          0x66,
+                          0xb8, 255, 193,
+                          0x66,
+                          0xb0, 63,
+                          0x66,
+                          0xb0, 127,
+                          0x66,
+                          0xb0, 191,
+                          0x66, },
+      17,
+      &floor_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "CEILING",
+      /* PUSHW[0] 255 128 ; -128
+        CEILING[] ; ceiling (-128) == -128
+        PUSHW[0] 255 127 ; -129
+        CEILING[] ; ceiling (-129) == -128
+        PUSHW[0] 255 191 ; -65
+        CEILING[] ; ceiling (-65) == -64
+        PUSHW[0] 255 255 ; -1
+        CEILING[] ; ceiling (-1) == 0
+        PUSHB[0] 63
+        CEILING[] ; ceiling (63) == 64
+        PUSHB[0] 65
+        CEILING[] ; ceiling (65) == 128
+        PUSHB[0] 128
+        CEILING[] ; ceiling (128) == 128 */
+      (unsigned char []) { 0xb8, 255, 128,
+                          0x67,
+                          0xb8, 255, 127,
+                          0x67,
+                          0xb8, 255, 191,
+                          0x67,
+                          0xb8, 255, 255,
+                          0x67,
+                          0xb0, 63,
+                          0x67,
+                          0xb0, 65,
+                          0x67,
+                          0xb0, 128,
+                          0x67, },
+      25,
+      &ceiling_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "ROUND",
+      /* ROUND[] */
+      (unsigned char []) { 0x68, },
+      1,
+      &round_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "NROUND",
+      /* PUSHB[0] 63
+        NROUND[] */
+      (unsigned char []) { 0xb0, 63,
+                          0x6c, },
+      3,
+      &nround_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "WCVTF",
+      /* PUSHB[1] 1 63
+         WCVTF[]
+         PUSHB[0] 1
+         RCVT[] */
+      (unsigned char []) { 0xb1, 1, 63,
+                          0x70,
+                          0xb0, 1,
+                          0x45, },
+      7,
+      &wcvtf_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "JROT",
+      /* PUSHB[1] 4 0
+        JROT[] ; this should not skip past the next instruction
+        PUSHB[1] 40 40
+        PUSHB[1] 3 1
+        JROT[] ; this should skip past the next instruction
+        PUSHB[0] 4 */
+      (unsigned char []) { 0xb1, 4, 0,
+                          0x78,
+                          0xb1, 40, 40,
+                          0xb1, 3, 1,
+                          0x78,
+                          0xb0, 4, },
+      13,
+      &jrot_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "JROF",
+      /* PUSHB[1] 4 0
+        JROF[] ; this should skip past the next instruction
+        PUSHB[1] 40 40
+        PUSHB[1] 3 1
+        JROF[] ; this should not skip past the next instruction
+        PUSHB[0] 4 */
+      (unsigned char []) { 0xb1, 4, 0,
+                          0x79,
+                          0xb1, 40, 40,
+                          0xb1, 3, 1,
+                          0x79,
+                          0xb0, 4, },
+      13,
+      &jrof_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "DELTAC1",
+      /* PUSHB[0] 2
+        SDB[] ; delta base now 2
+        PUSHB[0] 6
+        SDS[] ; delta shift now 6
+        PUSHB[2] 0xff 5 1 ; CVT index 5, ppem 15 + 2, magnitude 15
+         DELTAC1[]
+        PUSHB[0] 1
+         RCVT[] ; CVT index 5 should now be greater by 8 / 64
+
+         PUSHB[2] 0xef 5 1 ; CVT index 5, ppem 14 + 2, magnitude 15
+         DELTAC1[]
+         PUSHB[0] 1
+         RCVT[] ; CVT index 5 should be unchanged */
+      (unsigned char []) { 0xb0, 2,
+                          0x5e,
+                          0xb0, 6,
+                          0x5f,
+                          0xb2, 255, 5, 1,
+                          0x73,
+                          0xb0, 5,
+                          0x45,
+                          0xb2, 239, 5, 1,
+                          0x73,
+                          0xb0, 5,
+                          0x45, },
+      22,
+      &deltac1_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "DELTAC2",
+      /* PUSHB[0] 2
+        SDB[] ; delta base now 2
+        PUSHB[0] 6
+        SDS[] ; delta shift now 6
+        PUSHB[2] 0xff 5 1 ; CVT index 5, ppem 15 + 2 + 16, magnitude 15
+         DELTAC2[]
+        PUSHB[0] 1
+         RCVT[] ; CVT index 5 should be unchanged
+
+         PUSHB[2] 0xef 5 1 ; CVT index 5, ppem 14 + 2 + 16, magnitude 15
+         DELTAC2[]
+         PUSHB[0] 1
+         RCVT[] ; CVT index 5 should be unchanged */
+      (unsigned char []) { 0xb0, 2,
+                          0x5e,
+                          0xb0, 6,
+                          0x5f,
+                          0xb2, 255, 5, 1,
+                          0x74,
+                          0xb0, 5,
+                          0x45,
+                          0xb2, 239, 5, 1,
+                          0x74,
+                          0xb0, 5,
+                          0x45, },
+      22,
+      &deltac2_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "DELTAC3",
+      /* PUSHB[0] 2
+        SDB[] ; delta base now 2
+        PUSHB[0] 6
+        SDS[] ; delta shift now 6
+        PUSHB[2] 0xff 5 1 ; CVT index 5, ppem 15 + 2 + 32, magnitude 15
+         DELTAC3[]
+        PUSHB[0] 1
+         RCVT[] ; CVT index 5 should be unchanged
+
+         PUSHB[2] 0xef 5 1 ; CVT index 5, ppem 14 + 2 + 32, magnitude 15
+         DELTAC3[]
+         PUSHB[0] 1
+         RCVT[] ; CVT index 5 should be unchanged */
+      (unsigned char []) { 0xb0, 2,
+                          0x5e,
+                          0xb0, 6,
+                          0x5f,
+                          0xb2, 255, 5, 1,
+                          0x75,
+                          0xb0, 5,
+                          0x45,
+                          0xb2, 239, 5, 1,
+                          0x75,
+                          0xb0, 5,
+                          0x45, },
+      22,
+      &deltac3_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SROUND",
+      sfnt_sround_instructions,
+      ARRAYELTS (sfnt_sround_instructions),
+      &sround_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "S45ROUND",
+      sfnt_s45round_instructions,
+      ARRAYELTS (sfnt_s45round_instructions),
+      &s45round_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "RUTG",
+      /* RUTG[]
+        PUSHB[0] 1
+        ROUND[]
+        PUSHB[0] 64
+        ROUND[]
+        PUSHB[0] 0
+        ROUND[] */
+      (unsigned char []) { 0x7c,
+                          0xb0, 1,
+                          0x68,
+                          0xb0, 64,
+                          0x68,
+                          0xb0, 0,
+                          0x68, },
+      10,
+      &rutg_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "RDTG",
+      /* RUTG[]
+        PUSHB[0] 1
+        ROUND[]
+        PUSHB[0] 63
+        ROUND[]
+        PUSHB[0] 64
+        ROUND[] */
+      (unsigned char []) { 0x7d,
+                          0xb0, 1,
+                          0x68,
+                          0xb0, 63,
+                          0x68,
+                          0xb0, 64,
+                          0x68, },
+      10,
+      &rdtg_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SANGW",
+      /* PUSHB[0] 3
+        SANGW[] */
+      (unsigned char []) { 0xb0, 3,
+                          0x7e, },
+      3,
+      &sangw_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "AA",
+      /* PUSHB[0] 3
+        AA[] */
+      (unsigned char []) { 0xb0, 3,
+                          0x7f, },
+      3,
+      &aa_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SCANCTRL",
+      /* PUSHB[0] 1
+        SCANCTRL[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x85, },
+      3,
+      NULL,
+      sfnt_check_scanctrl,
+    },
+    {
+      "GETINFO",
+      /* PUSHB[0] 1
+         GETINFO[]
+         PUSHB[0] 6
+         GETINFO[] */
+      (unsigned char []) { 0xb0, 1,
+                          0x88,
+                          0xb0, 6,
+                          0x88, },
+      6,
+      &getinfo_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "IDEF",
+      /* PUSHB[0] 0x83
+        IDEF[]
+        PUSHB[3] 1 2 3 4
+        POP[]
+        ENDF[]
+        0x83 */
+      (unsigned char []) { 0xb0, 0x83,
+                          0x89,
+                          0xb3, 1, 2, 3, 4,
+                          0x21,
+                          0x2d,
+                          0x83, },
+      11,
+      &idef_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "ROLL",
+      /* PUSHB[4] 1 2 3 4 5
+         ROLL[] ; this should become 1 2 4 5 3 */
+      (unsigned char []) { 0xb4, 1, 2, 3, 4, 5,
+                          0x8a, },
+      7,
+      &roll_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "that ROLL correctly handles underflow",
+      /* PUSHB[1] 1 2
+         ROLL[] */
+      (unsigned char []) { 0xb1, 1, 2,
+                          0x8a, },
+      4,
+      &roll_1_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "MAX",
+      /* PUSHW[1] 0 70 255 186 ; 70, -70
+         MAX[] */
+      (unsigned char []) { 0xb9, 0, 70, 255, 186,
+                          0x8b, },
+      6,
+      &max_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "MIN",
+      /* PUSHW[1] 0 70 255 186 ; 70, -70
+         MIN[] */
+      (unsigned char []) { 0xb9, 0, 70, 255, 186,
+                          0x8c, },
+      6,
+      &min_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "SCANTYPE",
+      /* PUSHB[0] 0
+         SCANTYPE[] */
+      (unsigned char []) { 0xb0, 0,
+                          0x8d, },
+      3,
+      &scantype_test_args,
+      sfnt_generic_check,
+    },
+    {
+      "INSTCTRL",
+      /* PUSHB[1] 1 1
+        INSTCTRL[] ; (1 << 1) should now be set
+         PUSHB[1] 2 1
+        INSTCTRL[] ; (1 << 2) should now be set
+         PUSHB[1] 2 0
+        INSTCTRL[] ; (1 << 2) should no longer be set */
+      (unsigned char []) { 0xb1, 1, 1,
+                          0x8e,
+                          0xb1, 2, 1,
+                          0x8e,
+                          0xb1, 2, 0,
+                          0x8e, },
+      12,
+      NULL,
+      sfnt_check_instctrl,
+    },
+  };
+
+
+
+/* Instruction debugger.  */
+
+static void
+sfnt_setup_debugger (void)
+{
+  XGCValues gcv;
+  Font font;
+
+  display = XOpenDisplay (NULL);
+
+  if (!display)
+    exit (1);
+
+  window = XCreateSimpleWindow (display, DefaultRootWindow (display),
+                               0, 0, 200, 200, 0, 0,
+                               WhitePixel (display,
+                                           DefaultScreen (display)));
+  XMapWindow (display, window);
+
+  /* Select for the appropriate events.  */
+  XSelectInput (display, window, KeyPressMask | ExposureMask);
+
+  /* Find an appropriate font.  */
+  font = XLoadFont (display, "6x13");
+
+  if (!font)
+    exit (1);
+
+  /* The debugger has been set up.  Set up the GCs for drawing points
+     and backgrounds.  */
+
+  gcv.foreground = BlackPixel (display, DefaultScreen (display));
+  gcv.font = font;
+  point_gc = XCreateGC (display, window, GCForeground | GCFont,
+                       &gcv);
+  gcv.foreground = WhitePixel (display, DefaultScreen (display));
+  background_gc = XCreateGC (display, window, GCForeground, &gcv);
+}
+
+static const char *
+sfnt_name_instruction (unsigned char opcode)
+{
+  static const char *const opcode_names[256] = {
+    "7 SVTCA y",
+    "7 SVTCA x",
+    "8 SPvTCA y",
+    "8 SPvTCA x",
+    "8 SFvTCA y",
+    "8 SFvTCA x",
+    "8 SPvTL ||",
+    "7 SPvTL +",
+    "8 SFvTL ||",
+    "7 SFvTL +",
+    "5 SPvFS",
+    "5 SFvFS",
+    "3 GPv",
+    "3 GFv",
+    "6 SFvTPv",
+    "5 ISECT",
+
+    "4 SRP0",
+    "4 SRP1",
+    "4 SRP2",
+    "4 SZP0",
+    "4 SZP1",
+    "4 SZP2",
+    "4 SZPS",
+    "5 SLOOP",
+    "3 RTG",
+    "4 RTHG",
+    "3 SMD",
+    "4 ELSE",
+    "4 JMPR",
+    "6 SCvTCi",
+    "5 SSwCi",
+    "3 SSW",
+
+    "3 DUP",
+    "3 POP",
+    "5 CLEAR",
+    "4 SWAP",
+    "5 DEPTH",
+    "6 CINDEX",
+    "6 MINDEX",
+    "8 AlignPTS",
+    "7 INS_$28",
+    "3 UTP",
+    "8 LOOPCALL",
+    "4 CALL",
+    "4 FDEF",
+    "4 ENDF",
+    "7 MDAP[0]",
+    "7 MDAP[1]",
+
+    "6 IUP[0]",
+    "6 IUP[1]",
+    "6 SHP[0]",
+    "6 SHP[1]",
+    "6 SHC[0]",
+    "6 SHC[1]",
+    "6 SHZ[0]",
+    "6 SHZ[1]",
+    "5 SHPIX",
+    "2 IP",
+    "8 MSIRP[0]",
+    "8 MSIRP[1]",
+    "7 AlignRP",
+    "4 RTDG",
+    "7 MIAP[0]",
+    "7 MIAP[1]",
+
+    "6 NPushB",
+    "6 NPushW",
+    "2 WS",
+    "2 RS",
+    "5 WCvtP",
+    "4 RCvt",
+    "5 GC[0]",
+    "5 GC[1]",
+    "4 SCFS",
+    "5 MD[0]",
+    "5 MD[1]",
+    "5 MPPEM",
+    "3 MPS",
+    "6 FlipON",
+    "7 FlipOFF",
+    "5 DEBUG",
+
+    "2 LT",
+    "4 LTEQ",
+    "2 GT",
+    "4 GTEQ",
+    "2 EQ",
+    "3 NEQ",
+    "3 ODD",
+    "4 EVEN",
+    "2 IF",
+    "3 EIF",
+    "3 AND",
+    "2 OR",
+    "3 NOT",
+    "7 DeltaP1",
+    "3 SDB",
+    "3 SDS",
+
+    "3 ADD",
+    "3 SUB",
+    "3 DIV",
+    "3 MUL",
+    "3 ABS",
+    "3 NEG",
+    "5 FLOOR",
+    "7 CEILING",
+    "8 ROUND[0]",
+    "8 ROUND[1]",
+    "8 ROUND[2]",
+    "8 ROUND[3]",
+    "9 NROUND[0]",
+    "9 NROUND[1]",
+    "9 NROUND[2]",
+    "9 NROUND[3]",
+
+    "5 WCvtF",
+    "7 DeltaP2",
+    "7 DeltaP3",
+    "A DeltaCn[0]",
+    "A DeltaCn[1]",
+    "A DeltaCn[2]",
+    "6 SROUND",
+    "8 S45Round",
+    "4 JROT",
+    "4 JROF",
+    "4 ROFF",
+    "7 INS_$7B",
+    "4 RUTG",
+    "4 RDTG",
+    "5 SANGW",
+    "2 AA",
+
+    "6 FlipPT",
+    "8 FlipRgON",
+    "9 FlipRgOFF",
+    "7 INS_$83",
+    "7 INS_$84",
+    "8 ScanCTRL",
+    "9 SDPvTL[0]",
+    "9 SDPvTL[1]",
+    "7 GetINFO",
+    "4 IDEF",
+    "4 ROLL",
+    "3 MAX",
+    "3 MIN",
+    "8 ScanTYPE",
+    "8 InstCTRL",
+    "7 INS_$8F",
+
+    "7 INS_$90",
+    "7 GXAXIS",
+    "7 INS_$92",
+    "7 INS_$93",
+    "7 INS_$94",
+    "7 INS_$95",
+    "7 INS_$96",
+    "7 INS_$97",
+    "7 INS_$98",
+    "7 INS_$99",
+    "7 INS_$9A",
+    "7 INS_$9B",
+    "7 INS_$9C",
+    "7 INS_$9D",
+    "7 INS_$9E",
+    "7 INS_$9F",
+
+    "7 INS_$A0",
+    "7 INS_$A1",
+    "7 INS_$A2",
+    "7 INS_$A3",
+    "7 INS_$A4",
+    "7 INS_$A5",
+    "7 INS_$A6",
+    "7 INS_$A7",
+    "7 INS_$A8",
+    "7 INS_$A9",
+    "7 INS_$AA",
+    "7 INS_$AB",
+    "7 INS_$AC",
+    "7 INS_$AD",
+    "7 INS_$AE",
+    "7 INS_$AF",
+
+    "8 PushB[0]",
+    "8 PushB[1]",
+    "8 PushB[2]",
+    "8 PushB[3]",
+    "8 PushB[4]",
+    "8 PushB[5]",
+    "8 PushB[6]",
+    "8 PushB[7]",
+    "8 PushW[0]",
+    "8 PushW[1]",
+    "8 PushW[2]",
+    "8 PushW[3]",
+    "8 PushW[4]",
+    "8 PushW[5]",
+    "8 PushW[6]",
+    "8 PushW[7]",
+
+    "7 MDRP[G]",
+    "7 MDRP[B]",
+    "7 MDRP[W]",
+    "7 MDRP[?]",
+    "8 MDRP[rG]",
+    "8 MDRP[rB]",
+    "8 MDRP[rW]",
+    "8 MDRP[r?]",
+    "8 MDRP[mG]",
+    "8 MDRP[mB]",
+    "8 MDRP[mW]",
+    "8 MDRP[m?]",
+    "9 MDRP[mrG]",
+    "9 MDRP[mrB]",
+    "9 MDRP[mrW]",
+    "9 MDRP[mr?]",
+
+    "8 MDRP[pG]",
+    "8 MDRP[pB]",
+    "8 MDRP[pW]",
+    "8 MDRP[p?]",
+    "9 MDRP[prG]",
+    "9 MDRP[prB]",
+    "9 MDRP[prW]",
+    "9 MDRP[pr?]",
+    "9 MDRP[pmG]",
+    "9 MDRP[pmB]",
+    "9 MDRP[pmW]",
+    "9 MDRP[pm?]",
+    "A MDRP[pmrG]",
+    "A MDRP[pmrB]",
+    "A MDRP[pmrW]",
+    "A MDRP[pmr?]",
+
+    "7 MIRP[G]",
+    "7 MIRP[B]",
+    "7 MIRP[W]",
+    "7 MIRP[?]",
+    "8 MIRP[rG]",
+    "8 MIRP[rB]",
+    "8 MIRP[rW]",
+    "8 MIRP[r?]",
+    "8 MIRP[mG]",
+    "8 MIRP[mB]",
+    "8 MIRP[mW]",
+    "8 MIRP[m?]",
+    "9 MIRP[mrG]",
+    "9 MIRP[mrB]",
+    "9 MIRP[mrW]",
+    "9 MIRP[mr?]",
+
+    "8 MIRP[pG]",
+    "8 MIRP[pB]",
+    "8 MIRP[pW]",
+    "8 MIRP[p?]",
+    "9 MIRP[prG]",
+    "9 MIRP[prB]",
+    "9 MIRP[prW]",
+    "9 MIRP[pr?]",
+    "9 MIRP[pmG]",
+    "9 MIRP[pmB]",
+    "9 MIRP[pmW]",
+    "9 MIRP[pm?]",
+    "A MIRP[pmrG]",
+    "A MIRP[pmrB]",
+    "A MIRP[pmrW]",
+    "A MIRP[pmr?]"
+  };
+
+  return opcode_names[opcode];
+}
+
+static void
+sfnt_draw_debugger (struct sfnt_interpreter *interpreter)
+{
+  int x, y, i;
+  char buffer[80];
+  const char *name;
+  int opcode;
+
+  sprintf (buffer, "opcode:IP:depth: 0x%x:%d:%d",
+          interpreter->instructions[interpreter->IP],
+          interpreter->IP,
+          interpreter->call_depth);
+
+  /* Clear the window.  */
+  XFillRectangle (display, window, background_gc,
+                 0, 0, 65535, 65535);
+
+  /* Draw some information about the opcode.  */
+  XDrawString (display, window, point_gc, 0, 13, buffer,
+              strlen (buffer));
+
+  opcode = interpreter->instructions[interpreter->IP];
+
+  sprintf (buffer, "opcode: %s",
+          sfnt_name_instruction (opcode));
+
+  XDrawString (display, window, point_gc, 14, 27, buffer,
+              strlen (buffer));
+
+  if (interpreter->state.project
+      == sfnt_project_onto_x_axis_vector)
+    name = "X axis";
+  else if (interpreter->state.project
+          == sfnt_project_onto_y_axis_vector)
+    name = "Y axis";
+  else
+    name = "Any";
+
+  sprintf (buffer, "projection function: %s", name);
+
+  XDrawString (display, window, point_gc, 28, 42, buffer,
+              strlen (buffer));
+
+  /* Draw each point onto the window.  */
+  for (i = 0; i < interpreter->glyph_zone->num_points; ++i)
+    {
+      x = interpreter->glyph_zone->x_current[i] / 16;
+      y = (200 - interpreter->glyph_zone->y_current[i] / 16);
+
+      XFillRectangle (display, window, point_gc, x, y, 4, 4);
+    }
+}
+
+static void
+sfnt_run_hook (struct sfnt_interpreter *interpreter)
+{
+  pid_t pid;
+  XEvent event;
+
+#ifdef TEST_BREAK_AFTER
+  static unsigned int instructions;
+
+  if (++instructions < TEST_BREAK_AFTER)
+    return;
+#endif
+
+  pid = fork ();
+
+  if (pid == 0)
+    {
+      sfnt_setup_debugger ();
+
+      while (true)
+       {
+         XNextEvent (display, &event);
+
+         switch (event.type)
+           {
+           case KeyPress:
+             XDestroyWindow (display, window);
+             XCloseDisplay (display);
+             exit (0);
+             break;
+
+           case Expose:
+             sfnt_draw_debugger (interpreter);
+             break;
+           }
+       }
+    }
+  else
+    {
+      while (waitpid (pid, NULL, 0) != pid && errno == EINTR)
+       /* Spin.  */;
+    }
+}
+
+static struct sfnt_prep_table *exec_prep;
+static struct sfnt_fpgm_table *exec_fpgm;
+
+static const char *
+sfnt_identify_instruction (struct sfnt_interpreter *interpreter)
+{
+  static char buffer[256];
+  unsigned char *where;
+
+  where = interpreter->instructions + interpreter->IP;
+
+  if (exec_prep
+      && where >= exec_prep->instructions
+      && where < (exec_prep->instructions
+                 + exec_prep->num_instructions))
+    {
+      sprintf (buffer, "prep+%td",
+              where - exec_prep->instructions);
+      return buffer;
+    }
+
+  if (exec_fpgm->instructions
+      && where >= exec_fpgm->instructions
+      && where < (exec_fpgm->instructions
+                 + exec_fpgm->num_instructions))
+    {
+      sprintf (buffer, "fpgm+%td",
+              where - exec_fpgm->instructions);
+      return buffer;
+    }
+
+  sprintf (buffer, "IP+%td", where - interpreter->instructions);
+  return buffer;
+}
+
+static void
+sfnt_verbose (struct sfnt_interpreter *interpreter)
+{
+  struct sfnt_instructed_outline temp;
+  struct sfnt_glyph_outline *outline;
+  struct sfnt_raster *raster;
+  unsigned char opcode;
+  const char *name;
+  static unsigned int instructions;
+
+  /* Build a temporary outline containing the values of the
+     interpreter's glyph zone.  */
+
+  if (interpreter->glyph_zone)
+    {
+      temp.num_points = interpreter->glyph_zone->num_points;
+      temp.num_contours = interpreter->glyph_zone->num_contours;
+      temp.contour_end_points = interpreter->glyph_zone->contour_end_points;
+      temp.x_points = interpreter->glyph_zone->x_current;
+      temp.y_points = interpreter->glyph_zone->y_current;
+      temp.flags = interpreter->glyph_zone->flags;
+
+      outline = sfnt_build_instructed_outline (&temp);
+
+      if (!outline)
+       return;
+
+      printf ("outline bounds: %g %g, %g %g\n",
+             sfnt_coerce_fixed (outline->xmin),
+             sfnt_coerce_fixed (outline->ymin),
+             sfnt_coerce_fixed (outline->xmax),
+             sfnt_coerce_fixed (outline->ymax));
+
+      raster = sfnt_raster_glyph_outline (outline);
+
+      if (raster)
+       sfnt_test_raster (raster, NULL, 0);
+
+      xfree (outline);
+      xfree (raster);
+    }
+
+  opcode = interpreter->instructions[interpreter->IP];
+  printf ("opcode, number of instructions: %s %u\n",
+         sfnt_name_instruction (opcode), instructions++);
+  printf ("instruction: %s\n",
+         sfnt_identify_instruction (interpreter));
+
+  if (interpreter->state.project
+      == sfnt_project_onto_x_axis_vector)
+    name = "X axis";
+  else if (interpreter->state.project
+          == sfnt_project_onto_y_axis_vector)
+    name = "Y axis";
+  else
+    name = "Any";
+
+  printf ("projection function: %s\n", name);
+
+  printf ("proj and free vecs: %d %d %d %d\n",
+         interpreter->state.projection_vector.x,
+         interpreter->state.projection_vector.y,
+         interpreter->state.freedom_vector.x,
+         interpreter->state.freedom_vector.y);
+}
+
+static void
+sfnt_push_hook (struct sfnt_interpreter *interpreter,
+               uint32_t value)
+{
+  int32_t alternate;
+
+  alternate = value;
+
+  fprintf (stderr, "--> %"PRIi32"\n", alternate);
+}
+
+static void
+sfnt_pop_hook (struct sfnt_interpreter *interpreter,
+              uint32_t value)
+{
+  int32_t alternate;
+
+  alternate = value;
+
+  fprintf (stderr, "<<- %"PRIi32"\n", alternate);
+}
+
+
+
+static void
+sfnt_test_uvs (int fd, struct sfnt_cmap_format_14 *format14)
+{
+  struct sfnt_uvs_context *context;
+  size_t i, j;
+  sfnt_glyph glyph;
+  sfnt_char c;
+  struct sfnt_nondefault_uvs_table *uvs;
+
+  context = sfnt_create_uvs_context (format14, fd);
+
+  /* Print each variation selector and its associated ranges.  */
+
+  if (!context)
+    fprintf (stderr, "failed to read uvs data\n");
+  else
+    {
+      fprintf (stderr, "UVS context with %zu records and %zu tables\n",
+              context->num_records, context->nmemb);
+
+      for (i = 0; i < context->num_records; ++i)
+       {
+         if (!context->records[i].nondefault_uvs)
+           continue;
+
+         uvs = context->records[i].nondefault_uvs;
+
+         for (j = 0; j < uvs->num_uvs_mappings; ++j)
+           {
+             c = uvs->mappings[j].unicode_value;
+             glyph = sfnt_variation_glyph_for_char (uvs, c);
+
+             if (glyph != uvs->mappings[j].base_character_value)
+               abort ();
+
+             fprintf (stderr, "   UVS: %"PRIx32" (%"PRIx32") -> %"PRIu32"\n",
+                      c, context->records[i].selector, glyph);
+           }
+       }
+
+      sfnt_free_uvs_context (context);
+    }
+}
+
+
+
+/* Main entry point.  */
+
+/* Simple tests that were used while developing this file.  By the
+   time you are reading this, they probably no longer work.
+
+   Compile like so in this directory:
+
+    gcc -Demacs -I. -I. -I../lib -I../lib -MMD -MF deps/.d -MP
+    -fno-common -Wall -Warith-conversion -Wdate-time
+    -Wdisabled-optimization -Wdouble-promotion -Wduplicated-cond
+    -Wextra -Wformat-signedness -Winit-self -Winvalid-pch -Wlogical-op
+    -Wmissing-declarations -Wmissing-include-dirs -Wmissing-prototypes
+    -Wnested-externs -Wnull-dereference -Wold-style-definition
+    -Wopenmp-simd -Wpacked -Wpointer-arith -Wstrict-prototypes
+    -Wsuggest-attribute=format -Wsuggest-final-methods
+    -Wsuggest-final-types -Wtrampolines -Wuninitialized
+    -Wunknown-pragmas -Wunused-macros -Wvariadic-macros
+    -Wvector-operation-performance -Wwrite-strings -Warray-bounds=2
+    -Wattribute-alias=2 -Wformat=2 -Wformat-truncation=2
+    -Wimplicit-fallthrough=5 -Wshift-overflow=2 -Wuse-after-free=3
+    -Wvla-larger-than=4031 -Wredundant-decls
+    -Wno-missing-field-initializers -Wno-override-init
+    -Wno-sign-compare -Wno-type-limits -Wno-unused-parameter
+    -Wno-format-nonliteral -Wno-bidi-chars -g3 -O0 -DTEST sfnt.c -o
+    sfnt ../lib/libgnu.a -lX11 -lXrender
+
+   after gnulib has been built.  Then, run ./sfnt
+   /path/to/font.ttf.  */
+
+int
+main (int argc, char **argv)
+{
+  struct sfnt_offset_subtable *font;
+  struct sfnt_cmap_encoding_subtable *subtables;
+  struct sfnt_cmap_encoding_subtable_data **data;
+  struct sfnt_cmap_table *table;
+  int fd, i, j;
+  sfnt_char character;
+  struct sfnt_head_table *head;
+  struct sfnt_hhea_table *hhea;
+  struct sfnt_loca_table_short *loca_short;
+  struct sfnt_loca_table_long *loca_long;
+  struct sfnt_glyf_table *glyf;
+  struct sfnt_glyph *glyph;
+  sfnt_glyph code;
+  struct sfnt_test_dcontext dcontext;
+  struct sfnt_glyph_outline *outline;
+  struct timespec start, end, sub, sub1, sub2, sub3;
+  static struct sfnt_maxp_table *maxp;
+  struct sfnt_raster *raster;
+  struct sfnt_hmtx_table *hmtx;
+  struct sfnt_glyph_metrics metrics;
+  struct sfnt_name_table *name;
+  unsigned char *string;
+  struct sfnt_name_record record;
+  struct sfnt_meta_table *meta;
+  struct sfnt_ttc_header *ttc;
+  struct sfnt_interpreter *interpreter;
+  struct sfnt_cvt_table *cvt;
+  struct sfnt_fpgm_table *fpgm;
+  const char *trap;
+  struct sfnt_prep_table *prep;
+  struct sfnt_graphics_state state;
+  struct sfnt_instructed_outline *value;
+  struct sfnt_fvar_table *fvar;
+  struct sfnt_gvar_table *gvar;
+  struct sfnt_avar_table *avar;
+  struct sfnt_cvar_table *cvar;
+  sfnt_fixed scale;
+  char *fancy;
+  int *advances;
+  struct sfnt_raster **rasters;
+  size_t length;
+  char *axis_name;
+  struct sfnt_instance *instance;
+  struct sfnt_blend blend;
+  struct sfnt_metrics_distortion distortion;
+
+  if (argc < 2)
+    return 1;
+
+  instance = NULL;
+
+  if (!strcmp (argv[1], "--check-interpreter"))
+    {
+      interpreter = sfnt_make_test_interpreter ();
+
+      if (!interpreter)
+       abort ();
+
+      for (i = 0; i < ARRAYELTS (all_tests); ++i)
+       sfnt_run_interpreter_test (&all_tests[i], interpreter);
+
+      exit (0);
+    }
+
+  fd = open (argv[1], O_RDONLY);
+
+  if (fd < 1)
+    return 1;
+
+  ttc = NULL;
+
+  font = sfnt_read_table_directory (fd);
+
+  if (font == (struct sfnt_offset_subtable *) -1)
+    {
+      if (lseek (fd, 0, SEEK_SET) != 0)
+       return 1;
+
+      ttc = sfnt_read_ttc_header (fd);
+
+      if (!ttc)
+       return 1;
+
+      fprintf (stderr, "TrueType collection: %"PRIu32" fonts installed\n",
+              ttc->num_fonts);
+      fflush (stderr);
+
+      printf ("Which font? ");
+      if (scanf ("%d", &i) == EOF)
+       return 1;
+
+      if (i >= ttc->num_fonts || i < 0)
+       {
+         printf ("out of range\n");
+         return 1;
+       }
+
+      if (lseek (fd, ttc->offset_table[i], SEEK_SET)
+         != ttc->offset_table[i])
+       return 1;
+
+      font = sfnt_read_table_directory (fd);
+    }
+
+  if (!font || font == (struct sfnt_offset_subtable *) -1)
+    {
+      close (fd);
+      return 1;
+    }
+
+  for (i = 0; i < font->num_tables; ++i)
+    fprintf (stderr, "Found new subtable with tag %"PRIx32
+            " at offset %"PRIu32"\n",
+            font->subtables[i].tag,
+            font->subtables[i].offset);
+
+  table = sfnt_read_cmap_table (fd, font, &subtables, &data);
+
+  if (!table)
+    {
+      close (fd);
+      xfree (font);
+      return 1;
+    }
+
+  fprintf (stderr, "number of subtables: %"PRIu16"\n",
+          table->num_subtables);
+
+  for (i = 0; i < table->num_subtables; ++i)
+    {
+      fprintf (stderr, "Found cmap table %"PRIu32": %p\n",
+              subtables[i].offset, (void *) data[i]);
+
+      if (data[i])
+       fprintf (stderr, "  format: %"PRIu16"\n",
+                data[i]->format);
+    }
+
+  if (argc >= 3 && !strcmp (argv[2], "--check-variation-selectors"))
+    {
+      /* Look for a format 14 cmap table.  */
+
+      for (i = 0; i < table->num_subtables; ++i)
+       {
+         if (data[i]->format == 14)
+           {
+             fprintf (stderr, "format 14 subtable found\n");
+             sfnt_test_uvs (fd, (struct sfnt_cmap_format_14 *) data[i]);
+             return 0;
+           }
+       }
+
+      return 1;
+    }
+
+#define FANCY_PPEM 18
+#define EASY_PPEM  18
+
+  interpreter = NULL;
+  head = sfnt_read_head_table (fd, font);
+  hhea = sfnt_read_hhea_table (fd, font);
+  glyf = sfnt_read_glyf_table (fd, font);
+  maxp = sfnt_read_maxp_table (fd, font);
+  name = sfnt_read_name_table (fd, font);
+  meta = sfnt_read_meta_table (fd, font);
+  cvt  = sfnt_read_cvt_table (fd, font);
+  fpgm = sfnt_read_fpgm_table (fd, font);
+  prep = sfnt_read_prep_table (fd, font);
+  fvar = sfnt_read_fvar_table (fd, font);
+  gvar = sfnt_read_gvar_table (fd, font);
+  avar = sfnt_read_avar_table (fd, font);
+  cvar = NULL;
+  hmtx = NULL;
+
+  if (fvar && cvt)
+    cvar = sfnt_read_cvar_table (fd, font, fvar, cvt);
+
+  if (cvar)
+    fprintf (stderr, "cvar table found\n");
+
+  exec_prep = prep;
+  exec_fpgm = fpgm;
+  fancy = getenv ("SFNT_FANCY_TEST");
+
+  loca_long = NULL;
+  loca_short = NULL;
+
+  if (fvar)
+    {
+      fprintf (stderr, "FVAR table found!\n"
+              "version: %"PRIu16".%"PRIu16"\n"
+              "axis_count: %"PRIu16"\n"
+              "axis_size: %"PRIu16"\n"
+              "instance_count: %"PRIu16"\n"
+              "instance_size: %"PRIu16"\n",
+              fvar->major_version,
+              fvar->minor_version,
+              fvar->axis_count,
+              fvar->axis_size,
+              fvar->instance_count,
+              fvar->instance_size);
+
+      for (i = 0; i < fvar->axis_count; ++i)
+       {
+         if (name)
+           {
+             axis_name
+               = (char *) sfnt_find_name (name, fvar->axis[i].name_id,
+                                          &record);
+
+             if (axis_name)
+               fprintf (stderr, "axis no: %d; name: %.*s\n",
+                        i, record.length, axis_name);
+           }
+
+         fprintf (stderr, "  axis: %"PRIx32" %g %g %g\n",
+                  fvar->axis[i].axis_tag,
+                  sfnt_coerce_fixed (fvar->axis[i].min_value),
+                  sfnt_coerce_fixed (fvar->axis[i].default_value),
+                  sfnt_coerce_fixed (fvar->axis[i].max_value));
+       }
+
+      for (i = 0; i < fvar->instance_count; ++i)
+       {
+         if (name)
+           {
+             axis_name
+               = (char *) sfnt_find_name (name, fvar->instance[i].name_id,
+                                          &record);
+
+             if (axis_name)
+               fprintf (stderr, "instance no: %d; name: %.*s\n",
+                        i, record.length, axis_name);
+           }
+       }
+
+      if (fvar->instance_count > 1)
+       {
+         printf ("instance? ");
+
+         if (scanf ("%d", &i) == EOF)
+           goto free_lab;
+
+         if (i >= fvar->instance_count)
+           goto free_lab;
+
+         if (i >= 0)
+           instance = &fvar->instance[i];
+       }
+    }
+
+  if (gvar)
+    fprintf (stderr, "gvar table found\n");
+
+  if (avar)
+    {
+      fprintf (stderr, "avar table found\n");
+
+      for (i = 0; i < avar->axis_count; ++i)
+       {
+         fprintf (stderr, "axis: %d, %"PRIu16" pairs\n",
+                  i, avar->segments[i].pair_count);
+
+         for (j = 0; j < avar->segments[i].pair_count; ++j)
+           fprintf (stderr, "pair: %g, %g\n",
+                    (avar->segments[i].correspondence[j].from_coord
+                     / 16384.0),
+                    (avar->segments[i].correspondence[j].to_coord
+                     / 16384.0));
+       }
+    }
+
+  memset (&blend, 0, sizeof blend);
+
+  if (instance && gvar)
+    {
+      sfnt_init_blend (&blend, fvar, gvar, avar,
+                      cvar);
+
+      for (i = 0; i < fvar->axis_count; ++i)
+       blend.coords[i] = instance->coords[i];
+
+      sfnt_normalize_blend (&blend);
+    }
+
+  if (fancy)
+    {
+      length = strlen (fancy);
+      scale = sfnt_div_fixed (FANCY_PPEM, head->units_per_em);
+
+      if (hhea && maxp)
+       hmtx = sfnt_read_hmtx_table (fd, font, hhea, maxp);
+
+      if (!maxp || !head || !prep || !hmtx || !hhea
+         || table->num_subtables < 1)
+       exit (1);
+
+      if (head->index_to_loc_format)
+       {
+         loca_long = sfnt_read_loca_table_long (fd, font);
+         if (!loca_long)
+           return 1;
+
+         fprintf (stderr, "long loca table has %zu glyphs\n",
+                  loca_long->num_offsets);
+       }
+      else
+       {
+         loca_short = sfnt_read_loca_table_short (fd, font);
+         if (!loca_short)
+           return 1;
+
+         fprintf (stderr, "short loca table has %zu glyphs\n",
+                  loca_short->num_offsets);
+       }
+
+      interpreter = sfnt_make_interpreter (maxp, cvt, head, fvar,
+                                          FANCY_PPEM, FANCY_PPEM);
+      if (instance && gvar)
+       sfnt_vary_interpreter (interpreter, &blend);
+
+      if (!interpreter)
+       exit (1);
+
+      if (fpgm)
+       {
+         fprintf (stderr, "interpreting the font program, with"
+                  " %zu instructions\n", fpgm->num_instructions);
+         trap = sfnt_interpret_font_program (interpreter, fpgm);
+
+         if (trap)
+           fprintf (stderr, "**TRAP**: %s\n", trap);
+       }
+
+      if (prep)
+       {
+         fprintf (stderr, "interpreting the control value program, with"
+                  " %zu instructions\n", prep->num_instructions);
+         trap = sfnt_interpret_control_value_program (interpreter, prep,
+                                                      &state);
+
+         if (trap)
+           fprintf (stderr, "**TRAP**: %s\n", trap);
+       }
+
+      state = interpreter->state;
+
+      advances = alloca (sizeof *advances * length);
+      rasters = alloca (sizeof *rasters * length);
+
+      for (i = 0; i < length; ++i)
+       {
+         code = sfnt_lookup_glyph (fancy[i], data[0]);
+
+         if (!code)
+           exit (2);
+
+         glyph = sfnt_read_glyph (code, glyf, loca_short,
+                                  loca_long);
+
+         if (!glyph || !glyph->simple)
+           exit (3);
+
+         if (instance && gvar)
+           sfnt_vary_simple_glyph (&blend, code, glyph,
+                                   &distortion);
+
+         if (sfnt_lookup_glyph_metrics (code, -1,
+                                        &metrics,
+                                        hmtx, hhea,
+                                        head, maxp))
+           exit (4);
+
+         interpreter->state = state;
+         trap = sfnt_interpret_simple_glyph (glyph, interpreter,
+                                             &metrics, &value);
+
+         if (trap)
+           {
+             fprintf (stderr, "*TRAP*: %s\n", trap);
+             exit (5);
+           }
+
+         outline = sfnt_build_instructed_outline (value);
+
+         if (!outline)
+           exit (6);
+
+         xfree (value);
+
+         raster = sfnt_raster_glyph_outline (outline);
+
+         if (!raster)
+           exit (7);
+
+         xfree (outline);
+
+         rasters[i] = raster;
+         advances[i] = (sfnt_mul_fixed (metrics.advance, scale)
+                        + sfnt_mul_fixed (distortion.advance, scale));
+       }
+
+      sfnt_x_raster (rasters, advances, length, hhea, scale);
+      exit (0);
+    }
+
+  if (hhea && maxp)
+    hmtx = sfnt_read_hmtx_table (fd, font, hhea, maxp);
+
+  if (maxp)
+    fprintf (stderr, "maxp says num glyphs is %"PRIu16"\n",
+            maxp->num_glyphs);
+
+  if (name)
+    {
+      fprintf (stderr, "name table of format: %"PRIu16" count: %"
+              PRIu16"\n", name->format, name->count);
+
+      string = sfnt_find_name (name, SFNT_NAME_FONT_FAMILY,
+                              &record);
+
+      if (string)
+       fprintf (stderr, "FONT_FAMILY: %"PRIu16", %"PRIu16"\n",
+                record.platform_id, record.length);
+    }
+
+  if (meta)
+    {
+      fprintf (stderr, "meta table with count: %"PRIu32"\n",
+              meta->num_data_maps);
+
+      for (i = 0; i < meta->num_data_maps; ++i)
+       fprintf (stderr, "  meta tag: %"PRIx32"\n",
+                meta->data_maps[i].tag);
+    }
+
+  loca_long = NULL;
+  loca_short = NULL;
+
+  if (head)
+    {
+      fprintf (stderr, "HEAD table:\n"
+              "version: \t\t\t%g\n"
+              "revision: \t\t\t%g\n"
+              "checksum_adjustment: \t\t%"PRIu32"\n"
+              "magic: \t\t\t\t%"PRIx32"\n"
+              "flags: \t\t\t\t%"PRIx16"\n"
+              "units_per_em: \t\t\t%"PRIu16"\n"
+              "xmin, ymin, xmax, ymax: \t%d, %d, %d, %d\n"
+              "mac_style: \t\t\t%"PRIx16"\n"
+              "lowest_rec_ppem: \t\t%"PRIu16"\n"
+              "font_direction_hint: \t\t%"PRIi16"\n"
+              "index_to_loc_format: \t\t%"PRIi16"\n"
+              "glyph_data_format: \t\t%"PRIi16"\n",
+              sfnt_coerce_fixed (head->version),
+              sfnt_coerce_fixed (head->revision),
+              head->checksum_adjustment,
+              head->magic,
+              head->flags,
+              head->units_per_em,
+              (int) head->xmin,
+              (int) head->ymin,
+              (int) head->xmax,
+              (int) head->ymax,
+              head->mac_style,
+              head->lowest_rec_ppem,
+              head->font_direction_hint,
+              head->index_to_loc_format,
+              head->glyph_data_format);
+
+      if (head->index_to_loc_format)
+       {
+         loca_long = sfnt_read_loca_table_long (fd, font);
+         if (!loca_long)
+           return 1;
+
+         fprintf (stderr, "long loca table has %zu glyphs\n",
+                  loca_long->num_offsets);
+       }
+      else
+       {
+         loca_short = sfnt_read_loca_table_short (fd, font);
+         if (!loca_short)
+           return 1;
+
+         fprintf (stderr, "short loca table has %zu glyphs\n",
+                  loca_short->num_offsets);
+       }
+    }
+
+  if (hhea)
+    fprintf (stderr, "HHEA table:\n"
+            "version: \t\t\t%g\n"
+            "ascent, descent: \t\t%d %d\n"
+            "line_gap: \t\t\t%d\n"
+            "advance_width_max: \t\t%u\n"
+            "min_lsb: \t\t\t%d\n"
+            "min_rsb: \t\t\t%d\n"
+            "caret_srise: \t\t\t%d\n"
+            "caret_srun: \t\t\t%d\n",
+            sfnt_coerce_fixed (hhea->version),
+            (int) hhea->ascent,
+            (int) hhea->descent,
+            (int) hhea->line_gap,
+            (unsigned int) hhea->advance_width_max,
+            (int) hhea->min_left_side_bearing,
+            (int) hhea->min_right_side_bearing,
+            (int) hhea->caret_slope_rise,
+            (int) hhea->caret_slope_run);
+
+  if (head && maxp && maxp->version >= 0x00010000)
+    {
+      fprintf (stderr, "creating interpreter\n"
+              "the size of the stack is %"PRIu16"\n"
+              "the size of the twilight zone is %"PRIu16"\n"
+              "the size of the storage area is %"PRIu16"\n"
+              "there are at most %"PRIu16" idefs\n"
+              "there are at most %"PRIu16" fdefs\n"
+              "the cvt is %zu fwords in length\n",
+              maxp->max_stack_elements,
+              maxp->max_twilight_points,
+              maxp->max_storage,
+              maxp->max_instruction_defs,
+              maxp->max_function_defs,
+              cvt ? cvt->num_elements : 0ul);
+
+      interpreter = sfnt_make_interpreter (maxp, cvt, head,
+                                          fvar, FANCY_PPEM,
+                                          FANCY_PPEM);
+      state = interpreter->state;
+
+      if (instance && gvar)
+       sfnt_vary_interpreter (interpreter, &blend);
+
+      if (fpgm)
+       {
+         fprintf (stderr, "interpreting the font program, with"
+                  " %zu instructions\n", fpgm->num_instructions);
+
+         trap = sfnt_interpret_font_program (interpreter, fpgm);
+
+         if (trap)
+           fprintf (stderr, "**TRAP**: %s\n", trap);
+       }
+
+      if (prep)
+       {
+         fprintf (stderr, "interpreting the control value program, with"
+                  " %zu instructions\n", prep->num_instructions);
+
+         trap = sfnt_interpret_control_value_program (interpreter, prep,
+                                                      &state);
+
+         if (trap)
+           fprintf (stderr, "**TRAP**: %s\n", trap);
+       }
+    }
+
+  while (true)
+    {
+      printf ("table, character? ");
+
+      if (scanf ("%d %"SCNu32"", &i, &character) == EOF)
+       break;
+
+      if (i < 0 || i >= table->num_subtables)
+       {
+         printf ("table out of range\n");
+         continue;
+       }
+
+      if (!data[i])
+       {
+         printf ("table not present\n");
+         continue;
+       }
+
+      code = sfnt_lookup_glyph (character, data[i]);
+      printf ("glyph is %"PRIu32"\n", code);
+
+      if ((loca_long || loca_short) && glyf)
+       {
+         scale = sfnt_div_fixed (EASY_PPEM, head->units_per_em);
+         glyph = sfnt_read_glyph (code, glyf, loca_short,
+                                  loca_long);
+
+         if (glyph)
+           {
+             printf ("glyph is: %s\n",
+                     glyph->simple ? "simple" : "compound");
+
+             dcontext.glyf = glyf;
+             dcontext.loca_short = loca_short;
+             dcontext.loca_long = loca_long;
+             dcontext.hmtx = hmtx;
+             dcontext.hhea = hhea;
+             dcontext.maxp = maxp;
+
+             if (instance && gvar)
+               dcontext.blend = &blend;
+             else
+               dcontext.blend = NULL;
+
+             if (glyph->simple && instance && gvar)
+               {
+                 printf ("applying variations to simple glyph...\n");
+
+                 clock_gettime (CLOCK_THREAD_CPUTIME_ID, &start);
+                 if (sfnt_vary_simple_glyph (&blend, code, glyph,
+                                             &distortion))
+                   printf ("variation failed!\n");
+                 clock_gettime (CLOCK_THREAD_CPUTIME_ID, &end);
+                 sub = timespec_sub (end, start);
+
+                 printf ("time spent varying: %lld sec %ld nsec\n",
+                         (long long) sub.tv_sec, sub.tv_nsec);
+                 printf ("distortions: %"PRIi16", %"PRIi16"\n",
+                         distortion.origin, distortion.advance);
+               }
+             else if (instance && gvar)
+               {
+                 printf ("applying variations to compound glyph...\n");
+
+                 if (sfnt_vary_compound_glyph (&blend, code, glyph,
+                                               &distortion))
+                   printf ("variation failed!\n");
+               }
+
+             if (sfnt_decompose_glyph (glyph, sfnt_test_move_to,
+                                       sfnt_test_line_to,
+                                       sfnt_test_curve_to,
+                                       sfnt_test_get_glyph,
+                                       sfnt_test_free_glyph,
+                                       sfnt_test_get_metrics,
+                                       &dcontext))
+               printf ("decomposition failure\n");
+
+             if (sfnt_lookup_glyph_metrics (code, -1,
+                                            &metrics,
+                                            hmtx, hhea,
+                                            head, maxp))
+               {
+                 printf ("metrics lookup failure");
+                 memset (&metrics, 0, sizeof metrics);
+               }
+
+             /* Time this important bit.  */
+             clock_gettime (CLOCK_THREAD_CPUTIME_ID, &start);
+             outline = sfnt_build_glyph_outline (glyph, scale,
+                                                 &metrics,
+                                                 sfnt_test_get_glyph,
+                                                 sfnt_test_free_glyph,
+                                                 sfnt_test_get_metrics,
+                                                 &dcontext);
+
+             clock_gettime (CLOCK_THREAD_CPUTIME_ID, &end);
+             sub = timespec_sub (end, start);
+             memset (&sub1, 0, sizeof sub1);
+
+             if (outline)
+               {
+                 fprintf (stderr, "outline origin, rbearing: %"
+                          PRIi32" %"PRIi32"\n",
+                          outline->origin,
+                          outline->ymax - outline->origin);
+                 sfnt_test_max = outline->ymax - outline->ymin;
+
+                 for (i = 0; i < outline->outline_used; i++)
+                   printf ("ctx.%s (%g, %g) /* %g, %g */\n",
+                           ((outline->outline[i].flags
+                             & SFNT_GLYPH_OUTLINE_LINETO)
+                            ? "lineTo" : "moveTo"),
+                           sfnt_coerce_fixed (outline->outline[i].x
+                                              - outline->xmin),
+                           sfnt_coerce_fixed (sfnt_test_max
+                                              - (outline->outline[i].y
+                                                 - outline->ymin)),
+                           sfnt_coerce_fixed (outline->outline[i].x
+                                              - outline->xmin),
+                           sfnt_coerce_fixed (outline->outline[i].y
+                                              - outline->ymin));
+
+                 clock_gettime (CLOCK_THREAD_CPUTIME_ID, &start);
+                 sfnt_build_outline_edges (outline, sfnt_test_edge_ignore,
+                                           NULL);
+                 clock_gettime (CLOCK_THREAD_CPUTIME_ID, &end);
+                 sub1 = timespec_sub (end, start);
+
+                 sfnt_build_outline_edges (outline, sfnt_test_edge,
+                                           NULL);
+
+                 raster = NULL;
+
+                 clock_gettime (CLOCK_THREAD_CPUTIME_ID, &start);
+
+                 for (i = 0; i < 120; ++i)
+                   {
+                     xfree (raster);
+                     raster = sfnt_raster_glyph_outline (outline);
+                   }
+
+                 clock_gettime (CLOCK_THREAD_CPUTIME_ID, &end);
+                 sub2 = timespec_sub (end, start);
+
+                 /* Print out the raster.  */
+                 sfnt_test_raster (raster, hhea, scale);
+                 printf ("raster offsets: %d, %d\n",
+                         raster->offx, raster->offy);
+
+                 xfree (raster);
+
+                 printf ("outline bounds: %g %g, %g %g\n",
+                         sfnt_coerce_fixed (outline->xmin),
+                         sfnt_coerce_fixed (outline->ymin),
+                         sfnt_coerce_fixed (outline->xmax),
+                         sfnt_coerce_fixed (outline->ymax));
+               }
+
+             if (hmtx && head)
+               {
+                 if (!sfnt_lookup_glyph_metrics (code, EASY_PPEM,
+                                                 &metrics,
+                                                 hmtx, hhea,
+                                                 head, maxp))
+                   printf ("lbearing, advance: %g, %g\n",
+                           sfnt_coerce_fixed (metrics.lbearing),
+                           sfnt_coerce_fixed (metrics.advance));
+
+                 if (interpreter)
+                   {
+                     if (getenv ("SFNT_DEBUG"))
+                       interpreter->run_hook = sfnt_run_hook;
+                     else if (getenv ("SFNT_VERBOSE"))
+                       {
+                         interpreter->run_hook = sfnt_verbose;
+                         interpreter->push_hook = sfnt_push_hook;
+                         interpreter->pop_hook = sfnt_pop_hook;
+                       }
+
+                     if (!sfnt_lookup_glyph_metrics (code, -1,
+                                                     &metrics,
+                                                     hmtx, hhea,
+                                                     head, maxp))
+                       {
+                         printf ("interpreting glyph\n");
+                         interpreter->state = state;
+                         clock_gettime (CLOCK_THREAD_CPUTIME_ID, &start);
+                         if (glyph->simple)
+                           trap
+                             = sfnt_interpret_simple_glyph (glyph,
+                                                            interpreter,
+                                                            &metrics,
+                                                            &value);
+                         else
+#define GG sfnt_test_get_glyph
+#define FG sfnt_test_free_glyph
+                           trap
+                             = sfnt_interpret_compound_glyph (glyph,
+                                                              interpreter,
+                                                              &state,
+                                                              GG, FG,
+                                                              hmtx, hhea,
+                                                              maxp,
+                                                              &metrics,
+                                                              &dcontext,
+                                                              &value);
+#undef GG
+#undef FG
+                         clock_gettime (CLOCK_THREAD_CPUTIME_ID, &end);
+                         sub3 = timespec_sub (end, start);
+
+                         if (trap)
+                           printf ("**TRAP**: %s\n", trap);
+                         else
+                           {
+                             printf ("rasterizing instructed outline\n");
+                             if (outline)
+                               xfree (outline);
+                             outline = sfnt_build_instructed_outline (value);
+                             xfree (value);
+
+                             if (outline)
+                               {
+                                 raster = sfnt_raster_glyph_outline (outline);
+
+                                 if (raster)
+                                   {
+                                     sfnt_test_raster (raster, hhea, scale);
+                                     printf ("raster offsets: %d, %d\n",
+                                             raster->offx, raster->offy);
+                                     xfree (raster);
+                                   }
+                               }
+                           }
+
+                         fprintf (stderr, "execution time: %lld sec %ld nse"
+                                  "c\n",
+                                  (long long) sub3.tv_sec, sub3.tv_nsec);
+                       }
+
+                     interpreter->run_hook = NULL;
+                   }
+               }
+
+             printf ("time spent outlining: %lld sec %ld nsec\n",
+                     (long long) sub.tv_sec, sub.tv_nsec);
+             printf ("time spent building edges: %lld sec %ld nsec\n",
+                     (long long) sub1.tv_sec, sub1.tv_nsec);
+             printf ("time spent rasterizing: %lld sec %ld nsec\n",
+                     (long long) sub2.tv_sec / 120, sub2.tv_nsec / 120);
+
+             xfree (outline);
+           }
+
+         sfnt_free_glyph (glyph);
+       }
+    }
+
+ free_lab:
+
+  xfree (font);
+
+  for (i = 0; i < table->num_subtables; ++i)
+    xfree (data[i]);
+
+  if (instance && gvar)
+    sfnt_free_blend (&blend);
+
+  xfree (table);
+  xfree (data);
+  xfree (subtables);
+  xfree (head);
+  xfree (hhea);
+  xfree (loca_long);
+  xfree (loca_short);
+  xfree (glyf);
+  xfree (maxp);
+  xfree (hmtx);
+  xfree (name);
+  xfree (meta);
+  xfree (ttc);
+  xfree (cvt);
+  xfree (fpgm);
+  xfree (interpreter);
+  xfree (prep);
+  xfree (fvar);
+  xfree (gvar);
+  xfree (avar);
+  xfree (cvar);
+
+  return 0;
+}
+
+#endif
diff --git a/src/sfnt.h b/src/sfnt.h
new file mode 100644
index 00000000000..1a6b2209abc
--- /dev/null
+++ b/src/sfnt.h
@@ -0,0 +1,2003 @@
+/* sfnt format font support for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _SFNT_H_
+#define _SFNT_H_
+
+#include <stdint.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#include <sys/types.h>
+
+
+
+/* Container structure and enumerator definitions.  */
+
+/* The sfnt container format is organized into different tables, such
+   as ``cmap'' or ``glyf''.  Each of these tables has a specific
+   format and use.  These are all the tables known to Emacs.  */
+
+enum sfnt_table
+  {
+    SFNT_TABLE_CMAP,
+    SFNT_TABLE_GLYF,
+    SFNT_TABLE_HEAD,
+    SFNT_TABLE_HHEA,
+    SFNT_TABLE_HMTX,
+    SFNT_TABLE_LOCA,
+    SFNT_TABLE_MAXP,
+    SFNT_TABLE_NAME,
+    SFNT_TABLE_META,
+    SFNT_TABLE_CVT ,
+    SFNT_TABLE_FPGM,
+    SFNT_TABLE_PREP,
+    SFNT_TABLE_FVAR,
+    SFNT_TABLE_GVAR,
+    SFNT_TABLE_CVAR,
+    SFNT_TABLE_AVAR,
+  };
+
+#define SFNT_ENDOF(type, field, type1)                 \
+  ((size_t) offsetof (type, field) + sizeof (type1))
+
+/* Each of these structures must be aligned so that no compiler will
+   ever generate padding bytes on platforms where the alignment
+   requirements for uint32_t and uint16_t are no larger than 4 and 2
+   bytes respectively.
+
+   Pointer types are assumed to impose an alignmnent requirement no
+   less than that of uint32_t.
+
+   If a table has more than one kind of variable-length subtable array
+   at the end, make sure to pad subsequent subtables
+   appropriately.  */
+
+struct sfnt_offset_subtable
+{
+  /* The scaler type.  */
+  uint32_t scaler_type;
+
+  /* The number of tables.  */
+  uint16_t num_tables;
+
+  /* (Maximum power of 2 <= numTables) * 16.  */
+  uint16_t search_range;
+
+  /* log2 (maximum power of 2 <= numTables) */
+  uint16_t entry_selector;
+
+  /* numTables * 16 - searchRange.  */
+  uint16_t range_shift;
+
+  /* Variable length data.  */
+  struct sfnt_table_directory *subtables;
+};
+
+/* The table directory.  Follows the offset subtable, with one for
+   each table.  */
+
+struct sfnt_table_directory
+{
+  /* 4-byte identifier for each table.  See sfnt_table_names.  */
+  uint32_t tag;
+
+  /* Table checksum.  */
+  uint32_t checksum;
+
+  /* Offset from the start of the file.  */
+  uint32_t offset;
+
+  /* Length of the table in bytes, not subject to padding.  */
+  uint32_t length;
+};
+
+enum sfnt_scaler_type
+  {
+    SFNT_SCALER_TRUE = 0x74727565,
+    SFNT_SCALER_VER1 = 0x00010000,
+    SFNT_SCALER_TYP1 = 0x74797031,
+    SFNT_SCALER_OTTO = 0x4F54544F,
+  };
+
+typedef int32_t sfnt_fixed;
+typedef int16_t sfnt_fword;
+typedef uint16_t sfnt_ufword;
+
+#define sfnt_coerce_fixed(fixed) ((sfnt_fixed) (fixed) / 65535.0)
+
+typedef unsigned int sfnt_glyph;
+typedef unsigned int sfnt_char;
+
+struct sfnt_head_table
+{
+  /* The version.  This is a 16.16 fixed point number.  */
+  sfnt_fixed version;
+
+  /* The revision.  */
+  sfnt_fixed revision;
+
+  /* Checksum adjustment.  */
+  uint32_t checksum_adjustment;
+
+  /* Magic number, should be 0x5F0F3CF5.  */
+  uint32_t magic;
+
+  /* Flags for the font.  */
+  uint16_t flags;
+
+  /* Units per em.  */
+  uint16_t units_per_em;
+
+  /* Time of creation.  */
+  uint32_t created_high, created_low;
+
+  /* Time of modification.  */
+  uint32_t modified_high, modified_low;
+
+  /* Minimum bounds.  */
+  sfnt_fword xmin, ymin, xmax, ymax;
+
+  /* Mac specific stuff.  */
+  uint16_t mac_style;
+
+  /* Smallest readable size in pixels.  */
+  uint16_t lowest_rec_ppem;
+
+  /* Font direction hint.  */
+  int16_t font_direction_hint;
+
+  /* Index to loc format.  0 for short offsets, 1 for long.  */
+  int16_t index_to_loc_format;
+
+  /* Unused.  */
+  int16_t glyph_data_format;
+};
+
+struct sfnt_hhea_table
+{
+  /* The version.  This is a 16.16 fixed point number.  */
+  sfnt_fixed version;
+
+  /* The maximum ascent and descent values for this font.  */
+  sfnt_fword ascent, descent;
+
+  /* The typographic line gap.  */
+  sfnt_fword line_gap;
+
+  /* The maximum advance width.  */
+  sfnt_ufword advance_width_max;
+
+  /* The minimum bearings on either side.  */
+  sfnt_fword min_left_side_bearing, min_right_side_bearing;
+
+  /* The maximum extent.  */
+  sfnt_fword x_max_extent;
+
+  /* Caret slope.  */
+  int16_t caret_slope_rise, caret_slope_run;
+
+  /* Caret offset for non slanted fonts.  */
+  sfnt_fword caret_offset;
+
+  /* Reserved values.  */
+  int16_t reserved1, reserved2, reserved3, reserved4;
+
+  /* Should always be zero.  */
+  int16_t metric_data_format;
+
+  /* Number of advanced widths in metrics table.  */
+  uint16_t num_of_long_hor_metrics;
+};
+
+struct sfnt_cmap_table
+{
+  /* Should be zero.  */
+  uint16_t version;
+
+  /* Number of subtables.  */
+  uint16_t num_subtables;
+};
+
+enum sfnt_platform_id
+  {
+    SFNT_PLATFORM_UNICODE   = 0,
+    SFNT_PLATFORM_MACINTOSH = 1,
+    SFNT_PLATFORM_RESERVED  = 2,
+    SFNT_PLATFORM_MICROSOFT = 3,
+  };
+
+enum sfnt_unicode_platform_specific_id
+  {
+    SFNT_UNICODE_1_0                = 0,
+    SFNT_UNICODE_1_1                = 1,
+    SFNT_UNICODE_ISO_10646_1993             = 2,
+    SFNT_UNICODE_2_0_BMP            = 3,
+    SFNT_UNICODE_2_0                = 4,
+    SFNT_UNICODE_VARIATION_SEQUENCES = 5,
+    SFNT_UNICODE_LAST_RESORT        = 6,
+  };
+
+enum sfnt_macintosh_platform_specific_id
+  {
+    SFNT_MACINTOSH_ROMAN              = 0,
+    SFNT_MACINTOSH_JAPANESE           = 1,
+    SFNT_MACINTOSH_TRADITIONAL_CHINESE = 2,
+    SFNT_MACINTOSH_KOREAN             = 3,
+    SFNT_MACINTOSH_ARABIC             = 4,
+    SFNT_MACINTOSH_HEBREW             = 5,
+    SFNT_MACINTOSH_GREEK              = 6,
+    SFNT_MACINTOSH_RUSSIAN            = 7,
+    SFNT_MACINTOSH_RSYMBOL            = 8,
+    SFNT_MACINTOSH_DEVANGARI          = 9,
+    SFNT_MACINTOSH_GURMUKHI           = 10,
+    SFNT_MACINTOSH_GUJARATI           = 11,
+    SFNT_MACINTOSH_ORIYA              = 12,
+    SFNT_MACINTOSH_BENGALI            = 13,
+    SFNT_MACINTOSH_TAMIL              = 14,
+    SFNT_MACINTOSH_TELUGU             = 15,
+    SFNT_MACINTOSH_KANNADA            = 16,
+    SFNT_MACINTOSH_MALAYALAM          = 17,
+    SFNT_MACINTOSH_SINHALESE          = 18,
+    SFNT_MACINTOSH_BURMESE            = 19,
+    SFNT_MACINTOSH_KHMER              = 20,
+    SFNT_MACINTOSH_THAI                       = 21,
+    SFNT_MACINTOSH_LAOTIAN            = 22,
+    SFNT_MACINTOSH_GEORGIAN           = 23,
+    SFNT_MACINTOSH_ARMENIAN           = 24,
+    SFNT_MACINTOSH_SIMPLIFIED_CHINESE  = 25,
+    SFNT_MACINTOSH_TIBETIAN           = 26,
+    SFNT_MACINTOSH_MONGOLIAN          = 27,
+    SFNT_MACINTOSH_GEEZ                       = 28,
+    SFNT_MACINTOSH_SLAVIC             = 29,
+    SFNT_MACINTOSH_VIETNAMESE         = 30,
+    SFNT_MACINTOSH_SINDHI             = 31,
+    SFNT_MACINTOSH_UNINTERPRETED       = 32,
+  };
+
+enum sfnt_microsoft_platform_specific_id
+  {
+    SFNT_MICROSOFT_SYMBOL       = 0,
+    SFNT_MICROSOFT_UNICODE_BMP  = 1,
+    SFNT_MICROSOFT_SHIFT_JIS    = 2,
+    SFNT_MICROSOFT_PRC          = 3,
+    SFNT_MICROSOFT_BIG_FIVE     = 4,
+    SFNT_MICROSOFT_WANSUNG      = 5,
+    SFNT_MICROSOFT_JOHAB        = 6,
+    SFNT_MICROSOFT_UNICODE_UCS_4 = 10,
+  };
+
+struct sfnt_cmap_encoding_subtable
+{
+  /* The platform ID.  */
+  uint16_t platform_id;
+
+  /* Platform specific ID.  */
+  uint16_t platform_specific_id;
+
+  /* Mapping table offset.  */
+  uint32_t offset;
+};
+
+struct sfnt_cmap_encoding_subtable_data
+{
+  /* Format and possibly the length in bytes.  */
+  uint16_t format, length;
+};
+
+struct sfnt_cmap_format_0
+{
+  /* Format, set to 0.  */
+  uint16_t format;
+
+  /* Length in bytes.  Should be 262.  */
+  uint16_t length;
+
+  /* Language code.  */
+  uint16_t language;
+
+  /* Character code to glyph index map.  */
+  uint8_t glyph_index_array[256];
+};
+
+struct sfnt_cmap_format_2_subheader
+{
+  uint16_t first_code;
+  uint16_t entry_count;
+  int16_t id_delta;
+  uint16_t id_range_offset;
+};
+
+struct sfnt_cmap_format_2
+{
+  /* Format, set to 2.  */
+  uint16_t format;
+
+  /* Length in bytes.  */
+  uint16_t length;
+
+  /* Language code.  */
+  uint16_t language;
+
+  /* Array mapping high bytes to subheaders.  */
+  uint16_t sub_header_keys[256];
+
+  /* Variable length data.  */
+  struct sfnt_cmap_format_2_subheader *subheaders;
+  uint16_t *glyph_index_array;
+  uint16_t num_glyphs;
+};
+
+struct sfnt_cmap_format_4
+{
+  /* Format, set to 4.  */
+  uint16_t format;
+
+  /* Length in bytes.  */
+  uint16_t length;
+
+  /* Language code.  */
+  uint16_t language;
+
+  /* 2 * seg_count.  */
+  uint16_t seg_count_x2;
+
+  /* 2 * (2**FLOOR(log2(segCount))) */
+  uint16_t search_range;
+
+  /* log2(searchRange/2) */
+  uint16_t entry_selector;
+
+  /* (2 * segCount) - searchRange */
+  uint16_t range_shift;
+
+  /* Variable-length data.  */
+  uint16_t *end_code;
+  uint16_t *reserved_pad;
+  uint16_t *start_code;
+  int16_t *id_delta;
+  int16_t *id_range_offset;
+  uint16_t *glyph_index_array;
+
+  /* The number of elements in glyph_index_array.  */
+  size_t glyph_index_size;
+};
+
+struct sfnt_cmap_format_6
+{
+  /* Format, set to 6.  */
+  uint16_t format;
+
+  /* Length in bytes.  */
+  uint16_t length;
+
+  /* Language code.  */
+  uint16_t language;
+
+  /* First character code in subrange.  */
+  uint16_t first_code;
+
+  /* Number of character codes.  */
+  uint16_t entry_count;
+
+  /* Variable-length data.  */
+  uint16_t *glyph_index_array;
+};
+
+struct sfnt_cmap_format_8_or_12_group
+{
+  uint32_t start_char_code;
+  uint32_t end_char_code;
+  uint32_t start_glyph_code;
+};
+
+struct sfnt_cmap_format_8
+{
+  /* Format, set to 8.  */
+  uint16_t format;
+
+  /* Reserved.  */
+  uint16_t reserved;
+
+  /* Length in bytes.  */
+  uint32_t length;
+
+  /* Language code.  */
+  uint32_t language;
+
+  /* Tightly packed array of bits (8K bytes total) indicating whether
+     the particular 16-bit (index) value is the start of a 32-bit
+     character code.  */
+  uint8_t is32[65536];
+
+  /* Number of groups.  */
+  uint32_t num_groups;
+
+  /* Variable length data.  */
+  struct sfnt_cmap_format_8_or_12_group *groups;
+};
+
+/* cmap formats 10, 13 unsupported.  */
+
+struct sfnt_cmap_format_12
+{
+  /* Format, set to 12.  */
+  uint16_t format;
+
+  /* Reserved.  */
+  uint16_t reserved;
+
+  /* Length in bytes.  */
+  uint32_t length;
+
+  /* Language code.  */
+  uint32_t language;
+
+  /* Number of groups.  */
+  uint32_t num_groups;
+
+  /* Variable length data.  */
+  struct sfnt_cmap_format_8_or_12_group *groups;
+};
+
+struct sfnt_cmap_format_14
+{
+  /* Format, set to 14.  */
+  uint16_t format;
+
+  /* The length of the table in bytes.  */
+  uint32_t length;
+
+  /* Number of variation selector records.  */
+  uint16_t num_var_selector_records;
+
+  /* The offset of this table in the font file.  */
+  off_t offset;
+
+  /* Variable length data.  */
+  struct sfnt_variation_selector_record *records;
+};
+
+struct sfnt_variation_selector_record
+{
+  /* 24-bit unsigned variation selector.  */
+  unsigned int var_selector;
+
+  /* Offset to default UVS table.  */
+  uint32_t default_uvs_offset;
+
+  /* Offset to non-default UVS table.  */
+  uint32_t nondefault_uvs_offset;
+};
+
+struct sfnt_maxp_table
+{
+  /* Table version.  */
+  sfnt_fixed version;
+
+  /* The number of glyphs in this font - 1.  Set at version 0.5 or
+     later.  */
+  uint16_t num_glyphs;
+
+  /* These fields are only set in version 1.0 or later.  Maximum
+     points in a non-composite glyph.  */
+  uint16_t max_points;
+
+  /* Maximum contours in a non-composite glyph.  */
+  uint16_t max_contours;
+
+  /* Maximum points in a composite glyph.  */
+  uint16_t max_composite_points;
+
+  /* Maximum contours in a composite glyph.  */
+  uint16_t max_composite_contours;
+
+  /* 1 if instructions do not use the twilight zone (Z0), or 2 if
+     instructions do use Z0; should be set to 2 in most cases.  */
+  uint16_t max_zones;
+
+  /* Maximum points used in Z0.  */
+  uint16_t max_twilight_points;
+
+  /* Number of Storage Area locations.  */
+  uint16_t max_storage;
+
+  /* Number of FDEFs, equal to the highest function number + 1.  */
+  uint16_t max_function_defs;
+
+  /* Number of IDEFs.  */
+  uint16_t max_instruction_defs;
+
+  /* Maximum stack depth across Font Program ('fpgm' table), CVT
+     Program ('prep' table) and all glyph instructions (in the 'glyf'
+     table).  */
+  uint16_t max_stack_elements;
+
+  /* Maximum byte count for glyph instructions.  */
+  uint16_t max_size_of_instructions;
+
+  /* Maximum number of components referenced at ``top level'' for any
+     composite glyph.  */
+  uint16_t max_component_elements;
+
+  /* Maximum levels of recursion; 1 for simple components.  */
+  uint16_t max_component_depth;
+};
+
+struct sfnt_loca_table_short
+{
+  /* Offsets to glyph data divided by two.  */
+  uint16_t *offsets;
+
+  /* Size of the offsets list.  */
+  size_t num_offsets;
+};
+
+struct sfnt_loca_table_long
+{
+  /* Offsets to glyph data.  */
+  uint32_t *offsets;
+
+  /* Size of the offsets list.  */
+  size_t num_offsets;
+};
+
+struct sfnt_glyf_table
+{
+  /* Size of the glyph data.  */
+  size_t size;
+
+  /* Pointer to possibly unaligned glyph data.  */
+  unsigned char *glyphs;
+
+  /* Pointer to the start of the mapping.
+     Only initialized if this table was mmapped.  */
+  unsigned char *start;
+};
+
+struct sfnt_simple_glyph
+{
+  /* The total number of points in this glyph.  */
+  size_t number_of_points;
+
+  /* Array containing the last points of each contour.  */
+  uint16_t *restrict end_pts_of_contours;
+
+  /* Total number of bytes needed for instructions.  */
+  uint16_t instruction_length;
+
+  /* Instruction data.  */
+  uint8_t *restrict instructions;
+
+  /* Array of flags.  */
+  uint8_t *restrict flags;
+
+  /* Array of X coordinates.  */
+  int16_t *restrict x_coordinates;
+
+  /* Array of Y coordinates.  */
+  int16_t *restrict y_coordinates;
+
+  /* Pointer to the end of that array.  */
+  int16_t *restrict y_coordinates_end;
+};
+
+struct sfnt_compound_glyph_component
+{
+  /* Compound glyph flags.  */
+  uint16_t flags;
+
+  /* Component glyph index.  */
+  uint16_t glyph_index;
+
+  /* X-offset for component or point number; type depends on bits 0
+     and 1 in component flags.  */
+  union {
+    uint8_t a;
+    int8_t b;
+    uint16_t c;
+    int16_t d;
+  } argument1;
+
+  /* Y-offset for component or point number; type depends on bits 0
+     and 1 in component flags.  */
+  union {
+    uint8_t a;
+    int8_t b;
+    uint16_t c;
+    int16_t d;
+  } argument2;
+
+  /* Various scale formats.  */
+  union {
+    int16_t scale;
+    struct {
+      int16_t xscale;
+      int16_t yscale;
+    } a;
+    struct {
+      int16_t xscale;
+      int16_t scale01;
+      int16_t scale10;
+      int16_t yscale;
+    } b;
+  } u;
+};
+
+struct sfnt_compound_glyph
+{
+  /* Pointer to array of components.  */
+  struct sfnt_compound_glyph_component *components;
+
+  /* Number of elements in that array.  */
+  size_t num_components;
+
+  /* Instruction data.  */
+  uint8_t *instructions;
+
+  /* Length of instructions.  */
+  uint16_t instruction_length;
+};
+
+struct sfnt_glyph
+{
+  /* Number of contours in this glyph.  */
+  int16_t number_of_contours;
+
+  /* Coordinate bounds.  */
+  sfnt_fword xmin, ymin, xmax, ymax;
+
+  /* Distortion applied to the right side phantom point.  */
+  sfnt_fword advance_distortion;
+
+  /* Distortion applied to the origin point.  */
+  sfnt_fword origin_distortion;
+
+  /* Either a simple glyph or a compound glyph, depending on which is
+     set.  */
+  struct sfnt_simple_glyph *simple;
+  struct sfnt_compound_glyph *compound;
+};
+
+
+
+/* Glyph outline decomposition.  */
+
+struct sfnt_point
+{
+  /* X and Y in em space.  */
+  sfnt_fixed x, y;
+};
+
+typedef void (*sfnt_move_to_proc) (struct sfnt_point, void *);
+typedef void (*sfnt_line_to_proc) (struct sfnt_point, void *);
+typedef void (*sfnt_curve_to_proc) (struct sfnt_point,
+                                   struct sfnt_point,
+                                   void *);
+
+/* Forward declaration for use in sfnt_get_metrics_proc.  */
+struct sfnt_glyph_metrics;
+
+typedef struct sfnt_glyph *(*sfnt_get_glyph_proc) (sfnt_glyph, void *,
+                                                  bool *);
+typedef void (*sfnt_free_glyph_proc) (struct sfnt_glyph *, void *);
+typedef int (*sfnt_get_metrics_proc) (sfnt_glyph,
+                                     struct sfnt_glyph_metrics *,
+                                     void *);
+
+
+
+/* Decomposed glyph outline.  */
+
+struct sfnt_glyph_outline_command
+{
+  /* Flags for this outline command.  */
+  int flags;
+
+  /* X and Y position of this command.  */
+  sfnt_fixed x, y;
+};
+
+/* Structure describing a single recorded outline in fixed pixel
+   space.  */
+
+struct sfnt_glyph_outline
+{
+  /* Array of outlines elements.  */
+  struct sfnt_glyph_outline_command *outline;
+
+  /* Size of the outline data, and how much is full.  */
+  size_t outline_size, outline_used;
+
+  /* Rectangle defining bounds of the outline.  Namely, the minimum
+     and maximum X and Y positions.  */
+  sfnt_fixed xmin, ymin, xmax, ymax;
+
+  /* The origin point of the outline on the X axis.  Value defaults to
+     0.  */
+  sfnt_fixed origin;
+
+  /* Reference count.  Initially zero.  */
+  short refcount;
+};
+
+enum sfnt_glyph_outline_flags
+  {
+    SFNT_GLYPH_OUTLINE_LINETO    = (1 << 1),
+  };
+
+
+
+/* Glyph rasterization.  */
+
+struct sfnt_raster
+{
+  /* Pointer to coverage data.  */
+  unsigned char *cells;
+
+  /* Basic dimensions of the raster.  */
+  unsigned short width, height;
+
+  /* Integer offset to apply to positions in the raster so that they
+     start from the origin point of the glyph.  */
+  short offx, offy;
+
+  /* The raster stride.  */
+  unsigned short stride;
+
+  /* Reference count.  Initially zero.  */
+  unsigned short refcount;
+};
+
+struct sfnt_edge
+{
+  /* Next edge in this chain.  */
+  struct sfnt_edge *next;
+
+  /* Winding direction.  1 if clockwise, -1 if counterclockwise.  */
+  int winding;
+
+  /* X position, top and bottom of edges.  */
+  sfnt_fixed x, top, bottom;
+
+  /* Amount to move X by upon each change of Y.  */
+  sfnt_fixed step_x;
+};
+
+
+
+/* Polygon rasterization constants.  */
+
+enum
+  {
+    SFNT_POLY_SHIFT  = 3,
+    SFNT_POLY_SAMPLE = (1 << SFNT_POLY_SHIFT),
+    SFNT_POLY_MASK   = (SFNT_POLY_SAMPLE - 1),
+    SFNT_POLY_STEP   = (0x10000 >> SFNT_POLY_SHIFT),
+    SFNT_POLY_START  = (SFNT_POLY_STEP >> 1),
+  };
+
+
+
+/* Glyph metrics computation.  */
+
+struct sfnt_long_hor_metric
+{
+  uint16_t advance_width;
+  int16_t left_side_bearing;
+};
+
+struct sfnt_hmtx_table
+{
+  /* Array of horizontal metrics for each glyph.  */
+  struct sfnt_long_hor_metric *h_metrics;
+
+  /* Lbearing for remaining glyphs.  */
+  int16_t *left_side_bearing;
+};
+
+/* Structure describing the metrics of a single glyph.  The fields
+   mean the same as in XCharStruct, except they are 16.16 fixed point
+   values, and are missing significant information.  */
+
+struct sfnt_glyph_metrics
+{
+  /* Distance between origin and left edge of raster.  Positive
+     changes move rightwards.
+
+     If sfnt_lookup_glyph_metrics is given a pixel size of -1,
+     this is actually a sign extended fword.  */
+  sfnt_fixed lbearing;
+
+  /* Advance to next glyph's origin.
+
+     If sfnt_lookup_glyph_metrics is given a pixel size of -1, this is
+     actually a sign extended fword.  */
+  sfnt_fixed advance;
+};
+
+
+
+/* Font style parsing.  */
+
+struct sfnt_name_record
+{
+  /* Platform identifier code.  */
+  uint16_t platform_id;
+
+  /* Platform specific ID.  */
+  uint16_t platform_specific_id;
+
+  /* Language identifier.  */
+  uint16_t language_id;
+
+  /* Name identifier.  */
+  uint16_t name_id;
+
+  /* String length in bytes.  */
+  uint16_t length;
+
+  /* Offset from start of storage area.  */
+  uint16_t offset;
+};
+
+struct sfnt_name_table
+{
+  /* Format selector of name table.  */
+  uint16_t format;
+
+  /* Number of name records.  */
+  uint16_t count;
+
+  /* Offset to start of string data.  */
+  uint16_t string_offset;
+
+  /* Variable length data.  */
+  struct sfnt_name_record *name_records;
+
+  /* Start of string data.  */
+  unsigned char *data;
+};
+
+/* Name identifier codes.  These are Apple's codes, not
+   Microsoft's.  */
+
+enum sfnt_name_identifier_code
+  {
+    SFNT_NAME_COPYRIGHT_NOTICE                 = 0,
+    SFNT_NAME_FONT_FAMILY                      = 1,
+    SFNT_NAME_FONT_SUBFAMILY                   = 2,
+    SFNT_NAME_UNIQUE_SUBFAMILY_IDENTIFICATION  = 3,
+    SFNT_NAME_FULL_NAME                                = 4,
+    SFNT_NAME_NAME_TABLE_VERSION               = 5,
+    SFNT_NAME_POSTSCRIPT_NAME                  = 6,
+    SFNT_NAME_TRADEMARK_NOTICE                 = 7,
+    SFNT_NAME_MANUFACTURER_NAME                        = 8,
+    SFNT_NAME_DESIGNER                         = 9,
+    SFNT_NAME_DESCRIPTION                      = 10,
+    SFNT_NAME_FONT_VENDOR_URL                  = 11,
+    SFNT_NAME_FONT_DESIGNER_URL                        = 12,
+    SFNT_NAME_LICENSE_DESCRIPTION              = 13,
+    SFNT_NAME_LICENSE_INFORMATION_URL          = 14,
+    SFNT_NAME_PREFERRED_FAMILY                 = 16,
+    SFNT_NAME_PREFERRED_SUBFAMILY              = 17,
+    SFNT_NAME_COMPATIBLE_FULL                  = 18,
+    SFNT_NAME_SAMPLE_TEXT                      = 19,
+    SFNT_NAME_VARIATIONS_POSTSCRIPT_NAME_PREFIX = 25,
+  };
+
+struct sfnt_meta_data_map
+{
+  /* Identifier for the tag.  */
+  uint32_t tag;
+
+  /* Offset from start of table to data.  */
+  uint32_t data_offset;
+
+  /* Length of the data.  */
+  uint32_t data_length;
+};
+
+struct sfnt_meta_table
+{
+  /* Version of the table.  Currently set to 1.  */
+  uint32_t version;
+
+  /* Flags.  Currently 0.  */
+  uint32_t flags;
+
+  /* Offset from start of table to beginning of variable length
+     data.  */
+  uint32_t data_offset;
+
+  /* Number of data maps in the table.  */
+  uint32_t num_data_maps;
+
+  /* Beginning of variable length data.  */
+  struct sfnt_meta_data_map *data_maps;
+
+  /* The whole table contents.  */
+  unsigned char *data;
+};
+
+enum sfnt_meta_data_tag
+  {
+    SFNT_META_DATA_TAG_DLNG = 0x646c6e67,
+    SFNT_META_DATA_TAG_SLNG = 0x736c6e67,
+  };
+
+
+
+/* TrueType collection format support.  */
+
+struct sfnt_ttc_header
+{
+  /* TrueType collection ID tag.  */
+  uint32_t ttctag;
+
+  /* Version of the TTC header.  */
+  uint32_t version;
+
+  /* Number of fonts in the TTC header.  */
+  uint32_t num_fonts;
+
+  /* Array of offsets to the offset table for each font in the
+     file.  */
+  uint32_t *offset_table;
+
+  /* Tag indicating that a DSIG table exists, or 0.  Fields from here
+     on are only set on version 2.0 headers or later.  */
+  uint32_t ul_dsig_tag;
+
+  /* Length in bytes of the signature table, or 0 if there is no
+     signature.  */
+  uint32_t ul_dsig_length;
+
+  /* Offset in bytes of the dsig table from the beginning of the TTC
+     file.  */
+  uint32_t ul_dsig_offset;
+};
+
+enum sfnt_ttc_tag
+  {
+    SFNT_TTC_TTCF = 0x74746366,
+    SFNT_TTC_DSIG = 0x44534947,
+  };
+
+
+
+/* Unicode Variation Sequence (UVS) support.  */
+
+struct sfnt_default_uvs_table
+{
+  /* Number of ranges that follow.  */
+  uint32_t num_unicode_value_ranges;
+
+  /* Variable length data.  */
+  struct sfnt_unicode_value_range *ranges;
+};
+
+struct sfnt_unicode_value_range
+{
+  /* First value in this range.  */
+  unsigned int start_unicode_value;
+
+  /* Number of additional values in this range.  */
+  unsigned char additional_count;
+};
+
+struct sfnt_nondefault_uvs_table
+{
+  /* Number of UVS mappings which follow.  */
+  uint32_t num_uvs_mappings;
+
+  /* Variable length data.  */
+  struct sfnt_uvs_mapping *mappings;
+};
+
+struct sfnt_uvs_mapping
+{
+  /* Base character value.  */
+  unsigned int unicode_value;
+
+  /* Glyph ID of the base character value.  */
+  uint16_t base_character_value;
+};
+
+struct sfnt_mapped_variation_selector_record
+{
+  /* The variation selector.  */
+  unsigned int selector;
+
+  /* Its default UVS table.  */
+  struct sfnt_default_uvs_table *default_uvs;
+
+  /* Its nondefault UVS table.  */
+  struct sfnt_nondefault_uvs_table *nondefault_uvs;
+};
+
+/* Structure describing a single offset to load into a variation
+   selection context.  */
+
+struct sfnt_table_offset_rec
+{
+  /* The offset from the start of the font file.  */
+  off_t offset;
+
+  /* Whether or not the offset points to a non-default UVS table.  */
+  bool is_nondefault_table;
+
+  /* Pointer to the UVS table.  */
+  void *table;
+};
+
+struct sfnt_uvs_context
+{
+  /* Number of records and tables.  */
+  size_t num_records, nmemb;
+
+  /* Array of UVS tables.  */
+  struct sfnt_table_offset_rec *tables;
+
+  /* Array of variation selector records mapped to
+     their corresponding tables.  */
+  struct sfnt_mapped_variation_selector_record *records;
+};
+
+
+
+#if defined HAVE_MMAP && !defined TEST
+
+/* Memory mapping support.  */
+
+struct sfnt_mapped_table
+{
+  /* Pointer to table data.  */
+  void *data;
+
+  /* Pointer to table mapping.  */
+  void *mapping;
+
+  /* Size of mapped data and size of mapping.  */
+  size_t length, size;
+};
+
+#endif /* HAVE_MMAP && !TEST */
+
+
+
+/* Glyph variation support.  */
+
+/* 2.14 fixed point type used to represent versors of unit
+   vectors.  */
+typedef int16_t sfnt_f2dot14;
+
+/* Forward declaration used only for the distortable font stuff.  */
+struct sfnt_cvt_table;
+
+struct sfnt_variation_axis
+{
+  /* The axis tag.  */
+  uint32_t axis_tag;
+
+  /* The minimum style coordinate for the axis.  */
+  sfnt_fixed min_value;
+
+  /* The default style coordinate for the axis.  */
+  sfnt_fixed default_value;
+
+  /* The maximum style coordinate for the axis.  */
+  sfnt_fixed max_value;
+
+  /* Set to zero.  */
+  uint16_t flags;
+
+  /* Identifier under which this axis's name will be found in the
+     `name' table.  */
+  uint16_t name_id;
+};
+
+struct sfnt_instance
+{
+  /* The instance name ID.  */
+  uint16_t name_id;
+
+  /* Flags.  */
+  uint16_t flags;
+
+  /* Optional PostScript name.  */
+  uint16_t ps_name_id;
+
+  /* Coordinates of each defined instance.  */
+  sfnt_fixed *coords;
+};
+
+struct sfnt_fvar_table
+{
+  /* Major version; should be 1.  */
+  uint16_t major_version;
+
+  /* Minor version; should be 0.  */
+  uint16_t minor_version;
+
+  /* Offset in bytes from the beginning of the table to the beginning
+     of the first axis data.  */
+  uint16_t offset_to_data;
+
+  /* Reserved field; always 2.  */
+  uint16_t count_size_pairs;
+
+  /* Number of style axes in this font.  */
+  uint16_t axis_count;
+
+  /* The number of bytes in each variation axis record.  Currently 20
+     bytes.  */
+  uint16_t axis_size;
+
+  /* The number of named instances for the font found in the
+     instance array.  */
+  uint16_t instance_count;
+
+  /* The size of each instance record.  */
+  uint16_t instance_size;
+
+  /* Variable length data.  */
+  struct sfnt_variation_axis *axis;
+  struct sfnt_instance *instance;
+};
+
+struct sfnt_short_frac_correspondence
+{
+  /* Value in normalized user space.  */
+  sfnt_f2dot14 from_coord;
+
+  /* Value in normalized axis space.  */
+  sfnt_f2dot14 to_coord;
+};
+
+struct sfnt_short_frac_segment
+{
+  /* The number of pairs for this axis.  */
+  uint16_t pair_count;
+
+  /* Variable length data.  */
+  struct sfnt_short_frac_correspondence *correspondence;
+};
+
+struct sfnt_avar_table
+{
+  /* The version of the table.  Should be 1.0.  */
+  sfnt_fixed version;
+
+  /* Number of variation axes defined in this table.
+     XXX: why is this signed? */
+  int32_t axis_count;
+
+  /* Variable length data.  */
+  struct sfnt_short_frac_segment *segments;
+};
+
+struct sfnt_tuple_variation
+{
+  /* Tuple point numbers.  */
+  uint16_t *points;
+
+  /* Deltas.  */
+  sfnt_fword *deltas;
+
+  /* Tuple coordinates.  One for each axis specified in the [gaf]var
+     tables.  */
+  sfnt_f2dot14 *coordinates;
+
+  /* Intermediate start and end coordinates.  */
+  sfnt_f2dot14 *restrict intermediate_start;
+
+  /* Intermediate start and end coordinates.  */
+  sfnt_f2dot14 *restrict intermediate_end;
+
+  /* The number of points and deltas present.
+
+     UINT16_MAX and POINTS set to NULL means there are deltas for each
+     CVT entry.  */
+  uint16_t num_points;
+};
+
+struct sfnt_cvar_table
+{
+  /* The version of this CVT variations table.  */
+  sfnt_fixed version;
+
+  /* Flags.  */
+  uint16_t tuple_count;
+
+  /* Offset from the beginning of the table to the tuple data.  */
+  uint16_t data_offset;
+
+  /* Variable length data.  */
+  struct sfnt_tuple_variation *variation;
+};
+
+struct sfnt_gvar_table
+{
+  /* Version of the glyph variations table.  */
+  uint16_t version;
+
+  /* Reserved, currently 0.  */
+  uint16_t reserved;
+
+  /* The number of style axes for this font.  This must be the same
+     number as axisCount in the 'fvar' table.  */
+  uint16_t axis_count;
+
+  /* The number of shared coordinates.  */
+  uint16_t shared_coord_count;
+
+  /* Byte offset from the beginning of this table to the list of
+     shared style coordinates.  */
+  uint32_t offset_to_coord;
+
+  /* The number of glyphs in this font; this should match the number
+     of the glyphs store elsewhere in the font.  */
+  uint16_t glyph_count;
+
+  /* Bit-field that gives the format of the offset array that
+     follows. If the flag is 0, the type is uint16. If the flag is 1,
+     the type is unit 32.  */
+  uint16_t flags;
+
+  /* Byte offset from the beginning of this table to the first glyph
+     glyphVariationData.  */
+  uint32_t offset_to_data;
+
+  /* Number of bytes in the glyph variation data.  */
+  size_t data_size;
+
+  /* Byte offsets from the beginning of the glyphVariationData array
+     to the glyphVariationData for each glyph in the font.  The format
+     of this field is set by the flags field.  */
+  union {
+    uint16_t *offset_word;
+    uint32_t *offset_long;
+  } u;
+
+  /* Other variable length data.  */
+  sfnt_f2dot14 *global_coords;
+  unsigned char *glyph_variation_data;
+};
+
+/* Structure repesenting a set of axis coordinates and their
+   normalized equivalents.
+
+   To use this structure, call
+
+     sfnt_init_blend (&blend, fvar, gvar)
+
+   on a `struct sfnt_blend *', with an appropriate fvar and gvar
+   table.
+
+   Then, fill in blend.coords with the un-normalized coordinates,
+   and call
+
+     sfnt_normalize_blend (&blend)
+
+   finally, call sfnt_vary_simple_glyph and related functions.  */
+
+struct sfnt_blend
+{
+  /* The fvar table.  This determines the number of elements in each
+     of the arrays below.  */
+  struct sfnt_fvar_table *fvar;
+
+  /* The gvar table.  This provides the glyph variation data.  */
+  struct sfnt_gvar_table *gvar;
+
+  /* The avar table.  This provides adjustments to normalized axis
+     values, and may be NULL.  */
+  struct sfnt_avar_table *avar;
+
+  /* The cvar table.  This provides adjustments to CVT values, and may
+     be NULL.  */
+  struct sfnt_cvar_table *cvar;
+
+  /* Un-normalized coordinates.  */
+  sfnt_fixed *coords;
+
+  /* Normalized coordinates.  */
+  sfnt_fixed *norm_coords;
+};
+
+struct sfnt_metrics_distortion
+{
+  /* Distortion applied to the origin point.  */
+  sfnt_fword origin;
+
+  /* Distortion applied to the advance point.  */
+  sfnt_fword advance;
+};
+
+
+
+#define SFNT_CEIL_FIXED(fixed) (((fixed) + 0177777) & 037777600000)
+#define SFNT_FLOOR_FIXED(fixed) ((fixed) & 037777600000)
+
+
+
+/* Function declarations.  Keep these sorted by the order in which
+   they appear in sfnt.c.  Keep each line no longer than 80
+   columns.  */
+
+#ifndef TEST
+
+extern struct sfnt_offset_subtable *sfnt_read_table_directory (int);
+
+#define PROTOTYPE                              \
+  int, struct sfnt_offset_subtable *,          \
+  struct sfnt_cmap_encoding_subtable **,       \
+  struct sfnt_cmap_encoding_subtable_data ***
+extern struct sfnt_cmap_table *sfnt_read_cmap_table (PROTOTYPE);
+#undef PROTOTYPE
+
+extern sfnt_glyph sfnt_lookup_glyph (sfnt_char,
+                                    struct sfnt_cmap_encoding_subtable_data *);
+
+#define PROTOTYPE int, struct sfnt_offset_subtable *
+extern struct sfnt_head_table *sfnt_read_head_table (PROTOTYPE);
+extern struct sfnt_hhea_table *sfnt_read_hhea_table (PROTOTYPE);
+extern struct sfnt_loca_table_short *sfnt_read_loca_table_short (PROTOTYPE);
+extern struct sfnt_loca_table_long *sfnt_read_loca_table_long (PROTOTYPE);
+extern struct sfnt_maxp_table *sfnt_read_maxp_table (PROTOTYPE);
+extern struct sfnt_glyf_table *sfnt_read_glyf_table (PROTOTYPE);
+
+#ifdef HAVE_MMAP
+extern struct sfnt_glyf_table *sfnt_map_glyf_table (PROTOTYPE);
+extern int sfnt_unmap_glyf_table (struct sfnt_glyf_table *);
+#endif /* HAVE_MMAP */
+#undef PROTOTYPE
+
+extern struct sfnt_glyph *sfnt_read_glyph (sfnt_glyph, struct sfnt_glyf_table 
*,
+                                          struct sfnt_loca_table_short *,
+                                          struct sfnt_loca_table_long *);
+extern void sfnt_free_glyph (struct sfnt_glyph *);
+
+#define PROTOTYPE              \
+  struct sfnt_glyph *,         \
+  sfnt_fixed,                  \
+  struct sfnt_glyph_metrics *, \
+  sfnt_get_glyph_proc,         \
+  sfnt_free_glyph_proc,                \
+  sfnt_get_metrics_proc,       \
+  void *
+extern struct sfnt_glyph_outline *sfnt_build_glyph_outline (PROTOTYPE);
+#undef PROTOTYPE
+
+extern void sfnt_prepare_raster (struct sfnt_raster *,
+                                struct sfnt_glyph_outline *);
+
+#define PROTOTYPE struct sfnt_glyph_outline *
+extern struct sfnt_raster *sfnt_raster_glyph_outline (PROTOTYPE);
+#undef PROTOTYPE
+
+#define PROTOTYPE                      \
+  int,                                 \
+  struct sfnt_offset_subtable *,       \
+  struct sfnt_hhea_table *,            \
+  struct sfnt_maxp_table *
+extern struct sfnt_hmtx_table *sfnt_read_hmtx_table (PROTOTYPE);
+#undef PROTOTYPE
+
+extern int sfnt_lookup_glyph_metrics (sfnt_glyph, int,
+                                     struct sfnt_glyph_metrics *,
+                                     struct sfnt_hmtx_table *,
+                                     struct sfnt_hhea_table *,
+                                     struct sfnt_head_table *,
+                                     struct sfnt_maxp_table *);
+
+extern void sfnt_scale_metrics (struct sfnt_glyph_metrics *,
+                               sfnt_fixed);
+extern sfnt_fixed sfnt_get_scale (struct sfnt_head_table *, int);
+
+#define PROTOTYPE int, struct sfnt_offset_subtable *
+extern struct sfnt_name_table *sfnt_read_name_table (PROTOTYPE);
+#undef PROTOTYPE
+
+extern unsigned char *sfnt_find_name (struct sfnt_name_table *,
+                                     enum sfnt_name_identifier_code,
+                                     struct sfnt_name_record *);
+
+#define PROTOTYPE int, struct sfnt_offset_subtable *
+extern struct sfnt_meta_table *sfnt_read_meta_table (PROTOTYPE);
+#undef PROTOTYPE
+
+extern char *sfnt_find_metadata (struct sfnt_meta_table *,
+                                enum sfnt_meta_data_tag,
+                                struct sfnt_meta_data_map *);
+
+extern struct sfnt_ttc_header *sfnt_read_ttc_header (int);
+
+
+
+#define PROTOTYPE struct sfnt_cmap_format_14 *, int
+
+extern struct sfnt_uvs_context *sfnt_create_uvs_context (PROTOTYPE);
+
+#undef PROTOTYPE
+
+extern void sfnt_free_uvs_context (struct sfnt_uvs_context *);
+
+#define PROTOTYPE struct sfnt_nondefault_uvs_table *, sfnt_char
+
+extern sfnt_glyph sfnt_variation_glyph_for_char (PROTOTYPE);
+
+#undef PROTOTYPE
+
+
+
+#ifdef HAVE_MMAP
+
+extern int sfnt_map_table (int, struct sfnt_offset_subtable *,
+                          uint32_t, struct sfnt_mapped_table *);
+extern int sfnt_unmap_table (struct sfnt_mapped_table *);
+
+#endif /* HAVE_MMAP */
+
+
+
+extern void *sfnt_read_table (int, struct sfnt_offset_subtable *,
+                             uint32_t, size_t *);
+
+
+
+#define PROTOTYPE int, struct sfnt_offset_subtable *
+
+extern struct sfnt_fvar_table *sfnt_read_fvar_table (PROTOTYPE);
+extern struct sfnt_gvar_table *sfnt_read_gvar_table (PROTOTYPE);
+extern struct sfnt_avar_table *sfnt_read_avar_table (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE                              \
+  int,                                         \
+  struct sfnt_offset_subtable *,               \
+  struct sfnt_fvar_table *,                    \
+  struct sfnt_cvt_table *
+
+extern struct sfnt_cvar_table *sfnt_read_cvar_table (PROTOTYPE);
+
+#undef PROTOTYPE
+
+
+
+extern void sfnt_init_blend (struct sfnt_blend *,
+                            struct sfnt_fvar_table *,
+                            struct sfnt_gvar_table *,
+                            struct sfnt_avar_table *,
+                            struct sfnt_cvar_table *);
+extern void sfnt_free_blend (struct sfnt_blend *);
+extern void sfnt_normalize_blend (struct sfnt_blend *);
+
+
+
+extern int sfnt_vary_simple_glyph (struct sfnt_blend *, sfnt_glyph,
+                                  struct sfnt_glyph *,
+                                  struct sfnt_metrics_distortion *);
+extern int sfnt_vary_compound_glyph (struct sfnt_blend *, sfnt_glyph,
+                                    struct sfnt_glyph *,
+                                    struct sfnt_metrics_distortion *);
+
+#endif /* TEST */
+
+
+
+/* TrueType hinting support.  */
+
+/* Structure definitions for tables used by the TrueType
+   interpreter.  */
+
+struct sfnt_cvt_table
+{
+  /* Number of elements in the control value table.  */
+  size_t num_elements;
+
+  /* Pointer to elements in the control value table.  */
+  sfnt_fword *values;
+};
+
+struct sfnt_fpgm_table
+{
+  /* Number of instructions in the font program table.  */
+  size_t num_instructions;
+
+  /* Pointer to elements in the font program table.  */
+  unsigned char *instructions;
+};
+
+struct sfnt_prep_table
+{
+  /* Number of instructions in the control value program (pre-program)
+     table.  */
+  size_t num_instructions;
+
+  /* Pointer to elements in the preprogram table.  */
+  unsigned char *instructions;
+};
+
+
+
+/* Fixed point types used by the TrueType interpreter.  */
+
+/* 26.6 fixed point type used within the interpreter.  */
+typedef int32_t sfnt_f26dot6;
+
+/* 18.14 fixed point type used to calculate rounding details.  */
+typedef int32_t sfnt_f18dot14;
+
+
+
+/* Interpreter execution environment.  */
+
+struct sfnt_unit_vector
+{
+  /* X and Y versors of the 2d unit vector.  */
+  sfnt_f2dot14 x, y;
+};
+
+struct sfnt_interpreter_definition
+{
+  /* The opcode of this instruction or function.  */
+  uint16_t opcode;
+
+  /* The number of instructions.  */
+  uint16_t instruction_count;
+
+  /* Pointer to instructions belonging to the definition.  This
+     pointer points directly into the control value or font program.
+     Make sure both programs are kept around as long as the
+     interpreter continues to exist.  */
+  unsigned char *instructions;
+};
+
+/* This structure represents a ``struct sfnt_glyph'' that has been
+   scaled to a given pixel size.
+
+   It can either contain a simple glyph, or a decomposed compound
+   glyph; instructions are interpreted for both simple glyphs, simple
+   glyph components inside a compound glyph, and compound glyphs as a
+   whole.
+
+   In addition to the glyph data itself, it also records various
+   information for the instruction interpretation process:
+
+     - ``current'' point coordinates, which have been modified
+       by the instructing process.
+
+     - two phantom points at the origin and the advance of the
+       glyph.  */
+
+struct sfnt_interpreter_zone
+{
+  /* The number of points in this zone, including the two phantom
+     points at the end.  */
+  size_t num_points;
+
+  /* The number of contours in this zone.  */
+  size_t num_contours;
+
+  /* The end points of each contour.  */
+  size_t *contour_end_points;
+
+  /* Pointer to the X axis point data.  */
+  sfnt_f26dot6 *restrict x_points;
+
+  /* Pointer to the X axis current point data.  */
+  sfnt_f26dot6 *restrict x_current;
+
+  /* Pointer to the Y axis point data.  */
+  sfnt_f26dot6 *restrict y_points;
+
+  /* Pointer to the Y axis current point data.  */
+  sfnt_f26dot6 *restrict y_current;
+
+  /* Pointer to the flags associated with this data.  */
+  unsigned char *flags;
+};
+
+enum
+  {
+    /* Bits 1 stands for X_SHORT_VECTOR on disk and in the tables, but
+       this representation is not useful in memory.  Inside an
+       instructed glyph, this bit is repurposed to mean that the
+       corresponding point is a phantom point.  */
+    SFNT_POINT_PHANTOM = (1 << 1),
+    /* Bits 7 and 6 of a glyph point's flags is reserved.  This scaler
+       uses it to mean that the point has been touched in one axis or
+       another.  */
+    SFNT_POINT_TOUCHED_X = (1 << 7),
+    SFNT_POINT_TOUCHED_Y = (1 << 6),
+    SFNT_POINT_TOUCHED_BOTH = (SFNT_POINT_TOUCHED_X
+                              | SFNT_POINT_TOUCHED_Y),
+  };
+
+/* This is needed because `round' below needs an interpreter
+   argument.  */
+struct sfnt_interpreter;
+
+struct sfnt_graphics_state
+{
+  /* Pointer to the function used for rounding.  This function is
+     asymmetric, so -0.5 rounds up to 0, not -1.  It is up to the
+     caller to handle negative values.
+
+     Value is undefined unless sfnt_validate_gs has been called, and
+     the second argument may be used to provide detailed rounding
+     information (``super rounding state''.)  */
+  sfnt_f26dot6 (*round) (sfnt_f26dot6, struct sfnt_interpreter *);
+
+  /* Pointer to the function used to project euclidean vectors onto
+     the projection vector.  Value is the magnitude of the projected
+     vector.  */
+  sfnt_f26dot6 (*project) (sfnt_f26dot6, sfnt_f26dot6,
+                          struct sfnt_interpreter *);
+
+  /* Pointer to the function used to project euclidean vectors onto
+     the dual projection vector.  Value is the magnitude of the
+     projected vector.  */
+  sfnt_f26dot6 (*dual_project) (sfnt_f26dot6, sfnt_f26dot6,
+                               struct sfnt_interpreter *);
+
+  /* Pointer to the function used to move specified points
+     along the freedom vector by a distance specified in terms
+     of the projection vector.  */
+  void (*move) (sfnt_f26dot6 *restrict,
+               sfnt_f26dot6 *restrict, size_t,
+               struct sfnt_interpreter *,
+               sfnt_f26dot6, unsigned char *);
+
+  /* Dot product between the freedom and the projection vectors.  */
+  sfnt_f2dot14 vector_dot_product;
+
+  /* Controls whether the sign of control value table entries will be
+     changed to match the sign of the actual distance measurement with
+     which it is compared.  Setting auto flip to TRUE makes it
+     possible to control distances measured with or against the
+     projection vector with a single control value table entry. When
+     auto flip is set to FALSE, distances must be measured with the
+     projection vector.  */
+  bool auto_flip;
+
+  /* Limits the regularizing effects of control value table entries to
+     cases where the difference between the table value and the
+     measurement taken from the original outline is sufficiently
+     small.  */
+  sfnt_f26dot6 cvt_cut_in;
+
+  /* Establishes the base value used to calculate the range of point
+     sizes to which a given DELTAC[] or DELTAP[] instruction will
+     apply.  The formulas given below are used to calculate the range
+     of the various DELTA instructions.
+
+     DELTAC1 DELTAP1 (delta_base) through (delta_base + 15)
+     DELTAC2 DELTAP2 (delta_base + 16) through (delta_base + 31)
+     DELTAC3 DELTAP3 (delta_base + 32) through (delta_base + 47)
+
+     Please keep this documentation in sync with the TrueType
+     reference manual.  */
+  unsigned short delta_base;
+
+  /* Determines the range of movement and smallest magnitude of
+     movement (the step) in a DELTAC[] or DELTAP[] instruction.
+     Changing the value of the delta shift makes it possible to trade
+     off fine control of point movement for range of movement.  A low
+     delta shift favors range of movement over fine control.  A high
+     delta shift favors fine control over range of movement.  The step
+     has the value 1/2 to the power delta shift.  The range of
+     movement is calculated by taking the number of steps allowed (16)
+     and multiplying it by the step.
+
+     The legal range for delta shift is zero through six.  Negative
+     values are illegal.  */
+  unsigned short delta_shift;
+
+  /* A second projection vector set to a line defined by the original
+     outline location of two points.  The dual projection vector is
+     used when it is necessary to measure distances from the scaled
+     outline before any instructions were executed.  */
+  struct sfnt_unit_vector dual_projection_vector;
+
+  /* A unit vector that establishes an axis along which points can
+     move.  */
+  struct sfnt_unit_vector freedom_vector;
+
+  /* Makes it possible to turn off instructions under some
+     circumstances.  When flag 1 is set, changes to the graphics state
+     made in the control value program will be ignored.  When flag is
+     1, grid fitting instructions will be ignored.  */
+  unsigned char instruct_control;
+
+  /* Makes it possible to repeat certain instructions a designated
+     number of times.  The default value of one assures that unless
+     the value of loop is altered, these instructions will execute one
+     time.  */
+  unsigned short loop;
+
+  /* Establishes the smallest possible value to which a distance will
+     be rounded.  */
+  sfnt_f26dot6 minimum_distance;
+
+  /* A unit vector whose direction establishes an axis along which
+     distances are measured.  */
+  struct sfnt_unit_vector projection_vector;
+
+  /* Determines the manner in which values are rounded. Can be set to
+     a number of predefined states or to a customized state with the
+     SROUND or S45ROUND instructions.  */
+  int round_state;
+
+  /* Reference points.  These reference point numbers, which together
+     with a zone designation, specify a point in either the glyph zone
+     or the twilight zone.  */
+  uint16_t rp0, rp1, rp2;
+
+  /* Flags which determine whether the interpreter will activate
+     dropout control for the current glyph.  */
+  int scan_control;
+
+  /* The distance difference below which the interpreter will replace
+     a CVT distance or an actual distance in favor of the single width
+     value.  */
+  sfnt_f26dot6 sw_cut_in;
+
+  /* The value used in place of the control value table distance or
+     the actual distance value when the difference between that
+     distance and the single width value is less than the single width
+     cut-in.  */
+  sfnt_f26dot6 single_width_value;
+
+  /* Zone pointers, which reference a zone.  */
+  int zp0, zp1, zp2;
+};
+
+struct sfnt_interpreter
+{
+  /* The number of elements in the stack.  */
+  uint16_t max_stack_elements;
+
+  /* The number of instructions in INSTRUCTIONS.  */
+  uint16_t num_instructions;
+
+  /* Size of the storage area.  */
+  uint16_t storage_size;
+
+  /* Size of the function definition area.  */
+  uint16_t function_defs_size;
+
+  /* Size of the instruction definition area.  */
+  uint16_t instruction_defs_size;
+
+  /* Size of the twilight zone.  */
+  uint16_t twilight_zone_size;
+
+  /* The instruction pointer.  This points to the instruction
+     currently being executed.  */
+  int IP;
+
+  /* The current scale.  */
+  sfnt_fixed scale;
+
+  /* The current ppem and point size.  */
+  int ppem, point_size;
+
+  /* The execution stack.  This has at most max_stack_elements
+     elements.  */
+  uint32_t *stack;
+
+  /* Pointer past the top of the stack.  */
+  uint32_t *SP;
+
+  /* The size of the control value table.  */
+  size_t cvt_size;
+
+  /* Pointer to instructions currently being executed.  */
+  unsigned char *restrict instructions;
+
+  /* The twilight zone.  May not be NULL.  */
+  sfnt_f26dot6 *restrict twilight_x, *restrict twilight_y;
+
+  /* The original X positions of points in the twilight zone.  */
+  sfnt_f26dot6 *restrict twilight_original_x;
+
+  /* The original Y positions of points in the twilight zone.
+
+     Apple does not directly say whether or not points in the twilight
+     zone can have their original positions changed.  But this is
+     implied by ``create points in the twilight zone''.  */
+  sfnt_f26dot6 *restrict twilight_original_y;
+
+  /* The scaled outlines being manipulated.  May be NULL.  */
+  struct sfnt_interpreter_zone *glyph_zone;
+
+  /* The glyph advance width.  Value is undefined unless GLYPH_ZONE is
+     set.  */
+  sfnt_f26dot6 advance_width;
+
+  /* The storage area.  */
+  uint32_t *storage;
+
+  /* Control value table values.  */
+  sfnt_f26dot6 *cvt;
+
+  /* Function definitions.  */
+  struct sfnt_interpreter_definition *function_defs;
+
+  /* Instruction definitions.  */
+  struct sfnt_interpreter_definition *instruction_defs;
+
+  /* Interpreter registers.  */
+  struct sfnt_graphics_state state;
+
+  /* Detailed rounding state used when state.round_state indicates
+     that fine grained rounding should be used.
+
+     PERIOD says how often a round value occurs, for numbers
+     increasing from PHASE to infinity.
+
+     THRESHOLD says when to round a value between two increasing
+     periods towards the larger period.  */
+  sfnt_f26dot6 period, phase, threshold;
+
+  /* The depth of any ongoing calls.  */
+  int call_depth;
+
+  /* Jump buffer for traps.  */
+  jmp_buf trap;
+
+  /* What was the trap.  */
+  const char *trap_reason;
+
+  /* Number of variation axes provided by this distortable font.  */
+  int n_axis;
+
+  /* Normalized axis coordinates set for this distortable font.  */
+  sfnt_fixed *norm_coords;
+
+#ifdef TEST
+  /* If non-NULL, function called before each instruction is
+     executed.  */
+  void (*run_hook) (struct sfnt_interpreter *);
+
+  /* If non-NULL, function called before each stack element is
+     pushed.  */
+  void (*push_hook) (struct sfnt_interpreter *, uint32_t);
+
+  /* If non-NULL, function called before each stack element is
+     popped.  */
+  void (*pop_hook) (struct sfnt_interpreter *, uint32_t);
+#endif
+};
+
+
+
+/* Glyph hinting.  */
+
+/* Structure describing a single scaled and fitted outline.  */
+
+struct sfnt_instructed_outline
+{
+  /* The number of points in this contour, including the two phantom
+     points at the end.  */
+  size_t num_points;
+
+  /* The number of contours in this outline.  */
+  size_t num_contours;
+
+  /* The end points of each contour.  */
+  size_t *contour_end_points;
+
+  /* The points of each contour, with two additional phantom points at
+     the end.  */
+  sfnt_f26dot6 *restrict x_points, *restrict y_points;
+
+  /* The flags of each point.  */
+  unsigned char *flags;
+};
+
+
+
+/* Functions used to read tables used by the TrueType interpreter.  */
+
+#ifndef TEST
+
+#define PROTOTYPE int, struct sfnt_offset_subtable *
+
+extern struct sfnt_cvt_table *sfnt_read_cvt_table (PROTOTYPE);
+extern struct sfnt_fpgm_table *sfnt_read_fpgm_table (PROTOTYPE);
+extern struct sfnt_prep_table *sfnt_read_prep_table (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE                              \
+  struct sfnt_maxp_table *,                    \
+  struct sfnt_cvt_table *,                     \
+  struct sfnt_head_table *,                    \
+  struct sfnt_fvar_table *,                    \
+  int, int
+
+extern struct sfnt_interpreter *sfnt_make_interpreter (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE                              \
+  struct sfnt_interpreter *,                   \
+  struct sfnt_fpgm_table *
+
+extern const char *sfnt_interpret_font_program (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE                              \
+  struct sfnt_interpreter *,                   \
+  struct sfnt_prep_table *,                    \
+  struct sfnt_graphics_state *
+
+extern const char *sfnt_interpret_control_value_program (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE struct sfnt_instructed_outline *
+
+extern struct sfnt_glyph_outline *sfnt_build_instructed_outline (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE                              \
+  struct sfnt_glyph *,                         \
+  struct sfnt_interpreter *,                   \
+  struct sfnt_glyph_metrics *,                 \
+  struct sfnt_instructed_outline **
+
+extern const char *sfnt_interpret_simple_glyph (PROTOTYPE);
+
+#undef PROTOTYPE
+
+#define PROTOTYPE                              \
+  struct sfnt_glyph *,                         \
+  struct sfnt_interpreter *,                   \
+  struct sfnt_graphics_state *,                        \
+  sfnt_get_glyph_proc,                         \
+  sfnt_free_glyph_proc,                                \
+  struct sfnt_hmtx_table *,                    \
+  struct sfnt_hhea_table *,                    \
+  struct sfnt_maxp_table *,                    \
+  struct sfnt_glyph_metrics *,                 \
+  void *,                                      \
+  struct sfnt_instructed_outline **
+
+extern const char *sfnt_interpret_compound_glyph (PROTOTYPE);
+
+#undef PROTOTYPE
+
+
+
+extern void sfnt_vary_interpreter (struct sfnt_interpreter *,
+                                  struct sfnt_blend *);
+
+#endif /* TEST */
+
+
+
+#endif /* _SFNT_H_ */
diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c
new file mode 100644
index 00000000000..53589078cda
--- /dev/null
+++ b/src/sfntfont-android.c
@@ -0,0 +1,792 @@
+/* sfnt format font driver for Android.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef __aarch64__
+#include <arm_neon.h>
+#endif
+
+#include <android/api-level.h>
+#include <android/log.h>
+
+#include "androidterm.h"
+#include "sfntfont.h"
+#include "pdumper.h"
+#include "blockinput.h"
+#include "android.h"
+
+/* Structure describing a temporary buffer.  */
+
+struct sfntfont_android_scanline_buffer
+{
+  /* Size of this buffer.  */
+  size_t buffer_size;
+
+  /* Pointer to the buffer data.  */
+  void *buffer_data;
+};
+
+/* Array of directories to search for system fonts.  */
+static char *system_font_directories[] =
+  {
+    (char *) "/system/fonts",
+    (char *) "/product/fonts",
+    /* This should be filled in by init_sfntfont_android.  */
+    (char[PATH_MAX]) { },
+  };
+
+/* The font cache.  */
+static Lisp_Object font_cache;
+
+/* The scanline buffer.  */
+static struct sfntfont_android_scanline_buffer scanline_buffer;
+
+/* The largest size of the scanline buffer since the last window
+   update.  */
+static size_t max_scanline_buffer_size;
+
+
+
+/* Return a temporary buffer for storing scan lines.
+   Set BUFFER to the buffer upon success.  */
+
+#ifndef __aarch64__
+
+#define GET_SCANLINE_BUFFER(buffer, height, stride)            \
+  do                                                           \
+    {                                                          \
+      size_t _size;                                            \
+                                                               \
+      if (INT_MULTIPLY_WRAPV (height, stride, &_size))         \
+       memory_full (SIZE_MAX);                                 \
+                                                               \
+      if (_size < MAX_ALLOCA)                                  \
+       (buffer) = alloca (_size);                              \
+      else                                                     \
+       {                                                       \
+         if (_size > scanline_buffer.buffer_size)              \
+           {                                                   \
+             (buffer)                                          \
+               = scanline_buffer.buffer_data                   \
+               = xrealloc (scanline_buffer.buffer_data,        \
+                           _size);                             \
+             scanline_buffer.buffer_size = _size;              \
+           }                                                   \
+         else if (_size <= scanline_buffer.buffer_size)        \
+           (buffer) = scanline_buffer.buffer_data;             \
+         /* This is unreachable but clang says it is.  */      \
+         else                                                  \
+           emacs_abort ();                                     \
+                                                               \
+         max_scanline_buffer_size                              \
+           = max (_size, max_scanline_buffer_size);            \
+       }                                                       \
+    } while (false);
+
+#else
+
+#define GET_SCANLINE_BUFFER(buffer, height, stride)            \
+  do                                                           \
+    {                                                          \
+      size_t _size;                                            \
+      void *_temp;                                             \
+                                                               \
+      if (INT_MULTIPLY_WRAPV (height, stride, &_size))         \
+       memory_full (SIZE_MAX);                                 \
+                                                               \
+      if (_size > scanline_buffer.buffer_size)                 \
+       {                                                       \
+         if (posix_memalign (&_temp, 16, _size))               \
+           memory_full (_size);                                \
+         free (scanline_buffer.buffer_data);                   \
+         (buffer)                                              \
+           = scanline_buffer.buffer_data                       \
+           = _temp;                                            \
+         scanline_buffer.buffer_size = _size;                  \
+       }                                                       \
+      else if (_size <= scanline_buffer.buffer_size)           \
+       (buffer) = scanline_buffer.buffer_data;                 \
+      /* This is unreachable but clang says it is.  */         \
+      else                                                     \
+       emacs_abort ();                                         \
+                                                               \
+      max_scanline_buffer_size                                 \
+      = max (_size, max_scanline_buffer_size);                 \
+    } while (false);
+
+#endif
+
+
+
+/* Scale each of the four packed bytes in P in the low 16 bits of P by
+   SCALE.  Return the result.
+
+   SCALE is an integer between 0 and 256.  */
+
+static unsigned int
+sfntfont_android_scale32 (unsigned int scale, unsigned int p)
+{
+  uint32_t ag, rb;
+  uint32_t scaled_ag, scaled_rb;
+
+  ag = (p & 0xFF00FF00) >> 8;
+  rb = (p & 0x00FF00FF);
+
+  scaled_ag = (scale * ag) & 0xFF00FF00;
+  scaled_rb = (scale * rb) >> 8 & 0x00FF00FF;
+
+  return scaled_ag | scaled_rb;
+}
+
+static unsigned int
+sfntfont_android_mul8x2 (unsigned int a8, unsigned int b32)
+{
+  unsigned int i;
+
+  b32 &= 0xff00ff;
+  i = a8 * b32 + 0x800080;
+
+  return (i + ((i >> 8) & 0xff00ff)) >> 8 & 0xff00ff;
+}
+
+#define U255TO256(x) ((unsigned short) (x) + ((x) >> 7))
+
+/* Blend two pixels SRC and DST without utilizing any control flow.
+   Both SRC and DST are expected to be in premultiplied ABGB8888
+   format.  Value is returned in premultiplied ARGB8888 format.  */
+
+static unsigned int
+sfntfont_android_blend (unsigned int src, unsigned int dst)
+{
+  unsigned int a, br_part, ag_part, both;
+
+  a = (src >> 24);
+  br_part = sfntfont_android_mul8x2 (255 - a, dst);
+  ag_part = sfntfont_android_mul8x2 (255 - a, dst >> 8) << 8;
+
+  both = ag_part | br_part;
+
+  /* This addition need not be saturating because both has already
+     been multiplied by 255 - a.  */
+  return both + src;
+}
+
+#ifdef __aarch64__
+
+/* Like U255TO256, but operates on vectors.  */
+
+static uint16x8_t
+sfntfont_android_u255to256 (uint8x8_t in)
+{
+  return vaddl_u8 (vshr_n_u8 (in, 7), in);
+}
+
+/* Use processor features to efficiently composite four pixels at SRC
+   to DST.  */
+
+static void
+sfntfont_android_over_8888_1 (unsigned int *src, unsigned int *dst)
+{
+  uint8x8_t alpha;
+  uint16x8_t alpha_c16, v1, v3, v4;
+  uint8x8_t b, g, r, a, v2, v5;
+  uint8x8x4_t _src, _dst;
+
+  /* Pull in src and dst.
+
+     This loads bytes, not words, so little endian ABGR becomes
+     RGBA.  */
+  _src = vld4_u8 ((const uint8_t *) src);
+  _dst = vld4_u8 ((const uint8_t *) dst);
+
+  /* Load constants.  */
+  v4 = vdupq_n_u16 (256);
+  v5 = vdup_n_u8 (0);
+
+  /* Load src alpha.  */
+  alpha = _src.val[3];
+
+  /* alpha_c16 = 256 - 255TO256 (alpha).  */
+  alpha_c16 = sfntfont_android_u255to256 (alpha);
+  alpha_c16 = vsubq_u16 (v4, alpha_c16);
+
+  /* Cout = Csrc + Cdst * alpha_c.  */
+  v1 = vaddl_u8 (_dst.val[2], v5);
+  v2 = _src.val[2];
+  v3 = vmulq_u16 (v1, alpha_c16);
+  b = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
+
+  v1 = vaddl_u8 (_dst.val[1], v5);
+  v2 = _src.val[1];
+  v3 = vmulq_u16 (v1, alpha_c16);
+  g = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
+
+  v1 = vaddl_u8 (_dst.val[0], v5);
+  v2 = _src.val[0];
+  v3 = vmulq_u16 (v1, alpha_c16);
+  r = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
+
+#if 0
+  /* Aout = Asrc + Adst * alpha_c.  */
+  v1 = vaddl_u8 (_dst.val[3], v5);
+  v2 = _src.val[3];
+  v3 = vmulq_u16 (v1, alpha_c16);
+  a = vqadd_u8 (v2, vshrn_n_u16 (v3, 8));
+#else
+  /* We know that Adst is always 1, so Asrc + Adst * (1 - Asrc) is
+     always 1.  */
+  a = vdup_n_u8 (255);
+#endif
+
+  /* Store back in dst.  */
+  _dst.val[0] = r;
+  _dst.val[1] = g;
+  _dst.val[2] = b;
+  _dst.val[3] = a;
+  vst4_u8 ((uint8_t *) dst, _dst);
+}
+
+/* Use processor features to efficiently composite the buffer at SRC
+   to DST.  Composite at most MAX - SRC words.
+
+   If either SRC or DST are not yet properly aligned, value is 1.
+   Otherwise, value is 0, and *X is incremented to the start of any
+   trailing data which could not be composited due to data alignment
+   constraints.  */
+
+static int
+sfntfont_android_over_8888 (unsigned int *src, unsigned int *dst,
+                           unsigned int *max, unsigned int *x)
+{
+  size_t i;
+  ptrdiff_t how_much;
+  void *s, *d;
+
+  /* Figure out how much can be composited by this loop.  */
+  how_much = (max - src) & ~7;
+
+  /* Return if there is not enough to vectorize.  */
+  if (!how_much)
+    return 1;
+
+  /* Now increment *X by that much so the containing loop can process
+     the remaining pixels one-by-one.  */
+
+  *x += how_much;
+
+  for (i = 0; i < how_much; i += 8)
+    {
+      s = (src + i);
+      d = (dst + i);
+
+      sfntfont_android_over_8888_1 (s, d);
+    }
+
+  return 0;
+}
+
+#endif
+
+/* Composite the bitmap described by BUFFER, STRIDE and TEXT_RECTANGLE
+   onto the native-endian ABGR8888 bitmap described by DEST and
+   BITMAP_INFO.  RECT is the subset of the bitmap to composite.  */
+
+static void
+sfntfont_android_composite_bitmap (unsigned char *restrict buffer,
+                                  size_t stride,
+                                  unsigned char *restrict dest,
+                                  AndroidBitmapInfo *bitmap_info,
+                                  struct android_rectangle *text_rectangle,
+                                  struct android_rectangle *rect)
+{
+  unsigned int *src_row;
+  unsigned int *dst_row;
+  unsigned int i, src_y, x, src_x, max_x, dst_x;
+#ifdef __aarch64__
+  unsigned int lim_x;
+#endif
+
+  if ((intptr_t) dest & 3 || bitmap_info->stride & 3)
+    /* This shouldn't be possible as Android is supposed to align the
+       bitmap to at least a 4 byte boundary.  */
+    emacs_abort ();
+  else
+    {
+      for (i = 0; i < rect->height; ++i)
+       {
+         if (i + rect->y >= bitmap_info->height)
+           /* Done.  */
+           return;
+
+         src_y = i + (rect->y - text_rectangle->y);
+
+         if (src_y > text_rectangle->height)
+           /* Huh? */
+           return;
+
+         src_row = (unsigned int *) ((buffer + src_y * stride));
+         dst_row = (unsigned int *) (dest + ((i + rect->y)
+                                             * bitmap_info->stride));
+
+         /* Figure out where the loop below should end.  */
+         max_x = min (rect->width, bitmap_info->width - rect->x);
+
+         /* Keep this loop simple! */
+         for (x = 0; x < max_x; ++x)
+           {
+             src_x = x + (rect->x - text_rectangle->x);
+             dst_x = x + rect->x;
+
+#ifdef __aarch64__
+             /* This is the largest value of src_x.  */
+             lim_x = max_x + (rect->x - text_rectangle->x);
+
+             if (!sfntfont_android_over_8888 (src_row + src_x,
+                                              dst_row + dst_x,
+                                              src_row + lim_x,
+                                              &x))
+               {
+                 /* Decrement X by one so the for loop can increment
+                    it again.  */
+                 x--;
+                 continue;
+               }
+#endif
+               dst_row[dst_x]
+                 = sfntfont_android_blend (src_row[src_x],
+                                           dst_row[dst_x]);
+           }
+       }
+    }
+}
+
+/* Calculate the union containing both A and B, both boxes.  Place the
+   result in RESULT.  */
+
+static void
+sfntfont_android_union_boxes (struct gui_box a, struct gui_box b,
+                             struct gui_box *result)
+{
+  result->x1 = min (a.x1, b.x1);
+  result->y1 = min (a.y1, b.y1);
+  result->x2 = max (a.x2, b.x2);
+  result->y2 = max (a.y2, b.y2);
+}
+
+/* Draw the specified glyph rasters from FROM to TO on behalf of S,
+   using S->gc.  Fill the background if WITH_BACKGROUND is true.
+
+   See init_sfntfont_vendor and sfntfont_draw for more details.  */
+
+static void
+sfntfont_android_put_glyphs (struct glyph_string *s, int from,
+                            int to, int x, int y, bool with_background,
+                            struct sfnt_raster **rasters,
+                            int *x_coords)
+{
+  struct android_rectangle background, text_rectangle, rect;
+  struct gui_box text, character;
+  unsigned int *buffer, *row;
+  unsigned char *restrict raster_row;
+  size_t stride, i;
+  AndroidBitmapInfo bitmap_info;
+  unsigned char *bitmap_data;
+  jobject bitmap;
+  int left, top, temp_y;
+  unsigned int prod, raster_y;
+  unsigned long foreground, back_pixel, rb;
+
+  if (!s->gc->num_clip_rects)
+    /* Clip region is empty.  */
+    return;
+
+  if (from == to)
+    /* Nothing to draw.  */
+    return;
+
+  /* Swizzle the foreground and background in s->gc into BGR, then add
+     an alpha channel.  */
+  foreground = s->gc->foreground;
+  back_pixel = s->gc->background;
+  rb = foreground & 0x00ff00ff;
+  foreground &= ~0x00ff00ff;
+  foreground |= rb >> 16 | rb << 16 | 0xff000000;
+  rb = back_pixel & 0x00ff00ff;
+  back_pixel &= ~0x00ff00ff;
+  back_pixel |= rb >> 16 | rb << 16 | 0xff000000;
+
+  prepare_face_for_display (s->f, s->face);
+
+  /* Build the scanline buffer.  Figure out the bounds of the
+     background.  */
+  memset (&background, 0, sizeof background);
+
+  if (with_background)
+    {
+      background.x = x;
+      background.y = y - FONT_BASE (s->font);
+      background.width = s->width;
+      background.height = FONT_HEIGHT (s->font);
+    }
+
+  /* Now figure out the bounds of the text.  */
+
+  if (rasters[0])
+    {
+      text.x1 = x_coords[0] + rasters[0]->offx;
+      text.x2 = text.x1 + rasters[0]->width;
+      text.y1 = y - rasters[0]->height - rasters[0]->offy;
+      text.y2 = y - rasters[0]->offy;
+    }
+  else
+    memset (&text, 0, sizeof text);
+
+  for (i = 1; i < to - from; ++i)
+    {
+      /* See if text has to be extended.  */
+
+      if (!rasters[i])
+       continue;
+
+      character.x1 = x_coords[i] + rasters[i]->offx;
+      character.x2 = character.x1 + rasters[i]->width;
+      character.y1 = y - rasters[i]->height - rasters[i]->offy;
+      character.y2 = y - rasters[i]->offy;
+
+      sfntfont_android_union_boxes (text, character, &text);
+    }
+
+  /* Union the background rect with the text rectangle.  */
+  text_rectangle.x = text.x1;
+  text_rectangle.y = text.y1;
+  text_rectangle.width = text.x2 - text.x1;
+  text_rectangle.height = text.y2 - text.y1;
+  gui_union_rectangles (&background, &text_rectangle,
+                       &text_rectangle);
+
+  /* Allocate enough to hold text_rectangle.height, aligned to 8 (or
+     16) bytes.  Then fill it with the background.  */
+#ifndef __aarch64__
+  stride = ((text_rectangle.width * sizeof *buffer) + 7) & ~7;
+#else
+  stride = ((text_rectangle.width * sizeof *buffer) + 15) & ~15;
+#endif
+  GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride);
+
+  /* Try to optimize out this memset if the background rectangle
+     contains the whole text rectangle.  */
+
+  if (!with_background || memcmp (&background, &text_rectangle,
+                                 sizeof text_rectangle))
+    memset (buffer, 0, text_rectangle.height * stride);
+
+  if (with_background)
+    {
+      /* Fill the background.  First, offset the background rectangle
+        to become relative from text_rectangle.x,
+        text_rectangle.y.  */
+      background.x = background.x - text_rectangle.x;
+      background.y = background.y - text_rectangle.y;
+      eassert (background.x >= 0 && background.y >= 0);
+
+      for (temp_y = background.y; (temp_y
+                                  < (background.y
+                                     + background.height));
+          ++temp_y)
+       {
+         row = (unsigned int *) ((unsigned char *) buffer
+                                 + stride * temp_y);
+
+         for (x = background.x; x < background.x + background.width; ++x)
+           row[x] = back_pixel;
+       }
+    }
+
+  /* Draw all the rasters onto the buffer.  */
+  for (i = 0; i < to - from; ++i)
+    {
+      if (!rasters[i])
+       continue;
+
+      /* Figure out the top and left of the raster relative to
+        text_rectangle.  */
+      left = x_coords[i] + rasters[i]->offx - text_rectangle.x;
+
+      /* Note that negative offy represents the part of the text that
+        lies below the baseline.  */
+      top = (y - (rasters[i]->height + rasters[i]->offy)
+            - text_rectangle.y);
+      eassert (left >= 0 && top >= 0);
+
+      /* Draw the raster onto the temporary bitmap using the
+        foreground color scaled by the alpha map.  */
+
+      for (raster_y = 0; raster_y < rasters[i]->height; ++raster_y)
+       {
+         row = (unsigned int *) ((unsigned char *) buffer
+                                 + stride * (raster_y + top));
+         raster_row = &rasters[i]->cells[raster_y * rasters[i]->stride];
+
+         for (x = 0; x < rasters[i]->width; ++x)
+           {
+             prod
+               = sfntfont_android_scale32 (U255TO256 (raster_row[x]),
+                                           foreground);
+             row[left + x]
+               = sfntfont_android_blend (prod, row[left + x]);
+           }
+       }
+    }
+
+  /* Lock the bitmap.  It must be unlocked later.  */
+  bitmap_data = android_lock_bitmap (FRAME_ANDROID_DRAWABLE (s->f),
+                                    &bitmap_info, &bitmap);
+
+  /* If locking the bitmap fails, just discard the data that was
+     allocated.  */
+  if (!bitmap_data)
+    return;
+
+  /* Loop over each clip rect in the GC.  */
+  eassert (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);
+
+  if (s->gc->num_clip_rects > 0)
+    {
+      for (i = 0; i < s->gc->num_clip_rects; ++i)
+       {
+         if (!gui_intersect_rectangles (&s->gc->clip_rects[i],
+                                        &text_rectangle, &rect))
+           /* Outside the clip region.  */
+           continue;
+
+         /* Composite the intersection onto the buffer.  */
+         sfntfont_android_composite_bitmap ((unsigned char *) buffer,
+                                            stride, bitmap_data,
+                                            &bitmap_info,
+                                            &text_rectangle, &rect);
+       }
+    }
+  else /* gc->num_clip_rects < 0 */
+    sfntfont_android_composite_bitmap ((unsigned char *) buffer,
+                                      stride, bitmap_data,
+                                      &bitmap_info,
+                                      &text_rectangle,
+                                      &text_rectangle);
+
+  /* Release the bitmap.  */
+  AndroidBitmap_unlockPixels (android_java_env, bitmap);
+  ANDROID_DELETE_LOCAL_REF (bitmap);
+
+  /* Damage the window by the text rectangle.  */
+  android_damage_window (FRAME_ANDROID_DRAWABLE (s->f),
+                        &text_rectangle);
+
+#undef MAX_ALLOCA
+}
+
+
+
+/* Shrink the scanline buffer after a window update.  If
+   max_scanline_buffer_size is not zero, and is less than
+   scanline_buffer.buffer_size / 2, then resize the scanline buffer to
+   max_scanline_buffer_size.  */
+
+void
+sfntfont_android_shrink_scanline_buffer (void)
+{
+  if (!max_scanline_buffer_size)
+    return;
+
+  if (max_scanline_buffer_size
+      < scanline_buffer.buffer_size / 2)
+    {
+      scanline_buffer.buffer_size
+       = max_scanline_buffer_size;
+      scanline_buffer.buffer_data
+       = xrealloc (scanline_buffer.buffer_data,
+                   max_scanline_buffer_size);
+    }
+
+  max_scanline_buffer_size = 0;
+}
+
+
+
+/* Font driver definition.  */
+
+/* Return the font cache for this font driver.  F is ignored.  */
+
+static Lisp_Object
+sfntfont_android_get_cache (struct frame *f)
+{
+  return font_cache;
+}
+
+/* The Android sfntfont driver.  */
+const struct font_driver android_sfntfont_driver =
+  {
+    .type = LISPSYM_INITIALLY (Qsfnt_android),
+    .case_sensitive = true,
+    .get_cache = sfntfont_android_get_cache,
+    .list = sfntfont_list,
+    .match = sfntfont_match,
+    .draw = sfntfont_draw,
+    .open_font = sfntfont_open,
+    .close_font = sfntfont_close,
+    .encode_char = sfntfont_encode_char,
+    .text_extents = sfntfont_text_extents,
+    .list_family = sfntfont_list_family,
+    .get_variation_glyphs = sfntfont_get_variation_glyphs,
+
+#ifdef HAVE_HARFBUZZ
+    /* HarfBuzz support is enabled transparently on Android without
+       using a separate font driver.  */
+    .begin_hb_font = sfntfont_begin_hb_font,
+    .combining_capability = hbfont_combining_capability,
+    .shape = hbfont_shape,
+    .otf_capability = hbfont_otf_capability,
+#endif /* HAVE_HARFBUZZ */
+  };
+
+
+
+/* This is an ugly hack that should go away, but I can't think of
+   how.  */
+
+DEFUN ("android-enumerate-fonts", Fandroid_enumerate_fonts,
+       Sandroid_enumerate_fonts, 0, 0, 0,
+       doc: /* Enumerate fonts present on the system.
+
+Signal an error if fonts have already been enumerated.  This would
+normally have been done in C, but reading fonts require Lisp to be
+loaded before character sets are made available.  */)
+  (void)
+{
+  DIR *dir;
+  int i;
+  struct dirent *dirent;
+  char name[PATH_MAX * 2];
+  static bool enumerated;
+
+  if (enumerated)
+    error ("Fonts have already been enumerated");
+  enumerated = true;
+
+  block_input ();
+
+  /* Scan through each of the system font directories.  Enumerate each
+     font that looks like a TrueType font.  */
+  for (i = 0; i < ARRAYELTS (system_font_directories); ++i)
+    {
+      dir = opendir (system_font_directories[i]);
+
+      __android_log_print (ANDROID_LOG_VERBOSE, __func__,
+                          "Loading fonts from: %s",
+                          system_font_directories[i]);
+
+      if (!dir)
+       continue;
+
+      while ((dirent = readdir (dir)))
+       {
+         /* If it contains (not ends with!) with .ttf or .ttc, then
+            enumerate it.  */
+
+         if ((strstr (dirent->d_name, ".ttf")
+              || strstr (dirent->d_name, ".ttc"))
+             /* Ignore the non-variable Roboto font.  */
+             && (i != 0 || strcmp (dirent->d_name,
+                                   "RobotoStatic-Regular.ttf")))
+           {
+             sprintf (name, "%s/%s", system_font_directories[i],
+                      dirent->d_name);
+             sfnt_enum_font (name);
+           }
+       }
+
+      closedir (dir);
+    }
+
+  unblock_input ();
+
+  return Qnil;
+}
+
+
+
+static void
+syms_of_sfntfont_android_for_pdumper (void)
+{
+  init_sfntfont_vendor (Qsfnt_android, &android_sfntfont_driver,
+                       sfntfont_android_put_glyphs);
+  register_font_driver (&android_sfntfont_driver, NULL);
+}
+
+void
+init_sfntfont_android (void)
+{
+  if (!android_init_gui)
+    return;
+
+  /* Make sure to pick the right Sans Serif font depending on what
+     version of Android the device is running.  */
+  if (android_get_current_api_level () >= 15)
+    Vsfnt_default_family_alist
+      = list3 (Fcons (build_string ("Monospace"),
+                     build_string ("Droid Sans Mono")),
+              /* Android doesn't come with a Monospace Serif font, so
+                 this will have to do.  */
+              Fcons (build_string ("Monospace Serif"),
+                     build_string ("Droid Sans Mono")),
+              Fcons (build_string ("Sans Serif"),
+                     build_string ("Roboto")));
+  else
+    Vsfnt_default_family_alist
+      = list3 (Fcons (build_string ("Monospace"),
+                     build_string ("Droid Sans Mono")),
+              Fcons (build_string ("Monospace Serif"),
+                     build_string ("Droid Sans Mono")),
+              Fcons (build_string ("Sans Serif"),
+                     build_string ("Droid Sans")));
+
+  /* Set up the user fonts directory.  This directory is ``fonts'' in
+     the Emacs files directory.  */
+  snprintf (system_font_directories[2], PATH_MAX, "%s/fonts",
+           android_get_home_directory ());
+}
+
+void
+syms_of_sfntfont_android (void)
+{
+  DEFSYM (Qsfnt_android, "sfnt-android");
+  DEFSYM (Qandroid_enumerate_fonts, "android-enumerate-fonts");
+  Fput (Qandroid, Qfont_driver_superseded_by, Qsfnt_android);
+
+  font_cache = list (Qnil);
+  staticpro (&font_cache);
+
+  defsubr (&Sandroid_enumerate_fonts);
+
+  pdumper_do_now_and_after_load (syms_of_sfntfont_android_for_pdumper);
+}
diff --git a/src/sfntfont.c b/src/sfntfont.c
new file mode 100644
index 00000000000..f2dc05c886e
--- /dev/null
+++ b/src/sfntfont.c
@@ -0,0 +1,3991 @@
+/* sfnt format font driver for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "lisp.h"
+
+#include "blockinput.h"
+#include "charset.h"
+#include "coding.h"
+#include "font.h"
+#include "frame.h"
+#include "math.h"
+#include "sfnt.h"
+#include "sfntfont.h"
+
+#ifdef HAVE_HARFBUZZ
+#include <hb.h>
+#include <hb-ot.h>
+#endif /* HAVE_HARFBUZZ */
+
+/* For FRAME_FONT.  */
+#include TERM_HEADER
+
+/* Generic font driver for sfnt-based fonts (currently TrueType, but
+   it would be easy to add CFF support in the future with a PostScript
+   renderer.)
+
+   This is not a complete font driver.  Hooks must be supplied by the
+   platform implementer to draw glyphs.  */
+
+
+
+/* Tables associated with each font, be it distortable or not.  This
+   allows different font objects sharing the same underlying font file
+   to share tables.  */
+
+struct sfnt_font_tables
+{
+  /* Various tables required to use the font.  */
+  struct sfnt_cmap_table *cmap;
+  struct sfnt_hhea_table *hhea;
+  struct sfnt_maxp_table *maxp;
+  struct sfnt_head_table *head;
+  struct sfnt_hmtx_table *hmtx;
+  struct sfnt_glyf_table *glyf;
+  struct sfnt_loca_table_short *loca_short;
+  struct sfnt_loca_table_long *loca_long;
+  struct sfnt_prep_table *prep;
+  struct sfnt_fpgm_table *fpgm;
+  struct sfnt_cvt_table *cvt;
+  struct sfnt_fvar_table *fvar;
+  struct sfnt_avar_table *avar;
+  struct sfnt_gvar_table *gvar;
+  struct sfnt_cvar_table *cvar;
+
+  /* The selected character map.  */
+  struct sfnt_cmap_encoding_subtable_data *cmap_data;
+
+  /* Data identifying that character map.  */
+  struct sfnt_cmap_encoding_subtable cmap_subtable;
+
+  /* The UVS context.  */
+  struct sfnt_uvs_context *uvs;
+
+#ifdef HAVE_MMAP
+  /* Whether or not the glyph table has been mmapped.  */
+  bool glyf_table_mapped;
+#endif /* HAVE_MMAP */
+
+#ifdef HAVE_HARFBUZZ
+  /* File descriptor associated with this font.  */
+  int fd;
+
+  /* The table directory of the font file.  */
+  struct sfnt_offset_subtable *directory;
+#endif /* HAVE_HARFBUZZ */
+};
+
+/* Description of a font that hasn't been opened.  */
+
+struct sfnt_font_desc
+{
+  /* Next font in this list.  */
+  struct sfnt_font_desc *next;
+
+  /* Family name of the font.  */
+  Lisp_Object family;
+
+  /* Style name of the font.  */
+  Lisp_Object style;
+
+  /* Designer (foundry) of the font.  */
+  Lisp_Object designer;
+
+  /* Style tokens that could not be parsed.  */
+  Lisp_Object adstyle;
+
+  /* List of design languages.  */
+  Lisp_Object languages;
+
+  /* Font registry that this font supports.  */
+  Lisp_Object registry;
+
+  /* Vector of instances.  Each element is another of the instance's
+     `style', `adstyle', and numeric width, weight, and slant.  May be
+     nil.  */
+  Lisp_Object instances;
+
+  /* Numeric width, weight, slant and spacing.  */
+  int width, weight, slant, spacing;
+
+  /* Path to the font file.  */
+  char *path;
+
+  /* char table consisting of characters already known to be
+     present in the font.  */
+  Lisp_Object char_cache;
+
+  /* Whether or not the character map can't be used by Emacs.  */
+  bool cmap_invalid;
+
+  /* The header of the cmap being used.  May be invalid, in which case
+     platform_id will be 500.  */
+  struct sfnt_cmap_encoding_subtable subtable;
+
+  /* The offset of the table directory within PATH.  */
+  off_t offset;
+
+  /* The number of glyphs in this font.  Used to catch invalid cmap
+     tables.  This is actually the number of glyphs - 1.  */
+  int num_glyphs;
+
+  /* The number of references to the font tables below.  */
+  int refcount;
+
+  /* List of font tables.  */
+  struct sfnt_font_tables *tables;
+};
+
+/* List of fonts.  */
+
+static struct sfnt_font_desc *system_fonts;
+
+/* Font enumeration and matching.  The sfnt driver assumes it can read
+   data from each font at startup.  It then reads the head, meta and
+   name tables to determine font data, and records the font in a list
+   of system fonts that is then matched against.  */
+
+/* Set up the coding system CODING to decode string data from the
+   given platform id ID and platform specific id PLATFORM_SPECIFIC_ID.
+   Value is 0 upon success, 1 upon failure.  */
+
+static int
+sfnt_setup_coding_system (enum sfnt_platform_id id, int platform_specific_id,
+                         struct coding_system *coding)
+{
+  Lisp_Object system;
+
+  system = Qnil;
+
+  /* Figure out what coding system to use.  */
+
+  switch (id)
+    {
+    case SFNT_PLATFORM_UNICODE:
+      system = Qutf_16be;
+      break;
+
+    case SFNT_PLATFORM_MACINTOSH:
+
+      if (platform_specific_id == SFNT_MACINTOSH_ROMAN)
+       system = Qmac_roman;
+      else
+       /* MULE doesn't support the rest... */
+       system = Qnil;
+
+      break;
+
+    case SFNT_PLATFORM_MICROSOFT:
+      system = Qutf_16be;
+
+      /* Not sure if this is right.  */
+      if (platform_specific_id == SFNT_MICROSOFT_BIG_FIVE)
+       system = Qchinese_big5;
+
+      break;
+
+    default:
+      system = Qnil;
+    }
+
+  if (NILP (system))
+    return 1;
+
+  setup_coding_system (system, coding);
+  return 0;
+}
+
+/* Globals used to communicate inside the condition-case wrapper.  */
+static struct coding_system *sfnt_font_coding;
+
+/* The src_object being encoded from.  This should be on the stack as
+   well, or it will get garbage collected.  */
+static Lisp_Object sfnt_font_src_object;
+
+/* From-position.  */
+static ptrdiff_t sfnt_font_from, sfnt_font_from_byte;
+
+/* To-position.  */
+static ptrdiff_t sfnt_font_to, sfnt_font_to_byte;
+
+/* Destination object.  Once again, this should also be on the
+   stack.  */
+static Lisp_Object sfnt_font_dst_object;
+
+/* Error flag.  Set to true if a signal was caught.  */
+static bool sfnt_font_signal;
+
+static Lisp_Object
+sfnt_safe_decode_coding_object_1 (void)
+{
+  decode_coding_object (sfnt_font_coding,
+                       sfnt_font_src_object,
+                       sfnt_font_from,
+                       sfnt_font_from_byte,
+                       sfnt_font_to,
+                       sfnt_font_to_byte,
+                       sfnt_font_dst_object);
+  return Qnil;
+}
+
+static Lisp_Object
+sfnt_safe_decode_coding_object_2 (Lisp_Object error)
+{
+  sfnt_font_signal = true;
+
+  return Qnil;
+}
+
+/* Like decode_coding_object, but return 1 if a signal happens.  Value
+   is otherwise 0.  */
+
+static int
+sfnt_safe_decode_coding_object (struct coding_system *coding,
+                               Lisp_Object src_object,
+                               ptrdiff_t from, ptrdiff_t from_byte,
+                               ptrdiff_t to, ptrdiff_t to_byte,
+                               Lisp_Object dst_object)
+{
+  sfnt_font_coding = coding;
+  sfnt_font_src_object = src_object;
+  sfnt_font_from = from;
+  sfnt_font_from_byte = from_byte;
+  sfnt_font_to = to;
+  sfnt_font_to_byte = to_byte;
+  sfnt_font_dst_object = dst_object;
+  sfnt_font_signal = false;
+
+  internal_condition_case (sfnt_safe_decode_coding_object_1,
+                          Qt,
+                          sfnt_safe_decode_coding_object_2);
+
+  return (int) sfnt_font_signal;
+}
+
+/* Decode the specified string DATA.  The encoding is determined based
+   on PLATFORM_ID, PLATFORM_SPECIFIC_ID and LANGUAGE_ID.  Consult
+   sfnt.h and the TrueType Reference Manual for more details.  LENGTH
+   is the length of DATA in bytes.
+
+   Value is nil upon failure, else the decoded string.  */
+
+static Lisp_Object
+sfnt_decode_font_string (unsigned char *data, enum sfnt_platform_id id,
+                        int platform_specific_id, int language_id,
+                        size_t length)
+{
+  struct coding_system coding;
+
+  memset (&coding, 0, sizeof coding);
+  sfnt_setup_coding_system (id, platform_specific_id, &coding);
+  coding.mode |= CODING_MODE_SAFE_ENCODING;
+  coding.mode |= CODING_MODE_LAST_BLOCK;
+  /* Suppress producing escape sequences for composition.  */
+  coding.common_flags &= ~CODING_ANNOTATION_MASK;
+  coding.source = data;
+
+  if (sfnt_safe_decode_coding_object (&coding, Qnil, 0, 0,
+                                     length, length, Qt))
+    return Qnil;
+
+  return coding.dst_object;
+}
+
+/* Decode the family and style names from the name table NAME.  Return
+   0 and the family and style names upon success, else 1.  */
+
+static int
+sfnt_decode_family_style (struct sfnt_name_table *name,
+                         Lisp_Object *family, Lisp_Object *style)
+{
+  struct sfnt_name_record family_rec, style_rec;
+  unsigned char *family_data, *style_data;
+
+  /* Because MS-Windows is incapable of treating font families
+     comprising more than four styles correctly, the TrueType
+     specification incorporates additional PREFERRED_FAMILY and
+     PREFERRED_SUBFAMILY name resources that are meant to be consulted
+     over the traditional family and subfamily resources.  When
+     present within fonts supplying unusual styles, these names hold
+     the ``actual'' typographic family and style of the font, in lieu
+     of the font family with the style affixed to the front and
+     Regular.  */
+
+  family_data = sfnt_find_name (name, SFNT_NAME_PREFERRED_FAMILY,
+                               &family_rec);
+
+  if (!family_data)
+    family_data = sfnt_find_name (name, SFNT_NAME_FONT_FAMILY,
+                                 &family_rec);
+
+  style_data = sfnt_find_name (name, SFNT_NAME_PREFERRED_SUBFAMILY,
+                              &style_rec);
+
+  if (!style_data)
+    style_data = sfnt_find_name (name, SFNT_NAME_FONT_SUBFAMILY,
+                                &style_rec);
+
+  if (!family_data || !style_data)
+    return 1;
+
+  /* Now decode the data.  */
+  *family = sfnt_decode_font_string (family_data,
+                                    family_rec.platform_id,
+                                    family_rec.platform_specific_id,
+                                    family_rec.language_id,
+                                    family_rec.length);
+  *style = sfnt_decode_font_string (style_data,
+                                   style_rec.platform_id,
+                                   style_rec.platform_specific_id,
+                                   style_rec.language_id,
+                                   style_rec.length);
+
+  /* Return whether or not it was successful.  */
+  return (!NILP (*family) && !NILP (*style)) ? 0 : 1;
+}
+
+/* Decode the foundry names from the name table NAME.  Return the
+   foundry name, or nil upon failure.  */
+
+static Lisp_Object
+sfnt_decode_foundry_name (struct sfnt_name_table *name)
+{
+  struct sfnt_name_record designer_rec;
+  unsigned char *designer_data;
+
+  designer_data = sfnt_find_name (name, SFNT_NAME_DESIGNER,
+                                 &designer_rec);
+
+  if (!designer_data)
+    return Qnil;
+
+  return sfnt_decode_font_string (designer_data,
+                                 designer_rec.platform_id,
+                                 designer_rec.platform_specific_id,
+                                 designer_rec.language_id,
+                                 designer_rec.length);
+}
+
+/* Decode the name of the specified font INSTANCE using the given NAME
+   table.  Return the name of that instance, or nil upon failure.  */
+
+static Lisp_Object
+sfnt_decode_instance_name (struct sfnt_instance *instance,
+                          struct sfnt_name_table *name)
+{
+  struct sfnt_name_record name_rec;
+  unsigned char *name_data;
+
+  name_data = sfnt_find_name (name, instance->name_id,
+                             &name_rec);
+
+  if (!name_data)
+    return Qnil;
+
+  return sfnt_decode_font_string (name_data,
+                                 name_rec.platform_id,
+                                 name_rec.platform_specific_id,
+                                 name_rec.language_id,
+                                 name_rec.length);
+}
+
+struct sfnt_style_desc
+{
+  /* The C string to match against.  */
+  const char *c_string;
+
+  /* The value of the style field.  */
+  int value;
+};
+
+/* Array of style descriptions describing weight.  */
+static struct sfnt_style_desc sfnt_weight_descriptions[] =
+  {
+    { "thin", 0,               },
+    { "extralight", 40,                },
+    { "ultralight", 40,                },
+    { "light", 50,             },
+    { "demilight", 55,         },
+    { "semilight", 55,         },
+    { "book", 75,              },
+    { "medium", 100,           },
+    { "demibold", 180,         },
+    { "semibold", 180,         },
+    { "bold", 200,             },
+    { "extrabold", 205,                },
+    { "ultrabold", 205,                },
+    { "black", 210,            },
+    { "heavy", 210,            },
+    { "extrablack", 215,       },
+    { "ultrablack", 215,       },
+  };
+
+/* Array of style descriptions describing slant.  */
+static struct sfnt_style_desc sfnt_slant_descriptions[] =
+  {
+    { "italic", 200,   },
+    { "oblique", 210,  },
+  };
+
+/* Array of style descriptions describing width.  */
+static struct sfnt_style_desc sfnt_width_descriptions[] =
+  {
+    { "ultracondensed", 50,    },
+    { "extracondensed", 63,    },
+    { "condensed", 75,         },
+    { "semicondensed", 87,     },
+    { "semiexpanded", 113,     },
+    { "expanded", 125,         },
+    { "extraexpanded", 150,    },
+    { "ultraexpanded", 200,    },
+  };
+
+/* Figure out DESC->width, DESC->weight, DESC->slant and DESC->spacing
+   based on the style name passed as STYLE_NAME.
+
+   Also append any unknown tokens to DESC->adstyle.  */
+
+static void
+sfnt_parse_style (Lisp_Object style_name, struct sfnt_font_desc *desc)
+{
+  char *style, *single, *saveptr;
+  int i;
+  USE_SAFE_ALLOCA;
+
+  /* Fill in default values.  slant seems to not be consistent with
+     Fontconfig.  */
+  desc->weight = 80;
+  desc->slant = 100;
+  desc->width = 100;
+
+  /* Split the style into tokens delimited by spaces.  Attempt to find
+     a token specifying each of the weight, slant, or width attributes
+     using their respective descriptions arrays as a reference.  */
+
+  SAFE_ALLOCA_STRING (style, Fdowncase (style_name));
+  saveptr = NULL;
+
+  while ((single = strtok_r (style, " ", &saveptr)))
+    {
+      style = NULL;
+
+      if (desc->weight == 80)
+       {
+         /* Weight hasn't been found yet.  Scan through the weight
+            table.  */
+         for (i = 0; i < ARRAYELTS (sfnt_weight_descriptions); ++i)
+           {
+             if (!strcmp (sfnt_weight_descriptions[i].c_string,
+                          single))
+               {
+                 /* Weight found.  Continue on reading the slant and
+                    width.  */
+                 desc->weight = sfnt_weight_descriptions[i].value;
+                 goto next;
+               }
+           }
+       }
+
+      if (desc->slant == 100)
+       {
+         /* Slant hasn't been found yet.  Scan through the slant
+            table.  */
+         for (i = 0; i < ARRAYELTS (sfnt_slant_descriptions); ++i)
+           {
+             if (!strcmp (sfnt_slant_descriptions[i].c_string,
+                          single))
+               {
+                 /* Slant found.  Continue on reading the weight and
+                    width.  */
+                 desc->slant = sfnt_slant_descriptions[i].value;
+                 goto next;
+               }
+           }
+       }
+
+      if (desc->width == 100)
+       {
+         /* Width hasn't been found yet.  Scan through the width
+            table.  */
+         for (i = 0; i < ARRAYELTS (sfnt_width_descriptions); ++i)
+           {
+             if (!strcmp (sfnt_width_descriptions[i].c_string,
+                          single))
+               {
+                 /* Width found.  Continue on reading the slant and
+                    weight.  */
+                 desc->width = sfnt_width_descriptions[i].value;
+                 goto next;
+               }
+           }
+       }
+
+      /* This token is extraneous or was not recognized.  Capitalize
+        the first letter and set it as the adstyle.  */
+
+      if (strlen (single))
+       {
+         if (islower (single[0]))
+           single[0] = toupper (single[0]);
+
+         if (NILP (desc->adstyle))
+           desc->adstyle = build_string (single);
+         else
+           desc->adstyle = CALLN (Fconcat, desc->adstyle,
+                                  build_string (" "),
+                                  build_string (single));
+       }
+
+    next:
+      continue;
+    }
+
+  SAFE_FREE ();
+}
+
+/* Parse the list of design languages in META, a font metadata table,
+   and place the results in DESC->languages.  Do nothing if there is
+   no such metadata.  */
+
+static void
+sfnt_parse_languages (struct sfnt_meta_table *meta,
+                     struct sfnt_font_desc *desc)
+{
+  char *data, *metadata, *tag;
+  struct sfnt_meta_data_map map;
+  char *saveptr;
+
+  /* Look up the ``design languages'' metadata.  This is a comma (and
+     possibly space) separated list of scripts that the font was
+     designed for.  Here is an example of one such tag:
+
+       zh-Hans,Jpan,Kore
+
+     for a font that covers Simplified Chinese, along with Japanese
+     and Korean text.  */
+
+  saveptr = NULL;
+  data = sfnt_find_metadata (meta, SFNT_META_DATA_TAG_DLNG,
+                            &map);
+
+  if (!data)
+    {
+      /* Fall back to the supported languages metadata.  */
+      data = sfnt_find_metadata (meta, SFNT_META_DATA_TAG_SLNG,
+                                &map);
+
+      if (!data)
+       return;
+    }
+
+  USE_SAFE_ALLOCA;
+
+  /* Now copy metadata and add a trailing NULL byte.  */
+
+  if (map.data_length >= SIZE_MAX)
+    memory_full (SIZE_MAX);
+
+  metadata = SAFE_ALLOCA ((size_t) map.data_length + 1);
+  memcpy (metadata, data, map.data_length);
+  metadata[map.data_length] = '\0';
+
+  /* Loop through each script-language tag.  Note that there may be
+     extra leading spaces.  */
+  while ((tag = strtok_r (metadata, ",", &saveptr)))
+    {
+      metadata = NULL;
+
+      if (strstr (tag, "Hans") || strstr (tag, "Hant"))
+       desc->languages = Fcons (Qzh, desc->languages);
+
+      if (strstr (tag, "Japn"))
+       desc->languages = Fcons (Qja, desc->languages);
+
+      if (strstr (tag, "Kore"))
+       desc->languages = Fcons (Qko, desc->languages);
+    }
+
+  SAFE_FREE ();
+}
+
+/* Return the font registry corresponding to the encoding subtable
+   SUBTABLE.
+
+   Under X, the font registry is an atom registered with the Open
+   Group uniquely identifying the organization which defines the
+   font's character set.
+
+   In practice, the registry overlaps with the character set itself.
+   So Emacs just uses the ``registry'' field of each font object and
+   entity to represent both instead.  */
+
+static Lisp_Object
+sfnt_registry_for_subtable (struct sfnt_cmap_encoding_subtable *subtable)
+{
+  switch (subtable->platform_id)
+    {
+    case SFNT_PLATFORM_UNICODE:
+      /* Reject variation selector and last resort tables.  */
+      if ((subtable->platform_specific_id
+          == SFNT_UNICODE_VARIATION_SEQUENCES)
+         || (subtable->platform_specific_id
+             == SFNT_UNICODE_LAST_RESORT))
+       return Qnil;
+
+      return Qiso10646_1;
+
+    case SFNT_PLATFORM_MACINTOSH:
+
+      switch (subtable->platform_specific_id)
+       {
+       case SFNT_MACINTOSH_ROMAN:
+         /* X calls mac-roman ``apple-roman''.  */
+         return Qapple_roman;
+
+       default:
+         /* Some other Macintosh charset not supported by Emacs.  */
+         return Qnil;
+       }
+
+    case SFNT_PLATFORM_MICROSOFT:
+
+      /* Microsoft specific encodings.  */
+
+      switch (subtable->platform_specific_id)
+       {
+       case SFNT_MICROSOFT_SYMBOL:
+       case SFNT_MICROSOFT_UNICODE_BMP:
+         /* Symbols in the Unicode PUA are still Unicode.  */
+         return Qiso10646_1;
+
+       case SFNT_MICROSOFT_SHIFT_JIS:
+         return Qjisx0208_1983_0;
+
+       case SFNT_MICROSOFT_PRC:
+         return Qgbk;
+
+       case SFNT_MICROSOFT_JOHAB:
+         return Qksc5601_1987_0;
+
+       case SFNT_MICROSOFT_UNICODE_UCS_4:
+         return Qiso10646_1;
+       }
+
+    default:
+      return Qnil;
+    }
+}
+
+/* Return the type of characters that the cmap subtable SUBTABLE maps
+   from.  Value is:
+
+   2 if SUBTABLE maps from Unicode characters, including those outside
+   the Unicode Basic Multilingual Plane (BMP).
+
+   1 if SUBTABLE maps from Unicode characters within the BMP.
+
+   0 if SUBTABLE maps from some other character set that Emacs knows
+   about.
+
+   3 if SUBTABLE cannot be used by Emacs.  */
+
+static int
+sfntfont_identify_cmap (struct sfnt_cmap_encoding_subtable subtable)
+{
+  switch (subtable.platform_id)
+    {
+    case SFNT_PLATFORM_UNICODE:
+
+      /* Reject variation selector and last resort tables.  */
+      if ((subtable.platform_specific_id
+          == SFNT_UNICODE_VARIATION_SEQUENCES)
+         || (subtable.platform_specific_id
+             == SFNT_UNICODE_LAST_RESORT))
+       return 3;
+
+      /* 1.0, 1.1, ISO-10646-1993, and 2.0_BMP tables are all within
+        the BMP.  */
+      if (subtable.platform_specific_id < SFNT_UNICODE_2_0)
+       return 1;
+
+      return 2;
+
+    case SFNT_PLATFORM_MACINTOSH:
+
+      switch (subtable.platform_specific_id)
+       {
+       case SFNT_MACINTOSH_ROMAN:
+         /* mac-roman */
+         return 0;
+
+       default:
+         /* Some other Macintosh charset not supported by Emacs.  */
+         return 3;
+       }
+
+    case SFNT_PLATFORM_MICROSOFT:
+
+      /* Microsoft specific encodings.  */
+
+      switch (subtable.platform_specific_id)
+       {
+       case SFNT_MICROSOFT_SYMBOL:
+         /* Symbols in the Unicode PUA are still Unicode.  */
+         return 1;
+
+       case SFNT_MICROSOFT_UNICODE_BMP:
+         return 1;
+
+       case SFNT_MICROSOFT_SHIFT_JIS:
+         /* PCK aka japanese-jisx0208.  */
+         return 0;
+
+       case SFNT_MICROSOFT_PRC:
+         /* GBK, GB2312 or GB18030.  */
+         return 0;
+
+       case SFNT_MICROSOFT_JOHAB:
+         /* KS C 5601-1992, aka korean-ksc5601.  */
+         return 0;
+
+       case SFNT_MICROSOFT_UNICODE_UCS_4:
+         /* Unicode past the BMP.  */
+         return 2;
+       }
+
+    default:
+      return 3;
+    }
+}
+
+/* Figure out which registry DESC, backed by FD, whose table directory
+   is SUBTABLE, is likely to support.
+
+   Read the header of each subtable in the character map and compute
+   the registry to use; then, set DESC->registry to that value.  */
+
+static void
+sfnt_grok_registry (int fd, struct sfnt_font_desc *desc,
+                   struct sfnt_offset_subtable *subtable)
+{
+  struct sfnt_cmap_table *cmap;
+  struct sfnt_cmap_encoding_subtable *subtables;
+  int i;
+
+  cmap = sfnt_read_cmap_table (fd, subtable, &subtables, NULL);
+
+  if (!cmap)
+    return;
+
+  /* Now pick the ``best'' character map the same way as sfntfont_open
+     does.  The caveat is that since the subtable data has not been
+     read, Emacs cannot determine whether or not the encoding subtable
+     is valid.
+
+     Once platform_id is set, that value becomes much more
+     reliable.  */
+
+  /* First look for a non-BMP Unicode cmap.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      if (sfntfont_identify_cmap (subtables[i]) == 2)
+       {
+         desc->registry
+           = sfnt_registry_for_subtable (&subtables[i]);
+         goto done;
+       }
+    }
+
+  /* Next, look for a BMP only Unicode cmap.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      if (sfntfont_identify_cmap (subtables[i]) == 1)
+        {
+         desc->registry
+           = sfnt_registry_for_subtable (&subtables[i]);
+         goto done;
+       }
+    }
+
+  /* Finally, use the first cmap that appears and can be
+     identified.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      if (sfntfont_identify_cmap (subtables[i]) == 0)
+        {
+         desc->registry
+           = sfnt_registry_for_subtable (&subtables[i]);
+         goto done;
+       }
+    }
+
+  /* There are no cmaps available to Emacs.  */
+ done:
+  xfree (cmap);
+  xfree (subtables);
+}
+
+/* Return whether or not the font description PREV conflicts with the
+   newer font description DESC, and should be removed from the list of
+   system fonts.
+
+   If both PREV and DESC are variable fonts, remove styles within PREV
+   that overlap with DESC and return false.
+
+   If PREV is a variable font, potentially adjust its list of
+   instances.  */
+
+static bool
+sfnt_replace_fonts_p (struct sfnt_font_desc *prev,
+                     struct sfnt_font_desc *desc)
+{
+  int i, j, width, weight, slant, count_instance;
+  Lisp_Object tem, tem1;
+  bool family_equal_p;
+
+  family_equal_p = !NILP (Fstring_equal (prev->family,
+                                        desc->family));
+
+  if ((!NILP (desc->instances)
+       || !NILP (Fstring_equal (prev->style, desc->style)))
+      && family_equal_p)
+    {
+      /* If both inputs are GX fonts...  */
+      if (!NILP (desc->instances) && !NILP (prev->instances))
+       {
+         /* ...iterate over each of the styles provided by PREV.  If
+            they match any styles within DESC, remove the old style
+            from PREV.  */
+
+         count_instance = 0;
+         for (i = 0; i < ASIZE (prev->instances); ++i)
+           {
+             tem = AREF (prev->instances, i);
+
+             if (NILP (tem))
+               continue;
+
+             for (j = 0; j < ASIZE (desc->instances); ++j)
+               {
+                 tem1 = AREF (desc->instances, j);
+
+                 if (NILP (tem1))
+                   continue;
+
+                 if (!NILP (Fequal (tem1, tem)))
+                   {
+                     /* tem1 is identical to tem, so opt for it over
+                        tem.  */
+                     ASET (prev->instances, i, Qnil);
+                     goto next;
+                   }
+               }
+
+             /* Increment the number of instances remaining within
+                PREV.  */
+             count_instance++;
+
+           next:
+             ;
+           }
+
+         /* Return true if no instances remain inside
+            PREV->instances, so that the now purposeless desc may be
+            removed.  */
+         return !count_instance;
+       }
+
+      return true;
+    }
+
+  if (NILP (prev->instances) || !family_equal_p)
+    return false;
+
+  /* Look through instances in PREV to see if DESC provides the same
+     thing.  */
+
+  count_instance = 0;
+  for (i = 0; i < ASIZE (prev->instances); ++i)
+    {
+      tem = AREF (prev->instances, i);
+
+      if (NILP (tem))
+       continue;
+
+      width = XFIXNUM (AREF (tem, 2));
+      weight = XFIXNUM (AREF (tem, 3));
+      slant = XFIXNUM (AREF (tem, 4));
+
+      if (desc->width == width
+         && desc->weight == weight
+         && desc->slant == slant)
+       {
+         /* Remove this instance.  */
+         ASET (prev->instances, i, Qnil);
+         continue;
+       }
+
+      count_instance++;
+    }
+
+  /* Remove this desc if there are no more instances.  */
+  return count_instance < 1;
+}
+
+/* Enumerate the offset subtable SUBTABLES in the file FD, whose file
+   name is FILE.  OFFSET should be the offset of the subtable within
+   the font file, and is recorded for future use.  Value is 1 upon
+   failure, else 0.  */
+
+static int
+sfnt_enum_font_1 (int fd, const char *file,
+                 struct sfnt_offset_subtable *subtables,
+                 off_t offset)
+{
+  struct sfnt_font_desc *desc, **next, *prev;
+  struct sfnt_head_table *head;
+  struct sfnt_name_table *name;
+  struct sfnt_meta_table *meta;
+  struct sfnt_maxp_table *maxp;
+  struct sfnt_fvar_table *fvar;
+  struct sfnt_font_desc temp;
+  Lisp_Object family, style, instance, style1;
+  int i;
+
+  /* Create the font desc and copy in the file name.  */
+  desc = xzalloc (sizeof *desc + strlen (file) + 1);
+  desc->path = (char *) (desc + 1);
+  memcpy (desc->path, file, strlen (file) + 1);
+  desc->offset = offset;
+
+  /* Check that this is a TrueType font.  */
+  if (subtables->scaler_type != SFNT_SCALER_TRUE
+      && subtables->scaler_type != SFNT_SCALER_VER1)
+    goto bail1;
+
+  /* Read required tables.  */
+  head = sfnt_read_head_table (fd, subtables);
+  if (!head)
+    goto bail1;
+
+  name = sfnt_read_name_table (fd, subtables);
+  if (!name)
+    goto bail2;
+
+  maxp = sfnt_read_maxp_table (fd, subtables);
+  if (!maxp)
+    goto bail3;
+
+  /* meta is not required, nor present on many non-Apple fonts.  */
+  meta = sfnt_read_meta_table (fd, subtables);
+
+  /* Decode the family and style from the name table.  */
+  if (sfnt_decode_family_style (name, &family, &style))
+    goto bail4;
+
+  /* See if this is a distortable/variable/multiple master font (all
+     three terms mean the same time.)  */
+  fvar = sfnt_read_fvar_table (fd, subtables);
+
+  /* Set the family.  */
+  desc->family = family;
+  desc->designer = sfnt_decode_foundry_name (name);
+  desc->char_cache = Qnil;
+  desc->subtable.platform_id = 500;
+
+  /* Set the largest glyph identifier.  */
+  desc->num_glyphs = maxp->num_glyphs;
+
+  /* Parse the style.  */
+  sfnt_parse_style (style, desc);
+
+  /* If the meta table exists, parse the list of design languages.  */
+  if (meta)
+    sfnt_parse_languages (meta, desc);
+
+  /* Figure out the spacing.  Some fancy test like what Fontconfig
+     does is probably in order but not really necessary.  */
+  if (!NILP (Fstring_search (Fdowncase (family),
+                            build_string ("mono"),
+                            Qnil)))
+    desc->spacing = 100; /* FC_MONO */
+
+  /* Finally add mac-style flags.  Allow them to override styles that
+     have not been found.  */
+
+  if (head->mac_style & 01 && desc->weight == 80) /* Bold */
+    desc->weight = 200;
+
+  if (head->mac_style & 02 && desc->slant == 0) /* Italic */
+    desc->slant = 100;
+
+  /* Figure out what registry this font is likely to support.  */
+  sfnt_grok_registry (fd, desc, subtables);
+
+  if (fvar && fvar->instance_count)
+    {
+      /* If there is an fvar table with instances, then this is a font
+        which defines different axes along which the points in each
+        glyph can be changed.
+
+         Instead of enumerating the font itself, enumerate each
+         instance within, which specifies how to configure each axis
+         to achieve a specified style.  */
+
+      desc->instances = make_vector (fvar->instance_count, Qnil);
+
+      for (i = 0; i < fvar->instance_count; ++i)
+       {
+         style1 = sfnt_decode_instance_name (&fvar->instance[i],
+                                             name);
+
+         if (NILP (style1))
+           continue;
+
+         /* Now parse the style.  */
+         temp.adstyle = Qnil;
+         sfnt_parse_style (style1, &temp);
+
+         /* Set each field of the vector.  */
+         instance = make_vector (5, Qnil);
+         ASET (instance, 0, style1);
+         ASET (instance, 1, temp.adstyle);
+         ASET (instance, 2, make_fixnum (temp.width));
+         ASET (instance, 3, make_fixnum (temp.weight));
+         ASET (instance, 4, make_fixnum (temp.slant));
+
+         /* Place the vector in desc->instances.  */
+         ASET (desc->instances, i, instance);
+       }
+    }
+
+  /* Set the style, link the desc onto system_fonts and return.  */
+  desc->style = style;
+  desc->next = system_fonts;
+  system_fonts = desc;
+
+  /* Remove any fonts which have the same style as this one.  For
+     distortable fonts, only remove overlapping styles, unless this is
+     also a distortable font.  */
+
+  next = &system_fonts->next;
+  prev = *next;
+  for (; *next; prev = *next)
+    {
+      if (sfnt_replace_fonts_p (prev, desc))
+       {
+         *next = prev->next;
+         xfree (prev);
+       }
+      else
+       next = &prev->next;
+    }
+
+  xfree (fvar);
+  xfree (meta);
+  xfree (maxp);
+  xfree (name);
+  xfree (head);
+  return 0;
+
+ bail4:
+  xfree (meta);
+  xfree (maxp);
+ bail3:
+  xfree (name);
+ bail2:
+  xfree (head);
+ bail1:
+  xfree (desc);
+  return 1;
+}
+
+/* Enumerate the font FILE into the list of system fonts.  Return 1 if
+   it could not be enumerated, 0 otherwise.
+
+   Remove any font whose family and style is a duplicate of this one.
+
+   FILE can either be a TrueType collection file containing TrueType
+   fonts, or a TrueType font itself.  */
+
+int
+sfnt_enum_font (const char *file)
+{
+  int fd, rc;
+  struct sfnt_offset_subtable *subtables;
+  struct sfnt_ttc_header *ttc;
+  size_t i;
+
+  /* Now open the font for reading.  */
+  fd = emacs_open (file, O_RDONLY, 0);
+
+  if (fd == -1)
+    goto bail;
+
+  /* Read the table directory.  */
+  subtables = sfnt_read_table_directory (fd);
+
+  if (subtables == (struct sfnt_offset_subtable *) -1)
+    {
+      /* This is actually a TrueType container file.  Go back to the
+        beginning and read the TTC header.  */
+
+      if (lseek (fd, 0, SEEK_SET))
+       goto bail0;
+
+      ttc = sfnt_read_ttc_header (fd);
+
+      if (!ttc)
+       goto bail0;
+
+      /* Enumerate each of the fonts in the collection.  */
+
+      for (i = 0; i < ttc->num_fonts; ++i)
+       {
+         if (lseek (fd, ttc->offset_table[i], SEEK_SET)
+             != ttc->offset_table[i])
+           continue;
+
+         subtables = sfnt_read_table_directory (fd);
+
+         if (!subtables
+             /* This value means that FD was pointing at a TTC
+                header.  Since FD should already have been moved to
+                the beginning of the TrueType header above, it
+                follows that the font format is invalid.  */
+             || (subtables == (struct sfnt_offset_subtable *) -1))
+           continue;
+
+         sfnt_enum_font_1 (fd, file, subtables,
+                           ttc->offset_table[i]);
+         xfree (subtables);
+       }
+
+      /* Always treat reading containers as having been
+        successful.  */
+
+      emacs_close (fd);
+      xfree (ttc);
+      return 0;
+    }
+
+  if (!subtables)
+    goto bail0;
+
+  /* Now actually enumerate this font.  */
+  rc = sfnt_enum_font_1 (fd, file, subtables, 0);
+  xfree (subtables);
+  emacs_close (fd);
+  return rc;
+
+ bail0:
+  emacs_close (fd);
+ bail:
+  return 1;
+}
+
+
+
+/* Font discovery and matching.  */
+
+static struct charset *
+sfntfont_charset_for_name (Lisp_Object symbol)
+{
+  ptrdiff_t idx;
+  int id;
+
+  idx = CHARSET_SYMBOL_HASH_INDEX (symbol);
+
+  if (idx == -1)
+    return NULL;
+
+  /* Vcharset_hash_table is not a real variable, so Lisp programs
+     can't clobber it.  */
+  id = XFIXNUM (AREF (HASH_VALUE (XHASH_TABLE (Vcharset_hash_table),
+                                 idx),
+                     charset_id));
+
+  return CHARSET_FROM_ID (id);
+}
+
+/* Return the character set corresponding to a cmap subtable SUBTABLE.
+   Value is NULL if the subtable is not supported.  */
+
+static struct charset *
+sfntfont_charset_for_cmap (struct sfnt_cmap_encoding_subtable subtable)
+{
+  switch (subtable.platform_id)
+    {
+    case SFNT_PLATFORM_UNICODE:
+      /* Reject variation selector and last resort tables.  */
+      if ((subtable.platform_specific_id
+          == SFNT_UNICODE_VARIATION_SEQUENCES)
+         || (subtable.platform_specific_id
+             == SFNT_UNICODE_LAST_RESORT))
+       return NULL;
+
+      /* 1.0, 1.1, ISO-10646-1993, and 2.0_BMP tables are all within
+        the BMP.  */
+      if (subtable.platform_specific_id < SFNT_UNICODE_2_0)
+       return sfntfont_charset_for_name (Qunicode_bmp);
+
+      return sfntfont_charset_for_name (Qunicode);
+
+    case SFNT_PLATFORM_MACINTOSH:
+
+      switch (subtable.platform_specific_id)
+       {
+       case SFNT_MACINTOSH_ROMAN:
+         return sfntfont_charset_for_name (Qmac_roman);
+
+       default:
+         /* Some other Macintosh charset not supported by Emacs.  */
+         return NULL;
+       }
+
+    case SFNT_PLATFORM_MICROSOFT:
+
+      /* Microsoft specific encodings.  */
+
+      switch (subtable.platform_specific_id)
+       {
+       case SFNT_MICROSOFT_SYMBOL:
+         /* Symbols in the Unicode PUA are still Unicode.  */
+         return sfntfont_charset_for_name (Qunicode);
+
+       case SFNT_MICROSOFT_UNICODE_BMP:
+         return sfntfont_charset_for_name (Qunicode_bmp);
+
+       case SFNT_MICROSOFT_SHIFT_JIS:
+         /* PCK aka japanese-jisx0208.  */
+         return sfntfont_charset_for_name (Qjapanese_jisx0208);
+
+       case SFNT_MICROSOFT_PRC:
+         /* GBK, GB2312 or GB18030.  */
+         return sfntfont_charset_for_name (Qgbk);
+
+       case SFNT_MICROSOFT_JOHAB:
+         /* KS C 5601-1992, aka korean-ksc5601.  */
+         return sfntfont_charset_for_name (Qkorean_ksc5601);
+
+       case SFNT_MICROSOFT_UNICODE_UCS_4:
+         /* Unicode past the BMP.  */
+         return sfntfont_charset_for_name (Qucs);
+       }
+
+    default:
+      return NULL;
+    }
+}
+
+/* Pick the best character map in the cmap table CMAP.  Use the
+   subtables in SUBTABLES and DATA.  Return the subtable data and the
+   subtable in *SUBTABLE upon success, NULL otherwise.
+
+   If FORMAT14 is non-NULL, return any associated format 14 variation
+   selection context in *FORMAT14 should the selected charcter map be
+   a Unicode character map.  */
+
+static struct sfnt_cmap_encoding_subtable_data *
+sfntfont_select_cmap (struct sfnt_cmap_table *cmap,
+                     struct sfnt_cmap_encoding_subtable *subtables,
+                     struct sfnt_cmap_encoding_subtable_data **data,
+                     struct sfnt_cmap_encoding_subtable *subtable,
+                     struct sfnt_cmap_format_14 **format14)
+{
+  int i, j;
+
+  /* First look for a non-BMP Unicode cmap.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      if (data[i] && sfntfont_identify_cmap (subtables[i]) == 2)
+       {
+         *subtable = subtables[i];
+
+         if (!format14)
+           return data[i];
+
+         /* Search for a correspoinding format 14 character map.
+            This is used in conjunction with the selected character
+            map to map variation sequences.  */
+
+         for (j = 0; j < cmap->num_subtables; ++j)
+           {
+             if (data[j]
+                 && subtables[j].platform_id == SFNT_PLATFORM_UNICODE
+                 && (subtables[j].platform_specific_id
+                     == SFNT_UNICODE_VARIATION_SEQUENCES)
+                 && data[j]->format == 14)
+               *format14 = (struct sfnt_cmap_format_14 *) data[j];
+           }
+
+         return data[i];
+       }
+    }
+
+  /* Next, look for a BMP only Unicode cmap.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      if (data[i] && sfntfont_identify_cmap (subtables[i]) == 1)
+       {
+         *subtable = subtables[i];
+
+         if (!format14)
+           return data[i];
+
+         /* Search for a correspoinding format 14 character map.
+            This is used in conjunction with the selected character
+            map to map variation sequences.  */
+
+         for (j = 0; j < cmap->num_subtables; ++j)
+           {
+             if (data[j]
+                 && subtables[j].platform_id == SFNT_PLATFORM_UNICODE
+                 && (subtables[j].platform_specific_id
+                     == SFNT_UNICODE_VARIATION_SEQUENCES)
+                 && data[j]->format == 14)
+               *format14 = (struct sfnt_cmap_format_14 *) data[j];
+           }
+
+         return data[i];
+       }
+    }
+
+  /* Finally, use the first cmap that appears and can be
+     identified.  */
+
+  for (i = 0; i < cmap->num_subtables; ++i)
+    {
+      if (data[i] && sfntfont_identify_cmap (subtables[i]) == 0)
+       {
+         *subtable = subtables[i];
+         return data[i];
+       }
+    }
+
+  /* There are no cmaps available to Emacs.  */
+  return NULL;
+}
+
+/* Read the cmap from the font descriptor DESC, and place it in CMAP.
+   Keep *CMAP untouched if opening the cmap fails.  Set SUBTABLE to
+   the cmap's header upon success.  */
+
+static void
+sfntfont_read_cmap (struct sfnt_font_desc *desc,
+                   struct sfnt_cmap_encoding_subtable_data **cmap,
+                   struct sfnt_cmap_encoding_subtable *subtable)
+{
+  struct sfnt_offset_subtable *font;
+  struct sfnt_cmap_encoding_subtable *subtables;
+  struct sfnt_cmap_encoding_subtable_data **data;
+  struct sfnt_cmap_table *table;
+  int fd, i;
+
+  /* Pick a character map and place it in *CMAP.  */
+  fd = emacs_open (desc->path, O_RDONLY, 0);
+
+  if (fd < 0)
+    return;
+
+  /* Seek to the start of the font itself within its collection.  */
+
+  if (desc->offset
+      && lseek (fd, desc->offset, SEEK_SET) != desc->offset)
+    {
+      emacs_close (fd);
+      return;
+    }
+
+  font = sfnt_read_table_directory (fd);
+
+  /* Return if FONT is a TrueType collection: the file pointer should
+     already have been moved to the start of the table directory if
+     so.  */
+
+  if (!font || (font == (struct sfnt_offset_subtable *) -1))
+    {
+      emacs_close (fd);
+      return;
+    }
+
+  table = sfnt_read_cmap_table (fd, font, &subtables,
+                               &data);
+  xfree (font);
+
+  if (!table)
+    {
+      emacs_close (fd);
+      return;
+    }
+
+  /* Now pick the best character map.  */
+
+  *cmap = sfntfont_select_cmap (table, subtables, data,
+                               subtable, NULL);
+
+  /* Free the cmap data.  */
+
+  for (i = 0; i < table->num_subtables; ++i)
+    {
+      if (data[i] != *cmap)
+       xfree (data[i]);
+    }
+
+  xfree (data);
+  xfree (subtables);
+  xfree (table);
+  emacs_close (fd);
+}
+
+/* Return whether or not CHARACTER has an associated mapping in CMAP,
+   and the mapping points to a valid glyph.  DESC is the font
+   descriptor associated with the font.  */
+
+static bool
+sfntfont_glyph_valid (struct sfnt_font_desc *desc,
+                     sfnt_char font_character,
+                     struct sfnt_cmap_encoding_subtable_data *cmap)
+{
+  sfnt_glyph glyph;
+
+  glyph = sfnt_lookup_glyph (font_character, cmap);
+
+  if (!glyph)
+    return false;
+
+  return glyph <= desc->num_glyphs;
+}
+
+/* Look up a character CHARACTER in the font description DESC.  Cache
+   the results.  Return true if the character exists, false otherwise.
+
+   If *CMAP is NULL, select a character map for the font and save it
+   there.  Otherwise, use the character map in *CMAP.  Save data
+   associated with the character map in *SUBTABLE.  */
+
+static bool
+sfntfont_lookup_char (struct sfnt_font_desc *desc, Lisp_Object character,
+                     struct sfnt_cmap_encoding_subtable_data **cmap,
+                     struct sfnt_cmap_encoding_subtable *subtable)
+{
+  Lisp_Object cached;
+  sfnt_char font_character;
+  struct charset *charset;
+  bool present;
+
+  /* Return false for characters that don't fit in a char table.  */
+  if (XFIXNUM (character) > INT_MAX || XFIXNUM (character) < 0)
+    return false;
+
+  if (!NILP (desc->char_cache))
+    {
+      cached = char_table_ref (desc->char_cache,
+                              XFIXNUM (character));
+      if (!NILP (cached))
+       return (EQ (cached, Qlambda) ? false : true);
+    }
+
+  if (!*cmap && !desc->cmap_invalid)
+    sfntfont_read_cmap (desc, cmap, subtable);
+
+  /* Check that a cmap is now present.  */
+  if (!*cmap)
+    {
+      /* Opening the cmap failed.  Set desc->cmap_invalid to avoid
+        opening it again.  */
+      desc->cmap_invalid = true;
+      return false;
+    }
+
+  /* Otherwise, encode the character.  */
+
+  charset = sfntfont_charset_for_cmap (*subtable);
+  if (!charset)
+    /* Emacs missing charsets? */
+    return false;
+
+  font_character = ENCODE_CHAR (charset, (int) XFIXNUM (character));
+
+  if (font_character == CHARSET_INVALID_CODE (charset))
+    return false;
+
+  /* Now return whether or not the glyph is present.  Noto Sans
+     Georgian comes with a corrupt format 4 cmap table that somehow
+     tries to express glyphs greater than 65565.  */
+  present = sfntfont_glyph_valid (desc, font_character, *cmap);
+
+  /* Cache the result.  Store Qlambda when not present, Qt
+     otherwise.  */
+
+  if (NILP (desc->char_cache))
+    desc->char_cache = Fmake_char_table (Qfont_lookup_cache,
+                                        Qnil);
+
+  Fset_char_table_range (desc->char_cache, character,
+                        present ? Qt : Qlambda);
+  return present;
+}
+
+/* Return whether or not the specified registry A is ``compatible''
+   with registry B.
+
+   Compatibility does not refer to whether or not the font registries
+   have an identical character set or repertory of characters.
+
+   Instead, it refers to whether or not Emacs expects looking for A to
+   result in fonts used with B.  */
+
+static bool
+sfntfont_registries_compatible_p (Lisp_Object a, Lisp_Object b)
+{
+  if (EQ (a, Qiso8859_1) && EQ (b, Qiso10646_1))
+    return true;
+
+  return EQ (a, b);
+}
+
+/* Return whether or not the font description DESC satisfactorily
+   matches the font specification FONT_SPEC.
+
+   Value is 0 if there is no match, -1 if there is a match against
+   DESC itself, and the number of matching instances if the style
+   matches one or more instances defined in in DESC.  Return the index
+   of each matching instance in INSTANCES; it should be SIZE big.  */
+
+static int
+sfntfont_list_1 (struct sfnt_font_desc *desc, Lisp_Object spec,
+                int *instances, int size)
+{
+  Lisp_Object tem, extra, tail;
+  struct sfnt_cmap_encoding_subtable_data *cmap;
+  size_t i;
+  struct sfnt_cmap_encoding_subtable subtable;
+  int instance, num_instance;
+  Lisp_Object item;
+
+  /* cmap and subtable are caches for sfntfont_lookup_char.  */
+
+  /* Check that the family name in SPEC matches DESC->family if it is
+     specified.  */
+
+  tem = AREF (spec, FONT_FAMILY_INDEX);
+
+  /* If TEM is a family listed in Vsfnt_default_family_alist,
+     then use that instead.  */
+
+  if (SYMBOLP (tem) && CONSP (Vsfnt_default_family_alist))
+    {
+      tail = Vsfnt_default_family_alist;
+      FOR_EACH_TAIL_SAFE (tail)
+       {
+         if (!CONSP (XCAR (tail)))
+           continue;
+
+         if (STRINGP (XCAR (XCAR (tail)))
+             && STRINGP (XCDR (XCAR (tail)))
+             && !NILP (Fstring_equal (SYMBOL_NAME (tem),
+                                      XCAR (XCAR (tail)))))
+           {
+             /* Special family found.  */
+             tem = Fintern (XCDR (XCAR (tail)), Qnil);
+             break;
+           }
+       }
+    }
+
+  if (!NILP (tem) && NILP (Fstring_equal (SYMBOL_NAME (tem),
+                                         desc->family)))
+    return 0;
+
+  instance = -1;
+
+  /* If a registry is set and wrong, then reject the font desc
+     immediately.  This detects 50% of mismatches from fontset.c.
+
+     If DESC->registry is nil, then the registry couldn't be
+     determined beforehand.  */
+
+  tem = AREF (spec, FONT_REGISTRY_INDEX);
+  if (!NILP (tem) && !NILP (desc->registry)
+      && !sfntfont_registries_compatible_p (tem, desc->registry))
+    return 0;
+
+  /* Check the style.  If DESC is a fixed font, just check once.
+     Otherwise, check each instance.  */
+
+  if (NILP (desc->instances))
+    {
+      tem = AREF (spec, FONT_ADSTYLE_INDEX);
+      if (!NILP (tem) && NILP (Fequal (tem, desc->adstyle)))
+       return 0;
+
+      if (FONT_WIDTH_NUMERIC (spec) != -1
+         && FONT_WIDTH_NUMERIC (spec) != desc->width)
+       return 0;
+
+      if (FONT_WEIGHT_NUMERIC (spec) != -1
+         && FONT_WEIGHT_NUMERIC (spec) != desc->weight)
+       return 0;
+
+      if (FONT_SLANT_NUMERIC (spec) != -1
+         && FONT_SLANT_NUMERIC (spec) != desc->slant)
+       return 0;
+    }
+  else
+    {
+      num_instance = 0;
+
+      /* Find the indices of instances in this distortable font which
+        match the given font spec.  */
+
+      for (i = 0; i < ASIZE (desc->instances); ++i)
+       {
+         item = AREF (desc->instances, i);
+
+         if (NILP (item))
+           continue;
+
+         /* Check that the adstyle specified matches.  */
+
+         tem = AREF (spec, FONT_ADSTYLE_INDEX);
+         if (!NILP (tem) && NILP (Fequal (tem, AREF (item, 1))))
+           continue;
+
+         /* Check the style.  */
+
+         if (FONT_WIDTH_NUMERIC (spec) != -1
+             && (FONT_WIDTH_NUMERIC (spec)
+                 != XFIXNUM (AREF (item, 2))))
+           continue;
+
+         if (FONT_WEIGHT_NUMERIC (spec) != -1
+             && (FONT_WEIGHT_NUMERIC (spec)
+                 != XFIXNUM (AREF (item, 3))))
+           continue;
+
+         if (FONT_SLANT_NUMERIC (spec) != -1
+             && (FONT_SLANT_NUMERIC (spec)
+                 != XFIXNUM (AREF (item, 4))))
+           continue;
+
+         if (num_instance == size)
+           break;
+
+         /* A matching instance has been found.  Set its index, then
+            go back to the rest of the font matching.  */
+         instances[num_instance++] = i;
+       }
+
+      instance = num_instance;
+    }
+
+  /* Handle extras.  */
+  extra = AREF (spec, FONT_EXTRA_INDEX);
+
+  if (NILP (extra))
+    return instance;
+
+  tem = assq_no_quit (QCscript, extra);
+  cmap = NULL;
+
+  if (!NILP (tem))
+    {
+      /* If a script has been specified, look up its representative
+        characters and see if they are present in the font.  This
+        requires reading the cmap.  */
+      tem = assq_no_quit (XCDR (tem), Vscript_representative_chars);
+
+      if (CONSP (tem) && VECTORP (XCDR (tem)))
+       {
+         tem = XCDR (tem);
+
+         /* The vector contains characters, of which one must be
+            present in the font.  */
+         for (i = 0; i < ASIZE (tem); ++i)
+           {
+             if (FIXNUMP (AREF (tem, i)))
+               {
+                 if (!sfntfont_lookup_char (desc, AREF (tem, i),
+                                            &cmap, &subtable))
+                   goto fail;
+
+                 /* One character is enough to pass a font.  Don't
+                    look at too many.  */
+                 break;
+               }
+           }
+       }
+      else if (CONSP (tem) && CONSP (XCDR (tem)))
+       {
+         tem = XCDR (tem);
+
+         /* tem is a list of each characters, all of which must be
+            present in the font.  */
+         FOR_EACH_TAIL_SAFE (tem)
+           {
+             if (FIXNUMP (XCAR (tem))
+                 && !sfntfont_lookup_char (desc, XCAR (tem), &cmap,
+                                           &subtable))
+               goto fail;
+           }
+
+         /* One or more characters are missing.  */
+         if (!NILP (tem))
+           goto fail;
+       }
+      /* Fail if there are no matching fonts at all.  */
+      else if (NILP (tem))
+       goto fail;
+    }
+
+  /* Now check that the language is supported.  */
+  tem = assq_no_quit (QClang, extra);
+  if (!NILP (tem) && NILP (Fmemq (tem, desc->languages)))
+    goto fail;
+
+  /* Set desc->subtable if cmap was specified.  */
+  if (cmap)
+    desc->subtable = subtable;
+
+  xfree (cmap);
+  return instance;
+
+ fail:
+  /* The cmap might've been read in and require deallocation.  */
+  xfree (cmap);
+  return 0;
+}
+
+/* Type of font entities and font objects created.  */
+static Lisp_Object sfnt_vendor_name;
+
+/* Font driver used in font objects created.  */
+static const struct font_driver *sfnt_font_driver;
+
+/* Return the font registry corresponding to the font descriptor DESC.
+   Under X, the font registry is an atom registered with the Open
+   Group uniquely identifying the organization which defines the
+   font's character set.
+
+   In practice, the registry overlaps with the character set itself.
+   So Emacs just uses the ``registry'' field to represent both
+   instead.  */
+
+static Lisp_Object
+sfntfont_registry_for_desc (struct sfnt_font_desc *desc)
+{
+  struct sfnt_cmap_encoding_subtable_data *cmap;
+
+  cmap = NULL;
+
+  if (desc->cmap_invalid)
+    return Qnil;
+
+  if (desc->subtable.platform_id == 500)
+    {
+      /* Read in the cmap to determine the registry.  */
+      sfntfont_read_cmap (desc, &cmap, &desc->subtable);
+
+      if (!cmap)
+       {
+         desc->cmap_invalid = true;
+         return Qnil;
+       }
+    }
+
+  xfree (cmap);
+
+  if (desc->subtable.platform_id != 500)
+    /* desc->subtable.platform_id is now set.  CMAP is already free,
+       because it is not actually used.  */
+    return sfnt_registry_for_subtable (&desc->subtable);
+
+  return Qnil;
+}
+
+/* Return a font-entity that represents the font descriptor (unopened
+   font) DESC.  If INSTANCE is more than or equal to 1, then it is the
+   index of the instance in DESC that should be opened plus 1; in that
+   case, DESC must be a distortable font.  */
+
+static Lisp_Object
+sfntfont_desc_to_entity (struct sfnt_font_desc *desc, int instance)
+{
+  Lisp_Object entity, vector;
+
+  entity = font_make_entity ();
+
+  ASET (entity, FONT_TYPE_INDEX, sfnt_vendor_name);
+
+  if (!NILP (desc->designer))
+    ASET (entity, FONT_FOUNDRY_INDEX,
+         Fintern (desc->designer, Qnil));
+
+  ASET (entity, FONT_FAMILY_INDEX, Fintern (desc->family, Qnil));
+  ASET (entity, FONT_ADSTYLE_INDEX, Qnil);
+  ASET (entity, FONT_REGISTRY_INDEX,
+       sfntfont_registry_for_desc (desc));
+
+  /* Size of 0 means the font is scalable.  */
+  ASET (entity, FONT_SIZE_INDEX, make_fixnum (0));
+  ASET (entity, FONT_AVGWIDTH_INDEX, make_fixnum (0));
+  ASET (entity, FONT_SPACING_INDEX,
+       make_fixnum (desc->spacing));
+
+  if (instance >= 1)
+    {
+      if (NILP (desc->instances)
+         || instance > ASIZE (desc->instances))
+       emacs_abort ();
+
+      vector = AREF (desc->instances, instance - 1);
+      FONT_SET_STYLE (entity, FONT_WIDTH_INDEX,
+                     AREF (vector, 2));
+      FONT_SET_STYLE (entity, FONT_WEIGHT_INDEX,
+                     AREF (vector, 3));
+      FONT_SET_STYLE (entity, FONT_SLANT_INDEX,
+                     AREF (vector, 4));
+      ASET (entity, FONT_ADSTYLE_INDEX, AREF (vector, 1));
+    }
+  else
+    {
+      FONT_SET_STYLE (entity, FONT_WIDTH_INDEX,
+                     make_fixnum (desc->width));
+      FONT_SET_STYLE (entity, FONT_WEIGHT_INDEX,
+                     make_fixnum (desc->weight));
+      FONT_SET_STYLE (entity, FONT_SLANT_INDEX,
+                     make_fixnum (desc->slant));
+      ASET (entity, FONT_ADSTYLE_INDEX, desc->adstyle);
+    }
+
+  /* Set FONT_EXTRA_INDEX to a pointer to the font description.  Font
+     descriptions are never supposed to be freed.  */
+
+  ASET (entity, FONT_EXTRA_INDEX,
+       (instance >= 1
+        ? list2 (Fcons (Qfont_entity, make_mint_ptr (desc)),
+                 Fcons (Qfont_instance, make_fixnum (instance - 1)))
+        : list1 (Fcons (Qfont_entity, make_mint_ptr (desc)))));
+
+  return entity;
+}
+
+/* Return a list of font-entities matching the specified
+   FONT_SPEC.  */
+
+Lisp_Object
+sfntfont_list (struct frame *f, Lisp_Object font_spec)
+{
+  Lisp_Object matching, tem;
+  struct sfnt_font_desc *desc;
+  int i, rc, instances[100];
+
+  matching = Qnil;
+
+  block_input ();
+  /* Returning irrelevant results on receiving an OTF form will cause
+     fontset.c to loop over and over, making displaying some
+     characters very slow.  */
+  tem = assq_no_quit (QCotf, AREF (font_spec, FONT_EXTRA_INDEX));
+  if (CONSP (tem) && !NILP (XCDR (tem)))
+    {
+      unblock_input ();
+      return Qnil;
+    }
+
+  /* Loop through known system fonts and add them one-by-one.  */
+
+  for (desc = system_fonts; desc; desc = desc->next)
+    {
+      rc = sfntfont_list_1 (desc, font_spec, instances,
+                           ARRAYELTS (instances));
+
+      if (rc < 0)
+       matching = Fcons (sfntfont_desc_to_entity (desc, 0),
+                         matching);
+      else if (rc)
+       {
+         /* Add each matching instance.  */
+
+         for (i = 0; i < rc; ++i)
+           matching = Fcons (sfntfont_desc_to_entity (desc,
+                                                      instances[i] + 1),
+                             matching);
+       }
+    }
+
+  unblock_input ();
+
+  return matching;
+}
+
+/* Return the first font-entity matching the specified FONT_SPEC.  */
+
+Lisp_Object
+sfntfont_match (struct frame *f, Lisp_Object font_spec)
+{
+  Lisp_Object matches;
+
+  matches = sfntfont_list (f, font_spec);
+
+  if (!NILP (matches))
+    return XCAR (matches);
+
+  return Qnil;
+}
+
+
+
+enum
+  {
+    SFNT_OUTLINE_CACHE_SIZE = 256,
+    SFNT_RASTER_CACHE_SIZE  = 128,
+  };
+
+/* Caching subsystem.  Generating outlines from glyphs is expensive,
+   and so is rasterizing them, so two caches are maintained for both
+   glyph outlines and rasters.
+
+   Computing metrics also requires some expensive processing if the
+   glyph has instructions or distortions.  */
+
+struct sfnt_outline_cache
+{
+  /* Next and last cache buckets.  */
+  struct sfnt_outline_cache *next, *last;
+
+  /* Pointer to outline.  */
+  struct sfnt_glyph_outline *outline;
+
+  /* Reference to glyph metrics.  */
+  struct sfnt_glyph_metrics metrics;
+
+  /* What glyph this caches.  */
+  sfnt_glyph glyph;
+};
+
+struct sfnt_raster_cache
+{
+  /* Next and last cache buckets.  */
+  struct sfnt_raster_cache *next, *last;
+
+  /* Pointer to raster.  */
+  struct sfnt_raster *raster;
+
+  /* What glyph this caches.  */
+  sfnt_glyph glyph;
+};
+
+struct sfntfont_get_glyph_outline_dcontext
+{
+  /* Long and short loca tables.  */
+  struct sfnt_loca_table_long *loca_long;
+  struct sfnt_loca_table_short *loca_short;
+
+  /* glyf table.  */
+  struct sfnt_glyf_table *glyf;
+
+  /* hmtx, hhea and maxp tables utilized to acquire glyph metrics.  */
+  struct sfnt_hmtx_table *hmtx;
+  struct sfnt_hhea_table *hhea;
+  struct sfnt_maxp_table *maxp;
+
+  /* Variation settings, or NULL.  */
+  struct sfnt_blend *blend;
+};
+
+/* Return the glyph identified by GLYPH_ID from the glyf and loca
+   table specified in DCONTEXT.  Set *NEED_FREE to true.  */
+
+static struct sfnt_glyph *
+sfntfont_get_glyph (sfnt_glyph glyph_id, void *dcontext,
+                   bool *need_free)
+{
+  struct sfntfont_get_glyph_outline_dcontext *tables;
+  struct sfnt_glyph *glyph;
+  struct sfnt_metrics_distortion distortion;
+
+  tables = dcontext;
+  *need_free = true;
+
+  glyph = sfnt_read_glyph (glyph_id, tables->glyf,
+                          tables->loca_short,
+                          tables->loca_long);
+
+  if (tables->blend && glyph)
+    {
+      if (glyph->simple)
+       sfnt_vary_simple_glyph (tables->blend, glyph_id, glyph,
+                               &distortion);
+      else
+       sfnt_vary_compound_glyph (tables->blend, glyph_id, glyph,
+                                 &distortion);
+    }
+
+  /* Note that the distortion is not relevant for compound glyphs.  */
+  return glyph;
+}
+
+/* Free the glyph identified by GLYPH.  */
+
+static void
+sfntfont_free_glyph (struct sfnt_glyph *glyph, void *dcontext)
+{
+  sfnt_free_glyph (glyph);
+}
+
+/* Return unscaled glyph metrics for the glyph designated by the ID
+   GLYPH within *METRICS, utilizing tables within DCONTEXT.
+
+   Value is 1 upon failure, 0 otherwise.  */
+
+static int
+sfntfont_get_metrics (sfnt_glyph glyph, struct sfnt_glyph_metrics *metrics,
+                     void *dcontext)
+{
+  struct sfntfont_get_glyph_outline_dcontext *tables;
+
+  tables = dcontext;
+  return sfnt_lookup_glyph_metrics (glyph, -1, metrics,
+                                   tables->hmtx, tables->hhea,
+                                   NULL, tables->maxp);
+}
+
+/* Dereference the outline OUTLINE.  Free it once refcount reaches
+   0.  */
+
+static void
+sfntfont_dereference_outline (struct sfnt_glyph_outline *outline)
+{
+  eassert (outline->refcount > 0);
+
+  if (--outline->refcount)
+    return;
+
+  xfree (outline);
+}
+
+/* Get the outline corresponding to the specified GLYPH_CODE in CACHE.
+   Use the scale factor SCALE, the glyf table GLYF, and the head table
+   HEAD.  Keep *CACHE_SIZE updated with the number of elements in the
+   cache.
+
+   Distort the glyph using BLEND if INDEX is not -1.
+
+   Use the offset information in the long or short loca tables
+   LOCA_LONG and LOCA_SHORT, whichever is set.
+
+   Use the specified HMTX, HEAD, HHEA and MAXP tables when instructing
+   compound glyphs.
+
+   If INTERPRETER is non-NULL, then possibly use it and the
+   interpreter graphics STATE to instruct the glyph.
+
+   If METRICS is non-NULL, return the scaled glyph metrics after
+   variation and instructing.
+
+   Return the outline with an incremented reference count and enter
+   the generated outline into CACHE upon success, possibly discarding
+   any older outlines, or NULL on failure.  */
+
+static struct sfnt_glyph_outline *
+sfntfont_get_glyph_outline (sfnt_glyph glyph_code,
+                           struct sfnt_outline_cache *cache,
+                           sfnt_fixed scale, int *cache_size,
+                           struct sfnt_blend *blend,
+                           int index,
+                           struct sfnt_glyf_table *glyf,
+                           struct sfnt_head_table *head,
+                           struct sfnt_hmtx_table *hmtx,
+                           struct sfnt_hhea_table *hhea,
+                           struct sfnt_maxp_table *maxp,
+                           struct sfnt_loca_table_short *loca_short,
+                           struct sfnt_loca_table_long *loca_long,
+                           struct sfnt_interpreter *interpreter,
+                           struct sfnt_glyph_metrics *metrics,
+                           struct sfnt_graphics_state *state)
+{
+  struct sfnt_outline_cache *start;
+  struct sfnt_glyph_outline *outline;
+  struct sfnt_glyph *glyph;
+  struct sfntfont_get_glyph_outline_dcontext dcontext;
+  struct sfnt_instructed_outline *value;
+  const char *error;
+  struct sfnt_glyph_metrics temp;
+  struct sfnt_metrics_distortion distortion;
+
+  start = cache->next;
+  distortion.advance = 0;
+
+  /* See if the outline is already cached.  */
+  for (; start != cache; start = start->next)
+    {
+      if (start->glyph == glyph_code)
+       {
+         /* Move start to the start of the ring.  Then increase
+            start->outline->refcount and return it.  */
+
+         start->last->next = start->next;
+         start->next->last = start->last;
+
+         start->next = cache->next;
+         start->last = cache;
+         start->next->last = start;
+         start->last->next = start;
+         start->outline->refcount++;
+
+         if (metrics)
+           *metrics = start->metrics;
+
+         return start->outline;
+       }
+    }
+
+  /* Not already cached.  Get the glyph.  */
+  glyph = sfnt_read_glyph (glyph_code, glyf,
+                          loca_short, loca_long);
+
+  if (!glyph)
+    return NULL;
+
+  /* Distort the glyph if necessary.  */
+
+  if (index != -1)
+    {
+      if (glyph->simple)
+       {
+         if (sfnt_vary_simple_glyph (blend, glyph_code,
+                                     glyph, &distortion))
+           {
+             sfnt_free_glyph (glyph);
+             return NULL;
+           }
+       }
+      else if (sfnt_vary_compound_glyph (blend, glyph_code,
+                                        glyph, &distortion))
+       {
+         sfnt_free_glyph (glyph);
+         return NULL;
+       }
+    }
+
+  /* Try to instruct the glyph if INTERPRETER is specified.  */
+
+  outline = NULL;
+
+  dcontext.loca_long = loca_long;
+  dcontext.loca_short = loca_short;
+  dcontext.glyf = glyf;
+  dcontext.hhea = hhea;
+  dcontext.hmtx = hmtx;
+  dcontext.maxp = maxp;
+  dcontext.blend = (index != -1 ? blend : NULL);
+
+  /* Now load the glyph's unscaled metrics into TEMP.  */
+
+  if (sfnt_lookup_glyph_metrics (glyph_code, -1, &temp, hmtx, hhea,
+                                head, maxp))
+    goto fail;
+
+  /* Add the advance width distortion.  */
+  temp.advance += distortion.advance;
+
+  if (interpreter)
+    {
+      if (glyph->simple)
+       {
+         /* Restore the interpreter state from the snapshot taken
+            after loading the preprogram.  */
+         interpreter->state = *state;
+
+         error = sfnt_interpret_simple_glyph (glyph, interpreter,
+                                              &temp, &value);
+       }
+      else
+       /* Restoring the interpreter state is done by
+          sfnt_interpret_compound_glyph; all that must be done here
+          is to give the graphics state to that function.  */
+       error = sfnt_interpret_compound_glyph (glyph, interpreter,
+                                              state,
+                                              sfntfont_get_glyph,
+                                              sfntfont_free_glyph,
+                                              hmtx, hhea, maxp,
+                                              &temp, &dcontext,
+                                              &value);
+
+      if (!error)
+       {
+         outline = sfnt_build_instructed_outline (value);
+         xfree (value);
+       }
+    }
+
+  if (!outline)
+    {
+      if (!interpreter)
+       outline = sfnt_build_glyph_outline (glyph, scale,
+                                           &temp,
+                                           sfntfont_get_glyph,
+                                           sfntfont_free_glyph,
+                                           sfntfont_get_metrics,
+                                           &dcontext);
+      else
+       outline = sfnt_build_glyph_outline (glyph, scale,
+                                           &temp,
+                                           sfntfont_get_glyph,
+                                           sfntfont_free_glyph,
+                                           sfntfont_get_metrics,
+                                           &dcontext);
+    }
+
+  /* At this point, the glyph metrics are unscaled.  Scale them up.
+     If INTERPRETER is set, use the scale placed within.  */
+
+  sfnt_scale_metrics (&temp, scale);
+
+ fail:
+
+  xfree (glyph);
+
+  if (!outline)
+    return NULL;
+
+  if (index != -1)
+    /* Finally, adjust the left side bearing of the glyph metrics by
+       the origin point of the outline, should a distortion have been
+       applied.  The left side bearing is the distance from the origin
+       point to the left most point on the X axis.  */
+    temp.lbearing = outline->xmin - outline->origin;
+
+  start = xmalloc (sizeof *start);
+  start->glyph = glyph_code;
+  start->outline = outline;
+  start->metrics = temp;
+
+  /* One reference goes to the cache.  The second reference goes to
+     the caller.  */
+  outline->refcount = 2;
+
+  /* Link start onto the cache.  */
+  start->next = cache->next;
+  start->last = cache;
+  start->next->last = start;
+  start->last->next = start;
+
+  /* Update the cache size.  */
+  (*cache_size)++;
+
+  /* Figure out if the least recently used element has to be
+     evicted.  */
+  if (*cache_size > SFNT_OUTLINE_CACHE_SIZE)
+    {
+      start = cache->last;
+      eassert (start != cache);
+
+      /* Free the least recently used entry in the cache.  */
+      start->last->next = start->next;
+      start->next->last = start->last;
+      sfntfont_dereference_outline (start->outline);
+      xfree (start);
+
+      (*cache_size)--;
+    }
+
+  /* Return the cached outline and metrics.  */
+
+  if (metrics)
+    *metrics = temp;
+
+  return outline;
+}
+
+/* Free the outline cache referred to by CACHE.  Dereference each
+   outline contained therein.  */
+
+static void
+sfntfont_free_outline_cache (struct sfnt_outline_cache *cache)
+{
+  struct sfnt_outline_cache *next, *last;
+
+  /* Handle partly initialized fonts.  */
+  if (!cache->next)
+    return;
+
+  for (next = cache->next; next != cache;)
+    {
+      last = next;
+      next = next->next;
+
+      sfntfont_dereference_outline (last->outline);
+      xfree (last);
+    }
+
+  cache->next = cache;
+  cache->last = cache;
+}
+
+/* Dereference the raster RASTER.  Free it once refcount reaches
+   0.  */
+
+static void
+sfntfont_dereference_raster (struct sfnt_raster *raster)
+{
+  eassert (raster->refcount > 0);
+
+  if (--raster->refcount)
+    return;
+
+  xfree (raster);
+}
+
+/* Get the raster corresponding to the specified GLYPH_CODE in CACHE.
+   Use the outline named OUTLINE.  Keep *CACHE_SIZE updated with the
+   number of elements in the cache.  */
+
+static struct sfnt_raster *
+sfntfont_get_glyph_raster (sfnt_glyph glyph_code,
+                          struct sfnt_raster_cache *cache,
+                          struct sfnt_glyph_outline *outline,
+                          int *cache_size)
+{
+  struct sfnt_raster_cache *start;
+  struct sfnt_raster *raster;
+
+  /* See if the raster is already cached.  */
+  start = cache->next;
+
+  for (; start != cache; start = start->next)
+    {
+      if (start->glyph == glyph_code)
+       {
+         /* Move start to the start of the ring.  Them, increase
+            start->raster->refcount and return it.  */
+
+         start->last->next = start->next;
+         start->next->last = start->last;
+
+         start->next = cache->next;
+         start->last = cache;
+         start->next->last = start;
+         start->last->next = start;
+         start->raster->refcount++;
+
+         return start->raster;
+       }
+    }
+
+  /* Not already cached.  Raster the outline.  */
+  raster = sfnt_raster_glyph_outline (outline);
+
+  if (!raster)
+    return NULL;
+
+  start = xmalloc (sizeof *start);
+  start->glyph = glyph_code;
+  start->raster = raster;
+
+  /* One reference goes to the cache.  The second reference goes to
+     the caller.  */
+  raster->refcount = 2;
+
+  /* Link start onto the cache.  */
+  start->next = cache->next;
+  start->last = cache;
+  start->next->last = start;
+  start->last->next = start;
+
+  /* Update the cache size.  */
+  (*cache_size)++;
+
+  /* Figure out if the least recently used element has to be
+     evicted.  */
+  if (*cache_size > SFNT_OUTLINE_CACHE_SIZE)
+    {
+      start = cache->last;
+      eassert (start != cache);
+
+      /* Free the least recently used entry in the cache.  */
+      start->last->next = start->next;
+      start->next->last = start->last;
+      sfntfont_dereference_raster (start->raster);
+      xfree (start);
+
+      (*cache_size)--;
+    }
+
+  /* Return the cached raster.  */
+  return raster;
+}
+
+/* Free the raster cache referred to by CACHE.  Dereference each
+   raster contained therein.  */
+
+static void
+sfntfont_free_raster_cache (struct sfnt_raster_cache *cache)
+{
+  struct sfnt_raster_cache *next, *last;
+
+  /* Handle partly initialized fonts.  */
+  if (!cache->next)
+    return;
+
+  for (next = cache->next; next != cache;)
+    {
+      last = next;
+      next = next->next;
+
+      sfntfont_dereference_raster (last->raster);
+      xfree (last);
+    }
+
+  cache->next = cache;
+  cache->last = cache;
+}
+
+
+
+/* Opening fonts.  */
+
+struct sfnt_font_info
+{
+  /* Parent font structure.  */
+  struct font font;
+
+#ifdef HAVE_MMAP
+  /* The next font in this chain.  */
+  struct sfnt_font_info *next;
+#endif /* HAVE_MMAP */
+
+  /* The font description used to create this font.  Used to
+     dereference tables associated with this font.  */
+  struct sfnt_font_desc *desc;
+
+  /* Various tables required to use the font.  */
+  struct sfnt_cmap_table *cmap;
+  struct sfnt_hhea_table *hhea;
+  struct sfnt_maxp_table *maxp;
+  struct sfnt_head_table *head;
+  struct sfnt_hmtx_table *hmtx;
+  struct sfnt_glyf_table *glyf;
+  struct sfnt_loca_table_short *loca_short;
+  struct sfnt_loca_table_long *loca_long;
+  struct sfnt_prep_table *prep;
+  struct sfnt_fpgm_table *fpgm;
+  struct sfnt_cvt_table *cvt;
+
+  /* The selected character map.  */
+  struct sfnt_cmap_encoding_subtable_data *cmap_data;
+
+  /* Data identifying that character map.  */
+  struct sfnt_cmap_encoding_subtable cmap_subtable;
+
+  /* The UVS context.  */
+  struct sfnt_uvs_context *uvs;
+
+  /* Outline cache.  */
+  struct sfnt_outline_cache outline_cache;
+
+  /* Number of elements in the outline cache.  */
+  int outline_cache_size;
+
+  /* Raster cache.  */
+  struct sfnt_raster_cache raster_cache;
+
+  /* Number of elements in the raster cache.  */
+  int raster_cache_size;
+
+  /* Interpreter for grid fitting (if enabled).  */
+  struct sfnt_interpreter *interpreter;
+
+  /* Graphics state after the execution of the font and control value
+     programs.  */
+  struct sfnt_graphics_state state;
+
+  /* Factor used to convert from em space to pixel space.  */
+  sfnt_fixed scale;
+
+  /* The blend (configuration of this multiple master font).  */
+  struct sfnt_blend blend;
+
+  /* The index of the named instance used to initialize BLEND.
+     -1 if BLEND is not initialized.  */
+  int instance;
+
+#ifdef HAVE_MMAP
+  /* Whether or not the glyph table has been mmapped.  */
+  bool glyf_table_mapped;
+#endif /* HAVE_MMAP */
+
+#ifdef HAVE_HARFBUZZ
+  /* HarfBuzz font object.  */
+  hb_font_t *hb_font;
+
+  /* File descriptor associated with this font.  */
+  int fd;
+
+  /* The table directory of the font file.  */
+  struct sfnt_offset_subtable *directory;
+#endif /* HAVE_HARFBUZZ */
+};
+
+#ifdef HAVE_MMAP
+
+/* List of all open fonts.  */
+
+static struct sfnt_font_info *open_fonts;
+
+#endif /* HAVE_MMAP */
+
+/* Look up the glyph corresponding to the character C in FONT.  Return
+   0 upon failure, and the glyph otherwise.  */
+
+static sfnt_glyph
+sfntfont_lookup_glyph (struct sfnt_font_info *font_info, int c)
+{
+  struct charset *charset;
+  sfnt_char character;
+  sfnt_glyph glyph;
+
+  charset = CHARSET_FROM_ID (font_info->font.encoding_charset);
+
+  if (!charset)
+    return 0;
+
+  character = ENCODE_CHAR (charset, c);
+
+  if (character == CHARSET_INVALID_CODE (charset))
+    return 0;
+
+  /* Do the actual lookup with the encoded character.  */
+  glyph = sfnt_lookup_glyph (character, font_info->cmap_data);
+
+  return glyph;
+}
+
+/* Probe and set FONT_INFO->font.average_width,
+   FONT_INFO->font.space_width, and FONT_INFO->font.min_width
+   according to the tables contained therein.  */
+
+static void
+sfntfont_probe_widths (struct sfnt_font_info *font_info)
+{
+  int i, num_characters, total_width;
+  sfnt_glyph glyph;
+  struct sfnt_glyph_metrics metrics;
+
+  num_characters = 0;
+  total_width = 0;
+
+  /* First set some reasonable default values.  */
+  font_info->font.average_width = font_info->font.pixel_size;
+  font_info->font.space_width = font_info->font.pixel_size;
+  font_info->font.min_width = 1;
+
+  /* Next, loop through the common ASCII characters.  Tally up their
+     advance widths and set space_width if necessary.  */
+  for (i = 0; i < 127; ++i)
+    {
+      glyph = sfntfont_lookup_glyph (font_info, i);
+
+      if (!glyph)
+       continue;
+
+      /* Now look up the metrics of this glyph.  */
+      if (sfnt_lookup_glyph_metrics (glyph, font_info->font.pixel_size,
+                                    &metrics, font_info->hmtx,
+                                    font_info->hhea, font_info->head,
+                                    font_info->maxp))
+       continue;
+
+      /* Increase the number of characters.  */
+      num_characters++;
+
+      /* Add the advance to total_width.  */
+      total_width += SFNT_CEIL_FIXED (metrics.advance) / 65536;
+
+      /* Update min_width if it hasn't been set yet or is wider.  */
+      if (font_info->font.min_width == 1
+         || font_info->font.min_width > metrics.advance / 65536)
+       font_info->font.min_width = metrics.advance / 65536;
+
+      /* If i is the space character, set the space width.  Make sure
+        to round this up.  */
+      if (i == 32)
+       font_info->font.space_width
+         = SFNT_CEIL_FIXED (metrics.advance) / 65536;
+    }
+
+  /* Now, if characters were found, set average_width.  */
+  if (num_characters)
+    font_info->font.average_width = total_width / num_characters;
+}
+
+/* Initialize the instruction interpreter for INFO.  Load the font and
+   preprogram for the pixel size in INFO and its corresponding point
+   size POINT_SIZE.  Use the FVAR table in DESC.
+
+   The font tables in INFO must already have been initialized.
+
+   Set INFO->interpreter upon success, and leave that field intact
+   otherwise.  */
+
+static void
+sfntfont_setup_interpreter (struct sfnt_font_info *info,
+                           struct sfnt_font_desc *desc,
+                           int point_size)
+{
+  struct sfnt_cvt_table *cvt;
+  struct sfnt_fpgm_table *fpgm;
+  struct sfnt_prep_table *prep;
+  struct sfnt_interpreter *interpreter;
+  const char *error;
+  struct sfnt_graphics_state state;
+
+  /* Load the cvt, fpgm and prep already read.  */
+
+  cvt  = info->cvt ;
+  fpgm = info->fpgm;
+  prep = info->prep;
+
+  /* If both fpgm and prep are NULL, this font likely has no
+     instructions, so don't bother setting up the interpreter.  */
+
+  if (!fpgm && !prep)
+    goto bail;
+
+  /* If the interpreter does not use the operand stack at all, it is
+     useless.  In addition, some broken fonts specify some unnecessary
+     instructions in prep and set head->max_stack_elements to 0.
+
+     Don't create the interpreter in that case.  */
+
+  if (!info->maxp->max_stack_elements)
+    goto bail;
+
+  /* Now, create the interpreter using the limits in info->maxp and
+     info->head.  CVT can be NULL.  */
+
+  interpreter = sfnt_make_interpreter (info->maxp, cvt, info->head,
+                                      desc->tables->fvar,
+                                      info->font.pixel_size,
+                                      point_size);
+
+  /* Bail if the interpreter couldn't be created.  */
+  if (!interpreter)
+    goto bail;
+
+  if (fpgm)
+    {
+      /* Otherwise, evaluate the font and cvt programs.
+
+        FIXME: make sure infinite loops inside these programs
+        cannot lock up Emacs.  */
+
+      error = sfnt_interpret_font_program (interpreter, fpgm);
+
+      if (error)
+       {
+         /* If an error occurs, log it to the *Messages* buffer.  */
+         message_with_string ("While interpreting font program: %s",
+                              build_string (error), true);
+         goto bail1;
+       }
+
+      /* Save the graphics state.  */
+      state = interpreter->state;
+    }
+
+  if (prep)
+    {
+      /* This will overwrite state if the instruction control is set
+        appropriately.  */
+      error = sfnt_interpret_control_value_program (interpreter, prep,
+                                                   &state);
+
+      if (error)
+       {
+         /* If an error occurs, log it to the *Messages* buffer.  */
+         message_with_string ("While interpreting preprogram: %s",
+                              build_string (error), true);
+         goto bail1;
+       }
+    }
+
+  /* The interpreter has been properly set up.  */
+  info->fpgm = fpgm;
+  info->prep = prep;
+  info->cvt = cvt;
+  info->state = state;
+  info->interpreter = interpreter;
+
+  return;
+
+ bail1:
+  xfree (interpreter);
+ bail:
+  return;
+}
+
+/* Free each of the tables opened by `sfnt_open_tables', and possibly
+   file descriptors as well.  Then, free TABLES itself.  */
+
+static void
+sfnt_close_tables (struct sfnt_font_tables *tables)
+{
+  int rc;
+
+  xfree (tables->cmap);
+  xfree (tables->hhea);
+  xfree (tables->maxp);
+  xfree (tables->head);
+  xfree (tables->hmtx);
+#ifdef HAVE_MMAP
+  if (tables->glyf_table_mapped)
+    {
+      rc = sfnt_unmap_glyf_table (tables->glyf);
+
+      if (rc)
+       emacs_abort ();
+    }
+  else
+#endif /* HAVE_MMAP */
+    xfree (tables->glyf);
+  xfree (tables->loca_short);
+  xfree (tables->loca_long);
+  xfree (tables->prep);
+  xfree (tables->fpgm);
+  xfree (tables->cvt);
+  xfree (tables->fvar);
+  xfree (tables->avar);
+  xfree (tables->gvar);
+  xfree (tables->cvar);
+  xfree (tables->cmap_data);
+
+  if (tables->uvs)
+    sfnt_free_uvs_context (tables->uvs);
+
+#ifdef HAVE_HARFBUZZ
+  /* Close the font file.  */
+
+  if (tables->fd != -1)
+    {
+      emacs_close (tables->fd);
+      tables->fd = -1;
+    }
+
+  /* Free its table directory.  */
+  xfree (tables->directory);
+  tables->directory = NULL;
+#endif
+}
+
+/* Open font tables associated with the specified font description
+   DESC.  Return the font tables, or NULL upon failure.  */
+
+static struct sfnt_font_tables *
+sfnt_open_tables (struct sfnt_font_desc *desc)
+{
+  struct sfnt_font_tables *tables;
+  struct sfnt_offset_subtable *subtable;
+  int fd, i, rc;
+  struct sfnt_cmap_encoding_subtable *subtables;
+  struct sfnt_cmap_encoding_subtable_data **data;
+  struct sfnt_cmap_format_14 *format14;
+
+  tables = xzalloc (sizeof *tables);
+
+  /* Open the font.  */
+  fd = emacs_open (desc->path, O_RDONLY, 0);
+
+  if (fd == -1)
+    goto bail;
+
+  /* Seek to the offset specified to the table directory.  */
+
+  if (desc->offset
+      && lseek (fd, desc->offset, SEEK_SET) != desc->offset)
+    goto bail;
+
+  /* Read the offset subtable.  */
+  subtable = sfnt_read_table_directory (fd);
+
+  if (!subtable || (subtable == (struct sfnt_offset_subtable *) -1))
+    goto bail1;
+
+  /* Read required tables.  This font backend is supposed to be used
+     mostly on devices with flash memory, so the order in which they
+     are read is insignificant.  */
+
+  tables->cmap = sfnt_read_cmap_table (fd, subtable, &subtables,
+                                      &data);
+  if (!tables->cmap)
+    goto bail2;
+
+  format14 = NULL;
+  tables->cmap_data
+    = sfntfont_select_cmap (tables->cmap,
+                           subtables, data,
+                           &tables->cmap_subtable,
+                           &format14);
+
+  if (format14)
+    {
+      /* Build a UVS context from this format 14 mapping table.  A UVS
+         context contains each variation selector supported by the
+         font, and a list of ``non-default'' mappings between base
+         characters and variation glyph IDs.  */
+
+      tables->uvs = sfnt_create_uvs_context (format14, fd);
+      xfree (format14);
+    }
+
+  for (i = 0; i < tables->cmap->num_subtables; ++i)
+    {
+      if (data[i] != tables->cmap_data
+         /* format14 has already been freed.  */
+         && data[i] != (struct sfnt_cmap_encoding_subtable_data *) format14)
+       xfree (data[i]);
+    }
+
+  xfree (subtables);
+  xfree (data);
+
+  if (!tables->cmap_data)
+    goto bail3;
+
+  /* Read the hhea, maxp, glyf, and head tables.  */
+  tables->hhea = sfnt_read_hhea_table (fd, subtable);
+  tables->maxp = sfnt_read_maxp_table (fd, subtable);
+
+#ifdef HAVE_MMAP
+
+  /* First try to map the glyf table.  If that fails, then read the
+     glyf table.  */
+
+  tables->glyf = sfnt_map_glyf_table (fd, subtable);
+
+  /* Next, if this fails, read the glyf table.  */
+
+  if (!tables->glyf)
+#endif /* HAVE_MMAP */
+    tables->glyf = sfnt_read_glyf_table (fd, subtable);
+#ifdef HAVE_MMAP
+  else
+    tables->glyf_table_mapped = true;
+#endif /* HAVE_MMAP */
+
+  tables->head = sfnt_read_head_table (fd, subtable);
+
+  /* If any of those tables couldn't be read, bail.  */
+  if (!tables->hhea || !tables->maxp || !tables->glyf
+      || !tables->head)
+    goto bail4;
+
+  /* Now figure out which kind of loca table must be read based on
+     head->index_to_loc_format.  */
+
+  if (tables->head->index_to_loc_format)
+    {
+      tables->loca_long
+       = sfnt_read_loca_table_long (fd, subtable);
+
+      if (!tables->loca_long)
+       goto bail4;
+    }
+  else
+    {
+      tables->loca_short
+       = sfnt_read_loca_table_short (fd, subtable);
+
+      if (!tables->loca_short)
+       goto bail4;
+    }
+
+  /* Read the horizontal metrics table.  */
+  tables->hmtx = sfnt_read_hmtx_table (fd, subtable,
+                                      tables->hhea,
+                                      tables->maxp);
+  if (!tables->hmtx)
+    goto bail5;
+
+  /* Read instruction related font tables.  These might not be
+     present, which is OK, since instructing fonts is optional.  */
+  tables->prep = sfnt_read_prep_table (fd, subtable);
+  tables->fpgm = sfnt_read_fpgm_table (fd, subtable);
+  tables->cvt  = sfnt_read_cvt_table (fd, subtable);
+
+  /* Read distortion related tables.  These might not be present.  */
+  tables->fvar = sfnt_read_fvar_table (fd, subtable);
+  tables->avar = sfnt_read_avar_table (fd, subtable);
+  tables->gvar = sfnt_read_gvar_table (fd, subtable);
+
+  if (tables->cvt && tables->fvar)
+    tables->cvar = sfnt_read_cvar_table (fd, subtable, tables->fvar,
+                                        tables->cvt);
+
+#ifdef HAVE_HARFBUZZ
+  /* Now copy over the subtable if necessary, as it is needed to read
+     extra font tables required by HarfBuzz.  */
+  tables->directory = subtable;
+  tables->fd = fd;
+#else /* !HAVE_HARFBUZZ */
+  /* Otherwise, close the fd and free the table directory.  */
+  xfree (subtable);
+  emacs_close (fd);
+#endif /* HAVE_HARFBUZZ */
+
+  return tables;
+
+ bail5:
+  xfree (tables->loca_long);
+  xfree (tables->loca_short);
+ bail4:
+  xfree (tables->hhea);
+  xfree (tables->maxp);
+
+#ifdef HAVE_MMAP
+  if (tables->glyf_table_mapped)
+    {
+      rc = sfnt_unmap_glyf_table (tables->glyf);
+
+      if (rc)
+       emacs_abort ();
+    }
+  else
+#endif /* HAVE_MMAP */
+    xfree (tables->glyf);
+
+  xfree (tables->head);
+
+  /* This comes under bail4 due to a peculiarity of how the four
+     tables above are validated.  */
+  xfree (tables->cmap_data);
+ bail3:
+  if (tables->uvs)
+    sfnt_free_uvs_context (tables->uvs);
+
+  xfree (tables->cmap);
+ bail2:
+  xfree (subtable);
+ bail1:
+  emacs_close (fd);
+ bail:
+  xfree (tables);
+  return NULL;
+}
+
+/* Open or reference font tables corresponding to the specified font
+   DESC.  Return NULL upon failure.  */
+
+static struct sfnt_font_tables *
+sfnt_reference_font_tables (struct sfnt_font_desc *desc)
+{
+  if (desc->refcount)
+    {
+      desc->refcount++;
+      return desc->tables;
+    }
+
+  desc->tables = sfnt_open_tables (desc);
+
+  if (!desc->tables)
+    return NULL;
+
+  desc->refcount++;
+  return desc->tables;
+}
+
+/* Dereference font tables corresponding to the specified font
+   DESC.  */
+
+static void
+sfnt_dereference_font_tables (struct sfnt_font_desc *desc)
+{
+  if (!desc->refcount)
+    emacs_abort ();
+
+  if (--desc->refcount)
+    return;
+
+  sfnt_close_tables (desc->tables);
+  desc->tables = NULL;
+  return;
+}
+
+/* Open the font corresponding to the font-entity FONT_ENTITY.  Return
+   nil upon failure, else the opened font-object.  */
+
+Lisp_Object
+sfntfont_open (struct frame *f, Lisp_Object font_entity,
+              int pixel_size)
+{
+  struct sfnt_font_info *font_info;
+  struct font *font;
+  struct sfnt_font_desc *desc;
+  Lisp_Object font_object;
+  struct charset *charset;
+  int point_size, instance, i;
+  Display_Info *dpyinfo;
+  struct sfnt_font_tables *tables;
+  Lisp_Object tem;
+
+  if (XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX)) != 0)
+    pixel_size = XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX));
+  else if (pixel_size == 0)
+    {
+      /* This bit was copied from xfont.c.  The values might need
+        adjustment.  */
+
+      if (FRAME_FONT (f))
+       pixel_size = FRAME_FONT (f)->pixel_size;
+      else
+       pixel_size = 12;
+    }
+
+  /* Now find the font description corresponding to FONT_ENTITY.  */
+
+  tem = AREF (font_entity, FONT_EXTRA_INDEX);
+  if (NILP (tem))
+    return Qnil;
+
+  desc = xmint_pointer (XCDR (XCAR (tem)));
+
+  /* Finally, see if a specific instance is associated with
+     FONT_ENTITY.  */
+
+  instance = -1;
+  if (!NILP (XCDR (tem)))
+    instance = XFIXNUM (XCDR (XCAR (XCDR (tem))));
+
+  /* Build the font object.  */
+  font_object = font_make_object (VECSIZE (struct sfnt_font_info),
+                                 font_entity, pixel_size);
+  font_info = (struct sfnt_font_info *) XFONT_OBJECT (font_object);
+
+  block_input ();
+
+  /* Initialize all the font driver specific data.  */
+
+  font_info->cmap = NULL;
+  font_info->hhea = NULL;
+  font_info->maxp = NULL;
+  font_info->head = NULL;
+  font_info->glyf = NULL;
+  font_info->hmtx = NULL;
+  font_info->loca_short = NULL;
+  font_info->loca_long = NULL;
+  font_info->cmap_data = NULL;
+  font_info->prep = NULL;
+  font_info->fpgm = NULL;
+  font_info->cvt = NULL;
+  font_info->uvs = NULL;
+
+  font_info->outline_cache.next = &font_info->outline_cache;
+  font_info->outline_cache.last = &font_info->outline_cache;
+  font_info->outline_cache_size = 0;
+  font_info->raster_cache.next = &font_info->raster_cache;
+  font_info->raster_cache.last = &font_info->raster_cache;
+  font_info->raster_cache_size = 0;
+  font_info->interpreter = NULL;
+  font_info->scale = 0;
+  font_info->instance = -1;
+  font_info->blend.coords = NULL;
+#ifdef HAVE_MMAP
+  font_info->glyf_table_mapped = false;
+#endif /* HAVE_MMAP */
+#ifdef HAVE_HARFBUZZ
+  font_info->hb_font = NULL;
+  font_info->fd = -1;
+  font_info->directory = NULL;
+#endif /* HAVE_HARFBUZZ */
+
+  /* Read required tables.  This font backend is supposed to be used
+     mostly on devices with flash memory, so the order in which they
+     are read is insignificant.  */
+
+  tables = sfnt_reference_font_tables (desc);
+
+  if (!tables)
+    goto bail;
+
+  /* Copy fields from the table structure to the font for fast
+     access.  */
+  font_info->cmap = tables->cmap;
+  font_info->hhea = tables->hhea;
+  font_info->maxp = tables->maxp;
+  font_info->head = tables->head;
+  font_info->hmtx = tables->hmtx;
+  font_info->glyf = tables->glyf;
+  font_info->loca_short = tables->loca_short;
+  font_info->loca_long = tables->loca_long;
+  font_info->prep = tables->prep;
+  font_info->fpgm = tables->fpgm;
+  font_info->cvt  = tables->cvt ;
+  font_info->cmap_data = tables->cmap_data;
+  font_info->cmap_subtable = tables->cmap_subtable;
+  font_info->uvs = tables->uvs;
+
+  /* Calculate the font's scaling factor.  */
+  font_info->scale = sfnt_get_scale (font_info->head, pixel_size);
+
+  /* Fill in font data.  */
+  font = &font_info->font;
+  font->pixel_size = pixel_size;
+  font->driver = sfnt_font_driver;
+  font->encoding_charset = font->repertory_charset = -1;
+
+  /* Figure out which character set to use.  */
+  charset = sfntfont_charset_for_cmap (font_info->cmap_subtable);
+
+  if (!charset)
+    goto bail6;
+
+  /* Set the character set IDs.  */
+  font->encoding_charset = charset->id;
+  font->repertory_charset = charset->id;
+
+  /* Figure out the font ascent and descent.  */
+  font->ascent
+    = ceil (font_info->hhea->ascent
+           * pixel_size * 1.0 / font_info->head->units_per_em);
+  font->descent
+    = -floor (font_info->hhea->descent
+             * pixel_size * 1.0 / font_info->head->units_per_em);
+  font->height = font->ascent + font->descent;
+
+  /* Set font->max_width to the maximum advance width.  */
+  font->max_width = (font_info->hhea->advance_width_max
+                    * pixel_size * 1.0 / font_info->head->units_per_em);
+
+  /* Set generic attributes such as type and style.  */
+  ASET (font_object, FONT_TYPE_INDEX, sfnt_vendor_name);
+
+  if (!NILP (desc->designer))
+    ASET (font_object, FONT_FOUNDRY_INDEX,
+         Fintern (desc->designer, Qnil));
+
+  ASET (font_object, FONT_FAMILY_INDEX, Fintern (desc->family, Qnil));
+  ASET (font_object, FONT_ADSTYLE_INDEX, Qnil);
+  ASET (font_object, FONT_REGISTRY_INDEX,
+       sfntfont_registry_for_desc (desc));
+
+  /* Size of 0 means the font is scalable.  */
+  ASET (font_object, FONT_SIZE_INDEX, make_fixnum (0));
+  ASET (font_object, FONT_AVGWIDTH_INDEX, make_fixnum (0));
+  ASET (font_object, FONT_SPACING_INDEX,
+       make_fixnum (desc->spacing));
+
+  /* Set the font style.  */
+
+  FONT_SET_STYLE (font_object, FONT_WIDTH_INDEX,
+                 make_fixnum (desc->width));
+  FONT_SET_STYLE (font_object, FONT_WEIGHT_INDEX,
+                 make_fixnum (desc->weight));
+  FONT_SET_STYLE (font_object, FONT_SLANT_INDEX,
+                 make_fixnum (desc->slant));
+
+  ASET (font_object, FONT_ADSTYLE_INDEX, Qnil);
+
+  /* Find out the minimum, maximum and average widths.  */
+  sfntfont_probe_widths (font_info);
+
+  /* Clear various offsets.  */
+  font_info->font.baseline_offset = 0;
+  font_info->font.relative_compose = 0;
+  font_info->font.default_ascent = 0;
+  font_info->font.vertical_centering = 0;
+  font_info->font.underline_position = -1;
+  font_info->font.underline_thickness = 0;
+
+  /* Now try to set up grid fitting for this font.  */
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+  point_size = PIXEL_TO_POINT (pixel_size, (dpyinfo->resx
+                                           * dpyinfo->resy
+                                           / 2));
+  sfntfont_setup_interpreter (font_info, desc, point_size);
+
+  /* If an instance was specified and the font is distortable, set up
+     the blend.  */
+
+  if (instance != -1
+      && desc->tables->fvar && desc->tables->gvar
+      /* Make sure the instance is within range.  */
+      && instance < desc->tables->fvar->instance_count)
+    {
+      tem = AREF (desc->instances, instance);
+
+      if (!NILP (tem))
+       {
+         sfnt_init_blend (&font_info->blend, desc->tables->fvar,
+                          desc->tables->gvar, desc->tables->avar,
+                          desc->tables->cvar);
+
+         /* Copy over the coordinates.  */
+         for (i = 0; i < desc->tables->fvar->axis_count; ++i)
+           font_info->blend.coords[i]
+             = desc->tables->fvar->instance[instance].coords[i];
+
+         sfnt_normalize_blend (&font_info->blend);
+
+         /* Test whether or not the instance is actually redundant,
+            as all of its axis are at their default values.  If so,
+            free the instance.  */
+
+         for (i = 0; i < desc->tables->fvar->axis_count; ++i)
+           {
+             if (font_info->blend.norm_coords[i])
+               break;
+           }
+
+         if (i == desc->tables->fvar->axis_count)
+           {
+             sfnt_free_blend (&font_info->blend);
+             goto cancel_blend;
+           }
+
+         /* If an interpreter was specified, distort it now.  */
+
+         if (font_info->interpreter)
+           sfnt_vary_interpreter (font_info->interpreter,
+                                  &font_info->blend);
+
+         font_info->instance = instance;
+
+         /* Replace the style information with that of the
+            instance.  */
+
+         FONT_SET_STYLE (font_object, FONT_WIDTH_INDEX,
+                         AREF (tem, 2));
+         FONT_SET_STYLE (font_object, FONT_WEIGHT_INDEX,
+                         AREF (tem, 3));
+         FONT_SET_STYLE (font_object, FONT_SLANT_INDEX,
+                         AREF (tem, 4));
+         ASET (font_object, FONT_ADSTYLE_INDEX, Qnil);
+       }
+    }
+
+ cancel_blend:
+  /* Calculate the xfld name.  */
+  font->props[FONT_NAME_INDEX] = Ffont_xlfd_name (font_object, Qnil);
+
+#ifdef HAVE_HARFBUZZ
+  /* HarfBuzz will potentially read font tables after the font has
+     been opened by Emacs.  Keep the font open, and record its offset
+     subtable.  */
+  font_info->fd = tables->fd;
+  font_info->directory = tables->directory;
+#endif /* HAVE_HARFBUZZ */
+
+  /* Set font->desc so that font tables can be dereferenced if
+     anything goes wrong.  */
+  font_info->desc = desc;
+
+#ifdef HAVE_MMAP
+  /* Link the font onto the font table.  */
+  font_info->next = open_fonts;
+  open_fonts = font_info;
+#endif /* HAVE_MMAP */
+
+  /* All done.  */
+  unblock_input ();
+  return font_object;
+
+ bail6:
+  sfnt_dereference_font_tables (desc);
+  font_info->desc = NULL;
+ bail:
+  unblock_input ();
+  return Qnil;
+}
+
+
+
+/* Metrics computation and other similar font backend functions.  */
+
+/* Return the glyph code corresponding to C inside the font-object
+   FONT.  Value is the glyph code upon success, else
+   FONT_INVALID_CODE.  */
+
+unsigned int
+sfntfont_encode_char (struct font *font, int c)
+{
+  sfnt_glyph glyph;
+
+  /* Now look up the glyph.  */
+  glyph = sfntfont_lookup_glyph ((struct sfnt_font_info *) font, c);
+
+  if (!glyph)
+    return FONT_INVALID_CODE;
+
+  return glyph;
+}
+
+/* Measure the single glyph GLYPH in the font FONT and return its
+   metrics in *PCM.
+
+   Instruct the glyph if possible.
+
+   Value is 0 upon success, 1 otherwise.  */
+
+static int
+sfntfont_measure_pcm (struct sfnt_font_info *font, sfnt_glyph glyph,
+                     struct font_metrics *pcm)
+{
+  struct sfnt_glyph_metrics metrics;
+  struct sfnt_glyph_outline *outline;
+
+  /* Now get the glyph outline, which is required to obtain the rsb,
+     ascent and descent.  */
+  outline = sfntfont_get_glyph_outline (glyph, &font->outline_cache,
+                                       font->scale,
+                                       &font->outline_cache_size,
+                                       &font->blend,
+                                       font->instance,
+                                       font->glyf, font->head,
+                                       font->hmtx, font->hhea,
+                                       font->maxp,
+                                       font->loca_short,
+                                       font->loca_long,
+                                       font->interpreter, &metrics,
+                                       &font->state);
+
+  if (!outline)
+    return 1;
+
+  /* Round the left side bearing downwards.  */
+  pcm->lbearing = SFNT_FLOOR_FIXED (metrics.lbearing) / 65536;
+  pcm->rbearing = SFNT_CEIL_FIXED (outline->xmax) / 65536;
+
+  /* Round the advance, ascent and descent upwards.  */
+  pcm->width = SFNT_CEIL_FIXED (metrics.advance) / 65536;
+  pcm->ascent = SFNT_CEIL_FIXED (outline->ymax) / 65536;
+  pcm->descent = SFNT_CEIL_FIXED (-outline->ymin) / 65536;
+
+  sfntfont_dereference_outline (outline);
+  return 0;
+}
+
+/* Return the total text extents of NGLYPHS glyphs given as CODE in
+   the single font metrics array METRICS.  */
+
+void
+sfntfont_text_extents (struct font *font, const unsigned int *code,
+                      int nglyphs, struct font_metrics *metrics)
+{
+  int i, total_width;
+  struct font_metrics pcm;
+
+  total_width = 0;
+
+  /* First clear the metrics array.  */
+  memset (metrics, 0, sizeof *metrics);
+
+  /* Get the metrcs one by one, then sum them up.  */
+  for (i = 0; i < nglyphs; ++i)
+    {
+      if (!sfntfont_measure_pcm ((struct sfnt_font_info *) font,
+                                code[i], &pcm))
+       {
+         /* Add the per-char metric (PCM) to the metrics in
+            METRICS.  */
+
+         if (total_width + pcm.lbearing < metrics->lbearing)
+           metrics->lbearing = total_width + pcm.lbearing;
+
+         if (total_width + pcm.rbearing > metrics->rbearing)
+           metrics->rbearing = total_width + pcm.rbearing;
+
+         if (pcm.ascent > metrics->ascent)
+           metrics->ascent = pcm.ascent;
+
+         if (pcm.descent > metrics->descent)
+           metrics->descent = pcm.descent;
+
+         total_width += pcm.width;
+       }
+    }
+
+  metrics->width = total_width;
+}
+
+/* Close the font FONT, discarding all tables inside it and
+   dereferencing all cached outlines and rasters.  */
+
+void
+sfntfont_close (struct font *font)
+{
+  struct sfnt_font_info *info;
+#ifdef HAVE_MMAP
+  struct sfnt_font_info **next;
+#endif /* HAVE_MMAP */
+
+  info = (struct sfnt_font_info *) font;
+
+  /* If info->desc is still set, dereference the font tables.  */
+  if (info->desc)
+    sfnt_dereference_font_tables (info->desc);
+  info->desc = NULL;
+
+  /* Free the interpreter, which is created on a per font basis.  */
+  xfree (info->interpreter);
+
+  /* Clear these fields.  It seems that close can be called twice,
+     once during font driver destruction, and once during GC.  */
+
+  info->cmap = NULL;
+  info->hhea = NULL;
+  info->maxp = NULL;
+  info->head = NULL;
+  info->hhea = NULL;
+  info->glyf = NULL;
+  info->loca_short = NULL;
+  info->loca_long = NULL;
+  info->cmap_data = NULL;
+  info->prep = NULL;
+  info->fpgm = NULL;
+  info->cvt = NULL;
+  info->interpreter = NULL;
+  info->uvs = NULL;
+
+  /* Deinitialize the blend.  */
+  if (info->instance != -1 && info->blend.coords)
+    sfnt_free_blend (&info->blend);
+  info->instance = -1;
+
+#ifdef HAVE_MMAP
+
+  /* Unlink INFO.  */
+
+  next = &open_fonts;
+  while (*next && (*next) != info)
+    next = &(*next)->next;
+
+  if (*next)
+    *next = info->next;
+  info->next = NULL;
+
+#endif /* HAVE_MMAP */
+
+#ifdef HAVE_HARFBUZZ
+  /* These fields will be freed or closed by
+     sfnt_dereference_font_tables, but clear them here for good
+     measure.  */
+  info->directory = NULL;
+  info->fd = -1;
+
+  /* Free any hb_font created.  */
+
+  if (info->hb_font)
+    {
+      hb_font_destroy (info->hb_font);
+      info->hb_font = NULL;
+    }
+#endif
+
+  sfntfont_free_outline_cache (&info->outline_cache);
+  sfntfont_free_raster_cache (&info->raster_cache);
+}
+
+
+
+/* Glyph display.  */
+
+/* Function called to actually draw rasters to the glass.  */
+static sfntfont_put_glyph_proc sfnt_put_glyphs;
+
+/* Draw glyphs in S->char2b starting from FROM to TO, with the origin
+   at X and baseline at Y.  Fill the background from X, Y +
+   FONT_DESCENT to X + S->background_width, Y - FONT_ASCENT with the
+   background color if necessary.  Use the foreground and background
+   colors in S->gc.  */
+
+int
+sfntfont_draw (struct glyph_string *s, int from, int to,
+              int x, int y, bool with_background)
+{
+  int length;
+  struct sfnt_raster **rasters;
+  int *x_coords, current_x, i;
+  struct sfnt_glyph_outline *outline;
+  struct font *font;
+  struct sfnt_font_info *info;
+  struct sfnt_glyph_metrics metrics;
+
+  length = to - from;
+  font = s->font;
+  info = (struct sfnt_font_info *) font;
+
+  rasters = alloca (length * sizeof *rasters);
+  x_coords = alloca (length * sizeof *x_coords);
+  current_x = x;
+
+  /* Get rasters and outlines for them.  */
+  for (i = from; i < to; ++i)
+    {
+      /* Look up the outline.  */
+      outline = sfntfont_get_glyph_outline (s->char2b[i],
+                                           &info->outline_cache,
+                                           info->scale,
+                                           &info->outline_cache_size,
+                                           &info->blend,
+                                           info->instance,
+                                           info->glyf, info->head,
+                                           info->hmtx, info->hhea,
+                                           info->maxp,
+                                           info->loca_short,
+                                           info->loca_long,
+                                           info->interpreter,
+                                           &metrics,
+                                           &info->state);
+      x_coords[i - from] = 0;
+
+      if (!outline)
+       {
+         rasters[i - from] = NULL;
+         continue;
+       }
+
+      /* Rasterize the outline.  */
+      rasters[i - from] = sfntfont_get_glyph_raster (s->char2b[i],
+                                                    &info->raster_cache,
+                                                    outline,
+                                                    &info->raster_cache_size);
+      sfntfont_dereference_outline (outline);
+
+      if (!rasters[i - from])
+       continue;
+
+      /* Now work out where to put the outline.  */
+      x_coords[i - from] = current_x;
+
+      if (s->padding_p)
+       current_x += 1;
+      else
+       current_x += SFNT_CEIL_FIXED (metrics.advance) / 65536;
+    }
+
+  /* Call the window system function to put the glyphs to the
+     frame.  */
+  sfnt_put_glyphs (s, from, to, x, y, with_background,
+                  rasters, x_coords);
+
+  /* Dereference all the rasters.  */
+  for (i = 0; i < from - to; ++i)
+    {
+      if (rasters[i])
+       sfntfont_dereference_raster (rasters[i]);
+    }
+
+  return 1;
+}
+
+
+
+/* Other callbacks.  */
+
+/* Return a list of each font family known to Emacs.  F is supposed to
+   be a frame but is ignored.  */
+
+Lisp_Object
+sfntfont_list_family (struct frame *f)
+{
+  Lisp_Object families;
+  struct sfnt_font_desc *desc;
+
+  families = Qnil;
+
+  for (desc = system_fonts; desc; desc = desc->next)
+    /* Add desc->family to the list.  */
+    families = Fcons (desc->family, families);
+
+  /* Not sure if deleting duplicates is worth it.  Is this ever
+     called? */
+  return families;
+}
+
+
+
+/* Unicode Variation Selector (UVS) support.  This is typically
+   required for Harfbuzz.  */
+
+/* Given a FONT object, a character C, and VARIATIONS, return the
+   number of non-default variation glyphs, and their glyph ids in
+   VARIATIONS.
+
+   For each variation selector character K with a non-default glyph in
+   the variation selector range 0xFE00 to 0xFE0F, set variations[K -
+   0xFE0] to its ID.
+
+   For each variation selector character K with a non-default glyph in
+   the variation selector range 0xE0100 to 0xE01EF, set variations[K -
+   0xE0100 + 16] to its ID.
+
+   If value is more than 0, set all other members of VARIATIONS to 0.
+   Else, the contents of VARIATIONS are undefined.  */
+
+int
+sfntfont_get_variation_glyphs (struct font *font, int c,
+                              unsigned variations[256])
+{
+  struct sfnt_font_info *info;
+  size_t i;
+  int n;
+  struct sfnt_mapped_variation_selector_record *record;
+
+  info = (struct sfnt_font_info *) font;
+  n = 0;
+
+  /* Return 0 if there is no UVS mapping table.  */
+
+  if (!info->uvs)
+    return 0;
+
+  /* Clear the variations array.  */
+
+  memset (variations, 0, sizeof *variations * 256);
+
+  /* Find the first 0xFExx selector.  */
+
+  i = 0;
+  while (i < info->uvs->num_records
+        && info->uvs->records[i].selector < 0xfe00)
+    ++i;
+
+  /* Fill in selectors 0 to 15.  */
+
+  while (i < info->uvs->num_records
+        && info->uvs->records[i].selector <= 0xfe0f)
+    {
+      record = &info->uvs->records[i];
+
+      /* If record has no non-default mappings, continue on to the
+        next selector.  */
+
+      if (!record->nondefault_uvs)
+       goto next_selector;
+
+      /* Handle invalid unsorted tables.  */
+
+      if (record->selector < 0xfe00)
+       return 0;
+
+      /* Find the glyph ID associated with C and put it in
+        VARIATIONS.  */
+
+      variations[info->uvs->records[i].selector - 0xfe00]
+       = sfnt_variation_glyph_for_char (record->nondefault_uvs, c);
+
+      if (variations[info->uvs->records[i].selector - 0xfe00])
+       ++n;
+
+    next_selector:
+      ++i;
+    }
+
+  /* Find the first 0xE0100 selector.  */
+
+  i = 0;
+  while (i < info->uvs->num_records
+        && info->uvs->records[i].selector < 0xe0100)
+    ++i;
+
+  /* Fill in selectors 16 to 255.  */
+
+  while (i < info->uvs->num_records
+        && info->uvs->records[i].selector <= 0xe01ef)
+    {
+      record = &info->uvs->records[i];
+
+      /* If record has no non-default mappings, continue on to the
+        next selector.  */
+
+      if (!record->nondefault_uvs)
+       goto next_selector_1;
+
+      /* Handle invalid unsorted tables.  */
+
+      if (record->selector < 0xe0100)
+       return 0;
+
+      /* Find the glyph ID associated with C and put it in
+        VARIATIONS.  */
+
+      variations[info->uvs->records[i].selector - 0xe0100 + 16]
+       = sfnt_variation_glyph_for_char (record->nondefault_uvs, c);
+
+      if (variations[info->uvs->records[i].selector - 0xe0100 + 16])
+       ++n;
+
+    next_selector_1:
+      ++i;
+    }
+
+  return n;
+}
+
+
+
+/* mmap specific stuff.  */
+
+#ifdef HAVE_MMAP
+
+/* Return whether or not ADDR lies in a mapped glyph, and bus faults
+   should be ignored.  */
+
+bool
+sfntfont_detect_sigbus (void *addr)
+{
+  struct sfnt_font_info *info;
+
+  for (info = open_fonts; info; info = info->next)
+    {
+      if (info->glyf_table_mapped
+         && (unsigned char *) addr >= info->glyf->glyphs
+         && (unsigned char *) addr < (info->glyf->glyphs
+                                      + info->glyf->size))
+       return true;
+    }
+
+  return false;
+}
+
+#endif
+
+
+
+/* Harfbuzz font support.  */
+
+#ifdef HAVE_HARFBUZZ
+
+#ifdef HAVE_MMAP
+
+/* Unmap the specified table.  */
+
+static void
+sfntfont_unmap_blob (void *ptr)
+{
+  if (sfnt_unmap_table (ptr))
+    emacs_abort ();
+
+  xfree (ptr);
+}
+
+#endif /* HAVE_MMAP */
+
+/* Given a font DATA and a tag TAG, return the data of the
+   corresponding font table as a HarfBuzz blob.  */
+
+static hb_blob_t *
+sfntfont_get_font_table (hb_face_t *face, hb_tag_t tag, void *data)
+{
+  size_t size;
+  struct sfnt_font_info *info;
+#ifdef HAVE_MMAP
+  struct sfnt_mapped_table *table;
+  hb_blob_t *blob;
+
+  info = data;
+  table = xmalloc (sizeof *table);
+
+  if (!sfnt_map_table (info->fd, info->directory, tag,
+                      table))
+    {
+      /* Create an hb_blob_t and return it.
+         TODO: record this mapping properly so that SIGBUS can
+        be handled.  */
+
+      blob = hb_blob_create (table->data, table->length,
+                            HB_MEMORY_MODE_READONLY,
+                            table, sfntfont_unmap_blob);
+
+      /* Note that sfntfont_unmap_blob will be called if the empty
+        blob is returned.  */
+      return blob;
+    }
+
+  xfree (table);
+#else /* !HAVE_MMAP */
+
+  /* Try to read the table conventionally.  */
+  info = data;
+#endif /* HAVE_MMAP */
+
+  data = sfnt_read_table (info->fd, info->directory, tag,
+                         &size);
+
+  if (!data)
+    return NULL;
+
+  return hb_blob_create (data, size, HB_MEMORY_MODE_WRITABLE,
+                        data, xfree);
+}
+
+/* Create or return a HarfBuzz font object corresponding to the
+   specified FONT.  Return the scale to convert between fwords and
+   pixels in POSITION_UNIT.  */
+
+hb_font_t *
+sfntfont_begin_hb_font (struct font *font, double *position_unit)
+{
+  struct sfnt_font_info *info;
+  hb_face_t *face;
+  int factor;
+
+  info = (struct sfnt_font_info *) font;
+
+  if (info->hb_font)
+    {
+      /* Calculate the scale factor.  */
+      *position_unit = 1.0 / 64.0;
+      return info->hb_font;
+    }
+
+  /* Create a face and then a font.  */
+  face = hb_face_create_for_tables (sfntfont_get_font_table, font,
+                                   NULL);
+
+  if (hb_face_get_glyph_count (face) > 0)
+    {
+      info->hb_font = hb_font_create (face);
+      if (!info->hb_font)
+       goto bail;
+
+      factor = font->pixel_size;
+
+      /* Set the scale and PPEM values.  */
+      hb_font_set_scale (info->hb_font, factor * 64, factor * 64);
+      hb_font_set_ppem (info->hb_font, factor, factor);
+
+#ifdef HAVE_HB_FONT_SET_VAR_NAMED_INSTANCE
+      /* Set the instance if this is a distortable font.  */
+      if (info->instance != -1)
+       hb_font_set_var_named_instance (info->hb_font,
+                                       info->instance);
+#endif /* HAVE_HB_FONT_SET_VAR_NAMED_INSTANCE */
+
+      /* This is needed for HarfBuzz before 2.0.0; it is the default
+        in later versions.  */
+      hb_ot_font_set_funcs (info->hb_font);
+    }
+
+ bail:
+  hb_face_destroy (face);
+
+  /* Calculate the scale factor.  */
+  *position_unit = 1.0 / 64.0;
+  return info->hb_font;
+}
+
+#endif /* HAVE_HARFBUZZ */
+
+
+
+void
+syms_of_sfntfont (void)
+{
+  DEFSYM (Qutf_16be, "utf-16be");
+  DEFSYM (Qmac_roman, "mac-roman");
+  DEFSYM (Qchinese_big5, "chinese-big5");
+  DEFSYM (Qunicode_bmp, "unicode-bmp");
+  DEFSYM (Qucs, "ucs");
+  DEFSYM (Qjapanese_jisx0208, "japanese-jisx0208");
+  DEFSYM (Qgbk, "gbk");
+  DEFSYM (Qkorean_ksc5601, "korean-ksc5601");
+  DEFSYM (Qapple_roman, "apple-roman");
+  DEFSYM (Qjisx0208_1983_0, "jisx0208.1983-0");
+  DEFSYM (Qksc5601_1987_0, "ksc5601.1987-0");
+  DEFSYM (Qzh, "zh");
+  DEFSYM (Qja, "ja");
+  DEFSYM (Qko, "ko");
+  DEFSYM (Qfont_instance, "font-instance");
+
+  /* Char-table purpose.  */
+  DEFSYM (Qfont_lookup_cache, "font-lookup-cache");
+
+  /* Set up staticpros.  */
+  sfnt_vendor_name = Qnil;
+  staticpro (&sfnt_vendor_name);
+
+  /* This variable is supposed to be set by the platform specific part
+     of the font backend.  */
+  DEFVAR_LISP ("sfnt-default-family-alist", Vsfnt_default_family_alist,
+    doc: /* Alist between "emulated" and actual font family names.
+
+Much Emacs code assumes that font families named "Monospace" and "Sans
+Serif" exist, and map to the default monospace and Sans Serif fonts on
+a system.  When the `sfnt' font driver is asked to look for a font
+with one of the families in this alist, it uses its value instead.  */);
+  Vsfnt_default_family_alist = Qnil;
+}
+
+void
+mark_sfntfont (void)
+{
+  struct sfnt_font_desc *desc;
+
+  /* Mark each font desc.  */
+  for (desc = system_fonts; desc; desc = desc->next)
+    {
+      mark_object (desc->family);
+      mark_object (desc->style);
+      mark_object (desc->adstyle);
+      mark_object (desc->instances);
+      mark_object (desc->languages);
+      mark_object (desc->registry);
+      mark_object (desc->char_cache);
+      mark_object (desc->designer);
+    }
+}
+
+void
+init_sfntfont (void)
+{
+
+}
+
+
+
+/* Initialize the sfntfont font driver.  VENDOR_TYPE is the type of
+   all font entities created.  DRIVER is the font driver that is saved
+   in font objects.  PUT_GLYPHS is a function that is called with 8
+   arguments, S, FROM, TO, X, Y, WITH_BACKGROUND, RASTERS, and
+   X_COORDS, and should draw all the rasters in RASTERS to S->f,
+   originating at X_COORDS[i], Y, along with filling the background if
+   WITH_BACKGROUND is specified.  */
+
+void
+init_sfntfont_vendor (Lisp_Object vendor_name,
+                     const struct font_driver *driver,
+                     sfntfont_put_glyph_proc put_glyphs)
+{
+  sfnt_vendor_name = vendor_name;
+  sfnt_font_driver = driver;
+  sfnt_put_glyphs = put_glyphs;
+}
diff --git a/src/sfntfont.h b/src/sfntfont.h
new file mode 100644
index 00000000000..28267cdb1d6
--- /dev/null
+++ b/src/sfntfont.h
@@ -0,0 +1,78 @@
+/* sfnt format font driver for GNU Emacs.
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _SFNTFONT_H_
+#define _SFNTFONT_H_
+
+#include "lisp.h"
+#include "frame.h"
+#include "font.h"
+#include "sfnt.h"
+
+extern int sfnt_enum_font (const char *);
+
+
+/* Font driver callbacks.  */
+
+extern Lisp_Object sfntfont_list (struct frame *, Lisp_Object);
+extern Lisp_Object sfntfont_match (struct frame *, Lisp_Object);
+extern Lisp_Object sfntfont_open (struct frame *, Lisp_Object, int);
+
+extern unsigned int sfntfont_encode_char (struct font *, int);
+extern void sfntfont_text_extents (struct font *, const unsigned int *,
+                                  int, struct font_metrics *);
+extern void sfntfont_close (struct font *);
+extern int sfntfont_draw (struct glyph_string *, int, int,
+                         int, int, bool);
+extern Lisp_Object sfntfont_list_family (struct frame *);
+extern int sfntfont_get_variation_glyphs (struct font *, int, unsigned[256]);
+
+
+/* Initialization functions.  */
+
+typedef void (*sfntfont_put_glyph_proc) (struct glyph_string *, int, int,
+                                        int, int, bool, struct sfnt_raster **,
+                                        int *);
+
+extern void syms_of_sfntfont (void);
+extern void init_sfntfont (void);
+extern void mark_sfntfont (void);
+extern void init_sfntfont_vendor (Lisp_Object, const struct font_driver *,
+                                 sfntfont_put_glyph_proc);
+
+
+/* mmap specific functions.  */
+
+#ifdef HAVE_MMAP
+
+extern bool sfntfont_detect_sigbus (void *);
+
+#endif /* HAVE_MMAP */
+
+
+
+/* HarfBuzz specific functions.  */
+
+#ifdef HAVE_HARFBUZZ
+
+extern hb_font_t *sfntfont_begin_hb_font (struct font *, double *);
+
+#endif /* HAVE_HARFBUZZ */
+
+#endif /* _SFNTFONT_H_ */
diff --git a/src/sound.c b/src/sound.c
index 145100cd433..9c023725da0 100644
--- a/src/sound.c
+++ b/src/sound.c
@@ -1147,6 +1147,7 @@ alsa_write (struct sound_device *sd, const char *buffer, 
ptrdiff_t nbytes)
                 alsa_sound_perror ("Can't recover from underrun, prepare 
failed",
                                    err);
             }
+#ifdef ESTRPIPE
           else if (err == -ESTRPIPE)
             {
               while ((err = snd_pcm_resume (p->handle)) == -EAGAIN)
@@ -1160,6 +1161,7 @@ alsa_write (struct sound_device *sd, const char *buffer, 
ptrdiff_t nbytes)
                                        err);
                 }
             }
+#endif
           else
             alsa_sound_perror ("Error writing to sound device", err);
 
@@ -1384,7 +1386,7 @@ Internal use only, use `play-sound' instead.  */)
       /* Open the sound file.  */
       current_sound->fd =
        openp (list1 (Vdata_directory), attrs[SOUND_FILE], Qnil, &file, Qnil,
-              false, false);
+              false, false, NULL);
       if (current_sound->fd < 0)
        sound_perror ("Could not open sound file");
 
diff --git a/src/sysdep.c b/src/sysdep.c
index 443602a2d6d..52fbfbd1eb1 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -134,6 +134,14 @@ int _cdecl _spawnlp (int, const char *, const char *, ...);
 # include <sys/socket.h>
 #endif
 
+#ifdef HAVE_ANDROID
+#include "android.h"
+#endif
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#include "sfntfont.h"
+#endif
+
 /* Declare here, including term.h is problematic on some systems.  */
 extern void tputs (const char *, int, int (*)(int));
 
@@ -252,7 +260,7 @@ init_standard_fds (void)
   /* Set buferr if possible on platforms defining _PC_PIPE_BUF, as
      they support the notion of atomic writes to pipes.  */
   #ifdef _PC_PIPE_BUF
-    buferr = fdopen (STDERR_FILENO, "w");
+    buferr = emacs_fdopen (STDERR_FILENO, "w");
     if (buferr)
       setvbuf (buferr, NULL, _IOLBF, 0);
   #endif
@@ -790,6 +798,7 @@ init_sigio (int fd)
 #endif
 }
 
+#ifndef HAVE_ANDROID
 #ifndef DOS_NT
 #ifdef F_SETOWN
 static void
@@ -801,6 +810,7 @@ reset_sigio (int fd)
 }
 #endif /* F_SETOWN */
 #endif
+#endif
 
 void
 request_sigio (void)
@@ -972,6 +982,8 @@ narrow_foreground_group (int fd)
     tcsetpgrp_without_stopping (fd, getpid ());
 }
 
+#ifndef HAVE_ANDROID
+
 /* Set the tty to our original foreground group.  */
 static void
 widen_foreground_group (int fd)
@@ -979,6 +991,9 @@ widen_foreground_group (int fd)
   if (inherited_pgroup && setpgid (0, inherited_pgroup) == 0)
     tcsetpgrp_without_stopping (fd, inherited_pgroup);
 }
+
+#endif
+
 
 /* Getting and setting emacs_tty structures.  */
 
@@ -1496,6 +1511,8 @@ reset_sys_modes (struct tty_display_info *tty_out)
       fflush (stdout);
       return;
     }
+
+#ifndef HAVE_ANDROID
   if (!tty_out->term_initted)
     return;
 
@@ -1552,6 +1569,7 @@ reset_sys_modes (struct tty_display_info *tty_out)
 #endif
 
   widen_foreground_group (fileno (tty_out->input));
+#endif
 }
 
 #ifdef HAVE_PTYS
@@ -1802,7 +1820,45 @@ handle_arith_signal (int sig)
   xsignal0 (Qarith_error);
 }
 
-#if defined HAVE_STACK_OVERFLOW_HANDLING && !defined WINDOWSNT
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY && defined HAVE_MMAP
+
+static void
+handle_sigbus (int sig, siginfo_t *siginfo, void *arg)
+{
+  /* If this arrives during sfntfont_open, then Emacs may be
+     screwed.  */
+
+  if (sfntfont_detect_sigbus (siginfo->si_addr))
+    return;
+
+  handle_fatal_signal (sig);
+}
+
+/* Try to set up SIGBUS handling for the sfnt font driver.
+   Value is 1 upon failure, 0 otherwise.  */
+
+static int
+init_sigbus (void)
+{
+  struct sigaction sa;
+
+  sigfillset (&sa.sa_mask);
+  sa.sa_sigaction = handle_sigbus;
+  sa.sa_flags = SA_SIGINFO;
+
+  if (sigaction (SIGBUS, &sa, NULL))
+    return 1;
+
+  return 0;
+}
+
+#endif
+
+/* This does not work on Android and interferes with the system
+   tombstone generation.  */
+
+#if defined HAVE_STACK_OVERFLOW_HANDLING && !defined WINDOWSNT \
+  && (!defined HAVE_ANDROID || defined ANDROID_STUBIFY)
 
 /* Alternate stack used by SIGSEGV handler below.  */
 
@@ -1914,12 +1970,16 @@ init_sigsegv (void)
 
 #else /* not HAVE_STACK_OVERFLOW_HANDLING or WINDOWSNT */
 
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
+
 static bool
 init_sigsegv (void)
 {
   return 0;
 }
 
+#endif
+
 #endif /* HAVE_STACK_OVERFLOW_HANDLING && !WINDOWSNT */
 
 static void
@@ -2027,12 +2087,17 @@ init_signals (void)
 #endif /* __vax__ */
     }
 
+  /* SIGUSR1 and SIGUSR2 are used internally by the android_select
+     function.  */
+#if !defined HAVE_ANDROID
 #ifdef SIGUSR1
   add_user_signal (SIGUSR1, "sigusr1");
 #endif
 #ifdef SIGUSR2
   add_user_signal (SIGUSR2, "sigusr2");
 #endif
+#endif
+
   sigaction (SIGABRT, &thread_fatal_action, 0);
 #ifdef SIGPRE
   sigaction (SIGPRE, &thread_fatal_action, 0);
@@ -2056,10 +2121,15 @@ init_signals (void)
   sigaction (SIGEMT, &thread_fatal_action, 0);
 #endif
 #ifdef SIGBUS
-  sigaction (SIGBUS, &thread_fatal_action, 0);
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY && defined HAVE_MMAP
+  if (init_sigbus ())
+#endif
+    sigaction (SIGBUS, &thread_fatal_action, 0);
 #endif
+#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY
   if (!init_sigsegv ())
     sigaction (SIGSEGV, &thread_fatal_action, 0);
+#endif
 #ifdef SIGSYS
   sigaction (SIGSYS, &thread_fatal_action, 0);
 #endif
@@ -2313,7 +2383,8 @@ emacs_backtrace (int backtrace_limit)
     }
 }
 
-#ifndef HAVE_NTGUI
+#if !defined HAVE_NTGUI && !(defined HAVE_ANDROID              \
+                            && !defined ANDROID_STUBIFY)
 void
 emacs_abort (void)
 {
@@ -2335,11 +2406,20 @@ int
 emacs_fstatat (int dirfd, char const *filename, void *st, int flags)
 {
   int r;
-  while ((r = fstatat (dirfd, filename, st, flags)) != 0 && errno == EINTR)
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  while ((r = fstatat (dirfd, filename, st, flags)) != 0
+        && errno == EINTR)
+    maybe_quit ();
+#else
+  while ((r = android_fstatat (dirfd, filename, st, flags)) != 0
+        && errno == EINTR)
     maybe_quit ();
+#endif
   return r;
 }
 
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+
 static int
 sys_openat (int dirfd, char const *file, int oflags, int mode)
 {
@@ -2354,6 +2434,28 @@ sys_openat (int dirfd, char const *file, int oflags, int 
mode)
 #endif
 }
 
+#endif
+
+int
+sys_fstat (int fd, struct stat *statb)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return fstat (fd, statb);
+#else
+  return android_fstat (fd, statb);
+#endif
+}
+
+int
+sys_faccessat (int fd, const char *pathname, int mode, int flags)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return faccessat (fd, pathname, mode, flags);
+#else
+  return android_faccessat (fd, pathname, mode, flags);
+#endif
+}
+
 /* Assuming the directory DIRFD, open FILE for Emacs use,
    using open flags OFLAGS and mode MODE.
    Use binary I/O on systems that care about text vs binary I/O.
@@ -2362,6 +2464,8 @@ sys_openat (int dirfd, char const *file, int oflags, int 
mode)
    Do not fail merely because the open was interrupted by a signal.
    Allow the user to quit.  */
 
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+
 int
 emacs_openat (int dirfd, char const *file, int oflags, int mode)
 {
@@ -2374,10 +2478,23 @@ emacs_openat (int dirfd, char const *file, int oflags, 
int mode)
   return fd;
 }
 
+#endif
+
 int
 emacs_open (char const *file, int oflags, int mode)
 {
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+  int fd;
+#endif
+
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
   return emacs_openat (AT_FDCWD, file, oflags, mode);
+#else
+  while ((fd = android_open (file, oflags, mode)) < 0 && errno == EINTR)
+    maybe_quit ();
+
+  return fd;
+#endif
 }
 
 /* Same as above, but doesn't allow the user to quit.  */
@@ -2389,9 +2506,15 @@ emacs_open_noquit (char const *file, int oflags, int 
mode)
   if (! (oflags & O_TEXT))
     oflags |= O_BINARY;
   oflags |= O_CLOEXEC;
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
   do
     fd = open (file, oflags, mode);
   while (fd < 0 && errno == EINTR);
+#else
+  do
+    fd = android_open (file, oflags, mode);
+  while (fd < 0 && errno == EINTR);
+#endif
   return fd;
 }
 
@@ -2422,7 +2545,7 @@ emacs_fopen (char const *file, char const *mode)
       }
 
   fd = emacs_open (file, omode | oflags | bflag, 0666);
-  return fd < 0 ? 0 : fdopen (fd, mode);
+  return fd < 0 ? 0 : emacs_fdopen (fd, mode);
 }
 
 /* Create a pipe for Emacs use.  */
@@ -2441,6 +2564,8 @@ emacs_pipe (int fd[2])
    For the background behind this mess, please see Austin Group defect 529
    <https://austingroupbugs.net/view.php?id=529>.  */
 
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+
 #ifndef POSIX_CLOSE_RESTART
 # define POSIX_CLOSE_RESTART 1
 static int
@@ -2467,6 +2592,8 @@ posix_close (int fd, int flag)
 }
 #endif
 
+#endif
+
 /* Close FD, retrying if interrupted.  If successful, return 0;
    otherwise, return -1 and set errno to a non-EINTR value.  Consider
    an EINPROGRESS error to be successful, as that's merely a signal
@@ -2479,9 +2606,17 @@ posix_close (int fd, int flag)
 int
 emacs_close (int fd)
 {
+  int r;
+
   while (1)
     {
-      int r = posix_close (fd, POSIX_CLOSE_RESTART);
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+      r = posix_close (fd, POSIX_CLOSE_RESTART);
+#else
+      r = android_close (fd) == 0 || errno == EINTR ? 0 : -1;
+#define POSIX_CLOSE_RESTART 1
+#endif
+
       if (r == 0)
        return r;
       if (!POSIX_CLOSE_RESTART || errno != EINTR)
@@ -2492,6 +2627,106 @@ emacs_close (int fd)
     }
 }
 
+/* Wrapper around fdopen.  On Android, this calls `android_fclose' to
+   clear information associated with FD if necessary.  */
+
+FILE *
+emacs_fdopen (int fd, const char *mode)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return fdopen (fd, mode);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_fdopen (fd, mode);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+/* Wrapper around fclose.  On Android, this calls `android_fclose' to
+   clear information associated with the FILE's file descriptor if
+   necessary.  */
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+int
+emacs_fclose (FILE *stream)
+{
+  return android_fclose (stream);
+}
+#endif
+
+/* Wrappers around unlink, symlink, rename, renameat_noreplace, and
+   rmdir.  These operations handle asset and content directories on
+   Android, and may return EINTR.  */
+
+int
+emacs_unlink (const char *name)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return unlink (name);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_unlink (name);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_symlink (const char *target, const char *linkname)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return symlink (target, linkname);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_symlink (target, linkname);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_rmdir (const char *dirname)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return rmdir (dirname);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_rmdir (dirname);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_mkdir (const char *dirname, mode_t mode)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return mkdir (dirname, mode);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_mkdir (dirname, mode);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_renameat_noreplace (int srcfd, const char *src,
+                         int dstfd, const char *dst)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return renameat_noreplace (srcfd, src, dstfd, dst);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_renameat_noreplace (srcfd, src, dstfd, dst);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_rename (const char *src, const char *dst)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return rename (src, dst);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_rename (src, dst);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
+int
+emacs_fchmodat (int fd, const char *path, mode_t mode, int flags)
+{
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  return fchmodat (fd, path, mode, flags);
+#else /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */
+  return android_fchmodat (fd, path, mode, flags);
+#endif /* !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) */
+}
+
 /* Maximum number of bytes to read or write in a single system call.
    This works around a serious bug in Linux kernels before 2.6.16; see
    <https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=612839>.
@@ -2736,6 +2971,17 @@ errwrite (void const *buf, ptrdiff_t nbuf)
 void
 close_output_streams (void)
 {
+  /* Android comes with some kind of ``file descriptor sanitizer''
+     that aborts when stdout or stderr is closed.  (bug#65340)
+
+     Perform this unconditionally as long as __ANDROID__ is defined,
+     since the file descriptor sanitizer also applies to regular TTY
+     builds under Android.  */
+
+#ifdef __ANDROID__
+  fflush (stderr);
+  fflush (stdout);
+#else /* !__ANDROID__ */
   if (close_stream (stdout) != 0)
     {
       emacs_perror ("Write error to standard output");
@@ -2749,6 +2995,7 @@ close_output_streams (void)
             ? fflush (stderr) != 0 || ferror (stderr)
             : close_stream (stderr) != 0))
     _exit (EXIT_FAILURE);
+#endif /* __ANDROID__ */
 }
 
 #ifndef DOS_NT
@@ -3246,7 +3493,7 @@ get_up_time (void)
          Lisp_Object subsec = Fcons (make_fixnum (upfrac), make_fixnum (hz));
          up = Ftime_add (sec, subsec);
        }
-      fclose (fup);
+      emacs_fclose (fup);
     }
   unblock_input ();
 
@@ -3294,7 +3541,7 @@ procfs_ttyname (int rdev)
                }
            }
        }
-      fclose (fdev);
+      emacs_fclose (fdev);
     }
   unblock_input ();
   return build_string (name);
@@ -3336,7 +3583,7 @@ procfs_get_total_memory (void)
          }
       while (!done);
 
-      fclose (fmem);
+      emacs_fclose (fmem);
     }
   unblock_input ();
   return retval;
diff --git a/src/sysstdio.h b/src/sysstdio.h
index 5a973c833cc..8e9e5bec86c 100644
--- a/src/sysstdio.h
+++ b/src/sysstdio.h
@@ -28,8 +28,6 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <attribute.h>
 #include <unlocked-io.h>
 
-extern FILE *emacs_fopen (char const *, char const *)
-  ATTRIBUTE_MALLOC ATTRIBUTE_DEALLOC (fclose, 1);
 extern void errputc (int);
 extern void errwrite (void const *, ptrdiff_t);
 extern void close_output_streams (void);
diff --git a/src/term.c b/src/term.c
index 4df3de8f4a5..9bcb2cb1386 100644
--- a/src/term.c
+++ b/src/term.c
@@ -62,6 +62,8 @@ static int been_here = -1;
 #include "w32term.h"
 #endif
 
+#ifndef HAVE_ANDROID
+
 static void tty_set_scroll_region (struct frame *f, int start, int stop);
 static void turn_on_face (struct frame *, int face_id);
 static void turn_off_face (struct frame *, int face_id);
@@ -73,11 +75,15 @@ static void clear_tty_hooks (struct terminal *terminal);
 static void set_tty_hooks (struct terminal *terminal);
 static void dissociate_if_controlling_tty (int fd);
 static void delete_tty (struct terminal *);
+
+#endif
+
 static AVOID maybe_fatal (bool, struct terminal *, const char *, const char *,
                          ...)
   ATTRIBUTE_FORMAT_PRINTF (3, 5) ATTRIBUTE_FORMAT_PRINTF (4, 5);
 static AVOID vfatal (const char *, va_list) ATTRIBUTE_FORMAT_PRINTF (1, 0);
 
+#ifndef HAVE_ANDROID
 
 #define OUTPUT(tty, a)                                          \
   emacs_tputs ((tty), a,                                        \
@@ -95,6 +101,8 @@ static AVOID vfatal (const char *, va_list) 
ATTRIBUTE_FORMAT_PRINTF (1, 0);
 
 #define OUTPUT1_IF(tty, a) do { if (a) emacs_tputs ((tty), a, 1, cmputc); } 
while (0)
 
+#endif
+
 /* Display space properties.  */
 
 /* Chain of all tty device parameters.  */
@@ -117,10 +125,14 @@ enum no_color_bit
 
 /* internal state */
 
+#ifndef HAVE_ANDROID
+
 /* The largest frame width in any call to calculate_costs.  */
 
 static int max_frame_cols;
 
+#endif
+
 
 
 #ifdef HAVE_GPM
@@ -133,6 +145,8 @@ struct tty_display_info *gpm_tty = NULL;
 static int last_mouse_x, last_mouse_y;
 #endif /* HAVE_GPM */
 
+#ifndef HAVE_ANDROID
+
 /* Ring the bell on a tty. */
 
 static void
@@ -718,7 +732,20 @@ encode_terminal_code (struct glyph *src, int src_len,
   return (encode_terminal_dst);
 }
 
+#else /* !HAVE_ANDROID */
 
+unsigned char *
+encode_terminal_code (struct glyph *src, int src_len,
+                     struct coding_system *coding)
+{
+  /* Text terminals are simply not supported on Android.  */
+  coding->produced = 0;
+  return NULL;
+}
+
+#endif /* HAVE_ANDROID */
+
+#ifndef HAVE_ANDROID
 
 /* An implementation of write_glyphs for termcap frames. */
 
@@ -1046,8 +1073,10 @@ int
 string_cost (const char *str)
 {
   cost = 0;
+#ifndef HAVE_ANDROID
   if (str)
     tputs (str, 0, evalcost);
+#endif
   return cost;
 }
 
@@ -1058,8 +1087,10 @@ static int
 string_cost_one_line (const char *str)
 {
   cost = 0;
+#ifndef HAVE_ANDROID
   if (str)
     tputs (str, 1, evalcost);
+#endif
   return cost;
 }
 
@@ -1070,11 +1101,13 @@ int
 per_line_cost (const char *str)
 {
   cost = 0;
+#ifndef HAVE_ANDROID
   if (str)
     tputs (str, 0, evalcost);
   cost = - cost;
   if (str)
     tputs (str, 10, evalcost);
+#endif
   return cost;
 }
 
@@ -1147,11 +1180,14 @@ calculate_ins_del_char_costs (struct frame *f)
     *p++ = (ins_startup_cost += ins_cost_per_char);
 }
 
+#endif
+
 void
 calculate_costs (struct frame *frame)
 {
   FRAME_COST_BAUD_RATE (frame) = baud_rate;
 
+#ifndef HAVE_ANDROID
   if (FRAME_TERMCAP_P (frame))
     {
       struct tty_display_info *tty = FRAME_TTY (frame);
@@ -1206,13 +1242,15 @@ calculate_costs (struct frame *frame)
 
       cmcostinit (FRAME_TTY (frame)); /* set up cursor motion costs */
     }
+#endif
 }
 
-struct fkey_table {
+struct fkey_table
+{
   const char *cap, *name;
 };
 
-#ifndef DOS_NT
+#if !defined DOS_NT && !defined HAVE_ANDROID
   /* Termcap capability names that correspond directly to X keysyms.
      Some of these (marked "terminfo") aren't supplied by old-style
      (Berkeley) termcap entries.  They're listed in X keysym order;
@@ -1443,6 +1481,9 @@ term_get_fkeys_1 (void)
 #endif /* not DOS_NT */
 
 
+
+#ifndef HAVE_ANDROID
+
 /***********************************************************************
                       Character Display Information
  ***********************************************************************/
@@ -1519,14 +1560,17 @@ append_glyph (struct it *it)
     }
 }
 
+#endif
+
 /* For external use.  */
 void
 tty_append_glyph (struct it *it)
 {
+#ifndef HAVE_ANDROID
   append_glyph (it);
+#endif
 }
 
-
 /* Produce glyphs for the display element described by IT.  *IT
    specifies what we want to produce a glyph for (character, image, ...),
    and where in the glyph matrix we currently are (glyph row and hpos).
@@ -1549,6 +1593,7 @@ tty_append_glyph (struct it *it)
 void
 produce_glyphs (struct it *it)
 {
+#ifndef HAVE_ANDROID
   /* If a hook is installed, let it do the work.  */
 
   /* Nothing but characters are supported on terminal frames.  */
@@ -1661,8 +1706,11 @@ produce_glyphs (struct it *it)
     it->current_x += it->pixel_width;
   it->ascent = it->max_ascent = it->phys_ascent = it->max_phys_ascent = 0;
   it->descent = it->max_descent = it->phys_descent = it->max_phys_descent = 1;
+#endif
 }
 
+#ifndef HAVE_ANDROID
+
 /* Append glyphs to IT's glyph_row for the composition IT->cmp_id.
    Called from produce_composite_glyph for terminal frames if
    IT->glyph_row != NULL.  IT->face_id contains the character's
@@ -2020,6 +2068,7 @@ turn_off_face (struct frame *f, int face_id)
     OUTPUT1_IF (tty, tty->TS_orig_pair);
 }
 
+#endif /* !HAVE_ANDROID */
 
 /* Return true if the terminal on frame F supports all of the
    capabilities in CAPS simultaneously.  */
@@ -2027,8 +2076,9 @@ turn_off_face (struct frame *f, int face_id)
 bool
 tty_capable_p (struct tty_display_info *tty, unsigned int caps)
 {
+#ifndef HAVE_ANDROID
 #define TTY_CAPABLE_P_TRY(tty, cap, TS, NC_bit)                                
\
-  if ((caps & (cap)) && (!(TS) || !MAY_USE_WITH_COLORS_P(tty, NC_bit)))        
\
+  if ((caps & (cap)) && (!(TS) || !MAY_USE_WITH_COLORS_P (tty, NC_bit)))       
\
     return 0;
 
   TTY_CAPABLE_P_TRY (tty,
@@ -2048,6 +2098,9 @@ tty_capable_p (struct tty_display_info *tty, unsigned int 
caps)
 
   /* We can do it!  */
   return 1;
+#else
+  return false;
+#endif
 }
 
 /* Return non-zero if the terminal is capable to display colors.  */
@@ -2081,7 +2134,7 @@ TERMINAL does not refer to a text terminal.  */)
   return make_fixnum (t ? t->display_info.tty->TN_max_colors : 0);
 }
 
-#ifndef DOS_NT
+#if !defined DOS_NT && !defined HAVE_ANDROID
 
 /* Declare here rather than in the function, as in the rest of Emacs,
    to work around an HPUX compiler bug (?). See
@@ -2186,7 +2239,7 @@ set_tty_color_mode (struct tty_display_info *tty, struct 
frame *f)
     }
 }
 
-#endif /* !DOS_NT */
+#endif /* !DOS_NT && !HAVE_ANDROID */
 
 char *
 tty_type_name (Lisp_Object terminal)
@@ -2278,6 +2331,7 @@ suspended.
 A suspended tty may be resumed by calling `resume-tty' on it.  */)
   (Lisp_Object tty)
 {
+#ifndef HAVE_ANDROID
   struct terminal *t = decode_tty_terminal (tty);
   FILE *f;
 
@@ -2300,8 +2354,8 @@ A suspended tty may be resumed by calling `resume-tty' on 
it.  */)
 
 #ifndef MSDOS
       if (f != t->display_info.tty->output)
-        fclose (t->display_info.tty->output);
-      fclose (f);
+        emacs_fclose (t->display_info.tty->output);
+      emacs_fclose (f);
 #endif
 
       t->display_info.tty->input = 0;
@@ -2314,6 +2368,10 @@ A suspended tty may be resumed by calling `resume-tty' 
on it.  */)
 
   /* Clear display hooks to prevent further output.  */
   clear_tty_hooks (t);
+#else
+  /* This will always signal on Android.  */
+  decode_tty_terminal (tty);
+#endif
 
   return Qnil;
 }
@@ -2337,9 +2395,12 @@ TTY may be a terminal object, a frame, or nil (meaning 
the selected
 frame's terminal). */)
   (Lisp_Object tty)
 {
-  struct terminal *t = decode_tty_terminal (tty);
+#ifndef HAVE_ANDROID
+  struct terminal *t;
   int fd;
 
+  t = decode_tty_terminal (tty);
+
   if (!t)
     error ("Attempt to resume a non-text terminal device");
 
@@ -2354,7 +2415,7 @@ frame's terminal). */)
 #else  /* !MSDOS */
       fd = emacs_open (t->display_info.tty->name, O_RDWR | O_NOCTTY, 0);
       t->display_info.tty->input = t->display_info.tty->output
-       = fd < 0 ? 0 : fdopen (fd, "w+");
+       = fd < 0 ? 0 : emacs_fdopen (fd, "w+");
 
       if (! t->display_info.tty->input)
        {
@@ -2396,10 +2457,15 @@ frame's terminal). */)
     }
 
   set_tty_hooks (t);
+#else
+  decode_tty_terminal (tty);
+#endif
 
   return Qnil;
 }
 
+#ifndef HAVE_ANDROID
+
 DEFUN ("tty--set-output-buffer-size", Ftty__set_output_buffer_size,
        Stty__set_output_buffer_size, 1, 2, 0, doc:
        /* Set the output buffer size for a TTY.
@@ -2438,12 +2504,14 @@ A value of zero means TTY uses the system's default 
value.  */)
   error ("Not a tty terminal");
 }
 
+#endif
+
 
 /***********************************************************************
                               Mouse
  ***********************************************************************/
 
-#ifndef DOS_NT
+#if !defined DOS_NT && !defined HAVE_ANDROID
 
 /* Implementation of draw_row_with_mouse_face for TTY/GPM and macOS.  */
 void
@@ -2713,7 +2781,7 @@ DEFUN ("gpm-mouse-stop", Fgpm_mouse_stop, Sgpm_mouse_stop,
                               Menus
  ***********************************************************************/
 
-#if !defined (MSDOS)
+#if !defined (MSDOS) && !defined HAVE_ANDROID
 
 /* TTY menu implementation and main ideas are borrowed from msdos.c.
 
@@ -3813,10 +3881,12 @@ tty_menu_show (struct frame *f, int x, int y, int 
menuflags,
   return SAFE_FREE_UNBIND_TO (specpdl_count, entry);
 }
 
-#endif /* !MSDOS */
+#endif /* !MSDOS && !defined HAVE_ANDROID */
 
 
-#ifndef MSDOS
+
+#if !defined MSDOS && !defined HAVE_ANDROID
+
 /***********************************************************************
                            Initialization
  ***********************************************************************/
@@ -3846,7 +3916,7 @@ tty_free_frame_resources (struct frame *f)
   xfree (f->output_data.tty);
 }
 
-#else  /* MSDOS */
+#elif defined MSDOS
 
 /* Delete frame F's face cache.  */
 
@@ -3856,8 +3926,13 @@ tty_free_frame_resources (struct frame *f)
   eassert (FRAME_TERMCAP_P (f) || FRAME_MSDOS_P (f));
   free_frame_faces (f);
 }
-#endif /* MSDOS */
+
+#endif
+
 
+
+#ifndef HAVE_ANDROID
+
 /* Reset the hooks in TERMINAL.  */
 
 static void
@@ -3952,6 +4027,8 @@ dissociate_if_controlling_tty (int fd)
     }
 }
 
+#endif /* !HAVE_ANDROID */
+
 /* Create a termcap display on the tty device with the given name and
    type.
 
@@ -3961,11 +4038,23 @@ dissociate_if_controlling_tty (int fd)
 
    TERMINAL_TYPE is the termcap type of the device, e.g. "vt100".
 
-   If MUST_SUCCEED is true, then all errors are fatal.  */
+   If MUST_SUCCEED is true, then all errors are fatal.  This function
+   always signals on Android, where text terminals are prohibited by
+   system policy (and the required libraries are usually not
+   available.)  */
+
+#ifdef HAVE_ANDROID
+_Noreturn
+#endif
 
 struct terminal *
 init_tty (const char *name, const char *terminal_type, bool must_succeed)
 {
+#ifdef HAVE_ANDROID
+  maybe_fatal (must_succeed, 0, "Text terminals are not supported"
+              " under Android", "Text terminals are not supported"
+              " under Android");
+#else
   struct tty_display_info *tty = NULL;
   struct terminal *terminal = NULL;
 #ifndef DOS_NT
@@ -4039,7 +4128,7 @@ init_tty (const char *name, const char *terminal_type, 
bool must_succeed)
     tty->input = tty->output
       = ((fd < 0 || ! isatty (fd))
         ? NULL
-        : fdopen (fd, "w+"));
+        : emacs_fdopen (fd, "w+"));
 
     if (! tty->input)
       {
@@ -4455,6 +4544,7 @@ use the Bourne shell command 'TERM=...; export TERM' 
(C-shell:\n\
   init_sys_modes (tty);
 
   return terminal;
+#endif /* !HAVE_ANDROID */
 }
 
 
@@ -4479,8 +4569,13 @@ maybe_fatal (bool must_succeed, struct terminal 
*terminal,
 {
   va_list ap;
   va_start (ap, str2);
+
+#ifndef HAVE_ANDROID
   if (terminal)
     delete_tty (terminal);
+#else
+  eassert (terminal == NULL);
+#endif
 
   if (must_succeed)
     vfatal (str2, ap);
@@ -4498,6 +4593,8 @@ fatal (const char *str, ...)
 
 
 
+#ifndef HAVE_ANDROID
+
 /* Delete the given tty terminal, closing all frames on it.  */
 
 static void
@@ -4543,25 +4640,27 @@ delete_tty (struct terminal *terminal)
     {
       delete_keyboard_wait_descriptor (fileno (tty->input));
       if (tty->input != stdin)
-        fclose (tty->input);
+        emacs_fclose (tty->input);
     }
   if (tty->output && tty->output != stdout && tty->output != tty->input)
-    fclose (tty->output);
+    emacs_fclose (tty->output);
   if (tty->termscript)
-    fclose (tty->termscript);
+    emacs_fclose (tty->termscript);
 
   xfree (tty->old_tty);
   xfree (tty->Wcm);
   xfree (tty);
 }
 
+#endif
+
 void
 syms_of_term (void)
 {
   DEFVAR_BOOL ("system-uses-terminfo", system_uses_terminfo,
     doc: /* Non-nil means the system uses terminfo rather than termcap.
 This variable can be used by terminal emulator packages.  */);
-#ifdef TERMINFO
+#if defined TERMINFO || (defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
   system_uses_terminfo = 1;
 #else
   system_uses_terminfo = 0;
@@ -4602,21 +4701,25 @@ trigger redisplay.  */);
   defsubr (&Stty_top_frame);
   defsubr (&Ssuspend_tty);
   defsubr (&Sresume_tty);
+#ifndef HAVE_ANDROID
   defsubr (&Stty__set_output_buffer_size);
   defsubr (&Stty__output_buffer_size);
+#endif /* !HAVE_ANDROID */
 #ifdef HAVE_GPM
   defsubr (&Sgpm_mouse_start);
   defsubr (&Sgpm_mouse_stop);
 #endif /* HAVE_GPM */
 
-#ifndef DOS_NT
+#if !defined DOS_NT && !defined HAVE_ANDROID
   default_orig_pair = NULL;
   default_set_foreground = NULL;
   default_set_background = NULL;
-#endif /* !DOS_NT */
+#endif /* !DOS_NT && !HAVE_ANDROID */
 
+#ifndef HAVE_ANDROID
   encode_terminal_src = NULL;
   encode_terminal_dst = NULL;
+#endif
 
   DEFSYM (Qtty_mode_set_strings, "tty-mode-set-strings");
   DEFSYM (Qtty_mode_reset_strings, "tty-mode-reset-strings");
diff --git a/src/termhooks.h b/src/termhooks.h
index ba04a6b7759..6bf507aae33 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -63,7 +63,8 @@ enum output_method
   output_w32,
   output_ns,
   output_pgtk,
-  output_haiku
+  output_haiku,
+  output_android,
 };
 
 /* Input queue declarations and hooks.  */
@@ -307,7 +308,11 @@ enum event_kind
 
      In TOUCHSCREEN_BEGIN_EVENT and TOUCHSCREEN_END_EVENT, ARG is the
      unique ID of the touchpoint, and X and Y are the frame-relative
-     positions of the touchpoint.  */
+     positions of the touchpoint.
+
+     In TOUCHSCREEN_END_EVENT, non-0 modifiers means that the
+     touchpoint has been canceled.  (See (elisp)Touchscreen
+     Events.)  */
 
   , TOUCHSCREEN_UPDATE_EVENT
   , TOUCHSCREEN_BEGIN_EVENT
@@ -332,6 +337,12 @@ enum event_kind
      monitor configuration changed.  .timestamp gives the time on
      which the monitors changed.  */
   , MONITORS_CHANGED_EVENT
+
+#ifdef HAVE_HAIKU
+  /* In a NOTIFICATION_CLICKED_EVENT, .arg is an integer identifying
+     the notification that was clicked.  */
+  , NOTIFICATION_CLICKED_EVENT
+#endif /* HAVE_HAIKU */
 };
 
 /* Bit width of an enum event_kind tag at the start of structs and unions.  */
@@ -516,12 +527,13 @@ struct terminal
   /* Device-type dependent data shared amongst all frames on this terminal.  */
   union display_info
   {
-    struct tty_display_info *tty;     /* termchar.h */
-    struct x_display_info *x;         /* xterm.h */
-    struct w32_display_info *w32;     /* w32term.h */
-    struct ns_display_info *ns;       /* nsterm.h */
-    struct pgtk_display_info *pgtk; /* pgtkterm.h */
-    struct haiku_display_info *haiku; /* haikuterm.h */
+    struct tty_display_info *tty;              /* termchar.h */
+    struct x_display_info *x;                  /* xterm.h */
+    struct w32_display_info *w32;              /* w32term.h */
+    struct ns_display_info *ns;                        /* nsterm.h */
+    struct pgtk_display_info *pgtk;            /* pgtkterm.h */
+    struct haiku_display_info *haiku;          /* haikuterm.h */
+    struct android_display_info *android;      /* androidterm.h */
   } display_info;
 
 
@@ -595,7 +607,8 @@ struct terminal
      BGCOLOR.  */
   void (*query_frame_background_color) (struct frame *f, Emacs_Color *bgcolor);
 
-#if defined (HAVE_X_WINDOWS) || defined (HAVE_NTGUI) || defined (HAVE_PGTK)
+#if defined (HAVE_X_WINDOWS) || defined (HAVE_NTGUI) || defined (HAVE_PGTK) \
+  || defined (HAVE_ANDROID)
   /* On frame F, translate pixel colors to RGB values for the NCOLORS
      colors in COLORS.  Use cached information, if available.  */
 
@@ -930,6 +943,9 @@ extern struct terminal *terminal_list;
 #elif defined (HAVE_HAIKU)
 #define TERMINAL_FONT_CACHE(t)                                         \
   (t->type == output_haiku ? t->display_info.haiku->name_list_element : Qnil)
+#elif defined (HAVE_ANDROID)
+#define TERMINAL_FONT_CACHE(t)                                         \
+  (t->type == output_android ? t->display_info.android->name_list_element : 
Qnil)
 #endif
 
 extern struct terminal *decode_live_terminal (Lisp_Object);
diff --git a/src/terminal.c b/src/terminal.c
index d13e3466512..07c37883f0e 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -451,6 +451,8 @@ return values.  */)
       return Qpgtk;
     case output_haiku:
       return Qhaiku;
+    case output_android:
+      return Qandroid;
     default:
       emacs_abort ();
     }
diff --git a/src/textconv.c b/src/textconv.c
index 7ed8ede3544..57daa7e53b6 100644
--- a/src/textconv.c
+++ b/src/textconv.c
@@ -25,35 +25,67 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
    ability to ``undo'' or ``edit'' previously composed text.  This is
    most commonly seen in input methods for CJK laguages for X Windows,
    and is extensively used throughout Android by input methods for all
-   kinds of scripts.  */
+   kinds of scripts.
+
+   In addition, these input methods may also need to make detailed
+   edits to the content of a buffer.  That is also handled here.  */
 
 #include <config.h>
 
 #include "textconv.h"
 #include "buffer.h"
 #include "syntax.h"
+#include "blockinput.h"
+#include "keyboard.h"
 
 
 
-/* The window system's text conversion interface.
-   NULL when the window system has not set up text conversion.
+/* Define debugging macros.  */
+
+#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
+#if 0
+#include <android/log.h>
+
+#define TEXTCONV_DEBUG(fmt, ...)                                       \
+  __android_log_print (ANDROID_LOG_VERBOSE, "EmacsInputConnection",    \
+                      "%s: " fmt, __func__, ## __VA_ARGS__)
+#endif /* 0 */
+#endif /* defined HAVE_ANDROID && !defined ANDROID_STUBIFY */
+
+#ifndef TEXTCONV_DEBUG
+#define TEXTCONV_DEBUG(...) ((void) 0)
+#endif /* TEXTCONV_DEBUG */
 
-   This interface will later be heavily extended on the
-   feature/android branch to deal with Android's much less
-   straightforward text conversion protocols.  */
+
+
+/* The window system's text conversion interface.  NULL when the
+   window system has not set up text conversion.  */
 
 static struct textconv_interface *text_interface;
 
+/* How many times text conversion has been disabled.  */
+
+static int suppress_conversion_count;
+
+/* Flags used to determine what must be sent after a batch edit
+   ends.  */
+
+enum textconv_batch_edit_flags
+  {
+    PENDING_POINT_CHANGE   = 1,
+    PENDING_COMPOSE_CHANGE = 2,
+  };
+
 
 
-/* Copy the portion of the current buffer described by BEG, BEG_BYTE,
-   END, END_BYTE to the buffer BUFFER, which is END_BYTE - BEG_BYTEs
-   long.  */
+/* Copy the portion of the current buffer's text described by BEG,
+   BEG_BYTE, END, END_BYTE to the char * buffer BUFFER, which should
+   be at least END_BYTE - BEG_BYTEs long.  */
 
 static void
-copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
-            ptrdiff_t end, ptrdiff_t end_byte,
-            char *buffer)
+copy_buffer_text (ptrdiff_t beg, ptrdiff_t beg_byte,
+                 ptrdiff_t end, ptrdiff_t end_byte,
+                 char *buffer)
 {
   ptrdiff_t beg0, end0, beg1, end1, size;
 
@@ -77,33 +109,76 @@ copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
   size = end0 - beg0;
   memcpy (buffer, BYTE_POS_ADDR (beg0), size);
   if (beg1 != -1)
-    memcpy (buffer, BEG_ADDR + beg1, end1 - beg1);
+    memcpy (buffer + size, BEG_ADDR + beg1, end1 - beg1);
 }
 
 
 
 /* Conversion query.  */
 
+/* Return the position of the active mark, or -1 if there is no mark
+   or it is not active.  */
+
+static ptrdiff_t
+get_mark (void)
+{
+  if (!NILP (BVAR (current_buffer, mark_active))
+      && XMARKER (BVAR (current_buffer, mark))->buffer)
+    return marker_position (BVAR (current_buffer,
+                                 mark));
+
+  return -1;
+}
+
+/* Like Fselect_window.  However, if WINDOW is a minibuffer window
+   but not the active minibuffer window, select its frame's selected
+   window instead.  */
+
+static void
+select_window (Lisp_Object window, Lisp_Object norecord)
+{
+  struct window *w;
+
+  w = XWINDOW (window);
+
+  if (MINI_WINDOW_P (w)
+      && WINDOW_LIVE_P (window)
+      && !EQ (window, Factive_minibuffer_window ()))
+    window = WINDOW_XFRAME (w)->selected_window;
+
+  Fselect_window (window, norecord);
+}
+
 /* Perform the text conversion operation specified in QUERY and return
    the results.
 
-   Find the text between QUERY->position from point on F's selected
-   window and QUERY->factor times QUERY->direction from that
+   Find the text between QUERY->position from point on frame F's
+   selected window and QUERY->factor times QUERY->direction from that
    position.  Return it in QUERY->text.
 
+   If QUERY->position is TYPE_MINIMUM (EMACS_INT) or EMACS_INT_MAX,
+   start at the window's last point or mark, whichever is greater or
+   smaller.
+
    Then, either delete that text from the buffer if QUERY->operation
    is TEXTCONV_SUBSTITUTION, or return 0.
 
+   If FLAGS & TEXTCONV_SKIP_CONVERSION_REGION, then first move point
+   past the conversion region in the specified direction if it is
+   inside.
+
    Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION
    or if deleting the text was successful, and 1 otherwise.  */
 
 int
-textconv_query (struct frame *f, struct textconv_callback_struct *query)
+textconv_query (struct frame *f, struct textconv_callback_struct *query,
+               int flags)
 {
   specpdl_ref count;
-  ptrdiff_t pos, pos_byte, end, end_byte;
-  ptrdiff_t temp, temp1;
+  ptrdiff_t pos, pos_byte, end, end_byte, start;
+  ptrdiff_t temp, temp1, mark;
   char *buffer;
+  struct window *w;
 
   /* Save the excursion, as there will be extensive changes to the
      selected window.  */
@@ -113,14 +188,69 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
   /* Inhibit quitting.  */
   specbind (Qinhibit_quit, Qt);
 
-  /* Temporarily switch to F's selected window.  */
-  Fselect_window (f->selected_window, Qt);
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window ((WINDOW_LIVE_P (f->old_selected_window)
+                 ? f->old_selected_window
+                 : f->selected_window), Qt);
+  w = XWINDOW (selected_window);
 
   /* Now find the appropriate text bounds for QUERY.  First, move
      point QUERY->position steps forward or backwards.  */
 
   pos = PT;
 
+  /* If QUERY->position is EMACS_INT_MAX, use the last mark or the
+     ephemeral last point, whichever is greater.
+
+     The opposite applies for EMACS_INT_MIN.  */
+
+  mark = get_mark ();
+
+  if (query->position == EMACS_INT_MAX)
+    {
+      pos = (mark == -1
+            ? w->ephemeral_last_point
+            : max (w->ephemeral_last_point, mark));
+      goto escape1;
+    }
+  else if (query->position == TYPE_MINIMUM (EMACS_INT))
+    {
+      pos = (mark == -1
+            ? w->ephemeral_last_point
+            : min (w->ephemeral_last_point, mark));
+      goto escape1;
+    }
+
+  /* Next, if POS lies within the conversion region and the caller
+     asked for it to be moved away, move it away from the conversion
+     region.  */
+
+  if (flags & TEXTCONV_SKIP_CONVERSION_REGION
+      && MARKERP (f->conversion.compose_region_start))
+    {
+      start = marker_position (f->conversion.compose_region_start);
+      end = marker_position (f->conversion.compose_region_end);
+
+      if (pos >= start && pos < end)
+       {
+         switch (query->direction)
+           {
+           case TEXTCONV_FORWARD_CHAR:
+           case TEXTCONV_FORWARD_WORD:
+           case TEXTCONV_CARET_DOWN:
+           case TEXTCONV_NEXT_LINE:
+           case TEXTCONV_LINE_START:
+             pos = end;
+             break;
+
+           default:
+             pos = max (BEGV, start - 1);
+             break;
+           }
+       }
+    }
+
   /* If pos is outside the accessible part of the buffer or if it
      overflows, move back to point or to the extremes of the
      accessible region.  */
@@ -128,6 +258,8 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
   if (ckd_add (&pos, pos, query->position))
     pos = PT;
 
+ escape1:
+
   if (pos < BEGV)
     pos = BEGV;
 
@@ -160,7 +292,7 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
       break;
 
     case TEXTCONV_FORWARD_WORD:
-      /* Move forward by query->factor word.  */
+      /* Move forward by query->factor words.  */
       end = scan_words (pos, (EMACS_INT) query->factor);
 
       if (!end)
@@ -174,7 +306,7 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
       break;
 
     case TEXTCONV_BACKWARD_WORD:
-      /* Move backwards by query->factor word.  */
+      /* Move backwards by query->factor words.  */
       end = scan_words (pos, 0 - (EMACS_INT) query->factor);
 
       if (!end)
@@ -261,7 +393,7 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
 
   /* Return the string first.  */
   buffer = xmalloc (end_byte - pos_byte);
-  copy_buffer (pos, pos_byte, end, end_byte, buffer);
+  copy_buffer_text (pos, pos_byte, end, end_byte, buffer);
   query->text.text = buffer;
   query->text.length = end - pos;
   query->text.bytes = end_byte - pos_byte;
@@ -287,27 +419,1801 @@ textconv_query (struct frame *f, struct 
textconv_callback_struct *query)
   return 0;
 }
 
-
+/* Update the overlay displaying the conversion area on frame F after
+   a change to the conversion region.  */
 
-/* Window system interface.  These are called from the rest of
-   Emacs.  */
+static void
+sync_overlay (struct frame *f)
+{
+  if (MARKERP (f->conversion.compose_region_start)
+      && !NILP (Vtext_conversion_face))
+    {
+      if (NILP (f->conversion.compose_region_overlay))
+       {
+         f->conversion.compose_region_overlay
+           = Fmake_overlay (f->conversion.compose_region_start,
+                            f->conversion.compose_region_end, Qnil,
+                            Qt, Qnil);
+         Foverlay_put (f->conversion.compose_region_overlay,
+                       Qface, Vtext_conversion_face);
+       }
 
-/* Notice that F's selected window has been set from redisplay.
-   Reset F's input method state.  */
+      Fmove_overlay (f->conversion.compose_region_overlay,
+                    f->conversion.compose_region_start,
+                    f->conversion.compose_region_end, Qnil);
+    }
+  else if (!NILP (f->conversion.compose_region_overlay))
+    {
+      Fdelete_overlay (f->conversion.compose_region_overlay);
+      f->conversion.compose_region_overlay = Qnil;
+    }
+}
+
+/* Record a change to the current buffer as a result of an
+   asynchronous text conversion operation.
+
+   Consult the doc string of `text-conversion-edits' for the meaning
+   of BEG, END, and EPHEMERAL.  */
+
+static void
+record_buffer_change (ptrdiff_t beg, ptrdiff_t end,
+                     Lisp_Object ephemeral)
+{
+  Lisp_Object buffer, beg_marker, end_marker;
+
+  XSETBUFFER (buffer, current_buffer);
+
+  /* Make markers for both BEG and END.  */
+  beg_marker = build_marker (current_buffer, beg,
+                            CHAR_TO_BYTE (beg));
+
+  /* If BEG and END are identical, make sure to keep the markers
+     eq.  */
+
+  if (beg == end)
+    end_marker = beg_marker;
+  else
+    {
+      end_marker = build_marker (current_buffer, end,
+                                CHAR_TO_BYTE (end));
+
+      /* Otherwise, make sure the marker extends past inserted
+        text.  */
+      Fset_marker_insertion_type (end_marker, Qt);
+    }
+
+  Vtext_conversion_edits
+    = Fcons (list4 (buffer, beg_marker, end_marker,
+                   ephemeral),
+            Vtext_conversion_edits);
+}
+
+/* Reset text conversion state of frame F.  Delete any overlays or
+   markers inside.  */
 
 void
-report_selected_window_change (struct frame *f)
+reset_frame_state (struct frame *f)
 {
-  if (!text_interface)
+  struct text_conversion_action *last, *next;
+
+  /* Make the composition region markers point elsewhere.  */
+
+  if (!NILP (f->conversion.compose_region_start))
+    {
+      Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
+      Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
+      f->conversion.compose_region_start = Qnil;
+      f->conversion.compose_region_end = Qnil;
+    }
+
+  /* Delete the composition region overlay.  */
+
+  if (!NILP (f->conversion.compose_region_overlay))
+    Fdelete_overlay (f->conversion.compose_region_overlay);
+
+  /* Delete each text conversion action queued up.  */
+
+  next = f->conversion.actions;
+  while (next)
+    {
+      last = next;
+      next = next->next;
+
+      /* Say that the conversion is finished.  */
+      if (text_interface && text_interface->notify_conversion)
+       text_interface->notify_conversion (last->counter);
+
+      xfree (last);
+    }
+  f->conversion.actions = NULL;
+
+  /* Clear batch edit state.  */
+  f->conversion.batch_edit_count = 0;
+  f->conversion.batch_edit_flags = 0;
+}
+
+/* Return whether or not there are pending edits from an input method
+   on any frame.  */
+
+bool
+detect_conversion_events (void)
+{
+  Lisp_Object tail, frame;
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      /* See if there's a pending edit on this frame.  */
+      if (XFRAME (frame)->conversion.actions
+         && ((XFRAME (frame)->conversion.actions->operation
+              != TEXTCONV_BARRIER)
+             || (kbd_fetch_ptr == kbd_store_ptr)))
+       return true;
+    }
+
+  return false;
+}
+
+/* Restore the selected window WINDOW.  */
+
+static void
+restore_selected_window (Lisp_Object window)
+{
+  /* FIXME: not sure what to do if WINDOW has been deleted.  */
+  select_window (window, Qt);
+}
+
+/* Commit the given text in the composing region.  If there is no
+   composing region, then insert the text after frame F's selected
+   window's last point instead, unless the mark is active.  Finally,
+   remove the composing region.
+
+   If the mark is active, delete the text between mark and point.
+
+   Then, move point to POSITION relative to TEXT.  If POSITION is
+   greater than zero, it is relative to the character at the end of
+   TEXT; otherwise, it is relative to the start of TEXT.  */
+
+static void
+really_commit_text (struct frame *f, EMACS_INT position,
+                   Lisp_Object text)
+{
+  specpdl_ref count;
+  ptrdiff_t wanted, start, end, mark;
+  struct window *w;
+
+  /* If F's old selected window is no longer alive, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
     return;
 
-  text_interface->reset (f);
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window (f->old_selected_window, Qt);
+
+  /* Now detect whether or not there is a composing or active region.
+     If there is, then replace it with TEXT.  Don't do that
+     otherwise.  */
+
+  mark = get_mark ();
+  if (MARKERP (f->conversion.compose_region_start) || mark != -1)
+    {
+      /* Replace its contents.  Set START and END to the start and end
+        of the composing region if it exists.  */
+
+      if (MARKERP (f->conversion.compose_region_start))
+       {
+         start = marker_position (f->conversion.compose_region_start);
+         end = marker_position (f->conversion.compose_region_end);
+       }
+      else
+       {
+         /* Otherwise, set it to the start and end of the region.  */
+         start = min (mark, PT);
+         end = max (mark, PT);
+       }
+
+      /* Now delete whatever needs to go.  */
+
+      del_range_1 (start, end, true, false);
+      record_buffer_change (start, start, Qt);
+
+      /* Don't record changes if TEXT is empty.  */
+
+      if (SCHARS (text))
+       {
+         /* Insert the new text.  Make sure to inherit text
+            properties from the surroundings: if this doesn't happen,
+            CC Mode fontification can get thrown off and become very
+            slow.  */
+
+         insert_from_string (text, 0, 0, SCHARS (text),
+                             SBYTES (text), true);
+         record_buffer_change (start, PT, text);
+       }
+
+      /* Move to a the position specified in POSITION.  */
+
+      if (position <= 0)
+       {
+         /* If POSITION is less than zero, it is relative to the
+            start of the text that was inserted.  */
+         wanted = start;
+
+         if (INT_ADD_WRAPV (wanted, position, &wanted)
+             || wanted < BEGV)
+           wanted = BEGV;
+
+         if (wanted > ZV)
+           wanted = ZV;
+
+         set_point (wanted);
+       }
+      else
+       {
+         /* Otherwise, it is relative to the last character in
+            TEXT.  */
+         wanted = PT;
+
+         if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
+             || wanted > ZV)
+           wanted = ZV;
+
+         if (wanted < BEGV)
+           wanted = BEGV;
+
+         set_point (wanted);
+       }
+
+      /* Make the composition region markers point elsewhere.  */
+
+      if (!NILP (f->conversion.compose_region_start))
+       {
+         Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
+         Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
+         f->conversion.compose_region_start = Qnil;
+         f->conversion.compose_region_end = Qnil;
+       }
+
+      /* Delete the composition region overlay.  */
+
+      if (!NILP (f->conversion.compose_region_overlay))
+       Fdelete_overlay (f->conversion.compose_region_overlay);
+    }
+  else
+    {
+      /* Otherwise, move the text and point to an appropriate
+        location.  */
+      wanted = PT;
+
+      /* Don't record changes if TEXT is empty.  */
+
+      if (SCHARS (text))
+       {
+         /* Insert the new text.  Make sure to inherit text
+            properties from the surroundings: if this doesn't happen,
+            CC Mode fontification can get thrown off and become very
+            slow.  */
+
+         insert_from_string (text, 0, 0, SCHARS (text),
+                             SBYTES (text), true);
+
+         record_buffer_change (wanted, PT, text);
+       }
+
+      if (position <= 0)
+       {
+         if (INT_ADD_WRAPV (wanted, position, &wanted)
+             || wanted < BEGV)
+           wanted = BEGV;
+
+         if (wanted > ZV)
+           wanted = ZV;
+
+         set_point (wanted);
+       }
+      else
+       {
+         wanted = PT;
+
+         if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
+             || wanted > ZV)
+           wanted = ZV;
+
+         if (wanted < BEGV)
+           wanted = BEGV;
+
+         set_point (wanted);
+       }
+    }
+
+  /* This should deactivate the mark.  */
+  call0 (Qdeactivate_mark);
+
+  /* Print some debugging information.  */
+  TEXTCONV_DEBUG ("text inserted: %s, point now: %zd",
+                 SSDATA (text), PT);
+
+  /* Update the ephemeral last point.  */
+  w = XWINDOW (selected_window);
+  w->ephemeral_last_point = PT;
+  unbind_to (count, Qnil);
 }
 
-/* Register INTERFACE as the text conversion interface.  */
+/* Remove the composition region on the frame F, while leaving its
+   contents intact.  If UPDATE, also notify the input method of the
+   change.  */
 
-void
-register_texconv_interface (struct textconv_interface *interface)
+static void
+really_finish_composing_text (struct frame *f, bool update)
 {
-  text_interface = interface;
+  if (!NILP (f->conversion.compose_region_start))
+    {
+      Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
+      Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
+      f->conversion.compose_region_start = Qnil;
+      f->conversion.compose_region_end = Qnil;
+
+      if (update && text_interface
+         && text_interface->compose_region_changed)
+       (*text_interface->compose_region_changed) (f);
+    }
+
+  /* Delete the composition region overlay.  */
+
+  if (!NILP (f->conversion.compose_region_overlay))
+    Fdelete_overlay (f->conversion.compose_region_overlay);
+
+  TEXTCONV_DEBUG ("conversion region removed");
+}
+
+/* Set the composing text on frame F to TEXT.  Then, move point to an
+   appropriate position relative to POSITION, and call
+   `compose_region_changed' in the text conversion interface should
+   point not have been changed relative to F's old selected window's
+   last point.  */
+
+static void
+really_set_composing_text (struct frame *f, ptrdiff_t position,
+                          Lisp_Object text)
+{
+  specpdl_ref count;
+  ptrdiff_t start, wanted, end;
+  struct window *w;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  w = XWINDOW (f->old_selected_window);
+  select_window (f->old_selected_window, Qt);
+
+  /* Now set up the composition region if necessary.  */
+
+  if (!MARKERP (f->conversion.compose_region_start))
+    {
+      /* Set START and END.  */
+      start = PT;
+      wanted = end = get_mark ();
+
+      /* If END is -1, set it to start.  */
+
+      if (end == -1)
+       end = start;
+      else
+       {
+         /* Now sort start and end.  */
+         start = min (start, end);
+         end  = max (PT, wanted);
+       }
+
+      /* If END is not the same as start, delete the text in
+        between.  */
+
+      if (end != start)
+       {
+         del_range_1 (start, end, true, false);
+         set_point (start);
+         record_buffer_change (start, start, Qt);
+       }
+
+      /* Now set the markers which denote the composition region.  */
+      f->conversion.compose_region_start
+       = build_marker (current_buffer, PT, PT_BYTE);
+      f->conversion.compose_region_end
+       = build_marker (current_buffer, PT, PT_BYTE);
+
+      Fset_marker_insertion_type (f->conversion.compose_region_end,
+                                 Qt);
+    }
+  else
+    {
+      /* Delete the text between the start of the composing region and
+        its end.  */
+      start = marker_position (f->conversion.compose_region_start);
+      end = marker_position (f->conversion.compose_region_end);
+      del_range_1 (start, end, true, false);
+      set_point (start);
+
+      if (start != end)
+       record_buffer_change (start, start, Qt);
+    }
+
+  /* Insert the new text.  Make sure to inherit text properties from
+     the surroundings: if this doesn't happen, CC Mode fontification
+     can get thrown off and become very slow.  */
+
+  insert_from_string (text, 0, 0, SCHARS (text),
+                     SBYTES (text), true);
+
+  if (start != PT)
+    record_buffer_change (start, PT, Qt);
+
+  /* Now move point to an appropriate location.  */
+  if (position <= 0)
+    {
+      wanted = start;
+
+      if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
+         || wanted < BEGV)
+       wanted = BEGV;
+
+      if (wanted > ZV)
+       wanted = ZV;
+    }
+  else
+    {
+      end = marker_position (f->conversion.compose_region_end);
+      wanted = end;
+
+      /* end should be PT after the edit.  */
+      eassert (end == PT);
+
+      if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
+         || wanted > ZV)
+       wanted = ZV;
+
+      if (wanted < BEGV)
+       wanted = BEGV;
+    }
+
+  set_point (wanted);
+
+  /* This should deactivate the mark.  */
+  call0 (Qdeactivate_mark);
+
+  /* Move the composition overlay.  */
+  sync_overlay (f);
+
+  /* If TEXT is empty, remove the composing region.  This goes against
+     the documentation, but is ultimately what programs expect.  */
+
+  if (!SCHARS (text))
+    really_finish_composing_text (f, false);
+
+  /* If PT hasn't changed, the conversion region definitely has.
+     Otherwise, redisplay will update the input method instead.  */
+
+  if (PT == w->ephemeral_last_point
+      && text_interface
+      && text_interface->compose_region_changed)
+    {
+      if (f->conversion.batch_edit_count > 0)
+       f->conversion.batch_edit_flags |= PENDING_COMPOSE_CHANGE;
+      else
+       text_interface->compose_region_changed (f);
+    }
+
+  /* Update the ephemeral last point.  */
+  w = XWINDOW (selected_window);
+  w->ephemeral_last_point = PT;
+
+  if (SCHARS (text))
+    TEXTCONV_DEBUG ("conversion region set to: %td %td",
+                   marker_position (f->conversion.compose_region_start),
+                   marker_position (f->conversion.compose_region_end));
+  else
+    TEXTCONV_DEBUG ("conversion region removed; PT is now: %td", PT);
+
+  unbind_to (count, Qnil);
+}
+
+/* Set the composing region of frame F to START by END.  Make it if
+   it is not already set.  */
+
+static void
+really_set_composing_region (struct frame *f, ptrdiff_t start,
+                            ptrdiff_t end)
+{
+  specpdl_ref count;
+  struct window *w;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  /* If MAX (0, start) == end, then this should behave the same as
+     really_finish_composing_text.  */
+
+  if (max (0, start) == max (0, end))
+    {
+      really_finish_composing_text (f, false);
+      return;
+    }
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window (f->old_selected_window, Qt);
+
+  /* Now set up the composition region if necessary.  */
+
+  if (!MARKERP (f->conversion.compose_region_start))
+    {
+      f->conversion.compose_region_start = Fmake_marker ();
+      f->conversion.compose_region_end = Fmake_marker ();
+      Fset_marker_insertion_type (f->conversion.compose_region_end,
+                                 Qt);
+    }
+
+  Fset_marker (f->conversion.compose_region_start,
+              make_fixnum (start), Qnil);
+  Fset_marker (f->conversion.compose_region_end,
+              make_fixnum (end), Qnil);
+  sync_overlay (f);
+
+  TEXTCONV_DEBUG ("composing region set to: %td, %td; point is: %td",
+                 start, end, PT);
+
+  /* Update the ephemeral last point.  */
+  w = XWINDOW (selected_window);
+  w->ephemeral_last_point = PT;
+
+  unbind_to (count, Qnil);
+}
+
+/* Delete LEFT and RIGHT chars around point or the active mark,
+   whichever is larger, in frame F's selected window, avoiding the
+   composing region if necessary.  */
+
+static void
+really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
+                               ptrdiff_t right)
+{
+  specpdl_ref count;
+  ptrdiff_t start, end, a, b, a1, b1, lstart, rstart;
+  struct window *w;
+  Lisp_Object text;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window (f->old_selected_window, Qt);
+
+  /* Figure out where to start deleting from.  */
+
+  a = get_mark ();
+
+  if (a != -1 && a != PT)
+    lstart = rstart = max (a, PT);
+  else
+    lstart = rstart = PT;
+
+  /* Avoid the composing text.  This behavior is identical to how
+     Android's BaseInputConnection actually implements avoiding the
+     composing span.  */
+
+  if (MARKERP (f->conversion.compose_region_start))
+    {
+      a = marker_position (f->conversion.compose_region_start);
+      b = marker_position (f->conversion.compose_region_end);
+
+      a1 = min (a, b);
+      b1 = max (a, b);
+
+      lstart = min (lstart, min (PT, a1));
+      rstart = max (rstart, max (PT, b1));
+    }
+
+  if (lstart == rstart)
+    {
+      start = max (BEGV, lstart - left);
+      end = min (ZV, rstart + right);
+
+      text = del_range_1 (start, end, true, true);
+      record_buffer_change (start, start, text);
+    }
+  else
+    {
+      /* Don't record a deletion if the text which was deleted lies
+        after point.  */
+
+      start = rstart;
+      end = min (ZV, rstart + right);
+      text = del_range_1 (start, end, true, true);
+      record_buffer_change (start, start, Qnil);
+
+      /* Now delete what must be deleted on the left.  */
+
+      start = max (BEGV, lstart - left);
+      end = lstart;
+      text = del_range_1 (start, end, true, true);
+      record_buffer_change (start, start, text);
+    }
+
+  TEXTCONV_DEBUG ("deleted surrounding text: %td, %td; PT is now %td",
+                 left, right, PT);
+
+  /* if the mark is now equal to start, deactivate it.  */
+
+  if (get_mark () == PT)
+    call0 (Qdeactivate_mark);
+
+  /* Update the ephemeral last point.  */
+  w = XWINDOW (selected_window);
+  w->ephemeral_last_point = PT;
+
+  unbind_to (count, Qnil);
+}
+
+/* Update the interface with frame F's new point and mark.  If a batch
+   edit is in progress, schedule the update for when it finishes
+   instead.  */
+
+static void
+really_request_point_update (struct frame *f)
+{
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  if (f->conversion.batch_edit_count > 0)
+    f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+  else if (text_interface && text_interface->point_changed)
+    text_interface->point_changed (f,
+                                  XWINDOW (f->old_selected_window),
+                                  current_buffer);
+}
+
+/* Set point in frame F's selected window to POSITION.  If MARK is not
+   at POSITION, activate the mark and set MARK to that as well.
+
+   If point was not changed, signal an update through the text input
+   interface, which is necessary for the IME to acknowledge that the
+   change has completed.  */
+
+static void
+really_set_point_and_mark (struct frame *f, ptrdiff_t point,
+                          ptrdiff_t mark)
+{
+  specpdl_ref count;
+  struct window *w;
+
+  /* If F's old selected window is no longer live, fail.  */
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect (restore_selected_window,
+                        selected_window);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window (f->old_selected_window, Qt);
+
+  if (point == PT)
+    {
+      if (f->conversion.batch_edit_count > 0)
+       f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+      else if (text_interface && text_interface->point_changed)
+       text_interface->point_changed (f,
+                                      XWINDOW (f->old_selected_window),
+                                      current_buffer);
+    }
+  else
+    /* Set the point.  */
+    Fgoto_char (make_fixnum (point));
+
+  if (mark == point
+      && !NILP (BVAR (current_buffer, mark_active)))
+    call0 (Qdeactivate_mark);
+  else
+    call1 (Qpush_mark, make_fixnum (mark));
+
+  /* Update the ephemeral last point.  */
+  w = XWINDOW (selected_window);
+  w->ephemeral_last_point = PT;
+
+  TEXTCONV_DEBUG ("set point and mark: %td %td",
+                 PT, get_mark ());
+
+  unbind_to (count, Qnil);
+}
+
+/* Complete the edit specified by the counter value inside *TOKEN.  */
+
+static void
+complete_edit (void *token)
+{
+  if (text_interface && text_interface->notify_conversion)
+    text_interface->notify_conversion (*(unsigned long *) token);
+}
+
+/* Context for complete_edit_check.  */
+
+struct complete_edit_check_context
+{
+  /* The window.  */
+  struct window *w;
+
+  /* Whether or not editing was successful.  */
+  bool check;
+};
+
+/* Convert PTR to CONTEXT.  If CONTEXT->check is false, then update
+   CONTEXT->w's ephemeral last point and give it to the input method,
+   the assumption being that an editing operation signalled.  */
+
+static void
+complete_edit_check (void *ptr)
+{
+  struct complete_edit_check_context *context;
+  struct frame *f;
+
+  context = ptr;
+
+  if (!context->check)
+    {
+      /* Figure out the new position of point.  */
+      context->w->ephemeral_last_point
+       = window_point (context->w);
+
+      /* See if the frame is still alive.  */
+
+      f = WINDOW_XFRAME (context->w);
+
+      if (!FRAME_LIVE_P (f))
+       return;
+
+      if (text_interface && text_interface->point_changed)
+       {
+         if (f->conversion.batch_edit_count > 0)
+           f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+         else
+           text_interface->point_changed (f, context->w, NULL);
+       }
+    }
+}
+
+/* Process and free the text conversion ACTION.  F must be the frame
+   on which ACTION will be performed.
+
+   Value is the window which was used, or NULL.  */
+
+static struct window *
+handle_pending_conversion_events_1 (struct frame *f,
+                                   struct text_conversion_action *action)
+{
+  Lisp_Object data;
+  enum text_conversion_operation operation;
+  struct buffer *buffer UNINIT;
+  struct window *w;
+  specpdl_ref count;
+  unsigned long token;
+  struct complete_edit_check_context context;
+
+  /* Next, process this action and free it.  */
+
+  data = action->data;
+  operation = action->operation;
+  token = action->counter;
+  xfree (action);
+
+  /* Text conversion events can still arrive immediately after
+     `conversion_disabled_p' becomes true.  In that case, process all
+     events, but don't perform any associated actions.  */
+
+  if (conversion_disabled_p ())
+    return NULL;
+
+  /* check is a flag used by complete_edit_check to determine whether
+     or not the editing operation completed successfully.  */
+  context.check = false;
+
+  /* Make sure completion is signalled.  */
+  count = SPECPDL_INDEX ();
+  record_unwind_protect_ptr (complete_edit, &token);
+  w = NULL;
+
+  if (WINDOW_LIVE_P (f->old_selected_window))
+    {
+      w = XWINDOW (f->old_selected_window);
+      buffer = XBUFFER (WINDOW_BUFFER (w));
+      context.w = w;
+
+      /* Notify the input method of any editing failures.  */
+      record_unwind_protect_ptr (complete_edit_check, &context);
+    }
+
+  switch (operation)
+    {
+    case TEXTCONV_START_BATCH_EDIT:
+      f->conversion.batch_edit_count++;
+      break;
+
+    case TEXTCONV_END_BATCH_EDIT:
+      if (f->conversion.batch_edit_count > 0)
+       f->conversion.batch_edit_count--;
+
+      if (!WINDOW_LIVE_P (f->old_selected_window))
+       break;
+
+      if (f->conversion.batch_edit_flags & PENDING_POINT_CHANGE)
+       text_interface->point_changed (f, w, buffer);
+
+      if (f->conversion.batch_edit_flags & PENDING_COMPOSE_CHANGE)
+       text_interface->compose_region_changed (f);
+
+      f->conversion.batch_edit_flags = 0;
+      break;
+
+    case TEXTCONV_COMMIT_TEXT:
+      really_commit_text (f, XFIXNUM (XCAR (data)), XCDR (data));
+      break;
+
+    case TEXTCONV_FINISH_COMPOSING_TEXT:
+      really_finish_composing_text (f, !NILP (data));
+      break;
+
+    case TEXTCONV_SET_COMPOSING_TEXT:
+      really_set_composing_text (f, XFIXNUM (XCAR (data)),
+                                XCDR (data));
+      break;
+
+    case TEXTCONV_SET_COMPOSING_REGION:
+      really_set_composing_region (f, XFIXNUM (XCAR (data)),
+                                  XFIXNUM (XCDR (data)));
+      break;
+
+    case TEXTCONV_SET_POINT_AND_MARK:
+      really_set_point_and_mark (f, XFIXNUM (XCAR (data)),
+                                XFIXNUM (XCDR (data)));
+      break;
+
+    case TEXTCONV_DELETE_SURROUNDING_TEXT:
+      really_delete_surrounding_text (f, XFIXNUM (XCAR (data)),
+                                     XFIXNUM (XCDR (data)));
+      break;
+
+    case TEXTCONV_REQUEST_POINT_UPDATE:
+      really_request_point_update (f);
+      break;
+
+    case TEXTCONV_BARRIER:
+      if (kbd_fetch_ptr != kbd_store_ptr)
+       emacs_abort ();
+
+      /* Once a barrier is hit, synchronize F's selected window's
+        `ephemeral_last_point' with its current point.  The reason
+        for this is because otherwise a previous keyboard event may
+        have taken place without redisplay happening in between.  */
+
+      if (w)
+       w->ephemeral_last_point = window_point (w);
+      break;
+    }
+
+  /* Signal success.  */
+  context.check = true;
+  unbind_to (count, Qnil);
+
+  return w;
+}
+
+/* Decrement the variable pointed to by *PTR.  */
+
+static void
+decrement_inside (void *ptr)
+{
+  int *i;
+
+  i = ptr;
+  (*i)--;
+}
+
+/* Process any outstanding text conversion events.
+   This may run Lisp or signal.  */
+
+void
+handle_pending_conversion_events (void)
+{
+  struct frame *f;
+  Lisp_Object tail, frame;
+  struct text_conversion_action *action, *next;
+  bool handled;
+  static int inside;
+  specpdl_ref count;
+  ptrdiff_t last_point;
+  struct window *w;
+
+  handled = false;
+
+  /* Reset Vtext_conversion_edits.  Do not do this if called
+     reentrantly.  */
+
+  if (!inside)
+    Vtext_conversion_edits = Qnil;
+
+  inside++;
+
+  count = SPECPDL_INDEX ();
+  record_unwind_protect_ptr (decrement_inside, &inside);
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+      last_point = -1;
+      w = NULL;
+
+      /* Test if F has any outstanding conversion events.  Then
+        process them in bottom to up order.  */
+      while (true)
+       {
+         /* Update the input method if handled &&
+            w->ephemeral_last_point != last_point.  */
+         if (w && (last_point != w->ephemeral_last_point))
+           {
+             if (handled
+                 && last_point != -1
+                 && text_interface
+                 && text_interface->point_changed)
+               {
+                 if (f->conversion.batch_edit_count > 0)
+                   f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+                 else
+                   text_interface->point_changed (f, NULL, NULL);
+               }
+
+             last_point = w->ephemeral_last_point;
+           }
+
+         /* Reload action.  This needs to be reentrant as buffer
+            modification functions can call `read-char'.  */
+         action = f->conversion.actions;
+
+         /* If there are no more actions, break.  */
+
+         if (!action)
+           break;
+
+         /* If action is a barrier event and the keyboard buffer is
+            not yet empty, break out of the loop.  */
+
+         if (action->operation == TEXTCONV_BARRIER
+             && kbd_store_ptr != kbd_fetch_ptr)
+           break;
+
+         /* Unlink this action.  */
+         next = action->next;
+         f->conversion.actions = next;
+
+         /* Handle and free the action.  */
+         w = handle_pending_conversion_events_1 (f, action);
+         handled = true;
+       }
+    }
+
+  unbind_to (count, Qnil);
+}
+
+/* Start a ``batch edit'' in frame F.  During a batch edit,
+   point_changed will not be called until the batch edit ends.
+
+   Process the actual operation in the event loop in keyboard.c; then,
+   call `notify_conversion' in the text conversion interface with
+   COUNTER.  */
+
+void
+start_batch_edit (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_START_BATCH_EDIT;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* End a ``batch edit''.  It is ok to call this function even if a
+   batch edit has not yet started, in which case it does nothing.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+end_batch_edit (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_END_BATCH_EDIT;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Insert the specified STRING into frame F's selected-window's
+   buffer's composition region, and set point to POSITION relative to
+   STRING.
+
+   If there is no composition region, use the active region instead.
+   If that doesn't exist either, insert STRING after point.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+commit_text (struct frame *f, Lisp_Object string,
+            ptrdiff_t position, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_COMMIT_TEXT;
+  action->data = Fcons (make_fixnum (position), string);
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Remove the composition region and its overlay from frame F's
+   selected-window's current buffer.  Leave the text being composed
+   intact.
+
+   If UPDATE, call `compose_region_changed' after the region is
+   removed.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+finish_composing_text (struct frame *f, unsigned long counter,
+                      bool update)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_FINISH_COMPOSING_TEXT;
+  action->data = update ? Qt : Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Insert the given STRING and make it the currently active
+   composition.
+
+   If there is currently no composing or active region, then the new
+   value of point is used as the composing region.
+
+   Then, the composing or active region is replaced with the text in
+   the specified string.
+
+   Finally, move point to new_point, which is relative to either the
+   start or the end of OBJECT depending on whether or not it is less
+   than zero.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+set_composing_text (struct frame *f, Lisp_Object object,
+                   ptrdiff_t new_point, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_SET_COMPOSING_TEXT;
+  action->data = Fcons (make_fixnum (new_point),
+                       object);
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Make the region between START and END the currently active
+   ``composing region'' on frame F.
+
+   The ``composing region'' is a region of text in the buffer that is
+   about to undergo editing by the input method.  */
+
+void
+set_composing_region (struct frame *f, ptrdiff_t start,
+                     ptrdiff_t end, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  if (start > MOST_POSITIVE_FIXNUM)
+    start = MOST_POSITIVE_FIXNUM;
+
+  if (end > MOST_POSITIVE_FIXNUM)
+    end = MOST_POSITIVE_FIXNUM;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_SET_COMPOSING_REGION;
+  action->data = Fcons (make_fixnum (start),
+                       make_fixnum (end));
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Move point in frame F's selected-window's buffer to POINT and maybe
+   push MARK.
+
+   COUNTER means the same as in `start_batch_edit'.  */
+
+void
+textconv_set_point_and_mark (struct frame *f, ptrdiff_t point,
+                            ptrdiff_t mark, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  if (point > MOST_POSITIVE_FIXNUM)
+    point = MOST_POSITIVE_FIXNUM;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_SET_POINT_AND_MARK;
+  action->data = Fcons (make_fixnum (point),
+                       make_fixnum (mark));
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Delete LEFT and RIGHT characters around point in frame F's old
+   selected window.  */
+
+void
+delete_surrounding_text (struct frame *f, ptrdiff_t left,
+                        ptrdiff_t right, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_DELETE_SURROUNDING_TEXT;
+  action->data = Fcons (make_fixnum (left),
+                       make_fixnum (right));
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Request an immediate call to TEXT_INTERFACE->point_changed with the
+   new details of frame F's region unless a batch edit is in
+   progress.  */
+
+void
+request_point_update (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_REQUEST_POINT_UPDATE;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Request that text conversion on frame F pause until the keyboard
+   buffer becomes empty.
+
+   Use this function to ensure that edits associated with a keyboard
+   event complete before the text conversion edits after the barrier
+   take place.  */
+
+void
+textconv_barrier (struct frame *f, unsigned long counter)
+{
+  struct text_conversion_action *action, **last;
+
+  action = xmalloc (sizeof *action);
+  action->operation = TEXTCONV_BARRIER;
+  action->data = Qnil;
+  action->next = NULL;
+  action->counter = counter;
+  for (last = &f->conversion.actions; *last; last = &(*last)->next)
+    ;;
+  *last = action;
+  input_pending = true;
+}
+
+/* Return N characters of text around point in frame F's old selected
+   window.
+
+   If N is -1, return the text between point and mark instead, given
+   that the mark is active.
+
+   Set *START_RETURN to the position of the first character returned,
+   *START_OFFSET to the offset of the lesser of mark and point within
+   that text, *END_OFFSET to the greater of mark and point within that
+   text, and *LENGTH to the actual number of characters returned,
+   *BYTES to the actual number of bytes returned, and *MARK_ACTIVE to
+   whether or not the mark is active.
+
+   Value is NULL upon failure, and a malloced string upon success.  */
+
+char *
+get_extracted_text (struct frame *f, ptrdiff_t n,
+                   ptrdiff_t *start_return,
+                   ptrdiff_t *start_offset,
+                   ptrdiff_t *end_offset, ptrdiff_t *length,
+                   ptrdiff_t *bytes, bool *mark_active)
+{
+  specpdl_ref count;
+  ptrdiff_t start, end, start_byte, end_byte, mark;
+  char *buffer;
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return NULL;
+
+  /* Save the excursion, as there will be extensive changes to the
+     selected window.  */
+  count = SPECPDL_INDEX ();
+  record_unwind_protect_excursion ();
+
+  /* Inhibit quitting.  */
+  specbind (Qinhibit_quit, Qt);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window (f->old_selected_window, Qt);
+  buffer = NULL;
+
+  /* Figure out the bounds of the text to return.  */
+  if (n != -1)
+    {
+      /* Make sure n is at least 4, leaving two characters around
+        PT.  */
+      n = max (4, n);
+
+      start = PT - n / 2;
+      end = PT + n - n / 2;
+    }
+  else
+    {
+      if (!NILP (BVAR (current_buffer, mark_active))
+         && XMARKER (BVAR (current_buffer, mark))->buffer)
+       {
+         start = marker_position (BVAR (current_buffer, mark));
+         end = PT;
+
+         /* Sort start and end.  start_byte is used to hold a
+            temporary value.  */
+
+         if (start > end)
+           {
+             start_byte = end;
+             end = start;
+             start = start_byte;
+           }
+       }
+      else
+       goto finish;
+    }
+
+  start = max (start, BEGV);
+  end = min (end, ZV);
+
+  /* Detect overflow.  */
+
+  if (!(start <= PT && PT <= end))
+    goto finish;
+
+  /* Convert the character positions to byte positions.  */
+  start_byte = CHAR_TO_BYTE (start);
+  end_byte = CHAR_TO_BYTE (end);
+
+  /* Extract the text from the buffer.  */
+  buffer = xmalloc (end_byte - start_byte);
+  copy_buffer_text (start, start_byte, end, end_byte, buffer);
+
+  /* Get the mark.  If it's not active, use PT.  */
+
+  mark = get_mark ();
+  *mark_active = true;
+
+  if (mark == -1)
+    {
+      mark = PT;
+      *mark_active = false;
+    }
+
+  /* Return the offsets.  */
+  *start_return = start;
+  *start_offset = min (mark - start, PT - start);
+  *end_offset = max (mark - start, PT - start);
+  *length = end - start;
+  *bytes = end_byte - start_byte;
+
+  TEXTCONV_DEBUG ("get_extracted_text: PT, mark, start: %td, %td, %td",
+                 PT, mark, start);
+
+ finish:
+  unbind_to (count, Qnil);
+  return buffer;
+}
+
+/* Return the text between the positions pt - LEFT and pt + RIGHT,
+   where pt is the position of point in frame F's selected window.  If
+   the mark is active, return the range of text relative to the bounds
+   of the region instead.
+
+   Set *LENGTH to the number of characters returned, *BYTES to the
+   number of bytes returned, *OFFSET to the character position of the
+   returned text, and *START_RETURN and *END_RETURN to the mark and
+   point relative to that position.  */
+
+char *
+get_surrounding_text (struct frame *f, ptrdiff_t left,
+                     ptrdiff_t right, ptrdiff_t *length,
+                     ptrdiff_t *bytes, ptrdiff_t *offset,
+                     ptrdiff_t *start_return,
+                     ptrdiff_t *end_return)
+{
+  specpdl_ref count;
+  ptrdiff_t start, end, start_byte, end_byte, mark, temp;
+  char *buffer;
+
+  if (!WINDOW_LIVE_P (f->old_selected_window))
+    return NULL;
+
+  /* Save the excursion, as there will be extensive changes to the
+     selected window.  */
+  count = SPECPDL_INDEX ();
+  record_unwind_protect_excursion ();
+
+  /* Inhibit quitting.  */
+  specbind (Qinhibit_quit, Qt);
+
+  /* Temporarily switch to F's selected window at the time of the last
+     redisplay.  */
+  select_window (f->old_selected_window, Qt);
+  buffer = NULL;
+
+  /* Figure out the bounds of the text to return.  */
+
+  /* First, obtain start and end.  */
+  end = get_mark ();
+  start = PT;
+
+  /* If the mark is not active, make it start and end.  */
+
+  if (end == -1)
+    end = start;
+
+  /* Now sort start and end.  */
+
+  if (end < start)
+    {
+      temp = start;
+      start = end;
+      end = temp;
+    }
+
+  /* And subtract left and right.  */
+
+  if (INT_SUBTRACT_WRAPV (start, left, &start)
+      || INT_ADD_WRAPV (end, right, &end))
+    goto finish;
+
+  start = max (start, BEGV);
+  end = min (end, ZV);
+
+  /* Detect overflow.  */
+
+  if (!(start <= PT && PT <= end))
+    goto finish;
+
+  /* Convert the character positions to byte positions.  */
+  start_byte = CHAR_TO_BYTE (start);
+  end_byte = CHAR_TO_BYTE (end);
+
+  /* Extract the text from the buffer.  */
+  buffer = xmalloc (end_byte - start_byte);
+  copy_buffer_text (start, start_byte, end, end_byte, buffer);
+
+  /* Get the mark.  If it's not active, use PT.  */
+
+  mark = get_mark ();
+
+  if (mark == -1)
+    mark = PT;
+
+  /* Return the offsets.  Unlike `get_extracted_text', this need not
+     sort mark and point.  */
+
+  *offset = start;
+  *start_return = mark - start;
+  *end_return = PT - start;
+  *length = end - start;
+  *bytes = end_byte - start_byte;
+
+ finish:
+  unbind_to (count, Qnil);
+  return buffer;
+}
+
+/* Return whether or not text conversion is temporarily disabled.
+   `reset' should always call this to determine whether or not to
+   disable the input method.  */
+
+bool
+conversion_disabled_p (void)
+{
+  return suppress_conversion_count > 0;
+}
+
+
+
+/* Window system interface.  These are called from the rest of
+   Emacs.  */
+
+/* Notice that frame F's selected window has been set from redisplay.
+   Reset F's input method state.  */
+
+void
+report_selected_window_change (struct frame *f)
+{
+  struct window *w;
+
+  reset_frame_state (f);
+
+  if (!text_interface)
+    return;
+
+  /* When called from window.c, F's selected window has already been
+     redisplayed, but w->last_point has not yet been updated.  Update
+     it here to avoid race conditions when the IM asks for the initial
+     selection position immediately after.  */
+
+  if (WINDOWP (f->selected_window))
+    {
+      w = XWINDOW (f->selected_window);
+      w->ephemeral_last_point = window_point (w);
+    }
+
+  text_interface->reset (f);
+}
+
+/* Notice that point in frame F's selected window's current buffer has
+   changed.
+
+   F is the frame whose selected window was changed, WINDOW is the
+   window in question, and BUFFER is that window's buffer.
+
+   Tell the text conversion interface about the change; it will likely
+   pass the information on to the system input method.  */
+
+void
+report_point_change (struct frame *f, struct window *window,
+                    struct buffer *buffer)
+{
+  if (!text_interface || !text_interface->point_changed)
+    return;
+
+  if (f->conversion.batch_edit_count > 0)
+    f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
+  else
+    text_interface->point_changed (f, window, buffer);
+}
+
+/* Temporarily disable text conversion.  Must be paired with a
+   corresponding call to resume_text_conversion.  */
+
+void
+disable_text_conversion (void)
+{
+  Lisp_Object tail, frame;
+  struct frame *f;
+
+  suppress_conversion_count++;
+
+  if (!text_interface || suppress_conversion_count > 1)
+    return;
+
+  /* Loop through and reset the input method on each window system
+     frame.  It should call conversion_disabled_p and then DTRT.  */
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+      reset_frame_state (f);
+
+      if (FRAME_WINDOW_P (f) && FRAME_VISIBLE_P (f))
+       text_interface->reset (f);
+    }
+}
+
+/* Undo the effect of the last call to `disable_text_conversion'.  */
+
+void
+resume_text_conversion (void)
+{
+  Lisp_Object tail, frame;
+  struct frame *f;
+
+  suppress_conversion_count--;
+  eassert (suppress_conversion_count >= 0);
+
+  if (!text_interface || suppress_conversion_count)
+    return;
+
+  /* Loop through and reset the input method on each window system
+     frame.  It should call conversion_disabled_p and then DTRT.  */
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      f = XFRAME (frame);
+      reset_frame_state (f);
+
+      if (FRAME_WINDOW_P (f) && FRAME_VISIBLE_P (f))
+       text_interface->reset (f);
+    }
+}
+
+/* Register INTERFACE as the text conversion interface.  */
+
+void
+register_textconv_interface (struct textconv_interface *interface)
+{
+  text_interface = interface;
+}
+
+
+
+/* List of buffers whose text conversion state will be reset after a
+   key sequence is read.  */
+static Lisp_Object postponed_buffers;
+
+/* Reset the text conversion style of each frame whose selected buffer
+   is contained inside `postponed_buffers'.  Set `postponed_buffers'
+   to nil.  */
+
+void
+check_postponed_buffers (void)
+{
+  Lisp_Object buffer, tail, frame;
+  struct buffer *b;
+  struct frame *f;
+
+  buffer = postponed_buffers;
+  postponed_buffers = Qnil;
+
+  if (!text_interface->reset)
+    return;
+
+  FOR_EACH_TAIL (buffer)
+    {
+      b = XBUFFER (XCAR (buffer));
+
+      /* Continue if this is a dead buffer.  */
+
+      if (!BUFFER_LIVE_P (b))
+       continue;
+
+      /* If no windows are displaying B anymore, continue.  */
+
+      if (!buffer_window_count (b))
+       continue;
+
+      /* Look for frames which have B selected.  */
+
+      FOR_EACH_FRAME (tail, frame)
+       {
+         f = XFRAME (frame);
+
+         if (WINDOW_LIVE_P (f->old_selected_window)
+             && FRAME_WINDOW_P (f)
+             /* N.B. that the same frame can't be reset twice as long
+                as the list of buffers remains unique.  */
+             && EQ (XWINDOW (f->old_selected_window)->contents,
+                    XCAR (buffer)))
+           {
+             block_input ();
+             reset_frame_state (f);
+             text_interface->reset (f);
+             unblock_input ();
+           }
+       }
+    }
+}
+
+/* Lisp interface.  */
+
+DEFUN ("set-text-conversion-style", Fset_text_conversion_style,
+       Sset_text_conversion_style, 1, 2, 0,
+       doc: /* Set the current buffer's text conversion style to VALUE.
+
+After setting `text-conversion-style', force input methods
+editing in a selected window displaying this buffer on any frame
+to stop themselves.
+
+This can lead to a significant amount of time being taken by the input
+method resetting itself, so you should not use this function lightly;
+instead, set `text-conversion-style' before your buffer is displayed,
+and let redisplay manage the input method appropriately.
+
+If a key sequence is currently being read (either through the command
+loop or by a call to `read-key-sequence') and AFTER-KEY-SEQUENCE is
+non-nil, don't perform changes to the input method until the key
+sequence is read.  This is useful within a function bound to
+`input-decode-map' or `local-function-key-map', as it prevents the
+input method from being redundantly enabled according to VALUE if the
+replacement key sequence returned starts a new key sequence and makes
+`read-key-sequence' disable text conversion again.  */)
+  (Lisp_Object value, Lisp_Object after_key_sequence)
+{
+  Lisp_Object tail, frame;
+  struct frame *f;
+  Lisp_Object buffer;
+
+  bset_text_conversion_style (current_buffer, value);
+
+  if (!text_interface)
+    return Qnil;
+
+  /* If there are any selected windows displaying this buffer, reset
+     text conversion on their associated frames.  */
+
+  if (buffer_window_count (current_buffer))
+    {
+      buffer = Fcurrent_buffer ();
+
+      /* Postpone changes to the actual text conversion state if
+        AFTER_KEY_SEQUENCE is non-nil and a key sequence is being
+        read.  */
+
+      if (reading_key_sequence && !NILP (after_key_sequence))
+       {
+         if (NILP (Fmemq (buffer, postponed_buffers)))
+           /* `check_postponed_buffers' will hopefully be called soon
+              enough to avoid postponed_buffers growing
+              indefinitely.  */
+           postponed_buffers = Fcons (buffer, postponed_buffers);
+         return Qnil;
+       }
+
+      FOR_EACH_FRAME (tail, frame)
+       {
+         f = XFRAME (frame);
+
+         if (WINDOW_LIVE_P (f->old_selected_window)
+             && FRAME_WINDOW_P (f)
+             && (EQ (XWINDOW (f->old_selected_window)->contents,
+                     buffer)
+                 /* Always reset the text conversion style of the
+                    selected frame.  */
+                 || (f == SELECTED_FRAME ())))
+           {
+             block_input ();
+             reset_frame_state (f);
+             text_interface->reset (f);
+             unblock_input ();
+           }
+       }
+    }
+
+  return Qnil;
+}
+
+
+
+void
+syms_of_textconv (void)
+{
+  DEFSYM (Qaction, "action");
+  DEFSYM (Qtext_conversion, "text-conversion");
+  DEFSYM (Qpush_mark, "push-mark");
+  DEFSYM (Qunderline, "underline");
+  DEFSYM (Qoverriding_text_conversion_style,
+         "overriding-text-conversion-style");
+
+  DEFVAR_LISP ("text-conversion-edits", Vtext_conversion_edits,
+    doc: /* List of buffers that were last edited as result of text conversion.
+
+This list can be used while handling a `text-conversion' event to
+determine which changes have taken place.
+
+Each element of the list describes a single edit in a buffer, and
+is of the form:
+
+    (BUFFER BEG END EPHEMERAL)
+
+If an insertion or an edit to the buffer text is described, then BEG
+and END are markers which denote the bounds of the text that was
+changed or inserted.  If a deletion is described, then BEG and END are
+the same object.
+
+If EPHEMERAL is t, then the input method is preparing to make further
+edits to the text, so any actions that would otherwise be taken, such
+as indenting or automatically filling text, should not take place.
+
+Otherwise, it is either a string containing text that was inserted,
+text deleted before point, or nil if text was deleted after point.
+
+The list contents are ordered in the reverse order of editing, i.e.
+the latest edit first, so you must iterate through the list in reverse.  */);
+  Vtext_conversion_edits = Qnil;
+
+  DEFVAR_LISP ("overriding-text-conversion-style",
+              Voverriding_text_conversion_style,
+    doc: /* Non-buffer local version of `text-conversion-style'.
+
+If this variable is the symbol `lambda', it means to consult the
+buffer-local value of `text-conversion-style' to determine whether or
+not to activate the input method.  Otherwise, the value is used in
+preference to any buffer-local value of `text-conversion-style'.  */);
+  Voverriding_text_conversion_style = Qlambda;
+
+  DEFVAR_LISP ("text-conversion-face", Vtext_conversion_face,
+    doc: /* Face in which to display temporary edits by an input method.
+The value nil means to display no indication of a temporary edit.  */);
+  Vtext_conversion_face = Qunderline;
+
+  defsubr (&Sset_text_conversion_style);
+
+  postponed_buffers = Qnil;
+  staticpro (&postponed_buffers);
 }
diff --git a/src/textconv.h b/src/textconv.h
index f6e7eb7925f..feac5b805af 100644
--- a/src/textconv.h
+++ b/src/textconv.h
@@ -34,6 +34,21 @@ struct textconv_interface
      happen if the window is deleted or switches buffers, or an
      unexpected buffer change occurs.) */
   void (*reset) (struct frame *);
+
+  /* Notice that point or mark has moved in the specified frame's
+     selected window's selected buffer.  The second argument is the
+     window whose point changed, and the third argument is the
+     buffer.  */
+  void (*point_changed) (struct frame *, struct window *,
+                        struct buffer *);
+
+  /* Notice that the preconversion region has changed without point
+     being moved.  */
+  void (*compose_region_changed) (struct frame *);
+
+  /* Notice that an asynch conversion identified by COUNTER has
+     completed.  */
+  void (*notify_conversion) (unsigned long);
 };
 
 
@@ -103,7 +118,40 @@ struct textconv_callback_struct
   struct textconv_conversion_text text;
 };
 
-extern int textconv_query (struct frame *, struct textconv_callback_struct *);
-extern void register_texconv_interface (struct textconv_interface *);
+
+
+#define TEXTCONV_SKIP_CONVERSION_REGION (1 << 0)
+
+extern int textconv_query (struct frame *, struct textconv_callback_struct *,
+                          int);
+extern bool detect_conversion_events (void);
+extern void handle_pending_conversion_events (void);
+extern void start_batch_edit (struct frame *, unsigned long);
+extern void end_batch_edit (struct frame *, unsigned long);
+extern void commit_text (struct frame *, Lisp_Object, ptrdiff_t,
+                        unsigned long);
+extern void finish_composing_text (struct frame *, unsigned long,
+                                  bool);
+extern void set_composing_text (struct frame *, Lisp_Object,
+                               ptrdiff_t, unsigned long);
+extern void set_composing_region (struct frame *, ptrdiff_t, ptrdiff_t,
+                                 unsigned long);
+extern void textconv_set_point_and_mark (struct frame *, ptrdiff_t,
+                                        ptrdiff_t, unsigned long);
+extern void delete_surrounding_text (struct frame *, ptrdiff_t,
+                                    ptrdiff_t, unsigned long);
+extern void request_point_update (struct frame *, unsigned long);
+extern void textconv_barrier (struct frame *, unsigned long);
+extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *,
+                                ptrdiff_t *, ptrdiff_t *, ptrdiff_t *,
+                                ptrdiff_t *, bool *);
+extern char *get_surrounding_text (struct frame *, ptrdiff_t,
+                                  ptrdiff_t, ptrdiff_t *,
+                                  ptrdiff_t *, ptrdiff_t *,
+                                  ptrdiff_t *, ptrdiff_t *);
+extern bool conversion_disabled_p (void);
+extern void check_postponed_buffers (void);
+
+extern void register_textconv_interface (struct textconv_interface *);
 
 #endif /* _TEXTCONV_H_ */
diff --git a/src/treesit.c b/src/treesit.c
index 1f694e47201..550bc8b9ee0 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -1900,6 +1900,10 @@ Return nil if NODE has no parent.  If NODE is nil, 
return nil.  */)
   TSNode treesit_node = XTS_NODE (node)->node;
   Lisp_Object parser = XTS_NODE (node)->parser;
   TSTreeCursor cursor;
+  /* See the comments to treesit_cursor_helper about the algorithm for
+     finding the parent node.  The complexity is roughly proportional
+     to the square root of the current node's depth in the parse tree,
+     and we punt if the tree is too deep.  */
   if (!treesit_cursor_helper (&cursor, treesit_node, parser))
     return return_value;
 
diff --git a/src/verbose.mk.in b/src/verbose.mk.in
index a4e2aad9325..97799cee813 100644
--- a/src/verbose.mk.in
+++ b/src/verbose.mk.in
@@ -32,6 +32,11 @@ AM_V_GEN =
 AM_V_GLOBALS =
 AM_V_NO_PD =
 AM_V_RC =
+AM_V_JAVAC =
+AM_V_DX =
+AM_V_AAPT =
+AM_V_ZIPALIGN =
+AM_V_SILENT =
 else
 
 # Whether $(info ...) works.  This is to work around a bug in GNU Make
@@ -76,4 +81,10 @@ AM_V_GEN     = @$(info $   GEN      $@)
 AM_V_GLOBALS = @$(info $   GEN      globals.h)
 AM_V_NO_PD = --no-print-directory
 AM_V_RC      = @$(info $   RC       $@)
+
+# These are used for the Android port.
+AM_V_JAVAC     = @$(info $   JAVAC    $@)
+AM_V_D8                = @$(info $   D8       $@)
+AM_V_AAPT      = @$(info $   AAPT     $@)
+AM_V_SILENT    = @
 endif
diff --git a/src/w32.c b/src/w32.c
index a6bc0f4b2ee..c75beb630e5 100644
--- a/src/w32.c
+++ b/src/w32.c
@@ -10335,7 +10335,8 @@ check_windows_init_file (void)
         names from UTF-8 to ANSI.  */
       init_file = build_string ("term/w32-win");
       fd =
-       openp (Vload_path, init_file, Fget_load_suffixes (), NULL, Qnil, 0, 0);
+       openp (Vload_path, init_file, Fget_load_suffixes (), NULL, Qnil, 0, 0,
+              NULL);
       if (fd < 0)
        {
          Lisp_Object load_path_print = Fprin1_to_string (Vload_path,
diff --git a/src/w32fns.c b/src/w32fns.c
index dcf9a212bdd..07b389df84a 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -3852,7 +3852,7 @@ deliver_wm_chars (int do_translate, HWND hwnd, UINT msg, 
UINT wParam,
 
       /* What follows is just heuristics; the correct treatment requires
         non-destructive ToUnicode():
-          
http://search.cpan.org/~ilyaz/UI-KeyboardLayout/lib/UI/KeyboardLayout.pm#Can_an_application_on_Windows_accept_keyboard_events?_Part_IV:_application-specific_modifiers
+          
https://metacpan.org/dist/UI-KeyboardLayout/view/lib/UI/KeyboardLayout.pm#Can-an-application-on-Windows-accept-keyboard-events?-Part-IV:-application-specific-modifiers
 
         What one needs to find is:
           * which of the present modifiers AFFECT the resulting char(s)
@@ -3914,7 +3914,7 @@ deliver_wm_chars (int do_translate, HWND hwnd, UINT msg, 
UINT wParam,
         character is the same for AltGr-* (=rAlt-*) and Ctrl-Alt-* (in any
         combination of handedness).  For description of masks, see
 
-          
http://search.cpan.org/~ilyaz/UI-KeyboardLayout/lib/UI/KeyboardLayout.pm#Keyboard_input_on_Windows,_Part_I:_what_is_the_kernel_doing?
+          
https://metacpan.org/dist/UI-KeyboardLayout/view/lib/UI/KeyboardLayout.pm#Keyboard-input-on-Windows,-Part-I:-what-is-the-kernel-doing?
 
         By default, Emacs was using these coincidences via the following
         heuristics: it was treating:
diff --git a/src/w32font.c b/src/w32font.c
index 2917fa55f9f..0371b24e1d1 100644
--- a/src/w32font.c
+++ b/src/w32font.c
@@ -2031,7 +2031,7 @@ static void
 fill_in_logfont (struct frame *f, LOGFONT *logfont, Lisp_Object font_spec)
 {
   Lisp_Object tmp, extra;
-  int dpi = FRAME_RES_Y (f);
+  int dpi = FRAME_RES (f);
 
   tmp = AREF (font_spec, FONT_DPI_INDEX);
   if (FIXNUMP (tmp))
diff --git a/src/w32proc.c b/src/w32proc.c
index 77a4ac1ff7e..edc4394b17f 100644
--- a/src/w32proc.c
+++ b/src/w32proc.c
@@ -1956,7 +1956,7 @@ sys_spawnve (int mode, char *cmdname, char **argv, char 
**envp)
       program = build_string (cmdname);
       full = Qnil;
       openp (Vexec_path, program, Vexec_suffixes, &full, make_fixnum (X_OK),
-            0, 0);
+            0, 0, NULL);
       if (NILP (full))
        {
          errno = EINVAL;
diff --git a/src/w32term.c b/src/w32term.c
index 57dc6b465e4..a5f17a18213 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -3412,7 +3412,7 @@ w32_construct_mouse_wheel (struct input_event *result, 
W32Msg *msg,
            ((double)FRAME_LINE_HEIGHT (f) * scroll_unit)
            / ((double)WHEEL_DELTA / delta);
       nlines = value_to_report / FRAME_LINE_HEIGHT (f) + 0.5;
-      result->arg = list3 (make_fixnum (nlines),
+      result->arg = list3 (make_fixnum (eabs (nlines)),
                           make_float (0.0),
                           make_float (value_to_report));
     }
diff --git a/src/w32term.h b/src/w32term.h
index 69ef297cbcc..68237bfff1d 100644
--- a/src/w32term.h
+++ b/src/w32term.h
@@ -29,7 +29,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
    calls us.  The ALIGN_STACK attribute forces GCC to emit a preamble
    code to re-align the stack at function entry.  Further details
    about this can be found in
-   http://www.peterstock.co.uk/games/mingw_sse/.  */
+   https://www.peterstock.co.uk/games/mingw_sse/.  */
 #ifdef __GNUC__
 # if USE_STACK_LISP_OBJECTS && !defined _WIN64 && !defined __x86_64__  \
   && __GNUC__ + (__GNUC_MINOR__ > 1) >= 5
diff --git a/src/window.c b/src/window.c
index 2a0c62f5d53..968b982c135 100644
--- a/src/window.c
+++ b/src/window.c
@@ -3514,7 +3514,10 @@ window-start value is reasonable when this function is 
called.  */)
 void
 replace_buffer_in_windows (Lisp_Object buffer)
 {
-  call1 (Qreplace_buffer_in_windows, buffer);
+  /* When kill-buffer is called early during loadup, this function is
+     undefined.  */
+  if (!NILP (Ffboundp (Qreplace_buffer_in_windows)))
+    call1 (Qreplace_buffer_in_windows, buffer);
 }
 
 /* If BUFFER is shown in a window, safely replace it with some other
diff --git a/src/window.h b/src/window.h
index 2f793ebe438..413293420fd 100644
--- a/src/window.h
+++ b/src/window.h
@@ -286,6 +286,25 @@ struct window
        it should be positive.  */
     ptrdiff_t last_point;
 
+#ifdef HAVE_TEXT_CONVERSION
+    /* ``ephemeral'' last point position.  This is used while
+       processing text conversion events.
+
+       `last_point' is normally used during redisplay to indicate the
+       position of point as seem by the input method.  However, it is
+       not updated if consequtive conversions are processed at the
+       same time.
+
+       This `ephemeral_last_point' field is either the last point as
+       set in redisplay or the last point after a text editing
+       operation.  */
+    ptrdiff_t ephemeral_last_point;
+#endif
+
+    /* Value of mark in the selected window at the time of the last
+       redisplay.  -1 if the mark is not valid or active.  */
+    ptrdiff_t last_mark;
+
     /* Line number and position of a line somewhere above the top of the
        screen.  If this field is zero, it means we don't have a base line.  */
     ptrdiff_t base_line_number;
@@ -740,14 +759,14 @@ wset_next_buffers (struct window *w, Lisp_Object val)
    + WINDOW_RIGHT_PIXEL_EDGE (W))
 
 /* True if W is a menu bar window.  */
-#if defined (HAVE_X_WINDOWS) && ! defined (USE_X_TOOLKIT) && ! defined 
(USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
 #define WINDOW_MENU_BAR_P(W) \
   (WINDOWP (WINDOW_XFRAME (W)->menu_bar_window) \
    && (W) == XWINDOW (WINDOW_XFRAME (W)->menu_bar_window))
-#else
+#else /* !HAVE_WINDOW_SYSTEM || HAVE_EXT_MENU_BAR */
 /* No menu bar windows if X toolkit is in use.  */
 #define WINDOW_MENU_BAR_P(W) false
-#endif
+#endif /* HAVE_WINDOW_SYSTEM && !HAVE_EXT_MENU_BAR */
 
 /* True if W is a tab bar window.  */
 #if defined (HAVE_WINDOW_SYSTEM)
@@ -1114,9 +1133,11 @@ void set_window_buffer (Lisp_Object window, Lisp_Object 
buffer,
 
 extern Lisp_Object echo_area_window;
 
-/* Non-zero if we should redraw the mode lines on the next redisplay.
+/* Non-zero if we should redraw the mode line*s* on the next redisplay.
    Usually set to a unique small integer so we can track the main causes of
-   full redisplays in `redisplay--mode-lines-cause'.  */
+   full redisplays in `redisplay--mode-lines-cause'.
+   Here "mode lines" includes other elements not coming from the buffer's
+   text, such as header-lines, tab lines, frame names, menu-bars, ....  */
 
 extern int update_mode_lines;
 
@@ -1134,6 +1155,11 @@ extern int windows_or_buffers_changed;
 extern void wset_redisplay (struct window *w);
 extern void fset_redisplay (struct frame *f);
 extern void bset_redisplay (struct buffer *b);
+
+/* Routines to indicate that the mode-lines might need to be redisplayed.
+   Just as for `update_mode_lines`, this includes other elements not coming
+   from the buffer's text, such as header-lines, tab lines, frame names,
+   menu-bars, ....   */
 extern void bset_update_mode_line (struct buffer *b);
 extern void wset_update_mode_line (struct window *w);
 /* Call this to tell redisplay to look for other windows than selected-window
diff --git a/src/xdisp.c b/src/xdisp.c
index a3464c2c375..8970d5aaaf2 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -21,17 +21,17 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
    Redisplay.
 
-   Emacs separates the task of updating the display from code
-   modifying global state, e.g. buffer text.  This way functions
-   operating on buffers don't also have to be concerned with updating
-   the display.
-
-   Updating the display is triggered by the Lisp interpreter when it
-   decides it's time to do it.  This is done either automatically for
-   you as part of the interpreter's command loop or as the result of
-   calling Lisp functions like `sit-for'.  The C function
-   `redisplay_internal' in xdisp.c is the only entry into the inner
-   redisplay code.
+   Emacs separates the task of updating the display -- which we call
+   "redisplay" -- from the code modifying global state, e.g. buffer
+   text.  This way functions operating on buffers don't also have to
+   be concerned with updating the display as result of their
+   operations.
+
+   Redisplay is triggered by the Lisp interpreter when it decides it's
+   time to do it.  This is done either automatically for you as part
+   of the interpreter's command loop, or as the result of calling Lisp
+   functions like `sit-for'.  The C function `redisplay_internal' in
+   xdisp.c is the only entry into the inner redisplay code.
 
    The following diagram shows how redisplay code is invoked.  As you
    can see, Lisp calls redisplay and vice versa.
@@ -75,63 +75,97 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
    and to make these changes visible.  Preferably it would do that in
    a moderately intelligent way, i.e. fast.
 
-   Changes in buffer text can be deduced from window and buffer
+   At its highest level, redisplay can be divided into 3 distinct
+   steps, all of which are visible in `redisplay_internal':
+
+    . decide which frames need their windows to be considered for redisplay
+    . for each window whose display might need to be updated, compute
+      a structure, called "glyph matrix", which describes how it
+      should look on display
+    . actually update the display of windows on the glass where the
+      newly obtained glyph matrix differs from the one produced by the
+      previous redisplay cycle
+
+   The first of these steps is done by `redisplay_internal' itself, by
+   looping through all the frames and testing their various flags,
+   such as their visibility.  The result of this could be that only
+   the selected window on the selected frame must be redisplayed, or
+   it could conclude that other windows need to be considered as well.
+
+   The second step considers each window that might need to be
+   redisplayed.  This could be only the selected window, or the window
+   trees of one or more frames.  The function which considers a window
+   and decides whether it actually needs redisplay is
+   `redisplay_window'.  It does so by looking at the changes in
+   position of point, in buffer text, in text properties, overlays,
+   etc.  These changes can be deduced from window and buffer
    structures, and from some global variables like `beg_unchanged' and
-   `end_unchanged'.  The contents of the display are additionally
-   recorded in a `glyph matrix', a two-dimensional matrix of glyph
-   structures.  Each row in such a matrix corresponds to a line on the
-   display, and each glyph in a row corresponds to a column displaying
-   a character, an image, or what else.  This matrix is called the
-   `current glyph matrix' or `current matrix' in redisplay
-   terminology.
-
-   For buffer parts that have been changed since the last update, a
-   second glyph matrix is constructed, the so called `desired glyph
-   matrix' or short `desired matrix'.  Current and desired matrix are
-   then compared to find a cheap way to update the display, e.g. by
-   reusing part of the display by scrolling lines.  The actual update
-   of the display of each window by comparing the desired and the
-   current matrix is done by `update_window', which calls functions
-   which draw to the glass (those functions are specific to the type
-   of the window's frame: X, w32, NS, etc.).
+   `end_unchanged'.  The current contents of the display are recorded
+   in a `glyph matrix', a two-dimensional matrix of glyph structures.
+   Each row in such a matrix corresponds to a line on the display, and
+   each glyph in a row corresponds to a column displaying a character,
+   an image, or what else.  This matrix is called the `current glyph
+   matrix', or `current matrix', in redisplay terminology.
+
+   For buffer parts that have been changed since the last redisplay,
+   `redisplay_window' constructs a second glyph matrix, the so called
+   `desired glyph matrix' or short `desired matrix'.  It does so in
+   the most optimal way possible, avoiding the examination of text
+   that didn't change, reusing portions of the current matrix if
+   possible, etc.  It could, in particular, decide that a window
+   doesn't need to be redisplayed at all.
+
+   This second step of redisplay also updates the parts of the desired
+   matrix that correspond to the mode lines, header lines, and
+   tab-lines of the windows which need that; see `display_mode_lines'.
+
+   In the third and last step, the current and desired matrix are then
+   compared to find a cheap way to update the display, e.g. by reusing
+   part of the display by scrolling lines.  The actual update of the
+   display of each window, by comparing the desired and the current
+   matrix, is done by `update_window', which calls functions which
+   draw to the glass (those functions are specific to the type of the
+   window's frame: X, w32, NS, etc.).
 
    Once the display of a window on the glass has been updated, its
    desired matrix is used to update the corresponding rows of the
    current matrix, and then the desired matrix is discarded.
 
    You will find a lot of redisplay optimizations when you start
-   looking at the innards of redisplay.  The overall goal of all these
-   optimizations is to make redisplay fast because it is done
-   frequently.  Some of these optimizations are implemented by the
-   following functions:
+   looking at the innards of `redisplay_window'.  The overall goal of
+   all these optimizations is to make redisplay fast because it is
+   done frequently.  Some of these optimizations are implemented by
+   the following functions:
 
     . try_cursor_movement
 
-      This function tries to update the display if the text in the
-      window did not change and did not scroll, only point moved, and
-      it did not move off the displayed portion of the text.
+      This optimization is applicable if the text in the window did
+      not change and did not scroll, only point moved, and it did not
+      move off the displayed portion of the text.  In that case, the
+      window's glyph matrix is still valid, and only the position of
+      the cursor might need to be updated.
 
     . try_window_reusing_current_matrix
 
-      This function reuses the current matrix of a window when text
-      has not changed, but the window start changed (e.g., due to
+      This function reuses the current glyph matrix of a window when
+      text has not changed, but the window start changed (e.g., due to
       scrolling).
 
     . try_window_id
 
-      This function attempts to redisplay a window by reusing parts of
-      its existing display.  It finds and reuses the part that was not
-      changed, and redraws the rest.  (The "id" part in the function's
-      name stands for "insert/delete", not for "identification" or
-      somesuch.)
+      This function attempts to update a window's glyph matrix by
+      reusing parts of its current glyph matrix.  It finds and reuses
+      the part that was not changed, and regenerates the rest.  (The
+      "id" part in the function's name stands for "insert/delete", not
+      for "identification" or somesuch.)
 
     . try_window
 
-      This function performs the full, unoptimized, redisplay of a
-      single window assuming that its fonts were not changed and that
-      the cursor will not end up in the scroll margins.  (Loading
-      fonts requires re-adjustment of dimensions of glyph matrices,
-      which makes this method impossible to use.)
+      This function performs the full, unoptimized, generation of a
+      single window's glyph matrix, assuming that its fonts were not
+      changed and that the cursor will not end up in the scroll
+      margins.  (Loading fonts requires re-adjustment of dimensions of
+      glyph matrices, which makes this method impossible to use.)
 
    The optimizations are tried in sequence (some can be skipped if
    it is known that they are not applicable).  If none of the
@@ -140,16 +174,17 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
    Note that there's one more important optimization up Emacs's
    sleeve, but it is related to actually redrawing the potentially
-   changed portions of the window/frame, not to reproducing the
-   desired matrices of those potentially changed portions.  Namely,
-   the function update_frame and its subroutines, which you will find
-   in dispnew.c, compare the desired matrices with the current
-   matrices, and only redraw the portions that changed.  So it could
-   happen that the functions in this file for some reason decide that
-   the entire desired matrix needs to be regenerated from scratch, and
-   still only parts of the Emacs display, or even nothing at all, will
-   be actually delivered to the glass, because update_frame has found
-   that the new and the old screen contents are similar or identical.
+   changed portions of the window/frame as part of the third step, not
+   to generating the desired matrices of those potentially changed
+   portions.  Namely, the function `update_frame' and its subroutines,
+   which you will find in dispnew.c, compare the desired matrices with
+   the current matrices, and only redraw the portions that changed.
+   So it could happen that the functions in this file for some reason
+   decide that the entire desired matrix needs to be regenerated from
+   scratch, and still only parts of the Emacs display, or even nothing
+   at all, will be actually delivered to the glass, because
+   `update_frame' has found that the new and the old screen contents
+   are similar or identical.
 
    Desired matrices.
 
@@ -159,7 +194,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
    redisplay tries to optimize its work, and thus only generates
    glyphs for rows that need to be updated on the screen.  Rows that
    don't need to be updated are left "disabled", and their contents
-   should be ignored.
+   in the desired matrix should be ignored.
 
    The function `display_line' is the central function to look at if
    you are interested in how the rows of the desired matrix are
@@ -2724,6 +2759,7 @@ remember_mouse_glyph (struct frame *f, int gx, int gy, 
NativeRectangle *rect)
   enum window_part part;
   enum glyph_row_area area;
   int x, y, width, height;
+  int original_gx;
 
   if (mouse_fine_grained_tracking)
     {
@@ -2734,6 +2770,8 @@ remember_mouse_glyph (struct frame *f, int gx, int gy, 
NativeRectangle *rect)
   /* Try to determine frame pixel position and size of the glyph under
      frame pixel coordinates X/Y on frame F.  */
 
+  original_gx = gx;
+
   if (window_resize_pixelwise)
     {
       width = height = 1;
@@ -2949,6 +2987,15 @@ remember_mouse_glyph (struct frame *f, int gx, int gy, 
NativeRectangle *rect)
   gy += WINDOW_TOP_EDGE_Y (w);
 
  store_rect:
+  if (mouse_prefer_closest_glyph)
+    {
+      int half_width = width / 2;
+      width = half_width;
+
+      int bisection = gx + half_width;
+      if (original_gx > bisection)
+        gx = bisection;
+    }
   STORE_NATIVE_RECT (*rect, gx, gy, width, height);
 
   /* Visible feedback for debugging.  */
@@ -8292,9 +8339,17 @@ get_next_display_element (struct it *it)
       && success_p
       && FRAME_WINDOW_P (it->f))
     {
-      struct face *face = FACE_FROM_ID (it->f, it->face_id);
+      struct face *face = FACE_FROM_ID_OR_NULL (it->f, it->face_id);
 
-      if (it->what == IT_COMPOSITION && it->cmp_it.ch >= 0)
+      /* It shouldn't happen, ever, that FACE is NULL here, but
+         evidently some faulty fonts/fontsets can sometimes cause it.
+         In that case, we punt and consider the stuff undisplayable.  */
+      if (!face)
+       {
+         it->what = IT_GLYPHLESS;
+         it->glyphless_method = GLYPHLESS_DISPLAY_EMPTY_BOX;
+       }
+      else if (it->what == IT_COMPOSITION && it->cmp_it.ch >= 0)
        {
          /* Automatic composition with glyph-string.   */
          Lisp_Object gstring = composition_gstring_from_id (it->cmp_it.id);
@@ -11651,6 +11706,8 @@ WINDOW.  */)
 
   set_buffer_internal_1 (b);
 
+  ptrdiff_t base_line_pos = w->base_line_pos;
+  int end_valid = w->window_end_valid;
   if (!EQ (buffer, w->contents))
     {
       wset_buffer (w, buffer);
@@ -11663,6 +11720,11 @@ WINDOW.  */)
 
   unbind_to (count, Qnil);
 
+  /* Restore original values.  This is important if this function is
+     called from some ':eval' form in the middle of redisplay.  */
+  w->base_line_pos = base_line_pos;
+  w->window_end_valid = end_valid;
+
   return value;
 }
 
@@ -14585,21 +14647,32 @@ tab_bar_item_info (struct frame *f, struct glyph 
*glyph,
                             Qmenu_item, f->current_tab_bar_string);
   if (! FIXNUMP (prop))
     return false;
+
   *prop_idx = XFIXNUM (prop);
 
-  *close_p = !NILP (Fget_text_property (make_fixnum (charpos),
-                                        Qclose_tab,
-                                        f->current_tab_bar_string));
+  if (close_p)
+    *close_p = !NILP (Fget_text_property (make_fixnum (charpos),
+                                         Qclose_tab,
+                                         f->current_tab_bar_string));
 
   return true;
 }
 
 
-/* Get information about the tab-bar item at position X/Y on frame F.
-   Return in *GLYPH a pointer to the glyph of the tab-bar item in
-   the current matrix of the tab-bar window of F, or NULL if not
-   on a tab-bar item.  Return in *PROP_IDX the index of the tab-bar
-   item in F->tab_bar_items.  Value is
+/* Get information about the tab-bar item at position X/Y on frame F's
+   tab bar window.
+
+   Set *GLYPH to a pointer to the glyph of the tab-bar item in the
+   current matrix of the tab-bar window of F, or NULL if not on a
+   tab-bar item.  Return in *PROP_IDX the index of the tab-bar item in
+   F->tab_bar_items.
+
+   Place the window-relative vpos of Y in *VPOS, and the
+   window-relative hpos of X in *HPOS.  If CLOSE_P, set it to whether
+   or not the tab bar item represents a button that should close a
+   tab.
+
+   Value is
 
    -1  if X/Y is not on a tab-bar item
    0   if X/Y is on the same item that was highlighted before.
@@ -14607,7 +14680,7 @@ tab_bar_item_info (struct frame *f, struct glyph *glyph,
 
 static int
 get_tab_bar_item (struct frame *f, int x, int y, struct glyph **glyph,
-                  int *hpos, int *vpos, int *prop_idx, bool *close_p)
+                 int *hpos, int *vpos, int *prop_idx, bool *close_p)
 {
   struct window *w = XWINDOW (f->tab_bar_window);
   int area;
@@ -14625,6 +14698,38 @@ get_tab_bar_item (struct frame *f, int x, int y, 
struct glyph **glyph,
   return *prop_idx == f->last_tab_bar_item ? 0 : 1;
 }
 
+/* EXPORT:
+
+   Like `get_tab_bar_item'.  However, don't return anything for GLYPH,
+   HPOS, or VPOS, and treat X and Y as relative to F itself, as
+   opposed to its tab bar window.  */
+
+int
+get_tab_bar_item_kbd (struct frame *f, int x, int y, int *prop_idx,
+                     bool *close_p)
+{
+  struct window *w;
+  int area, vpos, hpos;
+  struct glyph *glyph;
+
+  w = XWINDOW (f->tab_bar_window);
+
+  /* Convert X and Y to window coordinates.  */
+  frame_to_window_pixel_xy (w, &x, &y);
+
+  /* Find the glyph under X/Y.  */
+  glyph = x_y_to_hpos_vpos (w, x, y, &hpos, &vpos, 0,
+                           0, &area);
+  if (glyph == NULL)
+    return -1;
+
+  /* Get the start of this tab-bar item's properties in
+     f->tab_bar_items.  */
+  if (!tab_bar_item_info (f, glyph, prop_idx, close_p))
+    return -1;
+
+  return *prop_idx == f->last_tab_bar_item ? 0 : 1;
+}
 
 /* EXPORT:
    Handle mouse button event on the tab-bar of frame F, at
@@ -14957,7 +15062,10 @@ update_tool_bar (struct frame *f, bool save_match_data)
 
 /* Set F->desired_tool_bar_string to a Lisp string representing frame
    F's desired tool-bar contents.  F->tool_bar_items must have
-   been set up previously by calling prepare_menu_bars.  */
+   been set up previously by calling prepare_menu_bars.
+
+   Also set F->tool_bar_wraps_p to whether or not the tool bar
+   contains explicit line breaking items.  */
 
 static void
 build_desired_tool_bar_string (struct frame *f)
@@ -14979,9 +15087,11 @@ build_desired_tool_bar_string (struct frame *f)
   size_needed = f->n_tool_bar_items;
 
   /* Reuse f->desired_tool_bar_string, if possible.  */
+
   if (size < size_needed || NILP (f->desired_tool_bar_string))
-    fset_desired_tool_bar_string
-      (f, Fmake_string (make_fixnum (size_needed), make_fixnum (' '), Qnil));
+    /* Don't initialize the contents of this string yet, as they will
+       be set within the loop below.  */
+    fset_desired_tool_bar_string (f, make_uninit_string (size_needed));
   else
     {
       AUTO_LIST4 (props, Qdisplay, Qnil, Qmenu_item, Qnil);
@@ -14989,6 +15099,8 @@ build_desired_tool_bar_string (struct frame *f)
                               props, f->desired_tool_bar_string);
     }
 
+  f->tool_bar_wraps_p = false;
+
   /* Put a `display' property on the string for the images to display,
      put a `menu_item' property on tool-bar items with a value that
      is the index of the item in F's tool-bar item vector.  */
@@ -15001,6 +15113,21 @@ build_desired_tool_bar_string (struct frame *f)
       bool selected_p = !NILP (PROP (TOOL_BAR_ITEM_SELECTED_P));
       int hmargin, vmargin, relief, idx, end;
 
+      if (!NILP (PROP (TOOL_BAR_ITEM_WRAP)))
+       {
+         /* This is a line wrap.  Instead of building a tool bar
+            item, display a new line character instead.  */
+         SSET (f->desired_tool_bar_string, i, '\n');
+
+         /* Set F->tool_bar_wraps_p.  This tells redisplay_tool_bar
+            to allow individual rows to be different heights.  */
+         f->tool_bar_wraps_p = true;
+         continue;
+       }
+
+      /* Replace this with a space character.  */
+      SSET (f->desired_tool_bar_string, i, ' ');
+
       /* If image is a vector, choose the image according to the
         button state.  */
       image = PROP (TOOL_BAR_ITEM_IMAGES);
@@ -15112,6 +15239,16 @@ build_desired_tool_bar_string (struct frame *f)
                            props, f->desired_tool_bar_string);
 #undef PROP
     }
+
+  /* Now replace each character between i and the end of the tool bar
+     string with spaces, to prevent stray newlines from accumulating
+     when the number of tool bar items decreases.  `size' is 0 if the
+     tool bar string is new, but in that case the string will have
+     been completely initialized anyway.  */
+
+  for (; i < size; ++i)
+    /* Replace this with a space character.  */
+    SSET (f->desired_tool_bar_string, i, ' ');
 }
 
 
@@ -15125,7 +15262,10 @@ build_desired_tool_bar_string (struct frame *f)
    If HEIGHT is -1, we are counting needed tool-bar lines, so don't
    count a final empty row in case the tool-bar width exactly matches
    the window width.
-*/
+
+   HEIGHT may also be -1 if there is an explicit line wrapping item
+   inside the tool bar; in that case, allow individual rows of the
+   tool bar to differ in height.  */
 
 static void
 display_tool_bar_line (struct it *it, int height)
@@ -15189,8 +15329,18 @@ display_tool_bar_line (struct it *it, int height)
          ++i;
        }
 
-      /* Stop at line end.  */
+      /* Stop at the end of the iterator, and move to the next line
+         upon a '\n' appearing in the tool bar string.  Tool bar
+         strings may contain multiple new line characters when
+         explicit wrap items are encountered.  */
+
       if (ITERATOR_AT_END_OF_LINE_P (it))
+       {
+         reseat_at_next_visible_line_start (it, false);
+         break;
+       }
+
+      if (ITERATOR_AT_END_P (it))
        break;
 
       set_iterator_to_next (it, true);
@@ -15217,7 +15367,8 @@ display_tool_bar_line (struct it *it, int height)
     last->left_box_line_p = true;
 
   /* Make line the desired height and center it vertically.  */
-  if ((height -= it->max_ascent + it->max_descent) > 0)
+  if (height != -1
+      && (height -= it->max_ascent + it->max_descent) > 0)
     {
       /* Don't add more than one line height.  */
       height %= FRAME_LINE_HEIGHT (it->f);
@@ -15251,6 +15402,7 @@ display_tool_bar_line (struct it *it, int height)
 /* Value is the number of pixels needed to make all tool-bar items of
    frame F visible.  The actual number of glyph rows needed is
    returned in *N_ROWS if non-NULL.  */
+
 static int
 tool_bar_height (struct frame *f, int *n_rows, bool pixelwise)
 {
@@ -15328,7 +15480,9 @@ redisplay_tool_bar (struct frame *f)
   struct window *w;
   struct it it;
   struct glyph_row *row;
+  bool change_height_p;
 
+  change_height_p = false;
   f->tool_bar_redisplayed = true;
 
   /* If frame hasn't a tool-bar window or if it is zero-height, don't
@@ -15381,6 +15535,15 @@ redisplay_tool_bar (struct frame *f)
          /* Always do that now.  */
          clear_glyph_matrix (w->desired_matrix);
          f->fonts_changed = true;
+
+         /* Kludge (this applies to the X Windows version as well as
+            Android): when the tool bar size changes,
+            adjust_window_size (presumably called by
+            change_tool_bar_height_hook) does not call through to
+            resize_frame_windows.  Pending further investigation,
+            just call it here as well.  */
+         resize_frame_windows (f, FRAME_INNER_HEIGHT (f), false);
+
          return true;
        }
     }
@@ -15403,18 +15566,39 @@ redisplay_tool_bar (struct frame *f)
        border = 0;
 
       rows = f->n_tool_bar_rows;
-      height = max (1, (it.last_visible_y - border) / rows);
-      extra = it.last_visible_y - border - height * rows;
 
-      while (it.current_y < it.last_visible_y)
+      if (f->tool_bar_wraps_p)
        {
-         int h = 0;
-         if (extra > 0 && rows-- > 0)
+         /* If the tool bar contains explicit line wrapping items,
+            don't force each row to have a fixed height.  */
+
+         while (!ITERATOR_AT_END_P (&it))
+           display_tool_bar_line (&it, -1);
+
+         /* Because changes to individual tool bar items may now
+            change the height of the tool bar, adjust the height of
+            the tool bar window if it is different from the tool bar
+            height in any way.  */
+
+         if (it.current_y != it.last_visible_y)
+           change_height_p = true;
+       }
+      else
+       {
+         height = max (1, (it.last_visible_y - border) / rows);
+         extra = it.last_visible_y - border - height * rows;
+
+         while (it.current_y < it.last_visible_y)
            {
-             h = (extra + rows - 1) / rows;
-             extra -= h;
+             int h = 0;
+             if (extra > 0 && rows-- > 0)
+               {
+                 h = (extra + rows - 1) / rows;
+                 extra -= h;
+               }
+
+             display_tool_bar_line (&it, height + h);
            }
-         display_tool_bar_line (&it, height + h);
        }
     }
   else
@@ -15430,8 +15614,6 @@ redisplay_tool_bar (struct frame *f)
 
   if (!NILP (Vauto_resize_tool_bars))
     {
-      bool change_height_p = false;
-
       /* If we couldn't display everything, change the tool-bar's
         height if there is room for more.  */
       if (IT_STRING_CHARPOS (it) < it.end_charpos)
@@ -16490,8 +16672,9 @@ redisplay_internal (void)
   enum {MAX_GARBAGED_FRAME_RETRIES = 2 };
   int garbaged_frame_retries = 0;
 
-  /* True means redisplay has to consider all windows on all
-     frames.  False, only selected_window is considered.  */
+  /* False means that only the selected_window needs to be updated.
+     True means that other windows may need to be updated as well,
+     so we need to consult `needs_no_update` for all windows.  */
   bool consider_all_windows_p;
 
   /* True means redisplay has to redisplay the miniwindow.  */
@@ -16568,7 +16751,7 @@ redisplay_internal (void)
         display area, displaying a different frame means redisplay
         the whole thing.  */
       SET_FRAME_GARBAGED (sf);
-#ifndef DOS_NT
+#if !defined DOS_NT && !defined HAVE_ANDROID
       set_tty_color_mode (FRAME_TTY (sf), sf);
 #endif
       FRAME_TTY (sf)->previous_frame = sf;
@@ -17409,6 +17592,9 @@ static void
 mark_window_display_accurate_1 (struct window *w, bool accurate_p)
 {
   struct buffer *b = XBUFFER (w->contents);
+#ifdef HAVE_TEXT_CONVERSION
+  ptrdiff_t prev_point, prev_mark;
+#endif /* HAVE_TEXT_CONVERSION */
 
   w->last_modified = accurate_p ? BUF_MODIFF (b) : 0;
   w->last_overlay_modified = accurate_p ? BUF_OVERLAY_MODIFF (b) : 0;
@@ -17438,12 +17624,59 @@ mark_window_display_accurate_1 (struct window *w, 
bool accurate_p)
       w->last_cursor_vpos = w->cursor.vpos;
       w->last_cursor_off_p = w->cursor_off_p;
 
+#ifdef HAVE_TEXT_CONVERSION
+      prev_point = w->last_point;
+      prev_mark = w->last_mark;
+#endif /* HAVE_TEXT_CONVERSION */
+
       if (w == XWINDOW (selected_window))
        w->last_point = BUF_PT (b);
       else
        w->last_point = marker_position (w->pointm);
 
-      w->window_end_valid = true;
+      /* w->last_mark is recorded for text conversion purposes.
+         Input methods aren't interested in the value of the mark
+         if it is inactive, so set it to -1 if it's not.  */
+
+      if (XMARKER (BVAR (b, mark))->buffer == b
+         && !NILP (BVAR (b, mark_active)))
+       w->last_mark = marker_position (BVAR (b, mark));
+      else
+       w->last_mark = -1;
+
+#ifdef HAVE_TEXT_CONVERSION
+      /* See the description of this field in struct window.  */
+      w->ephemeral_last_point = w->last_point;
+
+      /* Point motion is only propagated to the input method for use
+        in text conversion during a redisplay.  While this can lead
+        to inconsistencies when point has moved but the change has
+        not yet been displayed, it leads to better results most of
+        the time, as point often changes within calls to
+        `save-excursion', and the only way to detect such calls is to
+        observe that the next redisplay never ends with those changes
+        applied.
+
+         Changes to buffer text are immediately propagated to the
+         input method, and the position of point is also updated
+         during such a change, so the consequences are not that
+         severe.  */
+
+      if ((prev_point != w->last_point
+          || prev_mark != w->last_mark)
+         && FRAME_WINDOW_P (WINDOW_XFRAME (w))
+         && w == XWINDOW (WINDOW_XFRAME (w)->selected_window))
+       report_point_change (WINDOW_XFRAME (w), w, b);
+#endif /* HAVE_TEXT_CONVERSION */
+
+      struct glyph_row *row;
+      /* These conditions should be consistent with CHECK_WINDOW_END.  */
+      if (w->window_end_vpos < w->current_matrix->nrows
+         && ((row = MATRIX_ROW (w->current_matrix, w->window_end_vpos),
+              !row->enabled_p
+              || MATRIX_ROW_DISPLAYS_TEXT_P (row)
+              || MATRIX_ROW_VPOS (row, w->current_matrix) == 0)))
+       w->window_end_valid = true;
       w->update_mode_line = false;
       w->preserve_vscroll_p = false;
     }
@@ -17582,6 +17815,7 @@ redisplay_window_error (Lisp_Object error_data)
   if (max_redisplay_ticks > 0
       && CONSP (error_data)
       && EQ (XCAR (error_data), Qerror)
+      && CONSP (XCDR (error_data))
       && STRINGP (XCAR (XCDR (error_data))))
     Vdelayed_warnings_list = Fcons (list2 (XCAR (error_data),
                                           XCAR (XCDR (error_data))),
@@ -26494,7 +26728,7 @@ display_menu_bar (struct window *w)
   init_iterator (&it, w, -1, -1, f->desired_matrix->rows, MENU_FACE_ID);
   it.first_visible_x = 0;
   it.last_visible_x = FRAME_PIXEL_WIDTH (f);
-#elif defined (HAVE_X_WINDOWS) /* X without toolkit.  */
+#elif defined (HAVE_X_WINDOWS) || defined (HAVE_ANDROID)
   struct window *menu_window = NULL;
   struct face *face = FACE_FROM_ID (f, MENU_FACE_ID);
 
@@ -26564,7 +26798,11 @@ display_menu_bar (struct window *w)
   it.glyph_row->truncated_on_left_p = false;
   it.glyph_row->truncated_on_right_p = false;
 
-#if defined (HAVE_X_WINDOWS) && !defined (USE_X_TOOLKIT) && !defined (USE_GTK)
+  /* This will break the moment someone tries to add another window
+     system that uses the no toolkit menu bar.  Oh well.  At least
+     there will be an error, meaning he will correct the ifdef inside
+     which `face' is defined.  */
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
   /* Make a 3D menu bar have a shadow at its right end.  */
   extend_face_to_end_of_line (&it);
   if (face->box != FACE_NO_BOX)
@@ -26605,6 +26843,11 @@ display_menu_bar (struct window *w)
 #endif
 }
 
+/* This code is never used on Android where there are only GUI and
+   initial frames.  */
+
+#ifndef HAVE_ANDROID
+
 /* Deep copy of a glyph row, including the glyphs.  */
 static void
 deep_copy_glyph_row (struct glyph_row *to, struct glyph_row *from)
@@ -26725,6 +26968,9 @@ display_tty_menu_item (const char *item_text, int 
width, int face_id,
   row->full_width_p = saved_width;
   row->reversed_p = saved_reversed;
 }
+
+#endif
+
 
 /***********************************************************************
                              Mode Line
@@ -27117,7 +27363,7 @@ display_mode_element (struct it *it, int depth, int 
field_width, int precision,
 
                    oprops = Fcopy_sequence (oprops);
                    tem = props;
-                   while (CONSP (tem))
+                   while (CONSP (tem) && CONSP (XCDR (tem)))
                      {
                        oprops = plist_put (oprops, XCAR (tem),
                                            XCAR (XCDR (tem)));
@@ -27670,6 +27916,8 @@ are the selected window and the WINDOW's buffer).  */)
   if (NILP (buffer))
     buffer = w->contents;
   CHECK_BUFFER (buffer);
+  if (!BUFFER_LIVE_P (XBUFFER (buffer)))
+    error ("Attempt to format a mode line for a dead buffer");
 
   /* Make formatting the modeline a non-op when noninteractive, otherwise
      there will be problems later caused by a partially initialized frame.  */
@@ -29050,7 +29298,9 @@ calc_pixel_width_or_height (double *res, struct it *it, 
Lisp_Object prop,
       /* 'width': the width of FONT.  */
       if (EQ (prop, Qwidth))
        return OK_PIXELS (font
-                         ? FONT_WIDTH (font)
+                         ? (font->average_width
+                            ? font->average_width
+                            : font->space_width)
                          : FRAME_COLUMN_WIDTH (it->f));
 #else
       if (EQ (prop, Qheight) || EQ (prop, Qwidth))
@@ -31858,9 +32108,12 @@ produce_glyphless_glyph (struct it *it, bool 
for_no_font, Lisp_Object acronym)
   int len;
 
   /* Get the metrics of the base font.  We always refer to the current
-     ASCII face.  */
-  face = FACE_FROM_ID (it->f, it->face_id)->ascii_face;
-  font = face->font ? face->font : FRAME_FONT (it->f);
+     ASCII face, but if some faulty setup of fontsets causes that to
+     be NULL, we fall back to the frame's default font.  */
+  face = FACE_FROM_ID_OR_NULL (it->f, it->face_id);
+  if (face)
+    face = face->ascii_face;
+  font = (face && face->font) ? face->font : FRAME_FONT (it->f);
   normal_char_ascent_descent (font, -1, &it->ascent, &it->descent);
   it->ascent += font->baseline_offset;
   it->descent -= font->baseline_offset;
@@ -33550,6 +33803,7 @@ display_and_set_cursor (struct window *w, bool on,
      completely erased, to avoid the extra work of erasing the cursor
      twice.  In other words, phys_cursor_on_p can be true and the cursor
      still not be visible, or it has only been partly erased.  */
+
   if (on)
     {
       w->phys_cursor_ascent = glyph_row->ascent;
@@ -33563,9 +33817,15 @@ display_and_set_cursor (struct window *w, bool on,
       w->phys_cursor.vpos = vpos;
     }
 
-  FRAME_RIF (f)->draw_window_cursor (w, glyph_row, x, y,
-                                     new_cursor_type, new_cursor_width,
-                                     on, active_cursor);
+  /* If make_cursor_line_fully_visible is nil and the row is in fact
+     vscrolled out of the window, then glyph_row->y +
+     glyph_row->height will be less than or equal to 0.  Eschew
+     displaying the cursor in that case.  */
+
+  if (MATRIX_ROW_BOTTOM_Y (glyph_row) > 0)
+    FRAME_RIF (f)->draw_window_cursor (w, glyph_row, x, y,
+                                      new_cursor_type, new_cursor_width,
+                                      on, active_cursor);
 }
 
 
@@ -33663,7 +33923,9 @@ draw_row_with_mouse_face (struct window *w, int 
start_x, struct glyph_row *row,
     }
 #endif
 
+#ifndef HAVE_ANDROID
   tty_draw_row_with_mouse_face (w, row, start_hpos, end_hpos, draw);
+#endif
 }
 
 /* Display the active region described by mouse_face_* according to DRAW.  */
@@ -35154,7 +35416,8 @@ note_mouse_highlight (struct frame *f, int x, int y)
   struct buffer *b;
 
   /* When a menu is active, don't highlight because this looks odd.  */
-#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS)
+#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS) \
+  || defined (HAVE_ANDROID)
   if (popup_activated ())
     return;
 #endif
@@ -36335,14 +36598,10 @@ expose_frame (struct frame *f, int x, int y, int w, 
int h)
       |= expose_window (XWINDOW (f->tool_bar_window), &r);
 #endif
 
-#ifdef HAVE_X_WINDOWS
-#ifndef MSDOS
-#if ! defined (USE_X_TOOLKIT) && ! defined (USE_GTK)
+#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR
   if (WINDOWP (f->menu_bar_window))
     mouse_face_overwritten_p
       |= expose_window (XWINDOW (f->menu_bar_window), &r);
-#endif /* not USE_X_TOOLKIT and not USE_GTK */
-#endif
 #endif
 
   /* Some window managers support a focus-follows-mouse style with
@@ -36426,6 +36685,55 @@ gui_intersect_rectangles (const Emacs_Rectangle *r1, 
const Emacs_Rectangle *r2,
   return intersection_p;
 }
 
+/* EXPORT:
+   Determine the union of the rectangles A and B.  Return the smallest
+   rectangle encompassing both the bounds of A and B in *RESULT.  It
+   is safe for all three arguments to point to each other.  */
+
+void
+gui_union_rectangles (const Emacs_Rectangle *a, const Emacs_Rectangle *b,
+                     Emacs_Rectangle *result)
+{
+  struct gui_box a_box, b_box, result_box;
+
+  /* Handle special cases where one of the rectangles is empty.  */
+
+  if (!a->width || !a->height)
+    {
+      *result = *b;
+      return;
+    }
+  else if (!b->width || !b->height)
+    {
+      *result = *a;
+      return;
+    }
+
+  /* Convert A and B to boxes.  */
+  a_box.x1 = a->x;
+  a_box.y1 = a->y;
+  a_box.x2 = a->x + a->width;
+  a_box.y2 = a->y + a->height;
+
+  b_box.x1 = b->x;
+  b_box.y1 = b->y;
+  b_box.x2 = b->x + b->width;
+  b_box.y2 = b->y + b->height;
+
+  /* Compute the union of the boxes.  */
+  result_box.x1 = min (a_box.x1, b_box.x1);
+  result_box.y1 = min (a_box.y1, b_box.y1);
+  result_box.x2 = max (a_box.x2, b_box.x2);
+  result_box.y2 = max (a_box.y2, b_box.y2);
+
+  /* Convert result_box to an XRectangle and put the result in
+     RESULT.  */
+  result->x = result_box.x1;
+  result->y = result_box.y1;
+  result->width = result_box.x2 - result_box.x1;
+  result->height = result_box.y2 - result_box.y1;
+}
+
 #endif /* HAVE_WINDOW_SYSTEM */
 
 
@@ -37456,9 +37764,12 @@ may be more familiar to users.  */);
   display_raw_bytes_as_hex = false;
 
   DEFVAR_BOOL ("mouse-fine-grained-tracking", mouse_fine_grained_tracking,
-    doc: /* Non-nil for pixel-wise mouse-movement.
+    doc: /* Non-nil for pixelwise mouse-movement.
 When nil, mouse-movement events will not be generated as long as the
-mouse stays within the extent of a single glyph (except for images).  */);
+mouse stays within the extent of a single glyph (except for images).
+When nil and `mouse-prefer-closest-glyph' is non-nil, mouse-movement
+events will instead not be generated as long as the mouse stays within
+the extent of a single left/right half glyph (except for images).  */);
   mouse_fine_grained_tracking = false;
 
   DEFVAR_BOOL ("tab-bar--dragging-in-progress", tab_bar__dragging_in_progress,
diff --git a/src/xfaces.c b/src/xfaces.c
index 37b703984be..30487c0e8fb 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -254,6 +254,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #ifdef HAVE_HAIKU
 #define GCGraphicsExposures 0
 #endif /* HAVE_HAIKU */
+
+#ifdef HAVE_ANDROID
+#define GCGraphicsExposures 0
+#endif /* HAVE_ANDROID */
 #endif /* HAVE_WINDOW_SYSTEM */
 
 #include "buffer.h"
@@ -607,6 +611,39 @@ x_free_gc (struct frame *f, Emacs_GC *gc)
 }
 #endif  /* HAVE_NS */
 
+#ifdef HAVE_ANDROID
+
+/* Android real GCs.  */
+
+static struct android_gc *
+x_create_gc (struct frame *f, unsigned long value_mask,
+            Emacs_GC *xgcv)
+{
+  struct android_gc_values gcv;
+  unsigned long mask;
+
+  gcv.foreground = xgcv->foreground;
+  gcv.background = xgcv->background;
+
+  mask = 0;
+
+  if (value_mask & GCForeground)
+    mask |= ANDROID_GC_FOREGROUND;
+
+  if (value_mask & GCBackground)
+    mask |= ANDROID_GC_BACKGROUND;
+
+  return android_create_gc (mask, &gcv);
+}
+
+static void
+x_free_gc (struct frame *f, struct android_gc *gc)
+{
+  android_free_gc (gc);
+}
+
+#endif
+
 /***********************************************************************
                           Frames and faces
  ***********************************************************************/
@@ -1575,7 +1612,7 @@ the face font sort order, see 
`face-font-selection-order'.  */)
     {
       Lisp_Object font = AREF (vec, i);
       int point = PIXEL_TO_POINT (XFIXNUM (AREF (font, FONT_SIZE_INDEX)) * 10,
-                                 FRAME_RES_Y (f));
+                                 FRAME_RES (f));
       Lisp_Object spacing = Ffont_get (font, QCspacing);
       Lisp_Object v = CALLN (Fvector,
                             AREF (font, FONT_FAMILY_INDEX),
@@ -2136,7 +2173,7 @@ set_lface_from_font (struct frame *f, Lisp_Object lface,
 
   if (force_p || UNSPECIFIEDP (LFACE_HEIGHT (lface)))
     {
-      int pt = PIXEL_TO_POINT (font->pixel_size * 10, FRAME_RES_Y (f));
+      int pt = PIXEL_TO_POINT (font->pixel_size * 10, FRAME_RES (f));
 
       eassert (pt > 0);
       ASET (lface, LFACE_HEIGHT_INDEX, make_fixnum (pt));
@@ -6952,20 +6989,22 @@ where R,G,B are numbers between 0 and 255 and name is 
an arbitrary string.  */)
       int num;
 
       while (fgets (buf, sizeof (buf), fp) != NULL)
-       if (sscanf (buf, "%d %d %d %n", &red, &green, &blue, &num) == 3)
-         {
+       {
+         if (sscanf (buf, "%d %d %d %n", &red, &green, &blue, &num) == 3)
+           {
 #ifdef HAVE_NTGUI
-           int color = RGB (red, green, blue);
+             int color = RGB (red, green, blue);
 #else
-           int color = (red << 16) | (green << 8) | blue;
+             int color = (red << 16) | (green << 8) | blue;
 #endif
-           char *name = buf + num;
-           ptrdiff_t len = strlen (name);
-           len -= 0 < len && name[len - 1] == '\n';
-           cmap = Fcons (Fcons (make_string (name, len), make_fixnum (color)),
-                         cmap);
-         }
-      fclose (fp);
+             char *name = buf + num;
+             ptrdiff_t len = strlen (name);
+             len -= 0 < len && name[len - 1] == '\n';
+             cmap = Fcons (Fcons (make_string (name, len), make_fixnum 
(color)),
+                           cmap);
+           }
+       }
+      emacs_fclose (fp);
     }
   unblock_input ();
   return cmap;
diff --git a/src/xfns.c b/src/xfns.c
index 5c9f58e3a96..aea2f4b880e 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -3883,7 +3883,7 @@ xic_string_conversion_callback (XIC ic, XPointer 
client_data,
     request.operation = TEXTCONV_RETRIEVAL;
 
   /* Now perform the string conversion.  */
-  rc = textconv_query (f, &request);
+  rc = textconv_query (f, &request, 0);
 
   if (rc)
     {
@@ -4036,7 +4036,7 @@ setup_xi_event_mask (struct frame *f)
   selected->mask = ((unsigned char *) selected) + sizeof *selected;
   selected->mask_len = l;
   selected->deviceid = XIAllMasterDevices;
-#endif
+#endif /* !HAVE_XINPUT2_1 */
 
   mask.mask = m = alloca (l);
   memset (m, 0, l);
@@ -4056,7 +4056,19 @@ setup_xi_event_mask (struct frame *f)
   XISetMask (m, XI_FocusOut);
   XISetMask (m, XI_KeyPress);
   XISetMask (m, XI_KeyRelease);
-#endif
+#endif /* !USE_GTK */
+#if defined HAVE_XINPUT2_4
+  if (FRAME_DISPLAY_INFO (f)->xi2_version >= 4)
+    {
+      /* Select for gesture events.  Since this configuration doesn't
+        use GTK 3, Emacs is the only code that can change the XI
+        event mask, and can safely select for gesture events on
+        master pointers only.  */
+      XISetMask (m, XI_GesturePinchBegin);
+      XISetMask (m, XI_GesturePinchUpdate);
+      XISetMask (m, XI_GesturePinchEnd);
+    }
+#endif /* HAVE_XINPUT2_4 */
   XISelectEvents (FRAME_X_DISPLAY (f),
                  FRAME_X_WINDOW (f),
                  &mask, 1);
@@ -4065,7 +4077,7 @@ setup_xi_event_mask (struct frame *f)
      to get the event mask from the X server.  */
 #ifndef HAVE_XINPUT2_1
   memcpy (selected->mask, m, l);
-#endif
+#endif /* !HAVE_XINPUT2_1 */
 
   memset (m, 0, l);
 #endif /* !HAVE_GTK3 */
@@ -4080,35 +4092,46 @@ setup_xi_event_mask (struct frame *f)
                  FRAME_OUTER_WINDOW (f),
                  &mask, 1);
   memset (m, 0, l);
-#endif
+#endif /* USE_X_TOOLKIT */
 
 #ifdef HAVE_XINPUT2_2
   if (FRAME_DISPLAY_INFO (f)->xi2_version >= 2)
     {
+      /* Select for touch events from all devices.
+
+         Emacs will only process touch events originating
+         from slave devices, as master pointers may also
+         represent dependent touch devices.  */
       mask.deviceid = XIAllDevices;
 
       XISetMask (m, XI_TouchBegin);
       XISetMask (m, XI_TouchUpdate);
       XISetMask (m, XI_TouchEnd);
-#ifdef HAVE_XINPUT2_4
+      XISetMask (m, XI_TouchOwnership);
+
+#if defined HAVE_XINPUT2_4 && defined USE_GTK3
       if (FRAME_DISPLAY_INFO (f)->xi2_version >= 4)
        {
+         /* Now select for gesture events from all pointer devices.
+            Emacs will only handle gesture events from the master
+            pointer, but cannot afford to overwrite the event mask
+            set by GDK.  */
+
          XISetMask (m, XI_GesturePinchBegin);
          XISetMask (m, XI_GesturePinchUpdate);
          XISetMask (m, XI_GesturePinchEnd);
        }
-#endif
+#endif /* HAVE_XINPUT2_4 && USE_GTK3 */
 
-      XISelectEvents (FRAME_X_DISPLAY (f),
-                     FRAME_X_WINDOW (f),
+      XISelectEvents (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f),
                      &mask, 1);
     }
-#endif
+#endif /* HAVE_XINPUT2_2 */
 
 #ifndef HAVE_XINPUT2_1
   FRAME_X_OUTPUT (f)->xi_masks = selected;
   FRAME_X_OUTPUT (f)->num_xi_masks = 1;
-#endif
+#endif /* HAVE_XINPUT2_1 */
 
   unblock_input ();
 }
@@ -5370,6 +5393,17 @@ This function is an internal primitive--use `make-frame' 
instead.  */)
   gui_default_parameter (f, parms, Qfullscreen, Qnil,
                          "fullscreen", "Fullscreen", RES_TYPE_SYMBOL);
 
+#ifdef USE_CAIRO
+  /* Set the initial size of the Cairo surface to the frame's current
+     width and height.  If the window manager doesn't resize the new
+     frame after it's first mapped, Emacs will create a surface with
+     empty dimensions in response to to the initial exposure event,
+     which will persist until the next time it's resized.
+     (bug#64923) */
+  x_cr_update_surface_desired_size (f, FRAME_PIXEL_WIDTH (f),
+                                   FRAME_PIXEL_HEIGHT (f));
+#endif /* USE_CAIRO */
+
   /* Make the window appear on the frame and enable display, unless
      the caller says not to.  However, with explicit parent, Emacs
      cannot control visibility, so don't try.  */
@@ -5695,6 +5729,8 @@ that operating systems cannot be developed and 
distributed noncommercially.)
 The optional argument TERMINAL specifies which display to ask about.
 
 For GNU and Unix systems, this queries the X server software.
+For Android systems, value is the manufacturer who developed the Android
+system that is being used.
 For MS Windows and Nextstep the result is hard-coded.
 
 TERMINAL should be a terminal object, a frame or a display name (a string).
@@ -5718,7 +5754,8 @@ Protocol used on TERMINAL and the 3rd number is the 
distributor-specific
 release number.  For MS Windows, the 3 numbers report the OS major and
 minor version and build number.  For Nextstep, the first 2 numbers are
 hard-coded and the 3rd represents the OS version.  For Haiku, all 3
-numbers are hard-coded.
+numbers are hard-coded.  For Android, the first number represents the
+Android API level, and the next two numbers are all zero.
 
 See also the function `x-server-vendor'.
 
diff --git a/src/xmenu.c b/src/xmenu.c
index 6d32aa3e078..2d405d54deb 100644
--- a/src/xmenu.c
+++ b/src/xmenu.c
@@ -1617,6 +1617,7 @@ popup_selection_callback (Widget widget, LWLIB_ID id, 
XtPointer client_data)
 
 
 #ifdef HAVE_XINPUT2
+
 static void
 prepare_for_entry_into_toolkit_menu (struct frame *f)
 {
@@ -1680,6 +1681,19 @@ leave_toolkit_menu (void *data)
   XISetMask (m, XI_Enter);
   XISetMask (m, XI_Leave);
 
+#ifdef HAVE_XINPUT2_4
+  /* Select for gesture events.  Emacs selects for gesture events from
+     all master devices on non-GTK3 builds, so that event mask is also
+     clobbered by prepare_for_entry_into_toolkit_menu.  (bug#65129) */
+
+  if (dpyinfo->xi2_version >= 4)
+    {
+      XISetMask (m, XI_GesturePinchBegin);
+      XISetMask (m, XI_GesturePinchUpdate);
+      XISetMask (m, XI_GesturePinchEnd);
+    }
+#endif /* HAVE_XINPUT2_4 */
+
   FOR_EACH_FRAME (tail, frame)
     {
       f = XFRAME (frame);
@@ -1691,7 +1705,8 @@ leave_toolkit_menu (void *data)
                        &mask, 1);
     }
 }
-#endif
+
+#endif /* HAVE_XINPUT2 */
 
 /* ID is the LWLIB ID of the dialog box.  */
 
diff --git a/src/xterm.c b/src/xterm.c
index 9ecead03b08..d826eec2419 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -1215,6 +1215,8 @@ static void x_set_input_focus (struct x_display_info *, 
Window, Time);
 static void x_scroll_bar_redraw (struct scroll_bar *);
 #endif
 
+
+
 /* Global state maintained during a drag-and-drop operation.  */
 
 /* Flag that indicates if a drag-and-drop operation is in progress.  */
@@ -1543,6 +1545,8 @@ static struct x_client_list_window *x_dnd_toplevels;
    for `x_dnd_toplevels' to work.  */
 static bool x_dnd_use_toplevels;
 
+
+
 /* Motif drag-and-drop protocol support.  */
 
 /* Pointer to a variable which stores whether or not an X error
@@ -2914,6 +2918,11 @@ x_dnd_send_xm_leave_for_drop (struct x_display_info 
*dpyinfo,
                                     wdesc, &lmsg);
 }
 
+
+
+/* Drag-and-drop and XDND protocol primitives employed by the event
+   loop.  */
+
 static void
 x_dnd_free_toplevels (bool display_alive)
 {
@@ -3259,9 +3268,10 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
       if (!xm_property_reply)
        free (error);
 
-      extent_property_reply = xcb_get_property_reply (dpyinfo->xcb_connection,
-                                                     
extent_property_cookies[i],
-                                                     &error);
+      extent_property_reply
+       = xcb_get_property_reply (dpyinfo->xcb_connection,
+                                 extent_property_cookies[i],
+                                 &error);
 
       if (!extent_property_reply)
        free (error);
@@ -3342,7 +3352,8 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
 #else
          if (xm_property_reply
              && xm_property_reply->format == 8
-             && xm_property_reply->type == 
dpyinfo->Xatom_MOTIF_DRAG_RECEIVER_INFO
+             && (xm_property_reply->type
+                 == dpyinfo->Xatom_MOTIF_DRAG_RECEIVER_INFO)
              && xcb_get_property_value_length (xm_property_reply) >= 4)
            {
              xmdata = xcb_get_property_value (xm_property_reply);
@@ -3391,9 +3402,10 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
                  XFree (rects);
                }
 #else
-             bounding_rect_reply = xcb_shape_get_rectangles_reply 
(dpyinfo->xcb_connection,
-                                                                   
bounding_rect_cookies[i],
-                                                                   &error);
+             bounding_rect_reply
+               = xcb_shape_get_rectangles_reply (dpyinfo->xcb_connection,
+                                                 bounding_rect_cookies[i],
+                                                 &error);
 
              if (bounding_rect_reply)
                {
@@ -3404,7 +3416,8 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
                                                 * sizeof *tem->bounding_rects);
                  tem->n_bounding_rects = 0;
 
-                 for (; bounding_rect_iterator.rem; xcb_rectangle_next 
(&bounding_rect_iterator))
+                 for (; bounding_rect_iterator.rem;
+                      xcb_rectangle_next (&bounding_rect_iterator))
                    {
                      tem->bounding_rects[tem->n_bounding_rects].x
                        = bounding_rect_iterator.data->x;
@@ -3429,9 +3442,10 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
                  || (dpyinfo->xshape_major == 1
                      && dpyinfo->xshape_minor >= 1))
                {
-                 input_rect_reply = xcb_shape_get_rectangles_reply 
(dpyinfo->xcb_connection,
-                                                                    
input_rect_cookies[i],
-                                                                    &error);
+                 input_rect_reply
+                   = xcb_shape_get_rectangles_reply (dpyinfo->xcb_connection,
+                                                     input_rect_cookies[i],
+                                                     &error);
 
                  if (input_rect_reply)
                    {
@@ -3442,7 +3456,8 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
                                                  * sizeof *tem->input_rects);
                      tem->n_input_rects = 0;
 
-                     for (; input_rect_iterator.rem; xcb_rectangle_next 
(&input_rect_iterator))
+                     for (; input_rect_iterator.rem;
+                          xcb_rectangle_next (&input_rect_iterator))
                        {
                          tem->input_rects[tem->n_input_rects].x
                            = input_rect_iterator.data->x;
@@ -3509,17 +3524,25 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
          if (tem->n_input_rects == -1
              && tem->n_bounding_rects == 1
 #ifdef USE_XCB
-             && tem->bounding_rects[0].width == (geometry_reply->width
-                                                 + 
geometry_reply->border_width)
-             && tem->bounding_rects[0].height == (geometry_reply->height
-                                                  + 
geometry_reply->border_width)
-             && tem->bounding_rects[0].x == -geometry_reply->border_width
-             && tem->bounding_rects[0].y == -geometry_reply->border_width
+             && (tem->bounding_rects[0].width
+                 == (geometry_reply->width
+                     + geometry_reply->border_width))
+             && (tem->bounding_rects[0].height
+                 == (geometry_reply->height
+                     + geometry_reply->border_width))
+             && (tem->bounding_rects[0].x
+                 == -geometry_reply->border_width)
+             && (tem->bounding_rects[0].y
+                 == -geometry_reply->border_width)
 #else
-             && tem->bounding_rects[0].width == attrs.width + 
attrs.border_width
-             && tem->bounding_rects[0].height == attrs.height + 
attrs.border_width
-             && tem->bounding_rects[0].x == -attrs.border_width
-             && tem->bounding_rects[0].y == -attrs.border_width
+             && (tem->bounding_rects[0].width
+                 == attrs.width + attrs.border_width)
+             && (tem->bounding_rects[0].height
+                 == attrs.height + attrs.border_width)
+             && (tem->bounding_rects[0].x
+                 == -attrs.border_width)
+             && (tem->bounding_rects[0].y
+                 == -attrs.border_width)
 #endif
              )
            {
@@ -3542,9 +3565,10 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
 #ifdef HAVE_XCB_SHAPE
          if (dpyinfo->xshape_supported_p)
            {
-             bounding_rect_reply = xcb_shape_get_rectangles_reply 
(dpyinfo->xcb_connection,
-                                                                   
bounding_rect_cookies[i],
-                                                                   &error);
+             bounding_rect_reply
+               = xcb_shape_get_rectangles_reply (dpyinfo->xcb_connection,
+                                                 bounding_rect_cookies[i],
+                                                 &error);
 
              if (bounding_rect_reply)
                free (bounding_rect_reply);
@@ -3559,9 +3583,10 @@ x_dnd_compute_toplevels (struct x_display_info *dpyinfo)
                  || (dpyinfo->xshape_major == 1
                      && dpyinfo->xshape_minor >= 1)))
            {
-             input_rect_reply = xcb_shape_get_rectangles_reply 
(dpyinfo->xcb_connection,
-                                                                
input_rect_cookies[i],
-                                                                &error);
+             input_rect_reply
+               = xcb_shape_get_rectangles_reply (dpyinfo->xcb_connection,
+                                                 input_rect_cookies[i],
+                                                 &error);
 
              if (input_rect_reply)
                free (input_rect_reply);
@@ -3784,8 +3809,10 @@ x_dnd_get_target_window_1 (struct x_display_info 
*dpyinfo,
              if (tem->n_input_rects == -1
                  || x_dnd_get_target_window_2 (tem->input_rects,
                                                tem->n_input_rects,
-                                               tem->border_width + root_x - 
tem->x,
-                                               tem->border_width + root_y - 
tem->y))
+                                               (tem->border_width
+                                                + root_x - tem->x),
+                                               (tem->border_width
+                                                + root_y - tem->y)))
                {
                  chosen = tem;
                  break;
@@ -3872,11 +3899,12 @@ x_dnd_get_wm_state_and_proto (struct x_display_info 
*dpyinfo,
                                        (xcb_window_t) window,
                                        (xcb_atom_t) dpyinfo->Xatom_XdndProxy,
                                        XA_WINDOW, 0, 1);
-  xm_style_cookie = xcb_get_property (dpyinfo->xcb_connection, 0,
-                                     (xcb_window_t) window,
-                                     (xcb_atom_t) 
dpyinfo->Xatom_MOTIF_DRAG_RECEIVER_INFO,
-                                     (xcb_atom_t) 
dpyinfo->Xatom_MOTIF_DRAG_RECEIVER_INFO,
-                                     0, 4);
+  xm_style_cookie
+    = xcb_get_property (dpyinfo->xcb_connection, 0,
+                       (xcb_window_t) window,
+                       (xcb_atom_t) dpyinfo->Xatom_MOTIF_DRAG_RECEIVER_INFO,
+                       (xcb_atom_t) dpyinfo->Xatom_MOTIF_DRAG_RECEIVER_INFO,
+                       0, 4);
 
   reply = xcb_get_property_reply (dpyinfo->xcb_connection,
                                  wmstate_cookie, &error);
@@ -4920,6 +4948,11 @@ x_dnd_cleanup_drag_and_drop (void *frame)
   x_restore_events_after_dnd (f, &x_dnd_old_window_attrs);
 }
 
+
+
+/* Primitives for simplified drag-and-drop tracking when items are
+   being dragged between frames comprising the same Emacs session.  */
+
 static void
 x_dnd_note_self_position (struct x_display_info *dpyinfo, Window target,
                          unsigned short root_x, unsigned short root_y)
@@ -5050,6 +5083,10 @@ x_dnd_note_self_drop (struct x_display_info *dpyinfo, 
Window target,
   kbd_buffer_store_event (&ie);
 }
 
+
+
+/* Miscellaneous X event and graphics extension functions.  */
+
 /* Flush display of frame F.  */
 
 static void
@@ -5134,28 +5171,9 @@ record_event (char *locus, int type)
 
 #endif
 
-#ifdef HAVE_XINPUT2
-bool
-xi_frame_selected_for (struct frame *f, unsigned long event)
-{
-  XIEventMask *masks;
-  int i;
-
-  masks = FRAME_X_OUTPUT (f)->xi_masks;
-
-  if (!masks)
-    return false;
-
-  for (i = 0; i < FRAME_X_OUTPUT (f)->num_xi_masks; ++i)
-    {
-      if (masks[i].mask_len >= XIMaskLen (event)
-         && XIMaskIsSet (masks[i].mask, event))
-       return true;
-    }
+
 
-  return false;
-}
-#endif
+/* Miscelaneous event handling functions.  */
 
 static void
 x_toolkit_position (struct frame *f, int x, int y,
@@ -5300,10 +5318,36 @@ x_extension_initialize (struct x_display_info *dpyinfo)
 
 #endif /* HAVE_CAIRO */
 
+
+
+/* X input extension device and event mask management functions.  */
+
 #ifdef HAVE_XINPUT2
 
+bool
+xi_frame_selected_for (struct frame *f, unsigned long event)
+{
+  XIEventMask *masks;
+  int i;
+
+  masks = FRAME_X_OUTPUT (f)->xi_masks;
+
+  if (!masks)
+    return false;
+
+  for (i = 0; i < FRAME_X_OUTPUT (f)->num_xi_masks; ++i)
+    {
+      if (masks[i].mask_len >= XIMaskLen (event)
+         && XIMaskIsSet (masks[i].mask, event))
+       return true;
+    }
+
+  return false;
+}
+
 /* Convert XI2 button state IN to a standard X button modifier
    mask, and place it in OUT.  */
+
 static void
 xi_convert_button_state (XIButtonState *in, unsigned int *out)
 {
@@ -5325,7 +5369,7 @@ xi_convert_button_state (XIButtonState *in, unsigned int 
*out)
 
 #ifdef USE_GTK
 static
-#endif
+#endif /* USE_GTK */
 unsigned int
 xi_convert_event_state (XIDeviceEvent *xev)
 {
@@ -5351,12 +5395,13 @@ xi_convert_event_keyboard_state (XIDeviceEvent *xev)
 }
 
 /* Free all XI2 devices on DPYINFO.  */
+
 static void
 x_free_xi_devices (struct x_display_info *dpyinfo)
 {
 #ifdef HAVE_XINPUT2_2
   struct xi_touch_point_t *tem, *last;
-#endif
+#endif /* HAVE_XINPUT2_2 */
 
   block_input ();
 
@@ -5366,7 +5411,7 @@ x_free_xi_devices (struct x_display_info *dpyinfo)
        {
 #ifdef HAVE_XINPUT2_1
          xfree (dpyinfo->devices[i].valuators);
-#endif
+#endif /* HAVE_XINPUT2_1 */
 
 #ifdef HAVE_XINPUT2_2
          tem = dpyinfo->devices[i].touchpoints;
@@ -5376,7 +5421,7 @@ x_free_xi_devices (struct x_display_info *dpyinfo)
              tem = tem->next;
              xfree (last);
            }
-#endif
+#endif /* HAVE_XINPUT2_2 */
        }
 
       xfree (dpyinfo->devices);
@@ -5441,7 +5486,7 @@ xi_populate_scroll_valuator (struct xi_device_t *device,
   valuator->number = info->number;
 }
 
-#endif
+#endif /* HAVE_XINPUT2_1 */
 
 static void
 xi_populate_device_from_info (struct x_display_info *dpyinfo,
@@ -5453,14 +5498,14 @@ xi_populate_device_from_info (struct x_display_info 
*dpyinfo,
   int actual_valuator_count, c;
   XIScrollClassInfo *info;
   XIValuatorClassInfo *valuator_info;
-#endif
+#endif /* HAVE_XINPUT2_1 */
 #ifdef HAVE_XINPUT2_2
   XITouchClassInfo *touch_info;
-#endif
+#endif /* HAVE_XINPUT2_2 */
 
 #ifdef HAVE_XINPUT2_1
   USE_SAFE_ALLOCA;
-#endif
+#endif /* HAVE_XINPUT2_1 */
 
   /* Initialize generic information about the device: its ID, which
      buttons are currently pressed and thus presumably actively
@@ -5497,12 +5542,24 @@ xi_populate_device_from_info (struct x_display_info 
*dpyinfo,
      no input.
 
      The device attachment is a device ID whose meaning varies
-     depending on the device use.  If the device is a master device,
-     then the attachment is the device ID of the other device in its
-     seat (the master keyboard for master pointer devices, and vice
-     versa).  Otherwise, it is the ID of the master device the slave
+     depending on the device's use.  If a device is a master device,
+     then its attachment is the device ID of the other device in its
+     seat (the master keyboard for master pointer devices and vice
+     versa.)  Otherwise, it is the ID of the master device the slave
      device is attached to.  For slave devices not attached to any
-     seat, its value is undefined.  */
+     seat, its value is undefined.
+
+     Emacs receives ordinary pointer and keyboard events from the
+     master devices associated with each seat, discarding events from
+     slave devices.  However, multiplexing events from touch devices
+     onto a master device poses problems: if both dependent and direct
+     touch devices are attached to the same master pointer device, the
+     coordinate space of touch events sent from that seat becomes
+     ambiguous.  In addition, the X server does not send TouchEnd
+     events to cancel ongoing touch sequences if the slave device that
+     is their source is detached.  As a result of these ambiguities,
+     touch events are processed from and recorded onto their slave
+     devices instead.  */
 
   xi_device->device_id = device->deviceid;
   xi_device->grab = 0;
@@ -5516,7 +5573,7 @@ xi_populate_device_from_info (struct x_display_info 
*dpyinfo,
 #ifdef HAVE_XINPUT2_2
   xi_device->touchpoints = NULL;
   xi_device->direct_p = false;
-#endif
+#endif /* HAVE_XINPUT2_1 */
 
 #ifdef HAVE_XINPUT2_1
   if (!dpyinfo->xi2_version)
@@ -5582,9 +5639,34 @@ xi_populate_device_from_info (struct x_display_info 
*dpyinfo,
        case XITouchClass:
          {
            touch_info = (XITouchClassInfo *) device->classes[c];
-           xi_device->direct_p = touch_info->mode == XIDirectTouch;
+
+           /* touch_info->mode indicates the coordinate space that
+              this device reports in its touch events.
+
+              DirectTouch means that the device uses a coordinate
+              space that corresponds to locations on the screen.  It
+              is set by touch screen devices which are overlaid
+              over the raster itself.
+
+              The other value (DependentTouch) means that the device
+              uses a separate abstract coordinate space corresponding
+              to its own surface.  Emacs ignores events from these
+              devices because it does not support recognizing touch
+              gestures from surfaces other than the screen.
+
+              Master devices may report multiple touch classes for
+              attached slave devices, leaving the nature of touch
+              events they send ambiguous.  The problem of
+              discriminating between these events is bypassed
+              entirely through only processing touch events from the
+              slave devices where they originate.  */
+
+           if (touch_info->mode == XIDirectTouch)
+             xi_device->direct_p = true;
+           else
+             xi_device->direct_p = false;
          }
-#endif
+#endif /* HAVE_XINPUT2_2 */
        default:
          break;
        }
@@ -5611,7 +5693,7 @@ xi_populate_device_from_info (struct x_display_info 
*dpyinfo,
     }
 
   SAFE_FREE ();
-#endif
+#endif /* HAVE_XINPUT2_1 */
 }
 
 /* Populate our client-side record of all devices, which includes
@@ -5742,6 +5824,10 @@ xi_device_from_id (struct x_display_info *dpyinfo, int 
deviceid)
 
 #ifdef HAVE_XINPUT2_2
 
+/* Record a touch sequence with the identifier DETAIL from the given
+   FRAME on the specified DEVICE.  Round X and Y and record them as
+   its current position.  */
+
 static void
 xi_link_touch_point (struct xi_device_t *device,
                     int detail, double x, double y,
@@ -5751,19 +5837,28 @@ xi_link_touch_point (struct xi_device_t *device,
 
   touchpoint = xmalloc (sizeof *touchpoint);
   touchpoint->next = device->touchpoints;
-  touchpoint->x = x;
-  touchpoint->y = y;
+  touchpoint->x = lrint (x);
+  touchpoint->y = lrint (y);
   touchpoint->number = detail;
   touchpoint->frame = frame;
+  touchpoint->ownership = TOUCH_OWNERSHIP_NONE;
 
   device->touchpoints = touchpoint;
 }
 
-static bool
-xi_unlink_touch_point (int detail,
-                      struct xi_device_t *device)
+/* Free and remove the touch sequence with the identifier DETAIL.
+   DEVICE is the device in which the touch sequence should be
+   recorded.
+
+   Value is 0 if no touch sequence by that identifier exists inside
+   DEVICE, 1 if a touch sequence has been found but is not owned by
+   Emacs, and 2 otherwise.  */
+
+static int
+xi_unlink_touch_point (int detail, struct xi_device_t *device)
 {
   struct xi_touch_point_t *last, *tem;
+  enum xi_touch_ownership ownership;
 
   for (last = NULL, tem = device->touchpoints; tem;
        last = tem, tem = tem->next)
@@ -5775,12 +5870,17 @@ xi_unlink_touch_point (int detail,
          else
            last->next = tem->next;
 
+         ownership = tem->ownership;
          xfree (tem);
-         return true;
+
+         if (ownership == TOUCH_OWNERSHIP_SELF)
+           return 2;
+
+         return 1;
        }
     }
 
-  return false;
+  return 0;
 }
 
 /* Unlink all touch points associated with the frame F.
@@ -5813,6 +5913,10 @@ xi_unlink_touch_points (struct frame *f)
     }
 }
 
+/* Return the data associated with a touch sequence DETAIL recorded by
+   `xi_link_touch_point' from DEVICE, or NULL if it can't be
+   found.  */
+
 static struct xi_touch_point_t *
 xi_find_touch_point (struct xi_device_t *device, int detail)
 {
@@ -5858,8 +5962,12 @@ xi_reset_scroll_valuators_for_device_id (struct 
x_display_info *dpyinfo,
 }
 
 #endif /* HAVE_XINPUT2_1 */
+#endif /* HAVE_XINPUT2 */
 
-#endif
+
+
+/* Cairo context, X rendering extension, and GC auxiliary data
+   management functions.  */
 
 #ifdef USE_CAIRO
 
@@ -6345,6 +6453,7 @@ x_cr_export_frames (Lisp_Object frames, 
cairo_surface_type_t surface_type)
 #endif /* USE_CAIRO */
 
 #if defined HAVE_XRENDER
+
 void
 x_xr_apply_ext_clip (struct frame *f, GC gc)
 {
@@ -6368,7 +6477,8 @@ x_xr_reset_ext_clip (struct frame *f)
                        FRAME_X_PICTURE (f),
                        CPClipMask, &attrs);
 }
-#endif
+
+#endif /* HAVE_XRENDER */
 
 static void
 x_set_clip_rectangles (struct frame *f, GC gc, XRectangle *rectangles, int n)
@@ -6572,6 +6682,9 @@ x_fill_rectangle (struct frame *f, GC gc, int x, int y, 
int width, int height,
 #endif
 }
 
+
+
+/* Graphics primitives.  */
 
 static void
 x_clear_rectangle (struct frame *f, GC gc, int x, int y, int width, int height,
@@ -6864,6 +6977,8 @@ x_set_frame_alpha (struct frame *f)
   x_stop_ignoring_errors (dpyinfo);
 }
 
+
+
 /***********************************************************************
                    Starting and ending an update
  ***********************************************************************/
@@ -7559,6 +7674,8 @@ XTbuffer_flipping_unblocked_hook (struct frame *f)
 }
 #endif
 
+
+
 /**
  * x_clear_under_internal_border:
  *
@@ -13421,7 +13538,7 @@ xi_handle_new_classes (struct x_display_info *dpyinfo, 
struct xi_device_t *devic
   device->scroll_valuator_count = 0;
 #ifdef HAVE_XINPUT2_2
   device->direct_p = false;
-#endif
+#endif /* HAVE_XINPUT2_2 */
 
   for (i = 0; i < num_classes; ++i)
     {
@@ -13439,10 +13556,34 @@ xi_handle_new_classes (struct x_display_info 
*dpyinfo, struct xi_device_t *devic
        case XITouchClass:
          touch = (XITouchClassInfo *) classes[i];
 
+         /* touch_info->mode indicates the coordinate space that this
+            device reports in its touch events.
+
+            DirectTouch means that the device uses a coordinate space
+            that corresponds to locations on the screen.  It is set
+            by touch screen devices which are overlaid over the
+            raster itself.
+
+            The other value (DependentTouch) means that the device
+            uses a separate abstract coordinate space corresponding
+            to its own surface.  Emacs ignores events from these
+            devices because it does not support recognizing touch
+            gestures from surfaces other than the screen.
+
+            Master devices may report multiple touch classes for
+            attached slave devices, leaving the nature of touch
+            events they send ambiguous.  The problem of
+            discriminating between these events is bypassed entirely
+            through only processing touch events from the slave
+            devices where they originate.  */
+
          if (touch->mode == XIDirectTouch)
            device->direct_p = true;
+         else
+           device->direct_p = false;
+
          break;
-#endif
+#endif /* HAVE_XINPUT2_2 */
        }
     }
 
@@ -13480,7 +13621,7 @@ xi_handle_new_classes (struct x_display_info *dpyinfo, 
struct xi_device_t *devic
     }
 }
 
-#endif
+#endif /* HAVE_XINPUT2_1 */
 
 /* Handle EVENT, a DeviceChanged event.  Look up the device that
    changed, and update its information with the data in EVENT.  */
@@ -20057,15 +20198,24 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #ifdef HAVE_XKB
              int overflow;
              unsigned int consumed;
+             KeySym sym;
 
              if (dpyinfo->xkb_desc)
                {
+                 /* Translate the keycode into the keysym it
+                    represents, using STATE.  CONSUMED is set to the
+                    modifier bits consumed while undertaking this
+                    translation and should be subsequently ignored
+                    during keysym translation.  */
+
                  if (!XkbTranslateKeyCode (dpyinfo->xkb_desc,
                                            xkey.keycode, xkey.state,
                                            &consumed, &keysym))
                    goto done_keysym;
 
-                 overflow = 0;
+                 /* Save the original keysym in case
+                    XkbTranslateKeysym overflows.  */
+                 sym = keysym, overflow = 0;
 
                  nbytes = XkbTranslateKeySym (dpyinfo->display, &keysym,
                                               xkey.state & ~consumed,
@@ -20077,7 +20227,11 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                      copy_bufptr = SAFE_ALLOCA ((copy_bufsiz += overflow)
                                                 * sizeof *copy_bufptr);
                      overflow = 0;
-                     nbytes = XkbTranslateKeySym (dpyinfo->display, &keysym,
+
+                     /* Use the original keysym derived from the
+                        keycode translation in this second call to
+                        XkbTranslateKeysym.  */
+                     nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
                                                   xkey.state & ~consumed,
                                                   (char *) copy_bufptr,
                                                   copy_bufsiz, &overflow);
@@ -20104,6 +20258,24 @@ handle_one_xevent (struct x_display_info *dpyinfo,
            }
 #endif
 
+         /* See if keysym should make Emacs quit.  */
+
+         if (keysym == dpyinfo->quit_keysym
+             && (xkey.time - dpyinfo->quit_keysym_time
+                 <= 350))
+           {
+             Vquit_flag = Qt;
+             goto done_keysym;
+           }
+
+         if (keysym == dpyinfo->quit_keysym)
+           {
+             /* Otherwise, set the last time that keysym was
+                pressed.  */
+             dpyinfo->quit_keysym_time = xkey.time;
+             goto done_keysym;
+           }
+
           /* If not using XIM/XIC, and a compose sequence is in progress,
              we break here.  Otherwise, chars_matched is always 0.  */
           if (compose_status.chars_matched > 0 && nbytes == 0)
@@ -23566,7 +23738,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              Lisp_Object c;
 #ifdef HAVE_XKB
              unsigned int mods_rtrn;
-#endif
+#endif /* HAVE_XKB */
              int keycode = xev->detail;
              KeySym keysym;
              char copy_buffer[81];
@@ -23575,15 +23747,123 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              ptrdiff_t i;
              unsigned int old_state;
              struct xi_device_t *device, *source;
+             XKeyPressedEvent xkey;
 
              coding = Qlatin_1;
 
+             /* The code under this label is quite desultory.  There
+                are also several important discrepancies with the
+                core KeyPress code to mind.
+
+                There are three principal objectives:
+
+                The first is to produce a core or GDK translation of
+                this XI_KeyPress event, which is relayed to the
+                toolkit.  This transpires by setting `copy' to a
+                close copy of XEV, which is later copied or
+                dispatched to the toolkit by the code beneath the
+                OTHER label.
+
+                The second objective is to filter the event through
+                an input method, by generating a second copy of the
+                event expressly tailored for such a purpose.  The
+                core KeyPress code does not endeavor to do so;
+                instead, this action is taken prior to calling
+                handle_one_xevent.  Calls to `x_filter_event' or
+                `xg_filter_key' serve to implement this objective.
+
+                If the event is not removed by the input method's
+                filter, the third objective is to establish either a
+                keysym or a sequence of characters to insert, using
+                the information supplied within the key event.
+
+                When an input method connection is available, this
+                responsibility is vested in the hands of the input
+                method -- yet another copy of XEV as a core event is
+                produced, and the input method is responsible for
+                deriving a keysym or text to insert.
+
+                Otherwise, if the XKB extension is available, calls
+                are made to XkbTranslateKeyCode and
+                XkbTranslateKeySym.
+
+                And if all else fails, XEV is transformed into a core
+                event and provided to XLookupString, in a manner
+                analogous to the core event processing under the
+                KeyPress label.
+
+                A wide number of variables are employed during this
+                translation process.  The most pertinent ones are:
+
+                `copy'
+
+                  This variable is defined when an X toolkit
+                  incognizant of input extension events is being
+                  employed.  If a popup is active, Emacs copies
+                  fields of interest from the extension event to
+                  COPY, sets the `use_copy' flag, and jumps to the
+                  XI_OTHER label.  `copy' is then relayed to the
+                  toolkit.
+
+                `xkey'
+
+                  This variable is defined to a copy of the event
+                  used by input methods or XLookupString at various
+                  points during the execution of this label.
+
+                `coding'
+
+                  This variable is consulted at the conclusion of
+                  event generation, and holds the coding system
+                  for any generated string.
+
+                `keysym'
+
+                  This variable is eventually set to the keysym tied
+                  to the event, which may be directly provided within
+                  a generated struct input_event, should it bear a
+                  direct relation to an ASCII or Unicode character,
+                  or if it is a control key.
+
+                `copy_buffer', `copy_bufptr', `copy_bufsiz'
+
+                  These variables hold the buffer that incorporates
+                  characters generated during the keycode-to-keysym
+                  conversion process.
+
+                `nbytes'
+
+                  Holds the number of characters within that buffer,
+                  in bytes.  These characters are encoded using the
+                  coding system in `coding'.
+
+                  If greater than 0 and KEYSYM does not immediately
+                  relate to a function key, control key or character,
+                  it is provided as the string to insert within a
+                  MULTIBYTE_CHAR_KEYSTROKE_EVENT.
+
+                `state'
+
+                  Holds the keyboard and group (but not button)
+                  state.  After event filtering concludes, modifier
+                  bits within `extra_keyboard_modifiers' are also
+                  introduced.
+
+                This illustration may reflect the treatment taken
+                towards core key events to some degree.  */
+
              device = xi_device_from_id (dpyinfo, xev->deviceid);
              source = xi_device_from_id (dpyinfo, xev->sourceid);
 
              if (!device)
                goto XI_OTHER;
 
+             /* Convert the keyboard state within XEV to a core
+                modifier mask, later supplied as arguments to XKB and
+                core functions.  This encompasses the keyboard group
+                and effective modifiers but not the button state.  */
+             state = xi_convert_event_keyboard_state (xev);
+
 #if defined (USE_X_TOOLKIT) || defined (USE_GTK)
              /* Dispatch XI_KeyPress events when in menu.  */
              if (popup_activated ())
@@ -23599,7 +23879,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                  copy.xkey.root = xev->root;
                  copy.xkey.subwindow = xev->child;
                  copy.xkey.time = xev->time;
-                 copy.xkey.state = xi_convert_event_keyboard_state (xev);
+                 copy.xkey.state = state;
                  xi_convert_button_state (&xev->buttons, &copy.xkey.state);
 
                  copy.xkey.x = lrint (xev->event_x);
@@ -23608,10 +23888,10 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                  copy.xkey.y_root = lrint (xev->root_y);
                  copy.xkey.keycode = xev->detail;
                  copy.xkey.same_screen = True;
-#endif
+#endif /* USE_LUCID */
                  goto XI_OTHER;
                }
-#endif
+#endif /* USE_X_TOOLKIT || USE_GTK */
 
              x_display_set_last_user_time (dpyinfo, xev->time,
                                            xev->send_event, true);
@@ -23631,7 +23911,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #ifdef USE_GTK
              if (f)
                x_set_gtk_user_time (f, xev->time);
-#endif
+#endif /* USE_GTK */
 
              if (f)
                {
@@ -23643,7 +23923,9 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                                           xev->time);
                }
 
-             XKeyPressedEvent xkey;
+             /* Convert the XI event into a core event structure
+                provided to old Xlib functions and input method
+                filter functions.  */
 
              memset (&xkey, 0, sizeof xkey);
 
@@ -23655,8 +23937,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              xkey.root = xev->root;
              xkey.subwindow = xev->child;
              xkey.time = xev->time;
-             xkey.state = xi_convert_event_keyboard_state (xev);
-
+             xkey.state = state;
              xkey.x = lrint (xev->event_x);
              xkey.y = lrint (xev->event_y);
              xkey.x_root = lrint (xev->root_x);
@@ -23685,7 +23966,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                  *finish = X_EVENT_DROP;
                  goto XI_OTHER;
                }
-#else
+#else /* !USE_GTK */
              if (x_filter_event (dpyinfo, (XEvent *) &xkey))
                {
                  /* Try to attribute core key events from the input
@@ -23697,8 +23978,8 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                  *finish = X_EVENT_DROP;
                  goto XI_OTHER;
                }
-#endif
-#elif USE_GTK
+#endif /* HAVE_X_I18N */
+#elif USE_GTK /* && !HAVE_X_I18N */
              if ((x_gtk_use_native_input
                   || dpyinfo->prefer_native_input)
                  && xg_filter_key (any, event))
@@ -23712,48 +23993,17 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                  *finish = X_EVENT_DROP;
                  goto XI_OTHER;
                }
-#endif
+#endif /* HAVE_X_I18N || USE_GTK */
 
              state |= x_emacs_to_x_modifiers (dpyinfo, 
extra_keyboard_modifiers);
 
-#ifdef HAVE_XKB
-             if (dpyinfo->xkb_desc)
-               {
-                 unsigned int xkb_state;
-
-                 xkb_state = state & ~(1 << 13 | 1 << 14);
-                 xkb_state |= xev->group.effective << 13;
-
-                 if (!XkbTranslateKeyCode (dpyinfo->xkb_desc, keycode,
-                                           xkb_state, &mods_rtrn, &keysym))
-                   goto XI_OTHER;
-               }
-             else
-               {
-#endif
-                 int keysyms_per_keycode_return;
-                 KeySym *ksms = XGetKeyboardMapping (dpyinfo->display, 
keycode, 1,
-                                                     
&keysyms_per_keycode_return);
-                 if (!(keysym = ksms[0]))
-                   {
-                     XFree (ksms);
-                     goto XI_OTHER;
-                   }
-                 XFree (ksms);
-#ifdef HAVE_XKB
-               }
-#endif
-
-             if (keysym == NoSymbol)
-               goto XI_OTHER;
-
              /* If mouse-highlight is an integer, input clears out
                 mouse highlighting.  */
              if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
                  && (f == 0
 #if ! defined (USE_GTK)
                      || !EQ (f->tool_bar_window, hlinfo->mouse_face_window)
-#endif
+#endif /* !USE_GTK */
                      || !EQ (f->tab_bar_window, hlinfo->mouse_face_window))
                  )
                {
@@ -23774,7 +24024,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                     dialogs because in that case popup_activated is nonzero
                     (see above).  */
                  *finish = X_EVENT_DROP;
-#endif
+#endif /* USE_GTK */
 
                  XSETFRAME (inev.ie.frame_or_window, f);
                  inev.ie.timestamp = xev->time;
@@ -23811,25 +24061,54 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                        emacs_abort ();
                    }
                  else
-#endif
+#endif /* HAVE_X_I18N */
                    {
 #ifdef HAVE_XKB
-                     int overflow = 0;
-                     KeySym sym = keysym;
-
                      if (dpyinfo->xkb_desc)
                        {
-                         nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
-                                                      state & ~mods_rtrn, 
copy_bufptr,
-                                                      copy_bufsiz, &overflow);
+                         KeySym sym;
+                         int overflow;
+
+                         /* Translate the keycode into the keysym it
+                            represents, using STATE.  MODS_RTRN is
+                            set to the modifier bits consumed while
+                            undertaking this translation and should
+                            be subsequently ignored during keysym
+                            translation.  */
+
+                         if (!XkbTranslateKeyCode (dpyinfo->xkb_desc,
+                                                   keycode, state,
+                                                   &mods_rtrn, &keysym))
+                           goto xi_done_keysym;
+
+                         /* Save the original keysym in case
+                            XkbTranslateKeySym overflows.  */
+                         sym = keysym, overflow = 0;
+
+                         /* Translate this keysym and its modifier
+                            state into the actual symbol and string
+                            it represents.  */
+                         nbytes = XkbTranslateKeySym (dpyinfo->display,
+                                                      &keysym,
+                                                      state & ~mods_rtrn,
+                                                      copy_bufptr,
+                                                      copy_bufsiz,
+                                                      &overflow);
                          if (overflow)
                            {
-                             copy_bufptr = SAFE_ALLOCA ((copy_bufsiz += 
overflow)
-                                                        * sizeof *copy_bufptr);
+                             copy_bufptr
+                               = SAFE_ALLOCA ((copy_bufsiz += overflow)
+                                              * sizeof *copy_bufptr);
                              overflow = 0;
-                             nbytes = XkbTranslateKeySym (dpyinfo->display, 
&sym,
-                                                          state & ~mods_rtrn, 
copy_bufptr,
-                                                          copy_bufsiz, 
&overflow);
+
+                             /* Use the original keysym derived from
+                                the keycode translation.  */
+                             nbytes = XkbTranslateKeySym (dpyinfo->display,
+                                                          &sym,
+                                                          state & ~mods_rtrn,
+                                                          copy_bufptr,
+                                                          copy_bufsiz,
+                                                          &overflow);
 
                              if (overflow)
                                nbytes = 0;
@@ -23838,8 +24117,14 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                          coding = Qnil;
                        }
                      else
-#endif
+#endif /* HAVE_XKB */
                        {
+                         /* Save the state within XKEY, then remove
+                            all modifier keys Emacs understands from
+                            it, forestalling any attempt by
+                            XLookupString to introduce control
+                            characters.  */
+
                          old_state = xkey.state;
                          xkey.state &= ~ControlMask;
                          xkey.state &= ~(dpyinfo->meta_mod_mask
@@ -23865,7 +24150,25 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                      x_dnd_xm_use_help = true;
                      goto xi_done_keysym;
                    }
-#endif
+#endif /* XK_F1 */
+
+                 /* See if keysym should make Emacs quit.  */
+
+                 if (keysym == dpyinfo->quit_keysym
+                     && (xev->time - dpyinfo->quit_keysym_time
+                         <= 350))
+                   {
+                     Vquit_flag = Qt;
+                     goto xi_done_keysym;
+                   }
+
+                 if (keysym == dpyinfo->quit_keysym)
+                   {
+                     /* Otherwise, set the last time that keysym was
+                        pressed.  */
+                     dpyinfo->quit_keysym_time = xev->time;
+                     goto xi_done_keysym;
+                   }
 
                  /* First deal with keysyms which have defined
                     translations to characters.  */
@@ -24259,7 +24562,13 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              x_display_set_last_user_time (dpyinfo, xev->time,
                                            xev->send_event, true);
 
-             if (!device)
+             /* Don't process touch sequences from this device if
+                it's a master pointer.  Touch sequences aren't
+                canceled by the X server if a slave device is
+                detached, and master pointers may also represent
+                dependent touch devices.  */
+
+             if (!device || device->use == XIMasterPointer)
                goto XI_OTHER;
 
              if (xi_find_touch_point (device, xev->detail))
@@ -24416,27 +24725,75 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              goto XI_OTHER;
            }
 
+         case XI_TouchOwnership:
+           {
+             struct xi_device_t *device;
+             struct xi_touch_point_t *touchpoint;
+             XITouchOwnershipEvent *event;
+
+             /* All grabbing clients have decided to reject ownership
+                of this touch sequence.  */
+
+             event  = (XITouchOwnershipEvent *) xi_event;
+             device = xi_device_from_id (dpyinfo, event->deviceid);
+
+             if (!device || device->use == XIMasterPointer)
+               goto XI_OTHER;
+
+             touchpoint = xi_find_touch_point (device, event->touchid);
+
+             if (!touchpoint)
+               goto XI_OTHER;
+
+             /* As a result, Emacs should complete whatever editing
+                operations result from this touch sequence.  */
+             touchpoint->ownership = TOUCH_OWNERSHIP_SELF;
+
+             goto XI_OTHER;
+           }
+
          case XI_TouchUpdate:
            {
              struct xi_device_t *device, *source;
              struct xi_touch_point_t *touchpoint;
              Lisp_Object arg = Qnil;
 
+             /* If flags & TouchPendingEnd, the touch sequence has
+                already ended, but some grabbing clients remain
+                undecided as to whether they will obtain ownership of
+                the touch sequence.
+
+                Wait for them to make their decision, resulting in
+                TouchOwnership and TouchEnd events being sent.  */
+
+             if (xev->flags & XITouchPendingEnd)
+               goto XI_OTHER;
+
              device = xi_device_from_id (dpyinfo, xev->deviceid);
              source = xi_device_from_id (dpyinfo, xev->sourceid);
              x_display_set_last_user_time (dpyinfo, xev->time,
                                            xev->send_event, true);
 
-             if (!device)
+             /* Don't process touch sequences from this device if
+                it's a master pointer.  Touch sequences aren't
+                canceled by the X server if a slave device is
+                detached, and master pointers may also represent
+                dependent touch devices.  */
+
+             if (!device || device->use == XIMasterPointer)
                goto XI_OTHER;
 
              touchpoint = xi_find_touch_point (device, xev->detail);
 
-             if (!touchpoint)
+             if (!touchpoint
+                 /* Don't send this event if nothing has changed
+                    either.  */
+                 || (touchpoint->x == lrint (xev->event_x)
+                     && touchpoint->y == lrint (xev->event_y)))
                goto XI_OTHER;
 
-             touchpoint->x = xev->event_x;
-             touchpoint->y = xev->event_y;
+             touchpoint->x = lrint (xev->event_x);
+             touchpoint->y = lrint (xev->event_y);
 
              f = x_window_to_frame (dpyinfo, xev->event);
 
@@ -24450,8 +24807,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                       touchpoint; touchpoint = touchpoint->next)
                    {
                      if (touchpoint->frame == f)
-                       arg = Fcons (list3i (lrint (touchpoint->x),
-                                            lrint (touchpoint->y),
+                       arg = Fcons (list3i (touchpoint->x, touchpoint->y,
                                             lrint (touchpoint->number)),
                                     arg);
                    }
@@ -24468,19 +24824,25 @@ handle_one_xevent (struct x_display_info *dpyinfo,
          case XI_TouchEnd:
            {
              struct xi_device_t *device, *source;
-             bool unlinked_p;
+             int state;
 
              device = xi_device_from_id (dpyinfo, xev->deviceid);
              source = xi_device_from_id (dpyinfo, xev->sourceid);
              x_display_set_last_user_time (dpyinfo, xev->time,
                                            xev->send_event, true);
 
-             if (!device)
+             /* Don't process touch sequences from this device if
+                it's a master pointer.  Touch sequences aren't
+                canceled by the X server if a slave device is
+                detached, and master pointers may also represent
+                dependent touch devices.  */
+
+             if (!device || device->use == XIMasterPointer)
                goto XI_OTHER;
 
-             unlinked_p = xi_unlink_touch_point (xev->detail, device);
+             state = xi_unlink_touch_point (xev->detail, device);
 
-             if (unlinked_p)
+             if (state)
                {
                  f = x_window_to_frame (dpyinfo, xev->event);
 
@@ -24488,6 +24850,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
                    {
                      inev.ie.kind = TOUCHSCREEN_END_EVENT;
                      inev.ie.timestamp = xev->time;
+                     inev.ie.modifiers = state != 2;
 
                      XSETFRAME (inev.ie.frame_or_window, f);
                      XSETINT (inev.ie.x, lrint (xev->event_x));
@@ -24543,7 +24906,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
              x_display_set_last_user_time (dpyinfo, pev->time,
                                            pev->send_event, true);
 
-             if (!device)
+             if (!device || device->use != XIMasterPointer)
                goto XI_OTHER;
 
 #ifdef HAVE_XWIDGETS
@@ -29923,6 +30286,7 @@ x_term_init (Lisp_Object display_name, char 
*xrm_option, char *resource_name)
   struct terminal *terminal;
   struct x_display_info *dpyinfo;
   XrmDatabase xrdb;
+  Lisp_Object tem, quit_keysym;
 #ifdef USE_XCB
   xcb_connection_t *xcb_conn;
 #endif
@@ -29933,7 +30297,7 @@ x_term_init (Lisp_Object display_name, char 
*xrm_option, char *resource_name)
   GdkScreen *gscr;
 #endif
 #ifdef HAVE_XFIXES
-  Lisp_Object tem, lisp_name;
+  Lisp_Object lisp_name;
   int num_fast_selections;
   Atom selection_name;
 #ifdef USE_XCB
@@ -30210,6 +30574,28 @@ x_term_init (Lisp_Object display_name, char 
*xrm_option, char *resource_name)
     terminal->kboard->reference_count++;
   }
 
+  /* Now look through Vx_quit_keysym for the quit keysym associated
+     with this display.  */
+  tem = Vx_quit_keysym;
+  FOR_EACH_TAIL_SAFE (tem)
+    {
+      quit_keysym = XCAR (tem);
+
+      /* Check if its car is a string and its cdr a valid keysym.
+        Skip if it is not.  */
+
+      if (!CONSP (quit_keysym) || !FIXNUMP (XCDR (quit_keysym))
+         || !STRINGP (XCAR (quit_keysym)))
+       continue;
+
+      /* Check if this is the keysym to be used.  */
+
+      if (strcmp (SSDATA (XCAR (quit_keysym)), ServerVendor (dpy)))
+       continue;
+
+      dpyinfo->quit_keysym = XFIXNUM (XCDR (quit_keysym));
+    }
+
   /* Put this display on the chain.  */
   dpyinfo->next = x_display_list;
   x_display_list = dpyinfo;
@@ -30713,7 +31099,17 @@ x_term_init (Lisp_Object display_name, char 
*xrm_option, char *resource_name)
                       XkbNewKeyboardNotifyMask | XkbMapNotifyMask,
                       XkbNewKeyboardNotifyMask | XkbMapNotifyMask);
     }
-#endif
+
+  /* XFree86 extends XKBlib with a new Xlib control `ControlFallback',
+     which enables a search for symbols designating ASCII characters
+     within inactive groups during keycode translation when
+     ControlMask is set.  Users find this behavior gratuitous, so
+     disable it if present.  */
+
+#ifdef XkbLC_ControlFallback
+  XkbSetXlibControls (dpyinfo->display, XkbLC_ControlFallback, 0);
+#endif /* XkbLC_ControlFallback */
+#endif /* HAVE_XKB */
 
 #ifdef HAVE_XFIXES
   int xfixes_error_base;
@@ -31637,7 +32033,7 @@ init_xterm (void)
 #endif
 
 #ifdef HAVE_X_I18N
-  register_texconv_interface (&text_conversion_interface);
+  register_textconv_interface (&text_conversion_interface);
 #endif
 }
 
@@ -31971,7 +32367,9 @@ adjusted if the default value does not work for 
whatever reason.  */);
 A value of nil means Emacs doesn't use toolkit scroll bars.
 With the X Window system, the value is a symbol describing the
 X toolkit.  Possible values are: gtk, motif, xaw, or xaw3d.
-With MS Windows, Haiku windowing or Nextstep, the value is t.  */);
+With MS Windows, Haiku windowing or Nextstep, the value is t.
+With Android, the value is nil, but that is because Emacs on
+Android does not support scroll bars at all.  */);
 #ifdef USE_TOOLKIT_SCROLL_BARS
 #ifdef USE_MOTIF
   Vx_toolkit_scroll_bars = intern_c_string ("motif");
@@ -32107,10 +32505,12 @@ reported as iconified.  */);
 
   DEFVAR_BOOL ("x-input-grab-touch-events", x_input_grab_touch_events,
               doc: /* Non-nil means to actively grab touch events.
-This means touch sequences that started on an Emacs frame will
-reliably continue to receive updates even if the finger moves off the
-frame, but may cause crashes with some window managers and/or external
-programs.  */);
+This means touch sequences that are obtained through a passive grab on
+an Emacs frame (or a parent window of such a frame) will reliably
+continue to receive updates, but may cause crashes with some window
+managers and/or external programs.  Changing this option is only
+useful when other programs are making their own X requests pertaining
+to the window hierarchy of an Emacs frame.  */);
   x_input_grab_touch_events = true;
 
   DEFVAR_BOOL ("x-dnd-fix-motif-leave", x_dnd_fix_motif_leave,
@@ -32318,4 +32718,23 @@ frame placement via frame parameters, 
`set-frame-position', and
 `set-frame-size', along with the actual state of a frame after
 `x_make_frame_invisible'.  */);
   Vx_lax_frame_positioning = Qnil;
+
+  DEFVAR_LISP ("x-quit-keysym", Vx_quit_keysym,
+    doc: /* Keysyms which will cause Emacs to quit if rapidly pressed twice.
+
+This is used to support quitting on devices that do not have any kind
+of physical keyboard, or where the physical keyboard is incapable of
+entering `C-g'.  It defaults to `XF86XK_AudioLowerVolume' on XFree86
+and X.Org servers, and is unset.
+
+The value is an alist associating between strings, describing X server
+vendor names, and a single number describing the keysym to use.  The
+keysym to use for each display connection is determined upon
+connection setup, and does not reflect further changes to this
+variable.  */);
+  Vx_quit_keysym
+    = list2 (Fcons (build_string ("The X.Org Foundation"),
+                   make_int (269025041)),
+            Fcons (build_string ("The XFree86 Project, Inc."),
+                   make_int (269025041)));
 }
diff --git a/src/xterm.h b/src/xterm.h
index 34a713ea2ca..cb477645bfa 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -255,19 +255,71 @@ struct xi_scroll_valuator_t
 
 #ifdef HAVE_XINPUT2_2
 
+/* Enum describing the ownership of a touch point.
+
+   The input extension allows other clients to intercept touch
+   sequences destined for a client window through passively grabbing
+   for touch events on a parent window.
+
+   When a passive touch grab for an XI_TouchBegin event activates, one
+   grabbing client is designated the ``owner'' of the touch sequence
+   started by the grabbed event.  Touch events are then delivered to
+   both the grabbing client and other clients that have selected for
+   touch events on the subwindow.
+
+   The X server will not deliver TouchEnd events to clients other than
+   the owner until one grabbing client decides to take over processing
+   the touch event sequence, or no more grabbing clients remain.
+   Instead, a TouchUpdate event with the TouchPendingEnd flag is sent,
+   and the TouchEnd event is postponed until the decision is made and
+   all XI_TouchOwnership events are sent.
+
+   If the owner decides to take over processing the touch sequence, an
+   XI_TouchEnd event is delivered to all other clients receiving
+   events for the current touch sequence, who are then expected to
+   cancel or undo any actions which have taken place in reaction to
+   events from that sequence.
+
+   If the owner decides to relinquish ownership over the touch
+   sequence, the X server looks for another grabbing client, and
+   transfers touch ownership to that client instead.  Nothing changes
+   from the perspective of clients who have merely selected for events
+   from the subwindow, while an XI_TouchEnd event is delivered to the
+   old owner, and an XI_TouchOwnership event is delivered to the new
+   owner.
+
+   If all grabbing clients reject ownership over the touch sequence,
+   the X server delivers an XI_TouchOwnership event to the client that
+   has selected for touch events on the subwindow, the only client
+   that will receive events for this touch sequence from this time
+   forward.  */
+
+enum xi_touch_ownership
+  {
+    /* Emacs doesn't own this touch sequence.  */
+    TOUCH_OWNERSHIP_NONE,
+
+    /* Emacs owns this touch sequence.  */
+    TOUCH_OWNERSHIP_SELF,
+  };
+
 struct xi_touch_point_t
 {
-  /* The next touch point in this list.  */
-  struct xi_touch_point_t *next;
-
   /* The touchpoint detail.  */
   int number;
 
-  /* The last known X and Y position of the touchpoint.  */
-  double x, y;
+  /* Whether or not Emacs has ``exclusive'' access to this touch
+     point.  */
+  enum xi_touch_ownership ownership;
+
+  /* The last known rounded X and Y positions of the touchpoint.  */
+  int x, y;
 
   /* The frame associated with this touch point.  */
   struct frame *frame;
+
+  /* The next touch point in this list.  */
+  struct xi_touch_point_t *next;
 };
 
 #endif
@@ -924,6 +976,13 @@ struct x_display_info
      server_time_monotonic_p will be true).  */
   int_fast64_t server_time_offset;
 #endif
+
+  /* Keysym that will cause Emacs to quit if pressed twice within 150
+     ms.  */
+  KeySym quit_keysym;
+
+  /* The last time that keysym was pressed.  */
+  Time quit_keysym_time;
 };
 
 #ifdef HAVE_XINPUT2
diff --git a/test/Makefile.in b/test/Makefile.in
index 50835325373..b7719d7a492 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -326,6 +326,9 @@ check-all: mostlyclean check-no-automated-subdir
 check-maybe: check-no-automated-subdir
        @${MAKE} check-doit SELECTOR="${SELECTOR_ACTUAL}"
 
+check-byte-compile:
+       @${MAKE} $(ELFILES:.el=.elc)
+
 ## Run the tests.
 .PHONY: check-doit
 ## We can't put LOGFILES as prerequisites, because that would stop the
diff --git a/test/lisp/align-resources/align-post.c 
b/test/lisp/align-resources/align-post.c
deleted file mode 100644
index 157e1d6242a..00000000000
--- a/test/lisp/align-resources/align-post.c
+++ /dev/null
@@ -1,3 +0,0 @@
-int
-main (int       argc,
-      char     *argv[]);
diff --git a/test/lisp/align-resources/align-post.java 
b/test/lisp/align-resources/align-post.java
deleted file mode 100644
index e0ea8e727f1..00000000000
--- a/test/lisp/align-resources/align-post.java
+++ /dev/null
@@ -1,9 +0,0 @@
-class X
-{
-    String     field1;
-    String[]   field2;
-    int                field3;
-    int[]      field4;
-    X          field5;
-    X[]                field6;
-}
diff --git a/test/lisp/align-resources/align-pre.c 
b/test/lisp/align-resources/align-pre.c
deleted file mode 100644
index b1774181a40..00000000000
--- a/test/lisp/align-resources/align-pre.c
+++ /dev/null
@@ -1,3 +0,0 @@
-int
-main (int argc,
-      char *argv[]);
diff --git a/test/lisp/align-resources/align-pre.java 
b/test/lisp/align-resources/align-pre.java
deleted file mode 100644
index fe7a87a9393..00000000000
--- a/test/lisp/align-resources/align-pre.java
+++ /dev/null
@@ -1,9 +0,0 @@
-class X
-{
-    String     field1;
-    String[] field2;
-    int                field3;
-    int[] field4;
-    X          field5;
-    X[] field6;
-}
diff --git a/test/lisp/align-resources/align-regexp.erts 
b/test/lisp/align-resources/align-regexp.erts
new file mode 100644
index 00000000000..fbbd6d6bd33
--- /dev/null
+++ b/test/lisp/align-resources/align-regexp.erts
@@ -0,0 +1,13 @@
+Name: align function declaration
+
+=-=
+Fred (123) 456-7890
+Alice (123) 456-7890
+Mary-Anne (123) 456-7890
+Joe (123) 456-7890
+=-=
+Fred      (123) 456-7890
+Alice     (123) 456-7890
+Mary-Anne (123) 456-7890
+Joe       (123) 456-7890
+=-=-=
diff --git a/test/lisp/align-resources/c-mode.erts 
b/test/lisp/align-resources/c-mode.erts
new file mode 100644
index 00000000000..a28c2bdbed0
--- /dev/null
+++ b/test/lisp/align-resources/c-mode.erts
@@ -0,0 +1,23 @@
+Name: align function declaration
+
+=-=
+int
+main (int argc,
+      char *argv[]);
+=-=
+int
+main (int       argc,
+      char     *argv[]);
+=-=-=
+
+Name: example from Commentary
+
+=-=
+    int a = 1;
+    short foo = 2;
+    double blah = 4;
+=-=
+    int                a    = 1;
+    short      foo  = 2;
+    double     blah = 4;
+=-=-=
diff --git a/test/lisp/align-resources/conf-toml-mode.erts 
b/test/lisp/align-resources/conf-toml-mode.erts
new file mode 100644
index 00000000000..d1fcbd58708
--- /dev/null
+++ b/test/lisp/align-resources/conf-toml-mode.erts
@@ -0,0 +1,45 @@
+Name: align key-value pairs
+
+=-=
+[foo]
+foo1=10
+foo22=20
+
+[bar]
+bar333="example.org"
+bar4444 = "zzz"
+=-=
+[foo]
+foo1  = 10
+foo22 = 20
+
+[bar]
+bar333  = "example.org"
+bar4444 = "zzz"
+=-=-=
+
+Name: align list values
+
+=-=
+[foo]
+a = 1
+some_list = [
+    true,
+    false,
+]
+some_other_list = [
+    1,
+    2,
+]
+=-=
+[foo]
+a               = 1
+some_list       = [
+    true,
+    false,
+]
+some_other_list = [
+    1,
+    2,
+]
+=-=-=
diff --git a/test/lisp/align-resources/css-mode.erts 
b/test/lisp/align-resources/css-mode.erts
new file mode 100644
index 00000000000..e4455601083
--- /dev/null
+++ b/test/lisp/align-resources/css-mode.erts
@@ -0,0 +1,23 @@
+Name: align attributes
+
+=-=
+div {
+  border: 1px solid black;
+  padding: 25px 50px 75px 100px;
+  background-color: lightblue;
+}
+p.center {
+  text-align: center;
+  color: red;
+}
+=-=
+div {
+  border:           1px solid black;
+  padding:          25px 50px 75px 100px;
+  background-color: lightblue;
+}
+p.center {
+  text-align: center;
+  color:      red;
+}
+=-=-=
diff --git a/test/lisp/align-resources/java-mode.erts 
b/test/lisp/align-resources/java-mode.erts
new file mode 100644
index 00000000000..693a4123121
--- /dev/null
+++ b/test/lisp/align-resources/java-mode.erts
@@ -0,0 +1,23 @@
+Name: align class fields
+
+=-=
+class X
+{
+    String     field1;
+    String[] field2;
+    int                field3;
+    int[] field4;
+    X          field5;
+    X[] field6;
+}
+=-=
+class X
+{
+    String     field1;
+    String[]   field2;
+    int                field3;
+    int[]      field4;
+    X          field5;
+    X[]                field6;
+}
+=-=-=
diff --git a/test/lisp/align-resources/latex-mode.erts 
b/test/lisp/align-resources/latex-mode.erts
new file mode 100644
index 00000000000..cdc93e4a925
--- /dev/null
+++ b/test/lisp/align-resources/latex-mode.erts
@@ -0,0 +1,29 @@
+Name: tex-record-separator and basic-line-continuation
+
+=-=
+\documentclass{}
+
+\begin{document}
+
+\begin{tabular}{l|l}
+  \textit{Player name}  &\textit{Career home runs}  \\
+  \hline
+  Hank Aaron  &755 \\
+  Babe Ruth &714
+\end{tabular}
+
+\end{document}
+=-=
+\documentclass{}
+
+\begin{document}
+
+\begin{tabular}{l|l}
+  \textit{Player name} & \textit{Career home runs} \\
+  \hline
+  Hank Aaron           & 755                       \\
+  Babe Ruth            & 714
+\end{tabular}
+
+\end{document}
+=-=-=
diff --git a/test/lisp/align-resources/python-mode.erts 
b/test/lisp/align-resources/python-mode.erts
new file mode 100644
index 00000000000..1ce50b32dba
--- /dev/null
+++ b/test/lisp/align-resources/python-mode.erts
@@ -0,0 +1,29 @@
+Name: align assignments
+
+=-=
+foo = "bar"
+x = 1
+zzzzz = True
+y        = None
+=-=
+foo   = "bar"
+x     = 1
+zzzzz = True
+y     = None
+=-=-=
+
+Name: python-chain-logic and basic-line-continuation
+
+=-=
+if foo or\
+   b and \
+   bcxxx and \
+   c:
+    pass
+=-=
+if foo   or  \
+   b     and \
+   bcxxx and \
+   c:
+    pass
+=-=-=
diff --git a/test/lisp/align-tests.el b/test/lisp/align-tests.el
index 62ef9cf27fa..a4d9303827f 100644
--- a/test/lisp/align-tests.el
+++ b/test/lisp/align-tests.el
@@ -25,22 +25,47 @@
 (require 'ert-x)
 (require 'align)
 
-(defun test-align-compare (file function)
-  (should (equal
-           (with-temp-buffer
-             (insert-file-contents (ert-resource-file (format file "pre")))
-             (funcall function)
-             (align (point-min) (point-max))
-             (buffer-substring-no-properties (point-min) (point-max)))
-           (with-temp-buffer
-             (insert-file-contents (ert-resource-file (format file "post")))
-             (buffer-string)))))
+;;;; align
 
-(ert-deftest align-java ()
-  (test-align-compare "align-%s.java" #'java-mode))
+(defun test-align-transform-fun (function)
+  (lambda ()
+    (funcall function)
+    (align (point-min) (point-max))))
 
 (ert-deftest align-c ()
-  (test-align-compare "align-%s.c" #'c-mode))
+  (ert-test-erts-file (ert-resource-file "c-mode.erts")
+                      (test-align-transform-fun #'c-mode)))
+
+(ert-deftest align-css ()
+  (let ((indent-tabs-mode nil))
+    (ert-test-erts-file (ert-resource-file "css-mode.erts")
+                        (test-align-transform-fun #'css-mode))))
+
+(ert-deftest align-java ()
+  (ert-test-erts-file (ert-resource-file "java-mode.erts")
+                      (test-align-transform-fun #'java-mode)))
+
+(ert-deftest align-latex ()
+  (ert-test-erts-file (ert-resource-file "latex-mode.erts")
+                      (test-align-transform-fun #'latex-mode)))
+
+(ert-deftest align-python ()
+  (ert-test-erts-file (ert-resource-file "python-mode.erts")
+                      (test-align-transform-fun #'python-mode)))
+
+(ert-deftest align-toml ()
+  (let ((indent-tabs-mode nil))
+    (ert-test-erts-file (ert-resource-file "conf-toml-mode.erts")
+                        (test-align-transform-fun #'conf-toml-mode))))
+
+;;;; align-regexp
+
+(ert-deftest align-regexp ()
+  (let ((indent-tabs-mode nil))
+    (ert-test-erts-file (ert-resource-file "align-regexp.erts")
+                        (lambda ()
+                          (align-regexp (point-min) (point-max)
+                                        "\\(\\s-*\\)(")))))
 
 (provide 'align-tests)
 
diff --git a/test/lisp/auth-source-tests.el b/test/lisp/auth-source-tests.el
index ef915e5fc5b..ab1a437b303 100644
--- a/test/lisp/auth-source-tests.el
+++ b/test/lisp/auth-source-tests.el
@@ -435,5 +435,25 @@ machine c1 port c2 user c3 password c4\n"
             '((("machine" . "XM") ("login" . "XL") ("password" . "XP"))
               (("machine" . "YM") ("login" . "YL") ("password" . "YP")))))))
 
+(ert-deftest test-macos-keychain-search ()
+  "Test if the constructed command line arglist is correct."
+  (let ((auth-sources '(macos-keychain-internet macos-keychain-generic)))
+    ;; Redefine `call-process' to check command line arguments.
+    (cl-letf (((symbol-function 'call-process)
+               (lambda (_program _infile _destination _display
+                                 &rest args)
+                 ;; Arguments must be all strings
+                 (should (cl-every #'stringp args))
+                 ;; Argument number should be even
+                 (should (cl-evenp (length args)))
+                 (should (cond ((string= (car args) "find-internet-password")
+                                (let ((protocol (cl-member "-r" args :test 
#'string=)))
+                                  (if protocol
+                                      (= 4 (length (cadr protocol)))
+                                    t)))
+                               ((string= (car args) "find-generic-password")
+                                t))))))
+      (auth-source-search :user '("a" "b") :host '("example.org") :port 
'("irc" "ftp" "https")))))
+
 (provide 'auth-source-tests)
 ;;; auth-source-tests.el ends here
diff --git a/test/lisp/calc/calc-tests.el b/test/lisp/calc/calc-tests.el
index 41c47e5332c..5b11dd950ba 100644
--- a/test/lisp/calc/calc-tests.el
+++ b/test/lisp/calc/calc-tests.el
@@ -698,8 +698,8 @@ An existing calc stack is reused, otherwise a new one is 
created."
                      (calc-tests--not x w)))
 
       (dolist (n '(0 1 4 16 32 -1 -4 -16 -32))
-        (equal (calcFunc-clip x n)
-               (calc-tests--clip x n)))
+        (should (equal (calcFunc-clip x n)
+                       (calc-tests--clip x n))))
 
       (dolist (y '(0 1 #x1234 #x8000 #xabcd #xffff
                      #x12345678 #xabcdef12 #x80000000 #xffffffff
diff --git a/test/lisp/calculator-tests.el b/test/lisp/calculator-tests.el
index 7ac3b9ba37a..8786d5c6c3b 100644
--- a/test/lisp/calculator-tests.el
+++ b/test/lisp/calculator-tests.el
@@ -47,5 +47,11 @@
        (let ((calculator-input-radix nil))
          (should (equal (calculator-string-to-number str) expected)))))))
 
+(ert-deftest calculator-expt ()
+  (should (= (calculator-expt 2 -1) 0.5))
+  (should (= (calculator-expt -2 2) 4))
+  (should (= (calculator-expt -2 3) -8))
+  (should (= (calculator-expt 2 64) 18446744073709551616)))
+
 (provide 'calculator-tests)
 ;;; calculator-tests.el ends here
diff --git a/test/lisp/cedet/semantic/bovine/gcc-tests.el 
b/test/lisp/cedet/semantic/bovine/gcc-tests.el
index 5437d65d139..0b703fcaa2f 100644
--- a/test/lisp/cedet/semantic/bovine/gcc-tests.el
+++ b/test/lisp/cedet/semantic/bovine/gcc-tests.el
@@ -31,62 +31,88 @@
 
 ;;; From bovine-gcc:
 
-;; Example output of "gcc -v"
-(defvar semantic-gcc-test-strings
-  '(;; My old box:
-    "Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs
+(defmacro semantic-gcc-test (str)
+  `(let ((fields (semantic-gcc-fields ,str)))
+     (let-alist fields
+       (message "%S" fields)
+       ;; No longer test for prefixes.
+       ;; (should .--prefix)
+       (should .version)
+       (should (or .target
+                   .--target
+                   .--host)))))
+
+;; A bunch of sample gcc -v outputs from different machines.
+
+(ert-deftest semantic-gcc-test/1 ()
+  ;; My old box:
+  (semantic-gcc-test "Reading specs from 
/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs
 Configured with: ../configure --prefix=/usr --mandir=/usr/share/man 
--infodir=/usr/share/info --enable-shared --enable-threads=posix 
--disable-checking --with-system-zlib --enable-__cxa_atexit 
--host=i386-redhat-linux
 Thread model: posix
-gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
-    ;; Alex Ott:
-    "Using built-in specs.
+gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"))
+
+(ert-deftest semantic-gcc-test/2 ()
+  ;; Alex Ott:
+  (semantic-gcc-test "Using built-in specs.
 Target: i486-linux-gnu
 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.3.1-9ubuntu1' 
--with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs 
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared 
--with-system-zlib --libexecdir=/usr/lib --without-included-gettext 
--enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 
--program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug 
--enable-objc-gc --enable-mpfr --enable-targets=all --enable-chec [...]
 Thread model: posix
-gcc version 4.3.1 (Ubuntu 4.3.1-9ubuntu1)"
-    ;; My debian box:
-    "Using built-in specs.
+gcc version 4.3.1 (Ubuntu 4.3.1-9ubuntu1)"))
+
+(ert-deftest semantic-gcc-test/3 ()
+  ;; My Debian box:
+  (semantic-gcc-test "Using built-in specs.
 Target: x86_64-unknown-linux-gnu
 Configured with: ../../../sources/gcc/configure 
--prefix=/usr/local/glibc-2.3.6/x86_64/apps/gcc-4.2.3 
--with-gmp=/usr/local/gcc/gmp --with-mpfr=/usr/local/gcc/mpfr 
--enable-languages=c,c++,fortran 
--with-as=/usr/local/glibc-2.3.6/x86_64/apps/gcc-4.2.3/bin/as 
--with-ld=/usr/local/glibc-2.3.6/x86_64/apps/gcc-4.2.3/bin/ld --disable-multilib
 Thread model: posix
-gcc version 4.2.3"
-    ;; My mac:
-    "Using built-in specs.
+gcc version 4.2.3"))
+
+(ert-deftest semantic-gcc-test/4 ()
+  ;; My mac:
+  (semantic-gcc-test "Using built-in specs.
 Target: i686-apple-darwin8
 Configured with: /private/var/tmp/gcc/gcc-5341.obj~1/src/configure 
--disable-checking -enable-werror --prefix=/usr --mandir=/share/man 
--enable-languages=c,objc,c++,obj-c++ 
--program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ 
--with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib 
--build=powerpc-apple-darwin8 --with-arch=pentium-m --with-tune=prescott 
--program-prefix= --host=i686-apple-darwin8 --target=i686-apple-darwin8
 Thread model: posix
-gcc version 4.0.1 (Apple Computer, Inc. build 5341)"
-    ;; Ubuntu Intrepid
-    "Using built-in specs.
+gcc version 4.0.1 (Apple Computer, Inc. build 5341)"))
+
+(ert-deftest semantic-gcc-test/5 ()
+  ;; Ubuntu Intrepid
+  (semantic-gcc-test "Using built-in specs.
 Target: x86_64-linux-gnu
 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 
4.3.2-1ubuntu12' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs 
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared 
--with-system-zlib --libexecdir=/usr/lib --without-included-gettext 
--enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 
--program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug 
--enable-objc-gc --enable-mpfr --enable-checking=release --build [...]
 Thread model: posix
-gcc version 4.3.2 (Ubuntu 4.3.2-1ubuntu12)"
-    ;; Red Hat EL4
-    "Reading specs from /usr/lib/gcc/x86_64-redhat-linux/3.4.6/specs
+gcc version 4.3.2 (Ubuntu 4.3.2-1ubuntu12)"))
+
+(ert-deftest semantic-gcc-test/6 ()
+  ;; Red Hat EL4
+  (semantic-gcc-test "Reading specs from 
/usr/lib/gcc/x86_64-redhat-linux/3.4.6/specs
 Configured with: ../configure --prefix=/usr --mandir=/usr/share/man 
--infodir=/usr/share/info --enable-shared --enable-threads=posix 
--disable-checking --with-system-zlib --enable-__cxa_atexit 
--disable-libunwind-exceptions --enable-java-awt=gtk --host=x86_64-redhat-linux
 Thread model: posix
-gcc version 3.4.6 20060404 (Red Hat 3.4.6-10)"
-    ;; Red Hat EL5
-    "Using built-in specs.
+gcc version 3.4.6 20060404 (Red Hat 3.4.6-10)"))
+
+(ert-deftest semantic-gcc-test/7 ()
+  ;; Red Hat EL5
+  (semantic-gcc-test "Using built-in specs.
 Target: x86_64-redhat-linux
 Configured with: ../configure --prefix=/usr --mandir=/usr/share/man 
--infodir=/usr/share/info --enable-shared --enable-threads=posix 
--enable-checking=release --with-system-zlib --enable-__cxa_atexit 
--disable-libunwind-exceptions --enable-libgcj-multifile 
--enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk 
--disable-dssi --enable-plugin 
--with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic 
--host=x86_64-redhat-linux
 Thread model: posix
-gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)"
-    ;; David Engster's german gcc on ubuntu 4.3
-    "Es werden eingebaute Spezifikationen verwendet.
+gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)"))
+
+(ert-deftest semantic-gcc-test/8 ()
+  ;; David Engster's german gcc on ubuntu 4.3
+  (semantic-gcc-test "Es werden eingebaute Spezifikationen verwendet.
 Ziel: i486-linux-gnu
 Konfiguriert mit: ../src/configure -v --with-pkgversion='Ubuntu 
4.3.2-1ubuntu12' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs 
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared 
--with-system-zlib --libexecdir=/usr/lib --without-included-gettext 
--enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 
--program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug 
--enable-objc-gc --enable-mpfr --enable-targets=all --enable-ch [...]
 Thread-Modell: posix
-gcc-Version 4.3.2 (Ubuntu 4.3.2-1ubuntu12)"
-    ;; Damien Deville bsd
-    "Using built-in specs.
+gcc-Version 4.3.2 (Ubuntu 4.3.2-1ubuntu12)"))
+
+(ert-deftest semantic-gcc-test/9 ()
+  ;; Damien Deville bsd
+  (semantic-gcc-test "Using built-in specs.
 Target: i386-undermydesk-freebsd
 Configured with: FreeBSD/i386 system compiler
 Thread model: posix
-gcc version 4.2.1 20070719  [FreeBSD]"
-    )
-  "A bunch of sample gcc -v outputs from different machines.")
+gcc version 4.2.1 20070719  [FreeBSD]"))
 
 (defvar semantic-gcc-test-strings-fail
   '(;; A really old solaris box I found
@@ -95,19 +121,8 @@ gcc version 2.95.2 19991024 (release)"
     )
   "A bunch of sample gcc -v outputs that fail to provide the info we want.")
 
-(defun semantic-gcc-test-output-parser ()
+(ert-deftest semantic-gcc-test-output-parser/fail ()
   "Test the output parser against some collected strings."
-  (dolist (S semantic-gcc-test-strings)
-    (let* ((fields (semantic-gcc-fields S))
-           (v (cdr (assoc 'version fields)))
-           (h (or (cdr (assoc 'target fields))
-                  (cdr (assoc '--target fields))
-                  (cdr (assoc '--host fields))))
-           (p (cdr (assoc '--prefix fields))))
-      ;; No longer test for prefixes.
-      (when (not (and v h))
-        (let ((strs (split-string S "\n")))
-          (error "Test failed on %S\nV H P:\n%S %S %S" (car strs) v h p)))))
   (dolist (S semantic-gcc-test-strings-fail)
     (let* ((fields (semantic-gcc-fields S))
            (v (cdr (assoc 'version fields)))
@@ -118,14 +133,10 @@ gcc version 2.95.2 19991024 (release)"
       (when (and v h p)
         (error "Negative test failed on %S" S)))))
 
-(ert-deftest semantic-gcc-test-output-parser ()
-  (semantic-gcc-test-output-parser))
-
-(ert-deftest semantic-gcc-test-output-parser-this-machine ()
+(ert-deftest semantic-gcc-test-output-parser/this-machine ()
   "Test the output parser against the machine currently running Emacs."
   (skip-unless (and (executable-find "gcc")
                     (not (ert-gcc-is-clang-p))))
-  (let ((semantic-gcc-test-strings (list (semantic-gcc-query "gcc" "-v"))))
-    (semantic-gcc-test-output-parser)))
+  (semantic-gcc-test (semantic-gcc-query "gcc" "-v")))
 
 ;;; gcc-tests.el ends here
diff --git a/test/lisp/cus-edit-tests.el b/test/lisp/cus-edit-tests.el
index eca35d7c96a..9ceab16e194 100644
--- a/test/lisp/cus-edit-tests.el
+++ b/test/lisp/cus-edit-tests.el
@@ -92,5 +92,47 @@
             (buffer-substring-no-properties (point-min) (point-max)))))
     (should (string-search "Value `:foo' does not match type number"
                            warn-txt))))
+
+(defcustom cus-edit-test-bug63290-option nil
+  "Choice option for testing Bug#63290."
+  :type '(choice (alist
+                  :key-type (string :tag "key")
+                  :value-type (string :tag "value"))
+                 (const :tag "auto" auto)))
+
+(defcustom cus-edit-test-bug63290-option2 'some
+  "Choice option for testing Bug#63290."
+  :type '(choice
+          (const :tag "some" some)
+          (alist
+           :key-type (string :tag "key")
+           :value-type (string :tag "value"))))
+
+(ert-deftest cus-edit-test-bug63290 ()
+  "Test that changing a choice value back to an alist respects its nil value."
+  (customize-variable 'cus-edit-test-bug63290-option)
+  (search-forward "Value")
+  ;; Simulate changing the value.
+  (let* ((choice (widget-at))
+         (args (widget-get choice :args))
+         (list-opt (car (widget-get choice :children)))
+         (const-opt (nth 1 args)))
+    (widget-put choice :explicit-choice const-opt)
+    (widget-value-set choice (widget-default-get const-opt))
+    (widget-put choice :explicit-choice list-opt)
+    (widget-value-set choice (widget-default-get list-opt)))
+  ;; No empty key/value pairs should show up.
+  (should-not (search-forward "key" nil t))
+  (customize-variable 'cus-edit-test-bug63290-option2)
+  (search-forward "Value")
+  ;; Simulate changing the value.
+  (let* ((choice (widget-at))
+         (args (widget-get choice :args))
+         (list-opt (nth 1 args)))
+    (widget-put choice :explicit-choice list-opt)
+    (widget-value-set choice (widget-default-get list-opt)))
+  ;; No empty key/value pairs should show up.
+  (should-not (search-forward "key" nil t)))
+
 (provide 'cus-edit-tests)
 ;;; cus-edit-tests.el ends here
diff --git a/test/lisp/dired-tests.el b/test/lisp/dired-tests.el
index 0701b229edd..8f2b9af09c0 100644
--- a/test/lisp/dired-tests.el
+++ b/test/lisp/dired-tests.el
@@ -241,12 +241,12 @@
             (let ((buffers (find-file (concat (file-name-as-directory test-dir)
                                               "*")
                                       t)))
+              (setq allbufs (append buffers allbufs))
               (dolist (buf buffers)
                 (let ((pt (with-current-buffer buf (point))))
                   (switch-to-buffer (find-file-noselect test-dir))
                   (find-file (buffer-name buf))
-                  (should (equal (point) pt))))
-              (append buffers allbufs)))
+                  (should (equal (point) pt))))))
         (dolist (buf allbufs)
           (when (buffer-live-p buf) (kill-buffer buf)))))))
 
diff --git 
a/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-missing-keyword-arg.el
 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-missing-keyword-arg.el
new file mode 100644
index 00000000000..9369e78ff54
--- /dev/null
+++ 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-missing-keyword-arg.el
@@ -0,0 +1,3 @@
+;;; -*- lexical-binding: t -*-
+(defun foo ()
+  (make-process :name "ls"))
diff --git 
a/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-missing-keyword-value.el
 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-missing-keyword-value.el
new file mode 100644
index 00000000000..4226349afef
--- /dev/null
+++ 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-missing-keyword-value.el
@@ -0,0 +1,3 @@
+;;; -*- lexical-binding: t -*-
+(defun foo ()
+  (make-process :name "ls" :command))
diff --git 
a/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-repeated-keyword-arg.el
 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-repeated-keyword-arg.el
new file mode 100644
index 00000000000..18250f14ee9
--- /dev/null
+++ 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-repeated-keyword-arg.el
@@ -0,0 +1,3 @@
+;;; -*- lexical-binding: t -*-
+(defun foo ()
+  (make-process :name "ls" :command "ls" :name "ls"))
diff --git 
a/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-unknown-keyword-arg.el
 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-unknown-keyword-arg.el
new file mode 100644
index 00000000000..4721035780b
--- /dev/null
+++ 
b/test/lisp/emacs-lisp/bytecomp-resources/warn-make-process-unknown-keyword-arg.el
@@ -0,0 +1,4 @@
+;;; -*- lexical-binding: t -*-
+(defun foo ()
+  (make-process :name "ls" :command "ls"
+                :coding-system 'binary))
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el 
b/test/lisp/emacs-lisp/bytecomp-tests.el
index 34d03c5446d..262cbd62fa0 100644
--- a/test/lisp/emacs-lisp/bytecomp-tests.el
+++ b/test/lisp/emacs-lisp/bytecomp-tests.el
@@ -677,16 +677,18 @@ inner loops respectively."
                      (list x (funcall g))))))))
       (funcall (funcall f 'b)))
     (let ((f (lambda (x)
-               (let ((g (lambda () x))
-                     (h (lambda () (setq x (list x x)))))
-                 (let ((x 'a))
-                   (list x (funcall g) (funcall h)))))))
+               (lambda ()
+                 (let ((g (lambda () x))
+                       (h (lambda () (setq x (list x x)))))
+                   (let ((x 'a))
+                     (list x (funcall g) (funcall h))))))))
       (funcall (funcall f 'b)))
     (let ((f (lambda (x)
-               (let ((g (lambda () x))
-                     (h (lambda () (setq x (list x x)))))
-                 (let* ((x 'a))
-                   (list x (funcall g) (funcall h)))))))
+               (lambda ()
+                 (let ((g (lambda () x))
+                       (h (lambda () (setq x (list x x)))))
+                   (let* ((x 'a))
+                     (list x (funcall g) (funcall h))))))))
       (funcall (funcall f 'b)))
 
     ;; Test constant-propagation of access to captured variables.
@@ -780,6 +782,14 @@ inner loops respectively."
     ;; (+ 0 -0.0) etc
     (let ((x (bytecomp-test-identity -0.0)))
       (list x (+ x) (+ 0 x) (+ x 0) (+ 1 2 -3 x) (+ 0 x 0)))
+
+    ;; Unary comparisons: keep side-effect, return t
+    (let ((x 0))
+      (list (= (setq x 1))
+            x))
+    ;; Aristotelian identity optimisation
+    (let ((x (bytecomp-test-identity 1)))
+      (list (eq x x) (eql x x) (equal x x)))
     )
   "List of expressions for cross-testing interpreted and compiled code.")
 
@@ -1194,6 +1204,22 @@ byte-compiled.  Run with dynamic binding."
  "nowarn-inline-after-defvar.el"
  "Lexical argument shadows" 'reverse)
 
+(bytecomp--define-warning-file-test
+ "warn-make-process-missing-keyword-arg.el"
+ "called without required keyword argument :command")
+
+(bytecomp--define-warning-file-test
+ "warn-make-process-unknown-keyword-arg.el"
+ "called with unknown keyword argument :coding-system")
+
+(bytecomp--define-warning-file-test
+ "warn-make-process-repeated-keyword-arg.el"
+ "called with repeated keyword argument :name")
+
+(bytecomp--define-warning-file-test
+ "warn-make-process-missing-keyword-value.el"
+ "missing value for keyword argument :command")
+
 
 ;;;; Macro expansion.
 
@@ -1953,6 +1979,15 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode 
js-mode python-mode)) \
     ((setcdr c 5) (wrong-type-argument consp c))
     ((nth 2 "abcd") (wrong-type-argument listp "abcd"))
     ((elt (x y . z) 2) (wrong-type-argument listp z))
+    ((aref [2 3 5] p) (wrong-type-argument fixnump p))
+    ((aref #s(a b c) p) (wrong-type-argument fixnump p))
+    ((aref "abc" p) (wrong-type-argument fixnump p))
+    ((aref [2 3 5] 3) (args-out-of-range [2 3 5] 3))
+    ((aref #s(a b c) 3) (args-out-of-range #s(a b c) 3))
+    ((aset [2 3 5] q 1) (wrong-type-argument fixnump q))
+    ((aset #s(a b c) q 1) (wrong-type-argument fixnump q))
+    ((aset [2 3 5] -1 1) (args-out-of-range [2 3 5] -1))
+    ((aset #s(a b c) -1 1) (args-out-of-range #s(a b c) -1))
     ;; Many more to add
     ))
 
@@ -1967,17 +2002,17 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode 
js-mode python-mode)) \
         ;; Test both calling the function directly, and calling
         ;; a byte-compiled η-expansion (lambda (ARGS...) (FUN ARGS...))
         ;; which should turn the function call into a byte-op.
-        (dolist (byte-op '(nil t))
-          (ert-info ((prin1-to-string byte-op) :prefix "byte-op: ")
-            (let* ((fun
-                    (if byte-op
-                        (let* ((nargs (length (cdr call)))
-                               (formals (mapcar (lambda (i)
-                                                  (intern (format "x%d" i)))
-                                                (number-sequence 1 nargs))))
-                          (byte-compile
-                           `(lambda ,formals (,fun-sym ,@formals))))
-                      fun-sym))
+        (dolist (mode '(funcall byte-op))
+          (ert-info ((symbol-name mode) :prefix "mode: ")
+            (let* ((fun (pcase-exhaustive mode
+                          ('funcall fun-sym)
+                          ('byte-op
+                           (let* ((nargs (length (cdr call)))
+                                  (formals (mapcar (lambda (i)
+                                                     (intern (format "x%d" i)))
+                                                   (number-sequence 1 nargs))))
+                             (byte-compile
+                              `(lambda ,formals (,fun-sym ,@formals)))))))
                    (error-frame (bytecomp-tests--error-frame fun actuals)))
               (should (consp error-frame))
               (should (equal (car error-frame) (list 'error expected-error)))
@@ -1987,6 +2022,40 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode 
js-mode python-mode)) \
                                      (backtrace-frame-args frame))
                                call))))))))))
 
+(ert-deftest bytecomp--eq-symbols-with-pos-enabled ()
+  ;; Verify that we don't optimise away a binding of
+  ;; `symbols-with-pos-enabled' around an application of `eq' (bug#65017).
+  (let* ((sym-with-pos1 (read-positioning-symbols "sym"))
+         (sym-with-pos2 (read-positioning-symbols " sym"))  ; <- space!
+         (without-pos-eq (lambda (a b)
+                           (let ((symbols-with-pos-enabled nil))
+                             (eq a b))))
+         (without-pos-eq-compiled (byte-compile without-pos-eq))
+         (with-pos-eq (lambda (a b)
+                        (let ((symbols-with-pos-enabled t))
+                          (eq a b))))
+         (with-pos-eq-compiled (byte-compile with-pos-eq)))
+    (dolist (mode '(interpreted compiled))
+      (ert-info ((symbol-name mode) :prefix "mode: ")
+        (ert-info ("disabled" :prefix "symbol-pos: ")
+          (let ((eq-fn (pcase-exhaustive mode
+                         ('interpreted without-pos-eq)
+                         ('compiled    without-pos-eq-compiled))))
+            (should (equal (funcall eq-fn 'sym 'sym) t))
+            (should (equal (funcall eq-fn sym-with-pos1 'sym) nil))
+            (should (equal (funcall eq-fn 'sym sym-with-pos1) nil))
+            (should (equal (funcall eq-fn sym-with-pos1 sym-with-pos1) t))
+            (should (equal (funcall eq-fn sym-with-pos1 sym-with-pos2) nil))))
+        (ert-info ("enabled" :prefix "symbol-pos: ")
+          (let ((eq-fn (pcase-exhaustive mode
+                         ('interpreted with-pos-eq)
+                         ('compiled    with-pos-eq-compiled))))
+            (should (equal (funcall eq-fn 'sym 'sym) t))
+            (should (equal (funcall eq-fn sym-with-pos1 'sym) t))
+            (should (equal (funcall eq-fn 'sym sym-with-pos1) t))
+            (should (equal (funcall eq-fn sym-with-pos1 sym-with-pos1) t))
+            (should (equal (funcall eq-fn sym-with-pos1 sym-with-pos2) 
t))))))))
+
 ;; Local Variables:
 ;; no-byte-compile: t
 ;; End:
diff --git a/test/lisp/emacs-lisp/cl-macs-tests.el 
b/test/lisp/emacs-lisp/cl-macs-tests.el
index 983cbfc8bc7..56a49fd865a 100644
--- a/test/lisp/emacs-lisp/cl-macs-tests.el
+++ b/test/lisp/emacs-lisp/cl-macs-tests.el
@@ -708,6 +708,23 @@ collection clause."
                            (f lex-var)))))
       (should (equal (f nil) 'a)))))
 
+(ert-deftest cl-flet/edebug ()
+  "Check that we can instrument `cl-flet' forms (bug#65344)."
+  (with-temp-buffer
+    (print '(cl-flet (;; "Obscure" form of binding supported by cl-flet
+                      (x (progn (list 1 2) (lambda ())))
+                      ;; Destructuring lambda-list
+                      (y ((min max)) (list min max))
+                      ;; Regular binding plus shadowing.
+                      (z (a) a)
+                      (z (a) a))
+              (y '(1 2)))
+           (current-buffer))
+    (let ((edebug-all-forms t)
+          (edebug-initial-mode 'Go-nonstop))
+      ;; Just make sure the forms can be instrumented.
+      (eval-buffer))))
+
 (ert-deftest cl-macs--progv ()
   (defvar cl-macs--test)
   (defvar cl-macs--test1)
diff --git a/test/lisp/emacs-lisp/lisp-mode-tests.el 
b/test/lisp/emacs-lisp/lisp-mode-tests.el
index 3e906497020..825e6b6ab80 100644
--- a/test/lisp/emacs-lisp/lisp-mode-tests.el
+++ b/test/lisp/emacs-lisp/lisp-mode-tests.el
@@ -355,5 +355,28 @@ Expected initialization file: `%s'\"
   ;;   (should (equal (lisp-current-defun-name) "defblarg")))
   )
 
+(ert-deftest test-font-lock-keywords ()
+  "Keywords should be fontified in `font-lock-keyword-face`."
+  (with-temp-buffer
+    (emacs-lisp-mode)
+    (mapc (lambda (el-keyword)
+            (erase-buffer)
+            (insert (format "(%s some-symbol () \"hello\"" el-keyword))
+            (font-lock-ensure)
+            ;; Verify face property throughout the keyword
+            (let* ((begin (1+ (point-min)))
+                   (end (1- (+ begin (length el-keyword)))))
+              (mapc (lambda (pos)
+                      (should (equal (get-text-property pos 'face)
+                                     'font-lock-keyword-face)))
+                    (number-sequence begin end))))
+          '("defsubst" "cl-defsubst" "define-inline"
+            "define-advice" "defadvice" "defalias"
+            "define-derived-mode" "define-minor-mode"
+            "define-generic-mode" "define-global-minor-mode"
+            "define-globalized-minor-mode" "define-skeleton"
+            "define-widget" "ert-deftest" "defconst" "defcustom"
+            "defvaralias" "defvar-local" "defface" "define-error"))))
+
 (provide 'lisp-mode-tests)
 ;;; lisp-mode-tests.el ends here
diff --git a/test/lisp/emacs-lisp/macroexp-tests.el 
b/test/lisp/emacs-lisp/macroexp-tests.el
index 7bb38fe58f7..d0efbfd28c1 100644
--- a/test/lisp/emacs-lisp/macroexp-tests.el
+++ b/test/lisp/emacs-lisp/macroexp-tests.el
@@ -124,4 +124,20 @@
                      (dyn dyn dyn dyn)
                      (dyn dyn dyn lex))))))
 
+(defmacro macroexp--test-macro1 ()
+  (declare (obsolete "new-replacement" nil))
+  1)
+
+(defmacro macroexp--test-macro2 ()
+  '(macroexp--test-macro1))
+
+(ert-deftest macroexp--test-obsolete-macro ()
+  (should
+   (let ((res
+          (cl-letf (((symbol-function 'message) #'user-error))
+            (condition-case err
+                (macroexpand-all '(macroexp--test-macro2))
+              (user-error (error-message-string err))))))
+     (should (and (stringp res) (string-match "new-replacement" res))))))
+
 ;;; macroexp-tests.el ends here
diff --git a/test/lisp/emacs-lisp/map-tests.el 
b/test/lisp/emacs-lisp/map-tests.el
index 86c0e9e0503..2204743f794 100644
--- a/test/lisp/emacs-lisp/map-tests.el
+++ b/test/lisp/emacs-lisp/map-tests.el
@@ -577,6 +577,13 @@ See bug#58531#25 and bug#58563."
     (should (= b 2))
     (should-not c)))
 
+(ert-deftest test-map-let-default ()
+  (map-let (('foo a 3)
+            ('baz b 4))
+      '((foo . 1))
+    (should (equal a 1))
+    (should (equal b 4))))
+
 (ert-deftest test-map-merge ()
   "Test `map-merge'."
   (should (equal (sort (map-merge 'list '(a 1) '((b . 2) (c . 3))
@@ -617,6 +624,58 @@ See bug#58531#25 and bug#58563."
                      (list one two))
                    '(1 2)))))
 
+(ert-deftest test-map-plist-pcase-default ()
+  (let ((plist '(:two 2)))
+    (should (equal (pcase-let (((map (:two two 33)
+                                     (:three three 44))
+                                plist))
+                     (list two three))
+                   '(2 44)))))
+
+(ert-deftest test-map-pcase-matches ()
+  (let ((plist '(:two 2)))
+    (should (equal (pcase plist
+                     ((map (:two two 33)
+                           (:three three))
+                      (list two three))
+                     (_ 'fail))
+                   '(2 nil)))
+
+    (should (equal (pcase plist
+                     ((map (:two two 33)
+                           (:three three 44))
+                      (list two three))
+                     (_ 'fail))
+                   '(2 44)))
+
+    (should (equal (pcase plist
+                     ((map (:two two 33)
+                           (:three `(,a . ,b) '(11 . 22)))
+                      (list two a b))
+                     (_ 'fail))
+                   '(2 11 22)))
+
+    (should (equal 'fail
+                   (pcase plist
+                     ((map (:two two 33)
+                           (:three `(,a . ,b) 44))
+                      (list two a b))
+                     (_ 'fail))))
+
+    (should (equal 'fail
+                   (pcase plist
+                     ((map (:two two 33)
+                           (:three `(,a . ,b) nil))
+                      (list two a b))
+                     (_ 'fail))))
+
+    (should (equal 'fail
+                   (pcase plist
+                     ((map (:two two 33)
+                           (:three `(,a . ,b)))
+                      (list two a b))
+                     (_ 'fail))))))
+
 (ert-deftest test-map-setf-alist-insert-key ()
   (let ((alist))
     (should (equal (setf (map-elt alist 'key) 'value)
diff --git a/test/lisp/emacs-lisp/nadvice-tests.el 
b/test/lisp/emacs-lisp/nadvice-tests.el
index f6bd5733ba3..7dfa936214a 100644
--- a/test/lisp/emacs-lisp/nadvice-tests.el
+++ b/test/lisp/emacs-lisp/nadvice-tests.el
@@ -65,8 +65,9 @@
   (defun sm-test2 (x) (+ x 4))
   (declare-function sm-test2 nil)
   (should (equal (sm-test2 6) 10))
-  (defadvice sm-test2 (around sm-test activate)
-    ad-do-it (setq ad-return-value (* ad-return-value 5)))
+  (with-suppressed-warnings ((obsolete defadvice))
+    (defadvice sm-test2 (around sm-test activate)
+      ad-do-it (setq ad-return-value (* ad-return-value 5))))
   (should (equal (sm-test2 6) 50))
   (ad-deactivate 'sm-test2)
   (should (equal (sm-test2 6) 10))
@@ -81,8 +82,9 @@
   (should (equal (sm-test2 6) 20))
   (should (equal (null (get 'sm-test2 'defalias-fset-function)) t))
 
-  (defadvice sm-test4 (around wrap-with-toto activate)
-    ad-do-it (setq ad-return-value `(toto ,ad-return-value)))
+  (with-suppressed-warnings ((obsolete defadvice))
+    (defadvice sm-test4 (around wrap-with-toto activate)
+      ad-do-it (setq ad-return-value `(toto ,ad-return-value))))
   (defmacro sm-test4 (x) `(call-test4 ,x))
   (should (equal (macroexpand '(sm-test4 56)) '(toto (call-test4 56))))
   (defmacro sm-test4 (x) `(call-testq ,x))
@@ -90,8 +92,9 @@
 
   ;; This used to signal an error (bug#12858).
   (autoload 'sm-test6 "foo")
-  (defadvice sm-test6 (around test activate)
-    ad-do-it))
+  (with-suppressed-warnings ((obsolete defadvice))
+    (defadvice sm-test6 (around test activate)
+      ad-do-it)))
 
 (ert-deftest advice-tests-combination ()
   "Combining old style and new style advices."
@@ -100,8 +103,9 @@
   (should (equal (sm-test5 6) 10))
   (advice-add 'sm-test5 :around (lambda (f y) (* (funcall f y) 5)))
   (should (equal (sm-test5 6) 50))
-  (defadvice sm-test5 (around test activate)
-    ad-do-it (setq ad-return-value (+ ad-return-value 0.1)))
+  (with-suppressed-warnings ((obsolete defadvice))
+    (defadvice sm-test5 (around test activate)
+      ad-do-it (setq ad-return-value (+ ad-return-value 0.1))))
   (should (equal (sm-test5 5) 45.1))
   (ad-deactivate 'sm-test5)
   (should (equal (sm-test5 6) 50))
@@ -174,18 +178,20 @@ function being an around advice."
 (ert-deftest advice-test-interactive ()
   "Check handling of interactive spec."
   (defun sm-test8 (a) (interactive "p") a)
-  (defadvice sm-test8 (before adv1 activate) nil)
-  (defadvice sm-test8 (before adv2 activate) (interactive "P") nil)
+  (with-suppressed-warnings ((obsolete defadvice))
+    (defadvice sm-test8 (before adv1 activate) nil)
+    (defadvice sm-test8 (before adv2 activate) (interactive "P") nil))
   (should (equal (interactive-form 'sm-test8) '(interactive "P"))))
 
 (ert-deftest advice-test-preactivate ()
   (should (equal (null (get 'sm-test9 'defalias-fset-function)) t))
   (defun sm-test9 (a) (interactive "p") a)
   (should (equal (null (get 'sm-test9 'defalias-fset-function)) t))
-  (defadvice sm-test9 (before adv1 pre act protect compile) nil)
-  (should (equal (null (get 'sm-test9 'defalias-fset-function)) nil))
-  (defadvice sm-test9 (before adv2 pre act protect compile)
-    (interactive "P") nil)
+  (with-suppressed-warnings ((obsolete defadvice))
+    (defadvice sm-test9 (before adv1 pre act protect compile) nil)
+    (should (equal (null (get 'sm-test9 'defalias-fset-function)) nil))
+    (defadvice sm-test9 (before adv2 pre act protect compile)
+      (interactive "P") nil))
   (should (equal (interactive-form 'sm-test9) '(interactive "P"))))
 
 (ert-deftest advice-test-multiples ()
diff --git a/test/lisp/emacs-lisp/rx-tests.el b/test/lisp/emacs-lisp/rx-tests.el
index 028250b7352..e773ddf158e 100644
--- a/test/lisp/emacs-lisp/rx-tests.el
+++ b/test/lisp/emacs-lisp/rx-tests.el
@@ -41,19 +41,31 @@
   (should (equal (rx "" (or "ab" nonl) "")
                  "ab\\|.")))
 
+;; FIXME: Extend tests for `or', `not' etc to cover char pattern combination,
+;; including (syntax whitespace) and (syntax word).
+
 (ert-deftest rx-or ()
-  (should (equal (rx (or "ab" (| "c" nonl) "de"))
-                 "ab\\|c\\|.\\|de"))
+  (should (equal (rx (or "ab" (| "cd" nonl) "de"))
+                 "ab\\|cd\\|.\\|de"))
   (should (equal (rx (or "ab" "abc" ?a))
                  "\\(?:a\\(?:bc?\\)?\\)"))
   (should (equal (rx (or "ab" (| (or "abcd" "abcde")) (or "a" "abc")))
                  "\\(?:a\\(?:b\\(?:c\\(?:de?\\)?\\)?\\)?\\)"))
   (should (equal (rx (or "a" (eval (string ?a ?b))))
                  "\\(?:ab?\\)"))
+  (should (equal (rx (| nonl "ac") (| "bd" blank))
+                 "\\(?:.\\|ac\\)\\(?:bd\\|[[:blank:]]\\)"))
   (should (equal (rx (| nonl "a") (| "b" blank))
-                 "\\(?:.\\|a\\)\\(?:b\\|[[:blank:]]\\)"))
+                 ".[b[:blank:]]"))
   (should (equal (rx (|))
-                 "\\`a\\`")))
+                 "\\`a\\`"))
+  (should (equal (rx (or "a" (not anychar) punct ?c "b" (not (not ?d))))
+                 "[a-d[:punct:]]"))
+  (should (equal (rx (or nonl ?\n))
+                 "[^z-a]"))
+  (should (equal (rx (or "ab" "a" "b" blank (syntax whitespace) word "z"))
+                 "ab\\|[ab[:blank:]]\\|\\s-\\|[z[:word:]]"))
+  )
 
 (ert-deftest rx-def-in-or ()
   (rx-let ((a b)
@@ -98,7 +110,21 @@
                  "[\177Å\211\326-\377]"))
   ;; Split range; \177-\377ÿ should not be optimized to \177-\377.
   (should (equal (rx (any "\177-\377" ?ÿ))
-                 "[\177ÿ\200-\377]")))
+                 "[\177ÿ\200-\377]"))
+  ;; Range between normal chars and raw bytes: must be split to be parsed
+  ;; correctly by the Emacs regexp engine.
+  (should (equal (rx (any (0 . #x3fffff) word) (any (?G . #x3fff9a) word)
+                     (any (?Ü . #x3ffff2) word))
+                 (concat "[\0-\x3fff7f\x80-\xff[:word:]]"
+                         "[G-\x3fff7f\x80-\x9a[:word:]]"
+                         "[Ü-\x3fff7f\x80-\xf2[:word:]]")))
+  ;; As above but with ranges in string form. For historical reasons,
+  ;; we special-case ASCII-to-raw ranges to exclude non-ASCII unicode.
+  (should (equal (rx (any "\x00-\xff" alpha) (any "G-\x9a" alpha)
+                     (any "Ü-\xf2" alpha))
+                 (concat "[\0-\x7f\x80-\xff[:alpha:]]"
+                         "[G-\x7f\x80-\x9a[:alpha:]]"
+                         "[Ü-\x3fff7f\x80-\xf2[:alpha:]]"))))
 
 (ert-deftest rx-any ()
   (should (equal (rx (any ?A (?C . ?D) "F-H" "J-L" "M" "N-P" "Q" "RS"))
@@ -112,23 +138,33 @@
   (should (equal (rx (any "]" "^") (any "]" "-") (any "-" "^")
                      (not (any "]" "^")) (not (any "]" "-"))
                      (not (any "-" "^")))
-                 "[]^][]-][-^][^]^][^]-][^-^]"))
+                 "[]^][]-][-^][^]^][^]-][^^-]"))
   (should (equal (rx (any "]" "^" "-") (not (any "]" "^" "-")))
                  "[]^-][^]^-]"))
+  (should (equal (rx (any "^-f") (any "^-f" "-")
+                     (any "^-f" "z") (any "^-f" "z" "-"))
+                 "[_-f^][_-f^-][_-f^z][_-f^z-]"))
+  (should (equal (rx (not (any "^-f")) (not (any "^-f" "-"))
+                     (not (any "^-f" "z")) (not (any "^-f" "z" "-")))
+                 "[^^-f][^^-f-][^^-fz][^^-fz-]"))
+  (should (equal (rx (any "^-f" word) (any "^-f" "-" word))
+                 "[_-f^[:word:]][_-f^[:word:]-]"))
+  (should (equal (rx (not (any "^-f" word)) (not (any "^-f" "-" word)))
+                 "[^^-f[:word:]][^^-f[:word:]-]"))
   (should (equal (rx (any "-" ascii) (any "^" ascii) (any "]" ascii))
                  "[[:ascii:]-][[:ascii:]^][][:ascii:]]"))
   (should (equal (rx (not (any "-" ascii)) (not (any "^" ascii))
                      (not (any "]" ascii)))
-                 "[^[:ascii:]-][^[:ascii:]^][^][:ascii:]]"))
+                 "[^[:ascii:]-][^^[:ascii:]][^][:ascii:]]"))
   (should (equal (rx (any "-]" ascii) (any "^]" ascii) (any "-^" ascii))
                  "[][:ascii:]-][]^[:ascii:]][[:ascii:]^-]"))
   (should (equal (rx (not (any "-]" ascii)) (not (any "^]" ascii))
                      (not (any "-^" ascii)))
-                 "[^][:ascii:]-][^]^[:ascii:]][^[:ascii:]^-]"))
+                 "[^][:ascii:]-][^]^[:ascii:]][^^[:ascii:]-]"))
   (should (equal (rx (any "-]^" ascii) (not (any "-]^" ascii)))
                  "[]^[:ascii:]-][^]^[:ascii:]-]"))
   (should (equal (rx (any "^" lower upper) (not (any "^" lower upper)))
-                 "[[:lower:]^[:upper:]][^[:lower:]^[:upper:]]"))
+                 "[[:lower:][:upper:]^][^^[:lower:][:upper:]]"))
   (should (equal (rx (any "-" lower upper) (not (any "-" lower upper)))
                  "[[:lower:][:upper:]-][^[:lower:][:upper:]-]"))
   (should (equal (rx (any "]" lower upper) (not (any "]" lower upper)))
@@ -143,7 +179,7 @@
                  "[]-a-][^]-a-]"))
   (should (equal (rx (any "--]") (not (any "--]"))
                      (any "-" "^-a") (not (any "-" "^-a")))
-                 "[].-\\-][^].-\\-][-^-a][^-^-a]"))
+                 "[].-\\-][^].-\\-][_-a^-][^^-a-]"))
   (should (equal (rx (not (any "!a" "0-8" digit nonascii)))
                  "[^!0-8a[:digit:][:nonascii:]]"))
   (should (equal (rx (any) (not (any)))
@@ -155,7 +191,10 @@
                  "[a[:space:][:digit:]]"))
   (should (equal (rx (not "\n") (not ?\n) (not (any "\n")) (not-char ?\n)
                      (| (not (in "a\n")) (not (char ?\n (?b . ?b)))))
-          ".....")))
+                 "....."))
+  (should (equal (rx (or (in "g-k") (in "a-f") (or ?r (in "i-m" "n-q"))))
+                     "[a-r]"))
+  )
 
 (ert-deftest rx-pcase ()
   (should (equal (pcase "i18n" ((rx (let x (+ digit))) (list 'ok x)))
@@ -264,7 +303,7 @@
                  "^\\`\\'\\`\\'\\`\\'\\`\\'$"))
   (should (equal (rx point word-start word-end bow eow symbol-start symbol-end
                      word-boundary not-word-boundary not-wordchar)
-                 "\\=\\<\\>\\<\\>\\_<\\_>\\b\\B\\W"))
+                 "\\=\\<\\>\\<\\>\\_<\\_>\\b\\B[^[:word:]]"))
   (should (equal (rx digit numeric num control cntrl)
                  "[[:digit:]][[:digit:]][[:digit:]][[:cntrl:]][[:cntrl:]]"))
   (should (equal (rx hex-digit hex xdigit blank)
@@ -286,7 +325,7 @@
   (should (equal (rx (syntax whitespace) (syntax punctuation)
                      (syntax word) (syntax symbol)
                      (syntax open-parenthesis) (syntax close-parenthesis))
-                 "\\s-\\s.\\sw\\s_\\s(\\s)"))
+                 "\\s-\\s.\\w\\s_\\s(\\s)"))
   (should (equal (rx (syntax string-quote) (syntax paired-delimiter)
                      (syntax escape) (syntax character-quote)
                      (syntax comment-start) (syntax comment-end)
@@ -334,8 +373,9 @@
                  "\\B"))
   (should (equal (rx (not ascii) (not lower-case) (not wordchar))
                  "[^[:ascii:]][^[:lower:]][^[:word:]]"))
-  (should (equal (rx (not (syntax punctuation)) (not (syntax escape)))
-                 "\\S.\\S\\"))
+  (should (equal (rx (not (syntax punctuation)) (not (syntax escape))
+                     (not (syntax word)))
+                 "\\S.\\S\\\\W"))
   (should (equal (rx (not (category tone-mark)) (not (category lao)))
                  "\\C4\\Co"))
   (should (equal (rx (not (not ascii)) (not (not (not (any "a-z")))))
@@ -371,7 +411,16 @@
   (should (equal (rx (or (not (in "abc")) (not (char "bcd"))))
                  "[^bc]"))
   (should (equal (rx (or "x" (? "yz")))
-                 "x\\|\\(?:yz\\)?")))
+                 "x\\|\\(?:yz\\)?"))
+  (should (equal (rx (or anychar (not anychar)))
+                 "[^z-a]"))
+  (should (equal (rx (or (not (in "a-p")) (not (in "k-u"))))
+                 "[^k-p]"))
+  (should (equal (rx (or (not (in "a-p")) word (not (in "k-u"))))
+                 "[\0-jq-\x3fff7f\x80-\xff[:word:]]"))
+  (should (equal (rx (or (in "a-f" blank) (in "c-z") blank))
+                 "[a-z[:blank:]]"))
+  )
 
 (ert-deftest rx-def-in-charset-or ()
   (rx-let ((a (any "badc"))
@@ -590,6 +639,57 @@
              (rx-submatch-n '(group-n 3 (+ nonl) eol)))
            "\\(?3:.+$\\)")))
 
+;;; unit tests for internal functions
+
+(ert-deftest rx--interval-set-complement ()
+  (should (equal (rx--interval-set-complement '())
+                 '((0 . #x3fffff))))
+  (should (equal (rx--interval-set-complement '((10 . 20) (30 . 40)))
+                 '((0 . 9) (21 . 29) (41 . #x3fffff))))
+  (should (equal (rx--interval-set-complement '((0 . #x3fffff)))
+                 '()))
+  (should (equal (rx--interval-set-complement
+                  '((0 . 10) (20 . 20) (30 . #x3fffff)))
+                 '((11 . 19) (21 . 29)))))
+
+(ert-deftest rx--interval-set-union ()
+  (should (equal (rx--interval-set-union '() '()) '()))
+  (should (equal (rx--interval-set-union '() '((10 . 20) (30 . 40)))
+                 '((10 . 20) (30 . 40))))
+  (should (equal (rx--interval-set-union '((10 . 20) (30 . 40)) '())
+                 '((10 . 20) (30 . 40))))
+  (should (equal (rx--interval-set-union '((5 . 15) (18 . 24) (32 . 40))
+                                      '((10 . 20) (30 . 40) (50 . 60)))
+                 '((5 . 24) (30 . 40) (50 . 60))))
+  (should (equal (rx--interval-set-union '((10 . 20) (30 . 40) (50 . 60))
+                                      '((0 . 9) (21 . 29) (41 . 50)))
+                 '((0 . 60))))
+  (should (equal (rx--interval-set-union '((10 . 20) (30 . 40))
+                                      '((12 . 18) (28 . 42)))
+                 '((10 . 20) (28 . 42))))
+  (should (equal (rx--interval-set-union '((10 . 20) (30 . 40))
+                                      '((0 . #x3fffff)))
+                 '((0 . #x3fffff)))))
+
+(ert-deftest rx--interval-set-intersection ()
+  (should (equal (rx--interval-set-intersection '() '()) '()))
+  (should (equal (rx--interval-set-intersection '() '((10 . 20) (30 . 40)))
+                 '()))
+  (should (equal (rx--interval-set-intersection '((10 . 20) (30 . 40)) '())
+                 '()))
+  (should (equal (rx--interval-set-intersection '((5 . 15) (18 . 24) (32 . 40))
+                                          '((10 . 20) (30 . 40) (50 . 60)))
+                 '((10 . 15) (18 . 20) (32 . 40))))
+  (should (equal (rx--interval-set-intersection '((10 . 20) (30 . 40) (50 . 
60))
+                                          '((0 . 9) (21 . 29) (41 . 50)))
+                 '((50 . 50))))
+  (should (equal (rx--interval-set-intersection '((10 . 20) (30 . 40))
+                                          '((12 . 18) (28 . 42)))
+                 '((12 . 18) (30 . 40))))
+  (should (equal (rx--interval-set-intersection '((10 . 20) (30 . 40))
+                                          '((0 . #x3fffff)))
+                 '((10 . 20) (30 . 40)))))
+
 (provide 'rx-tests)
 
 ;;; rx-tests.el ends here
diff --git a/test/lisp/epg-tests.el b/test/lisp/epg-tests.el
index 3659a922fe3..ed9da90c029 100644
--- a/test/lisp/epg-tests.el
+++ b/test/lisp/epg-tests.el
@@ -111,14 +111,23 @@ 
jA0ECQMCdW8+qtS9Tin/0jUBO1/9Oz69BWPmtFKEeBM62WpFP4o1+bNzdxogdyeg
 -----END PGP MESSAGE-----
 ")))))
 
+(defun epg--gnupg-version-is-not-buggy ()
+  ;; We need to skip some versions of GnuPG, as they make tests hang.
+  ;; See Bug#63256 and https://dev.gnupg.org/T6481 as well as PROBLEMS.
+  ;; Known bad versions for now are 2.4.1--2.4.3.
+  (not (string-match (rx bos "gpg (GnuPG) 2.4." (+ digit))
+                     (shell-command-to-string "gpg --version"))))
+
 (ert-deftest epg-roundtrip-1 ()
- :expected-result (if (getenv "EMACS_HYDRA_CI") :failed :passed) ; fixme
+  :expected-result (if (getenv "EMACS_HYDRA_CI") :failed :passed) ; fixme
+  (skip-unless (epg--gnupg-version-is-not-buggy))
   (with-epg-tests (:require-passphrase t)
     (let ((cipher (epg-encrypt-string epg-tests-context "symmetric" nil)))
       (should (equal "symmetric"
                     (epg-decrypt-string epg-tests-context cipher))))))
 
 (ert-deftest epg-roundtrip-2 ()
+  (skip-unless (epg--gnupg-version-is-not-buggy))
   (with-epg-tests (:require-passphrase t
                   :require-public-key t
                   :require-secret-key t)
diff --git a/test/lisp/erc/erc-button-tests.el 
b/test/lisp/erc/erc-button-tests.el
index 3dacf95a59f..34ad06b7eb8 100644
--- a/test/lisp/erc/erc-button-tests.el
+++ b/test/lisp/erc/erc-button-tests.el
@@ -274,6 +274,34 @@
                         "abc" " %d def" " 45%s" 123 '\6)
                        "*** abc 123 def 456")))
 
+      (ert-info ("Respects buffer as first argument when given")
+        (should (equal (erc-button--display-error-notice-with-keys
+                        (make-erc-response) "abc") ; compat
+                       "*** abc"))
+        (should (equal (erc-button--display-error-notice-with-keys
+                        (current-buffer) "abc")
+                       "*** abc")))
+
+      (ert-info ("Accounts for nil members when concatenating")
+        (should (equal (erc-button--display-error-notice-with-keys
+                        "a" nil)
+                       "*** a"))
+        (should (equal (erc-button--display-error-notice-with-keys
+                        "a" nil " b")
+                       "*** a b"))
+        (should (equal (erc-button--display-error-notice-with-keys
+                        "a: %d" nil 1)
+                       "*** a: 1"))
+        (should (equal (erc-button--display-error-notice-with-keys
+                        "a: %d %s" 1 nil)
+                       "*** a: 1 nil"))
+        (should (equal (erc-button--display-error-notice-with-keys
+                        "a: " "%d %s" 1 nil)
+                       "*** a: 1 nil"))
+        (should (equal (erc-button--display-error-notice-with-keys
+                        "a: " nil "%d %s" 1 nil)
+                       "*** a: 1 nil")))
+
       (when noninteractive
         (unless mode
           (erc-button-mode -1))
diff --git a/test/lisp/erc/erc-fill-tests.el b/test/lisp/erc/erc-fill-tests.el
index 99ec4a9635e..b81d0c15558 100644
--- a/test/lisp/erc/erc-fill-tests.el
+++ b/test/lisp/erc/erc-fill-tests.el
@@ -241,6 +241,46 @@
         "<bob> " "<alice> " "<alice> " "<bob> " "<bob> " "<Dummy> " "<Dummy> ")
        (erc-fill-tests--compare "merge-02-right")))))
 
+(ert-deftest erc-fill-wrap--merge-action ()
+  :tags '(:unstable)
+  (unless (>= emacs-major-version 29)
+    (ert-skip "Emacs version too low, missing `buffer-text-pixel-size'"))
+
+  (erc-fill-tests--wrap-populate
+
+   (lambda ()
+     ;; Set this here so that the first few messages are from 1970
+     (let ((erc-fill-tests--time-vals (lambda () 1680332400)))
+       (erc-fill-tests--insert-privmsg "bob" "zero.")
+
+       (erc-process-ctcp-query
+        erc-server-process
+        (make-erc-response
+         :unparsed ":bob!~u@fake PRIVMSG #chan :\1ACTION one\1"
+         :sender "bob!~u@fake" :command "PRIVMSG"
+         :command-args '("#chan" "\1ACTION one\1") :contents "\1ACTION one\1")
+        "bob" "~u" "fake")
+
+       (erc-fill-tests--insert-privmsg "bob" "two.")
+
+       ;; Compat switch to opt out of overhanging speaker.
+       (let (erc-fill--wrap-action-dedent-p)
+         (erc-process-ctcp-query
+          erc-server-process
+          (make-erc-response
+           :unparsed ":bob!~u@fake PRIVMSG #chan :\1ACTION three\1"
+           :sender "bob!~u@fake" :command "PRIVMSG"
+           :command-args '("#chan" "\1ACTION three\1")
+           :contents "\1ACTION three\1")
+          "bob" "~u" "fake"))
+
+       (erc-fill-tests--insert-privmsg "bob" "four."))
+
+     (should (= erc-fill--wrap-value 27))
+     (erc-fill-tests--wrap-check-prefixes
+      "*** " "<alice> " "<bob> " "<bob> " "* bob " "<bob> " "* " "<bob> ")
+     (erc-fill-tests--compare "merge-wrap-01"))))
+
 (ert-deftest erc-fill-line-spacing ()
   :tags '(:unstable)
   (unless (>= emacs-major-version 29)
@@ -340,4 +380,41 @@
        (should (search-backward "ERC> " nil t))
        (execute-kbd-macro "\C-a")))))
 
+(ert-deftest erc-fill--left-hand-stamps ()
+  :tags '(:unstable)
+  (unless (>= emacs-major-version 29)
+    (ert-skip "Emacs version too low, missing `buffer-text-pixel-size'"))
+
+  (let ((erc-timestamp-only-if-changed-flag nil)
+        (erc-insert-timestamp-function #'erc-insert-timestamp-left))
+    (erc-fill-tests--wrap-populate
+     (lambda ()
+       (should (= 8 left-margin-width))
+       (pcase-let ((`((margin left-margin) ,displayed)
+                    (get-text-property erc-insert-marker 'display)))
+         (should (equal-including-properties
+                  displayed #("    ERC>" 4 8
+                              ( read-only t
+                                front-sticky t
+                                field erc-prompt
+                                erc-prompt t
+                                rear-nonsticky t
+                                font-lock-face erc-prompt-face)))))
+       (erc-fill-tests--compare "stamps-left-01")
+
+       (ert-info ("Shrink left margin by 1 col")
+         (erc-stamp--adjust-margin -1)
+         (with-silent-modifications (erc--refresh-prompt))
+         (should (= 7 left-margin-width))
+         (pcase-let ((`((margin left-margin) ,displayed)
+                      (get-text-property erc-insert-marker 'display)))
+           (should (equal-including-properties
+                    displayed #("   ERC>" 3 7
+                                ( read-only t
+                                  front-sticky t
+                                  field erc-prompt
+                                  erc-prompt t
+                                  rear-nonsticky t
+                                  font-lock-face erc-prompt-face))))))))))
+
 ;;; erc-fill-tests.el ends here
diff --git a/test/lisp/erc/erc-scenarios-base-renick.el 
b/test/lisp/erc/erc-scenarios-base-renick.el
index f1723200533..2bf3ef46257 100644
--- a/test/lisp/erc/erc-scenarios-base-renick.el
+++ b/test/lisp/erc/erc-scenarios-base-renick.el
@@ -275,8 +275,8 @@
         (funcall expect 3 "I never saw her before")
         (erc-scenarios-common-say "You aren't with Wage?")))
 
-    (erc-d-t-wait-for 3 (get-buffer "frenemy@foonet"))
-    (erc-d-t-wait-for 3 (get-buffer "frenemy@barnet"))
+    (erc-d-t-wait-for 10 (get-buffer "frenemy@foonet"))
+    (erc-d-t-wait-for 10 (get-buffer "frenemy@barnet"))
     (should-not (get-buffer "rando@foonet"))
     (should-not (get-buffer "rando@barnet"))
 
diff --git a/test/lisp/erc/erc-scenarios-log.el 
b/test/lisp/erc/erc-scenarios-log.el
index c37e6b323aa..fd030d90c2f 100644
--- a/test/lisp/erc/erc-scenarios-log.el
+++ b/test/lisp/erc/erc-scenarios-log.el
@@ -202,6 +202,7 @@
         (funcall expect -0.1 "please your lordship")))
 
     (erc-log-mode -1)
+    (erc-truncate-mode -1)
     (when noninteractive (delete-directory tempdir :recursive))))
 
 ;;; erc-scenarios-log.el ends here
diff --git a/test/lisp/erc/erc-scenarios-match.el 
b/test/lisp/erc/erc-scenarios-match.el
index 8a718962c55..cd899fddb98 100644
--- a/test/lisp/erc/erc-scenarios-match.el
+++ b/test/lisp/erc/erc-scenarios-match.el
@@ -62,11 +62,15 @@
                     'erc-current-nick-face))))))
 
 ;; When hacking on tests that use this fixture, it's best to run it
-;; interactively, and check for wierdness before and after doing
-;; M-: (remove-from-invisibility-spec 'erc-match) RET.
+;; interactively, and visually inspect the output with various
+;; combinations of:
+;;
+;;   M-x erc-match-toggle-hidden-fools RET
+;;   M-x erc-toggle-timestamps RET
+;;
 (defun erc-scenarios-match--invisible-stamp (hiddenp visiblep)
   (unless noninteractive
-    (kill-new "(remove-from-invisibility-spec 'erc-match)"))
+    (kill-new "erc-match-toggle-hidden-fools"))
 
   (erc-scenarios-common-with-cleanup
       ((erc-scenarios-common-dialog "join/legacy")
@@ -128,11 +132,11 @@
 
        ;; Leading stamp has combined `invisible' property value.
        (should (equal (get-text-property (pos-bol) 'invisible)
-                      '(timestamp erc-match)))
+                      '(timestamp match-fools)))
 
-       ;; Message proper has the `invisible' property `erc-match'.
+       ;; Message proper has the `invisible' property `match-fools'.
        (let ((msg-beg (next-single-property-change (pos-bol) 'invisible)))
-         (should (eq (get-text-property msg-beg 'invisible) 'erc-match))
+         (should (eq (get-text-property msg-beg 'invisible) 'match-fools))
          (should (>= (next-single-property-change msg-beg 'invisible nil)
                      (pos-eol)))))
 
@@ -147,19 +151,29 @@
           (= (next-single-property-change msg-beg 'invisible nil (pos-eol))
              (pos-eol))))))))
 
+(defun erc-scenarios-match--find-bol ()
+  (save-excursion
+    (should (get-text-property (1- (point)) 'erc-command))
+    (goto-char (should (previous-single-property-change (point) 'erc-command)))
+    (pos-bol)))
+
 (defun erc-scenarios-match--find-eol ()
   (save-excursion
-    (goto-char (next-single-property-change (point) 'erc-command))
+    (if-let ((next (next-single-property-change (point) 'erc-command)))
+        (goto-char next)
+      ;; We're already at the end of the message.
+      (should (get-text-property (1- (point)) 'erc-command)))
     (pos-eol)))
 
 ;; In most cases, `erc-hide-fools' makes line endings invisible.
-(ert-deftest erc-scenarios-match--stamp-right-fools-invisible ()
+(defun erc-scenarios-match--stamp-right-fools-invisible ()
   :tags '(:expensive-test)
   (let ((erc-insert-timestamp-function #'erc-insert-timestamp-right))
     (erc-scenarios-match--invisible-stamp
 
      (lambda ()
-       (let ((end (erc-scenarios-match--find-eol)))
+       (let ((beg (erc-scenarios-match--find-bol))
+             (end (erc-scenarios-match--find-eol)))
          ;; The end of the message is a newline.
          (should (= ?\n (char-after end)))
 
@@ -168,19 +182,23 @@
 
          ;; Stamps have a combined `invisible' property value.
          (should (equal (get-text-property (1- end) 'invisible)
-                        '(timestamp erc-match)))
+                        '(timestamp match-fools)))
 
          ;; The final newline is hidden by `match', not `stamps'
-         (should (equal (get-text-property end 'invisible) 'erc-match))
+         (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p))
+           (if erc-legacy-invisible-bounds-p
+               (should (eq (get-text-property end 'invisible) 'match-fools))
+             (should (eq (get-text-property beg 'invisible) 'match-fools))
+             (should-not (get-text-property end 'invisible))))
 
-         ;; The message proper has the `invisible' property `erc-match',
+         ;; The message proper has the `invisible' property `match-fools',
          ;; and it starts after the preceding newline.
-         (should (eq (get-text-property (pos-bol) 'invisible) 'erc-match))
+         (should (eq (get-text-property (pos-bol) 'invisible) 'match-fools))
 
          ;; It ends just before the timestamp.
          (let ((msg-end (next-single-property-change (pos-bol) 'invisible)))
            (should (equal (get-text-property msg-end 'invisible)
-                          '(timestamp erc-match)))
+                          '(timestamp match-fools)))
 
            ;; Stamp's `invisible' property extends throughout the stamp
            ;; and ends before the trailing newline.
@@ -197,6 +215,17 @@
            (should (eq (get-text-property inv-beg 'invisible)
                        'timestamp))))))))
 
+(ert-deftest erc-scenarios-match--stamp-right-fools-invisible ()
+  :tags '(:expensive-test)
+  (erc-scenarios-match--stamp-right-fools-invisible))
+
+(ert-deftest erc-scenarios-match--stamp-right-fools-invisible--nooffset ()
+  :tags '(:expensive-test)
+  (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p))
+    (should-not erc-legacy-invisible-bounds-p)
+    (let ((erc-legacy-invisible-bounds-p t))
+      (erc-scenarios-match--stamp-right-fools-invisible))))
+
 ;; This asserts that when `erc-fill-wrap-mode' is enabled, ERC hides
 ;; the preceding message's line ending.
 (ert-deftest erc-scenarios-match--stamp-right-invisible-fill-wrap ()
@@ -215,16 +244,16 @@
 
        ;; Stamps have a combined `invisible' property value.
        (should (equal (get-text-property (1- (pos-eol)) 'invisible)
-                      '(timestamp erc-match)))
+                      '(timestamp match-fools)))
 
-       ;; The message proper has the `invisible' property `erc-match',
+       ;; The message proper has the `invisible' property `match-fools',
        ;; which starts at the preceding newline...
-       (should (eq (get-text-property (1- (pos-bol)) 'invisible) 'erc-match))
+       (should (eq (get-text-property (1- (pos-bol)) 'invisible) 'match-fools))
 
        ;; ... and ends just before the timestamp.
        (let ((msgend (next-single-property-change (1- (pos-bol)) 'invisible)))
          (should (equal (get-text-property msgend 'invisible)
-                        '(timestamp erc-match)))
+                        '(timestamp match-fools)))
 
          ;; The newline before `erc-insert-marker' is still visible.
          (should-not (get-text-property (pos-eol) 'invisible))
@@ -242,8 +271,7 @@
        (let ((inv-beg (next-single-property-change (1- (pos-bol)) 'invisible)))
          (should (eq (get-text-property inv-beg 'invisible) 'timestamp)))))))
 
-(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static ()
-  :tags '(:expensive-test)
+(defun erc-scenarios-match--stamp-both-invisible-fill-static ()
   (should (eq erc-insert-timestamp-function
               #'erc-insert-timestamp-left-and-right))
 
@@ -265,8 +293,8 @@
              (search-forward "[23:59]"))))
 
        (ert-info ("Line endings in Bob's messages are invisible")
-         ;; The message proper has the `invisible' property `erc-match'.
-         (should (eq (get-text-property (pos-bol) 'invisible) 'erc-match))
+         ;; The message proper has the `invisible' property `match-fools'.
+         (should (eq (get-text-property (pos-bol) 'invisible) 'match-fools))
          (let* ((mbeg (next-single-property-change (pos-bol) 'erc-command))
                 (mend (next-single-property-change mbeg 'erc-command)))
 
@@ -283,9 +311,13 @@
            (should (= (next-single-property-change (pos-bol) 'erc-timestamp)
                       mend))
 
-           ;; Line ending has the `invisible' property `erc-match'.
+           ;; Line ending has the `invisible' property `match-fools'.
            (should (= (char-after mend) ?\n))
-           (should (eq (get-text-property mend'invisible) 'erc-match))))
+           (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p))
+             (if erc-legacy-invisible-bounds-p
+                 (should (eq (get-text-property mend 'invisible) 'match-fools))
+               (should (eq (get-text-property mbeg 'invisible) 'match-fools))
+               (should-not (get-text-property mend 'invisible))))))
 
        ;; Only the message right after Alice speaks contains stamps.
        (when (= 1 bob-utterance-counter)
@@ -298,7 +330,7 @@
              ;; Date stamp has a combined `invisible' property value
              ;; that extends until the start of the message proper.
              (should (equal (get-text-property (point) 'invisible)
-                            '(timestamp erc-match)))
+                            '(timestamp match-fools)))
              (should (= (next-single-property-change (point) 'invisible)
                         (1+ (pos-eol))))))
 
@@ -314,7 +346,7 @@
            (let ((msgend (next-single-property-change (pos-bol) 'invisible)))
              ;; Stamp has a combined `invisible' property value.
              (should (equal (get-text-property msgend 'invisible)
-                            '(timestamp erc-match)))
+                            '(timestamp match-fools)))
 
              ;; Combined `invisible' property spans entire timestamp.
              (should (= (next-single-property-change msgend 'invisible)
@@ -331,4 +363,15 @@
        (should-not (eq (field-at-pos (1- (pos-eol))) 'erc-timestamp))
        (should-not (next-single-property-change (pos-bol) 'invisible))))))
 
+(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static ()
+  :tags '(:expensive-test)
+  (erc-scenarios-match--stamp-both-invisible-fill-static))
+
+(ert-deftest erc-scenarios-match--stamp-both-invisible-fill-static--nooffset ()
+  :tags '(:expensive-test)
+  (with-suppressed-warnings ((obsolete erc-legacy-invisible-bounds-p))
+    (should-not erc-legacy-invisible-bounds-p)
+    (let ((erc-legacy-invisible-bounds-p t))
+      (erc-scenarios-match--stamp-both-invisible-fill-static))))
+
 ;;; erc-scenarios-match.el ends here
diff --git a/test/lisp/erc/erc-scenarios-status-sidebar.el 
b/test/lisp/erc/erc-scenarios-status-sidebar.el
index 92229121c9f..3a047bf3983 100644
--- a/test/lisp/erc/erc-scenarios-status-sidebar.el
+++ b/test/lisp/erc/erc-scenarios-status-sidebar.el
@@ -94,7 +94,7 @@
 ;; terminal, and we lack a fixture for that.  Please try running this
 ;; test interactively with both graphical Emacs and non.
 (declare-function erc-nickbar-mode "erc-speedbar" (arg))
-(declare-function erc-speedbar-close-nicknames-window "erc-speedbar" (kill))
+(declare-function erc-speedbar--get-timers "erc-speedbar" nil)
 (declare-function speedbar-timer-fn "speedbar" nil)
 (defvar erc-nickbar-mode)
 (defvar speedbar-buffer)
@@ -154,16 +154,21 @@
       (ert-info ("Core toggle and kill commands work")
         ;; Avoid using API, e.g., `erc-status-sidebar-buffer-exists-p',
         ;; etc. for testing commands that call those same functions.
-        (erc-nickbar-mode -1)
+        (call-interactively #'erc-nickbar-mode)
+        (should-not erc-nickbar-mode)
         (should-not (and speedbar-buffer
                          (get-buffer-window speedbar-buffer)))
+        (should speedbar-buffer)
+
         (erc-nickbar-mode +1)
         (should (and speedbar-buffer
                      (get-buffer-window speedbar-buffer)))
         (should (get-buffer " SPEEDBAR"))
-        (erc-speedbar-close-nicknames-window 'kill)
+        (erc-nickbar-mode -1)
         (should-not (get-buffer " SPEEDBAR"))
         (should-not erc-nickbar-mode)
-        (should-not (cdr (frame-list)))))))
+        (should-not (cdr (frame-list)))))
+
+    (should-not (erc-speedbar--get-timers))))
 
 ;;; erc-scenarios-status-sidebar.el ends here
diff --git a/test/lisp/erc/erc-stamp-tests.el b/test/lisp/erc/erc-stamp-tests.el
index 6da7ed4503d..c448416cd69 100644
--- a/test/lisp/erc/erc-stamp-tests.el
+++ b/test/lisp/erc/erc-stamp-tests.el
@@ -56,7 +56,7 @@
     (advice-remove 'erc-format-timestamp
                    'ert-deftest--erc-timestamp-use-align-to)))
 
-(ert-deftest erc-timestamp-use-align-to--nil ()
+(defun erc-stamp-tests--use-align-to--nil (compat)
   (erc-stamp-tests--insert-right
    (lambda ()
 
@@ -83,12 +83,20 @@
          (erc-display-message nil 'notice (current-buffer)
                               "twenty characters"))
        (should (search-forward-regexp (rx bol (+ "\t") (* " ") "[") nil t))
-       ;; Field excludes leading whitespace (arguably undesirable).
-       (should (eql ?\[ (char-after (field-beginning (point)))))
+       ;; Field includes leading whitespace.
+       (should (eql (if compat ?\[ ?\n)
+                    (char-after (field-beginning (point)))))
        ;; Timestamp extends to the end of the line.
        (should (eql ?\n (char-after (field-end (point)))))))))
 
-(ert-deftest erc-timestamp-use-align-to--t ()
+(ert-deftest erc-timestamp-use-align-to--nil ()
+  (ert-info ("Field starts on stamp text (compat)")
+    (let ((erc-stamp--omit-properties-on-folded-lines t))
+      (erc-stamp-tests--use-align-to--nil 'compat)))
+  (ert-info ("Field includes leaidng white space")
+    (erc-stamp-tests--use-align-to--nil nil)))
+
+(defun erc-stamp-tests--use-align-to--t (compat)
   (erc-stamp-tests--insert-right
    (lambda ()
 
@@ -110,10 +118,17 @@
            (erc-display-message nil nil (current-buffer) msg)))
        ;; Indented to pos (this is arguably a bug).
        (should (search-forward-regexp (rx bol (+ "\t") (* " ") "[") nil t))
-       ;; Field starts *after* leading space (arguably bad).
-       (should (eql ?\[ (char-after (field-beginning (point)))))
+       ;; Field includes leading space.
+       (should (eql (if compat ?\[ ?\n) (char-after (field-beginning 
(point)))))
        (should (eql ?\n (char-after (field-end (point)))))))))
 
+(ert-deftest erc-timestamp-use-align-to--t ()
+  (ert-info ("Field starts on stamp text (compat)")
+    (let ((erc-stamp--omit-properties-on-folded-lines t))
+      (erc-stamp-tests--use-align-to--t 'compat)))
+  (ert-info ("Field includes leaidng white space")
+    (erc-stamp-tests--use-align-to--t nil)))
+
 (ert-deftest erc-timestamp-use-align-to--integer ()
   (erc-stamp-tests--insert-right
    (lambda ()
@@ -140,7 +155,7 @@
        (should (eql ?\s (char-after (field-beginning (point)))))
        (should (eql ?\n (char-after (field-end (point)))))))))
 
-(ert-deftest erc-timestamp-use-align-to--margin ()
+(ert-deftest erc-stamp--display-margin-mode--right ()
   (erc-stamp-tests--insert-right
    (lambda ()
      (erc-stamp--display-margin-mode +1)
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index b5db5fe8764..9fdad823059 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -132,11 +132,11 @@
 (ert-deftest erc--with-dependent-type-match ()
   (should (equal (macroexpand-1
                   '(erc--with-dependent-type-match (repeat face) erc-match))
-                 '(backquote
-                   (repeat :match ,(lambda (w v)
-                                     (require 'erc-match)
-                                     (widget-editable-list-match w v))
-                           face)))))
+                 '(backquote-list*
+                   'repeat :match (lambda (w v)
+                                    (require 'erc-match)
+                                    (widget-editable-list-match w v))
+                   '(face)))))
 
 (defun erc-tests--send-prep ()
   ;; Caller should probably shadow `erc-insert-modify-hook' or
@@ -219,6 +219,7 @@
       (setq erc-hide-prompt '(server))
       (with-current-buffer "ServNet"
         (erc--hide-prompt erc-server-process)
+        (should (eq (get-text-property erc-insert-marker 'erc-prompt) 'hidden))
         (should (string= ">" (get-text-property erc-insert-marker 'display))))
 
       (with-current-buffer "#chan"
@@ -229,6 +230,7 @@
 
       (with-current-buffer "ServNet"
         (erc--unhide-prompt)
+        (should (eq (get-text-property erc-insert-marker 'erc-prompt) t))
         (should-not (get-text-property erc-insert-marker 'display))))
 
     (ert-info ("Value: channel")
@@ -242,7 +244,9 @@
 
       (with-current-buffer "#chan"
         (should (string= ">" (get-text-property erc-insert-marker 'display)))
+        (should (eq (get-text-property erc-insert-marker 'erc-prompt) 'hidden))
         (erc--unhide-prompt)
+        (should (eq (get-text-property erc-insert-marker 'erc-prompt) t))
         (should-not (get-text-property erc-insert-marker 'display))))
 
     (ert-info ("Value: query")
@@ -253,7 +257,9 @@
 
       (with-current-buffer "bob"
         (should (string= ">" (get-text-property erc-insert-marker 'display)))
+        (should (eq (get-text-property erc-insert-marker 'erc-prompt) 'hidden))
         (erc--unhide-prompt)
+        (should (eq (get-text-property erc-insert-marker 'erc-prompt) t))
         (should-not (get-text-property erc-insert-marker 'display)))
 
       (with-current-buffer "#chan"
@@ -1272,6 +1278,50 @@
 
           (should-not calls))))))
 
+(defmacro erc-tests--equal-including-properties (a b)
+  (list (if (< emacs-major-version 29)
+            'ert-equal-including-properties
+          'equal-including-properties)
+        a b))
+
+(ert-deftest erc--merge-prop ()
+  (with-current-buffer (get-buffer-create "*erc-test*")
+    ;; Baseline.
+    (insert "abc\n")
+    (erc--merge-prop 1 3 'erc-test 'x)
+    (should (erc-tests--equal-including-properties
+             (buffer-substring 1 4) #("abc" 0 2 (erc-test x))))
+    (erc--merge-prop 1 3 'erc-test 'y)
+    (should (erc-tests--equal-including-properties
+             (buffer-substring 1 4) #("abc" 0 2 (erc-test (y x)))))
+
+    ;; Multiple intervals.
+    (goto-char (point-min))
+    (insert "def\n")
+    (erc--merge-prop 1 2 'erc-test 'x)
+    (erc--merge-prop 2 3 'erc-test 'y)
+    (should (erc-tests--equal-including-properties
+             (buffer-substring 1 4)
+             #("def" 0 1 (erc-test x) 1 2 (erc-test y))))
+    (erc--merge-prop 1 3 'erc-test 'z)
+    (should (erc-tests--equal-including-properties
+             (buffer-substring 1 4)
+             #("def" 0 1 (erc-test (z x)) 1 2 (erc-test (z y)))))
+
+    ;; New val as list.
+    (goto-char (point-min))
+    (insert "ghi\n")
+    (erc--merge-prop 2 3 'erc-test '(y z))
+    (should (erc-tests--equal-including-properties
+             (buffer-substring 1 4) #("ghi" 1 2 (erc-test (y z)))))
+    (erc--merge-prop 1 3 'erc-test '(w x))
+    (should (erc-tests--equal-including-properties
+             (buffer-substring 1 4)
+             #("ghi" 0 1 (erc-test (w x)) 1 2 (erc-test (w x y z)))))
+
+    (when noninteractive
+      (kill-buffer))))
+
 (ert-deftest erc--split-string-shell-cmd ()
 
   ;; Leading and trailing space
@@ -1488,12 +1538,6 @@
     (kill-buffer "ExampleNet")
     (kill-buffer "#chan")))
 
-(defmacro erc-tests--equal-including-properties (a b)
-  (list (if (< emacs-major-version 29)
-            'ert-equal-including-properties
-          'equal-including-properties)
-        a b))
-
 (ert-deftest erc-format-privmessage ()
   ;; Basic PRIVMSG
   (should (erc-tests--equal-including-properties
diff --git a/test/lisp/erc/resources/base/netid/bouncer/barnet.eld 
b/test/lisp/erc/resources/base/netid/bouncer/barnet.eld
index d0fe3af8ea4..204d01fef77 100644
--- a/test/lisp/erc/resources/base/netid/bouncer/barnet.eld
+++ b/test/lisp/erc/resources/base/netid/bouncer/barnet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 3 "PASS :barnet:changeme"))
-((nick 3 "NICK tester"))
-((user 3 "USER user 0 * :tester")
+((pass 10 "PASS :barnet:changeme"))
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
  (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.barnet.org 003 tester :This server was created Wed, 12 May 2021 
07:41:08 UTC")
@@ -17,19 +17,19 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 10.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  ;; No mode answer ^
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.barnet.org 305 tester :You are no longer marked as being away"))
 
-((join 1 "JOIN #chan")
+((join 10 "JOIN #chan")
  (0 ":tester!~u@awyxgybtkx7uq.irc JOIN #chan")
  (0 ":irc.barnet.org 353 tester = #chan :@joe mike tester")
  (0 ":irc.barnet.org 366 tester #chan :End of NAMES list")
  (0.1 ":joe!~u@awyxgybtkx7uq.irc PRIVMSG #chan :tester, welcome!")
  (0 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :tester, welcome!"))
 
-((mode 3 "MODE #chan")
+((mode 10 "MODE #chan")
  (0 ":irc.barnet.org 324 tester #chan +nt")
  (0 ":irc.barnet.org 329 tester #chan 1620805269")
  (0.1 ":mike!~u@awyxgybtkx7uq.irc PRIVMSG #chan :joe: But you have outfaced 
them all.")
diff --git a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld 
b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld
index b0964fb9537..4445350ca0c 100644
--- a/test/lisp/erc/resources/base/netid/bouncer/foonet.eld
+++ b/test/lisp/erc/resources/base/netid/bouncer/foonet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 3 "PASS :foonet:changeme"))
-((nick 3 "NICK tester"))
-((user 3 "USER user 0 * :tester")
+((pass 10 "PASS :foonet:changeme"))
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :tester")
  (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
  (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.foonet.org 003 tester :This server was created Wed, 12 May 2021 
07:41:09 UTC")
@@ -17,19 +17,19 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 4.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  ;; No mode answer ^
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":irc.foonet.org 305 tester :You are no longer marked as being away"))
 
-((join 1 "JOIN #chan")
+((join 10 "JOIN #chan")
  (0 ":tester!~u@ertp7idh9jtgi.irc JOIN #chan")
  (0 ":irc.foonet.org 353 tester = #chan :@alice bob tester")
  (0 ":irc.foonet.org 366 tester #chan :End of NAMES list")
  (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :tester, welcome!")
  (0 ":bob!~u@ertp7idh9jtgi.irc PRIVMSG #chan :tester, welcome!"))
 
-((mode 3 "MODE #chan")
+((mode 10 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620805271")
  (0.1 ":alice!~u@ertp7idh9jtgi.irc PRIVMSG #chan :bob: He cannot be heard of. 
Out of doubt he is transported.")
diff --git a/test/lisp/erc/resources/base/reconnect/options.eld 
b/test/lisp/erc/resources/base/reconnect/options.eld
index 3b305d85594..e0952a2aece 100644
--- a/test/lisp/erc/resources/base/reconnect/options.eld
+++ b/test/lisp/erc/resources/base/reconnect/options.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 10 "PASS :changeme"))
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :tester")
  (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
  (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.foonet.org 003 tester :This server was created Tue, 04 May 2021 
05:06:18 UTC")
@@ -18,7 +18,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 3.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  (0 ":irc.foonet.org 221 tester +i")
  (0 ":irc.foonet.org NOTICE tester :This server is in debug mode.")
 
@@ -26,7 +26,7 @@
  (0 ":irc.foonet.org 353 tester = #chan :alice tester @bob")
  (0 ":irc.foonet.org 366 tester #chan :End of NAMES list"))
 
-((mode-chan 4 "MODE #chan")
+((mode-chan 10 "MODE #chan")
  (0 ":irc.foonet.org 324 tester #chan +nt")
  (0 ":irc.foonet.org 329 tester #chan 1620104779")
  (0.1 ":bob!~u@rz2v467q4rwhy.irc PRIVMSG #chan :tester, welcome!")
diff --git a/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld 
b/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld
index 0c8cdac0379..c9080cf39e9 100644
--- a/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld
+++ b/test/lisp/erc/resources/base/renick/queries/bouncer-barnet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 3 "PASS :barnet:changeme"))
-((nick 3 "NICK tester"))
-((user 3 "USER user 0 * :tester")
+((pass 10 "PASS :barnet:changeme"))
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :tester")
  (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
  (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.barnet.org 003 tester :This server was created Tue, 01 Jun 2021 
07:49:23 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
 
-((mode-user 3.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@286u8jcpis84e.irc JOIN #chan")
@@ -32,18 +32,18 @@
  (0 ":irc.barnet.org NOTICE tester :[09:13:24] This server is in debug mode 
and is logging all user I/O. If you do not wish for everything you send to be 
readable by the server owner(s), please disconnect.")
  (0 ":irc.barnet.org 305 tester :You are no longer marked as being away"))
 
-((mode 5 "MODE #chan")
+((mode 10 "MODE #chan")
  (0 ":irc.barnet.org 324 tester #chan +nt")
  (0 ":irc.barnet.org 329 tester #chan 1622538742")
  (0.1 ":joe!~u@286u8jcpis84e.irc PRIVMSG #chan :mike: By favors several which 
they did bestow.")
  (0.1 ":mike!~u@286u8jcpis84e.irc PRIVMSG #chan :joe: You, Roderigo! come, 
sir, I am for you."))
 
-((privmsg-a 5 "PRIVMSG rando :Linda said you were gonna kill me.")
+((privmsg-a 10 "PRIVMSG rando :Linda said you were gonna kill me.")
  (0.1 ":joe!~u@286u8jcpis84e.irc PRIVMSG #chan :mike: Play, music, then! Nay, 
you must do it soon.")
  (0.1 ":rando!~u@95i756tt32ym8.irc PRIVMSG tester :Linda said? I never saw her 
before I came up here.")
  (0.1 ":mike!~u@286u8jcpis84e.irc PRIVMSG #chan :joe: Of arts inhibited and 
out of warrant."))
 
-((privmsg-b 3 "PRIVMSG rando :You aren't with Wage?")
+((privmsg-b 10 "PRIVMSG rando :You aren't with Wage?")
  (0.1 ":joe!~u@286u8jcpis84e.irc PRIVMSG #chan :mike: But most of all, 
agreeing with the proclamation.")
  (0.1 ":rando!~u@95i756tt32ym8.irc PRIVMSG tester :I think you screwed up, 
Case.")
  (0.1 ":mike!~u@286u8jcpis84e.irc PRIVMSG #chan :joe: Good gentleman, go your 
gait, and let poor volk pass. An chud ha' bin zwaggered out of my life, 'twould 
not ha' bin zo long as 'tis by a vortnight. Nay, come not near th' old man; 
keep out, che vor ye, or ise try whether your costard or my ballow be the 
harder. Chill be plain with you.")
diff --git a/test/lisp/erc/resources/base/renick/queries/bouncer-foonet.eld 
b/test/lisp/erc/resources/base/renick/queries/bouncer-foonet.eld
index 162e8bf9655..2421651ebe8 100644
--- a/test/lisp/erc/resources/base/renick/queries/bouncer-foonet.eld
+++ b/test/lisp/erc/resources/base/renick/queries/bouncer-foonet.eld
@@ -1,7 +1,7 @@
 ;; -*- mode: lisp-data; -*-
-((pass 1 "PASS :foonet:changeme"))
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((pass 10 "PASS :foonet:changeme"))
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :tester")
  (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
  (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version 
oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.foonet.org 003 tester :This server was created Tue, 01 Jun 2021 
07:49:22 UTC")
@@ -17,7 +17,7 @@
  (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
  (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
 
-((mode-user 5.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  ;; No mode answer
  (0 ":irc.znc.in 306 tester :You have been marked as being away")
  (0 ":tester!~u@u4mvbswyw8gbg.irc JOIN #chan")
@@ -38,12 +38,12 @@
  (0.1 ":bob!~u@u4mvbswyw8gbg.irc PRIVMSG #chan :alice: When there is nothing 
living but thee, thou shalt be welcome. I had rather be a beggar's dog than 
Apemantus.")
  (0.1 ":alice!~u@u4mvbswyw8gbg.irc PRIVMSG #chan :bob: You have simply misused 
our sex in your love-prate: we must have your doublot and hose plucked over 
your head, and show the world what the bird hath done to her own nest."))
 
-((privmsg-a 6 "PRIVMSG rando :I here")
+((privmsg-a 10 "PRIVMSG rando :I here")
  (0.1 ":bob!~u@u4mvbswyw8gbg.irc PRIVMSG #chan :alice: And I will make thee 
think thy swan a crow.")
  (0.1 ":rando!~u@bivkhq8yav938.irc PRIVMSG tester :u are dumb")
  (0.1 ":alice!~u@u4mvbswyw8gbg.irc PRIVMSG #chan :bob: Lie not, to say mine 
eyes are murderers."))
 
-((privmsg-b 3 "PRIVMSG rando :not so")
+((privmsg-b 10 "PRIVMSG rando :not so")
  (0.1 ":bob!~u@u4mvbswyw8gbg.irc PRIVMSG #chan :alice: Commit myself, my 
person, and the cause.")
  ;; Nick change
  (0.1 ":rando!~u@bivkhq8yav938.irc NICK frenemy")
diff --git a/test/lisp/erc/resources/erc-scenarios-common.el 
b/test/lisp/erc/resources/erc-scenarios-common.el
index 32e7556d602..2eb040d28d9 100644
--- a/test/lisp/erc/resources/erc-scenarios-common.el
+++ b/test/lisp/erc/resources/erc-scenarios-common.el
@@ -122,6 +122,7 @@
       (inhibit-interaction t)
       (auth-source-do-cache nil)
       (timer-list (copy-sequence timer-list))
+      (timer-idle-list (copy-sequence timer-idle-list))
       (erc-auth-source-parameters-join-function nil)
       (erc-autojoin-channels-alist nil)
       (erc-server-auto-reconnect nil)
@@ -288,7 +289,7 @@ buffer-naming collisions involving bouncers in ERC."
         (erc-d-t-search-for 1 "<bob>")
         (erc-d-t-absent-for 0.1 "<joe>")
         (should (eq erc-server-process erc-server-process-foo))
-        (erc-d-t-search-for 10 "ape is dead")
+        (erc-d-t-search-for 15 "ape is dead")
         (erc-d-t-wait-for 5 (not (erc-server-process-alive)))))
 
     (ert-info ("#chan@<esid> is exclusive to barnet")
diff --git a/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld 
b/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld
new file mode 100644
index 00000000000..a3d533c87b5
--- /dev/null
+++ b/test/lisp/erc/resources/fill/snapshots/merge-wrap-01.eld
@@ -0,0 +1 @@
+#("\n\n\n[Thu Jan  1 1970]\n*** This server is in debug mode and is logging 
all user I/O. If you do not wish for everything you send to be readable by the 
server owner(s), please disconnect.[00:00]\n<alice> bob: come, you are a 
tedious fool: to the purpose. What was done to Elbow's wife, that he hath cause 
to complain of? Come me to what was done to her.\n<bob> alice: Either your 
unparagoned mistress is dead, or she's outprized by a trifle.\n\n[Sat Apr  1 
2023]\n<bob> zero.[07:00]\n* bob [...]
\ No newline at end of file
diff --git a/test/lisp/erc/resources/fill/snapshots/stamps-left-01.eld 
b/test/lisp/erc/resources/fill/snapshots/stamps-left-01.eld
new file mode 100644
index 00000000000..f62b65cd170
--- /dev/null
+++ b/test/lisp/erc/resources/fill/snapshots/stamps-left-01.eld
@@ -0,0 +1 @@
+#("\n\n[00:00]*** This server is in debug mode and is logging all user I/O. If 
you do not wish for everything you send to be readable by the server owner(s), 
please disconnect.\n[00:00]<alice> bob: come, you are a tedious fool: to the 
purpose. What was done to Elbow's wife, that he hath cause to complain of? Come 
me to what was done to her.\n[00:00]<bob> alice: Either your unparagoned 
mistress is dead, or she's outprized by a trifle.\n" 2 9 (erc-timestamp 0 
display (#4=(margin left-margi [...]
\ No newline at end of file
diff --git a/test/lisp/erc/resources/services/auth-source/libera.eld 
b/test/lisp/erc/resources/services/auth-source/libera.eld
index c8dbc9d425a..dfc25221508 100644
--- a/test/lisp/erc/resources/services/auth-source/libera.eld
+++ b/test/lisp/erc/resources/services/auth-source/libera.eld
@@ -1,6 +1,6 @@
 ;; -*- mode: lisp-data; -*-
-((nick 1 "NICK tester"))
-((user 1 "USER user 0 * :tester")
+((nick 10 "NICK tester"))
+((user 5 "USER user 0 * :tester")
  (0.26 ":zirconium.libera.chat NOTICE * :*** Checking Ident")
  (0.01 ":zirconium.libera.chat NOTICE * :*** Looking up your hostname...")
  (0.01 ":zirconium.libera.chat NOTICE * :*** No Ident response")
@@ -35,15 +35,15 @@
  (0.01 ":zirconium.libera.chat 372 tester :- Email:                      
support@libera.chat")
  (0.00 ":zirconium.libera.chat 376 tester :End of /MOTD command."))
 
-((mode-user 1.2 "MODE tester +i")
+((mode-user 10 "MODE tester +i")
  (0.02 ":tester MODE tester :+Zi")
  (0.02 ":NickServ!NickServ@services.libera.chat NOTICE tester :This nickname 
is registered. Please choose a different nickname, or identify via \2/msg 
NickServ IDENTIFY tester <password>\2"))
 
-((privmsg 2 "PRIVMSG NickServ :IDENTIFY changeme")
+((privmsg 10 "PRIVMSG NickServ :IDENTIFY changeme")
  (0.96 ":NickServ!NickServ@services.libera.chat NOTICE tester :You are now 
identified for \2tester\2.")
  (0.25 ":NickServ!NickServ@services.libera.chat NOTICE tester :Last login 
from: \2~tester@school.edu/tester\2 on Jun 18 01:15:56 2021 +0000."))
 
-((quit 5 "QUIT :\2ERC\2")
+((quit 10 "QUIT :\2ERC\2")
  (0.19 ":tester!~user@static-198-54-131-100.cust.tzulo.com QUIT :Client Quit"))
 
 ((linger 1 LINGER))
diff --git a/test/lisp/eshell/em-dirs-tests.el 
b/test/lisp/eshell/em-dirs-tests.el
index d30b3d7d73f..9864b72ba78 100644
--- a/test/lisp/eshell/em-dirs-tests.el
+++ b/test/lisp/eshell/em-dirs-tests.el
@@ -99,4 +99,27 @@
      (eshell-match-command-output "echo $-[1][/ 1 3]"
                                   "(\"some\" \"here\")\n"))))
 
+(ert-deftest em-dirs-test/cd ()
+  "Test that changing directories with `cd' works."
+  (ert-with-temp-directory tmpdir
+    (write-region "text" nil (expand-file-name "file.txt" tmpdir))
+    (with-temp-eshell
+     (eshell-match-command-output (format "cd '%s'" tmpdir)
+                                  "\\`\\'")
+     (should (equal default-directory tmpdir)))))
+
+(ert-deftest em-dirs-test/cd/list-files-after-cd ()
+  "Test that listing files after `cd' works."
+  (let ((eshell-list-files-after-cd t))
+    (ert-with-temp-directory tmpdir
+      (write-region "text" nil (expand-file-name "file.txt" tmpdir))
+      (with-temp-eshell
+       (eshell-match-command-output (format "cd '%s'" tmpdir)
+                                    "file.txt\n")
+       (should (equal default-directory tmpdir))
+       ;; Make sure we didn't update the last-command information when
+       ;; running "ls".
+       (should (equal eshell-last-command-name "#<function eshell/cd>"))
+       (should (equal eshell-last-arguments (list tmpdir)))))))
+
 ;; em-dirs-tests.el ends here
diff --git a/test/lisp/eshell/em-extpipe-tests.el 
b/test/lisp/eshell/em-extpipe-tests.el
index 1184b5df5f8..bdffcd9b320 100644
--- a/test/lisp/eshell/em-extpipe-tests.el
+++ b/test/lisp/eshell/em-extpipe-tests.el
@@ -48,26 +48,29 @@
             ;; buffer into `input'.  The substitution logic is
             ;; appropriate for only the use we put it to in this file.
             `(ert-with-temp-file temp
-               (let ((temp-buffer (generate-new-buffer " *temp*" t)))
+               (let ((temp-buffer (generate-new-buffer " *tmp*" t)))
                  (unwind-protect
                      (let ((input
                             (replace-regexp-in-string
                              "temp\\([^>]\\|\\'\\)" temp
-                             (string-replace "#<buffer temp>"
-                                             (buffer-name temp-buffer)
-                                             input))))
+                             (string-replace
+                              "#<buffer temp>"
+                              (concat "#<buffer " (buffer-name temp-buffer) 
">")
+                              input))))
                        ,@body)
                    (when (buffer-name temp-buffer)
                      (kill-buffer temp-buffer))))))
           (temp-should-string= (expected)
-            `(string= ,expected (string-trim-right
-                                 (with-temp-buffer
-                                   (insert-file-contents temp)
-                                   (buffer-string)))))
+            `(should (string= ,expected
+                              (string-trim-right
+                               (with-temp-buffer
+                                 (insert-file-contents temp)
+                                 (buffer-string))))))
           (temp-buffer-should-string= (expected)
-            `(string= ,expected (string-trim-right
-                                 (with-current-buffer temp-buffer
-                                   (buffer-string))))))
+            `(should (string= ,expected
+                              (string-trim-right
+                               (with-current-buffer temp-buffer
+                                 (buffer-string)))))))
        (skip-unless shell-file-name)
        (skip-unless shell-command-switch)
        (skip-unless (executable-find shell-file-name))
diff --git a/test/lisp/eshell/em-glob-tests.el 
b/test/lisp/eshell/em-glob-tests.el
index c33af88a374..6e07225657c 100644
--- a/test/lisp/eshell/em-glob-tests.el
+++ b/test/lisp/eshell/em-glob-tests.el
@@ -26,6 +26,13 @@
 (require 'ert)
 (require 'em-glob)
 
+(require 'eshell-tests-helpers
+         (expand-file-name "eshell-tests-helpers"
+                           (file-name-directory (or load-file-name
+                                                    default-directory))))
+
+(defvar eshell-prefer-lisp-functions)
+
 (defmacro with-fake-files (files &rest body)
   "Evaluate BODY forms, pretending that FILES exist on the filesystem.
 FILES is a list of file names that should be reported as
@@ -54,6 +61,60 @@ component ending in \"symlink\" is treated as a symbolic 
link."
 
 ;;; Tests:
 
+(ert-deftest em-glob-test/expand/splice-results ()
+  "Test that globs are spliced into the argument list when
+`eshell-glob-splice-results' is non-nil."
+  (let ((eshell-prefer-lisp-functions t)
+        (eshell-glob-splice-results t))
+    (with-fake-files '("a.el" "b.el" "c.txt")
+      ;; Ensure the default expansion splices the glob.
+      (eshell-command-result-equal "list *.el" '("a.el" "b.el"))
+      (eshell-command-result-equal "list *.txt" '("c.txt"))
+      (eshell-command-result-equal "list *.no" '("*.no")))))
+
+(ert-deftest em-glob-test/expand/no-splice-results ()
+  "Test that globs are treated as lists when
+`eshell-glob-splice-results' is nil."
+  (let ((eshell-prefer-lisp-functions t)
+        (eshell-glob-splice-results nil))
+    (with-fake-files '("a.el" "b.el" "c.txt")
+      ;; Ensure the default expansion splices the glob.
+      (eshell-command-result-equal "list *.el" '(("a.el" "b.el")))
+      (eshell-command-result-equal "list *.txt" '(("c.txt")))
+      ;; The no-matches case is special here: the glob is just the
+      ;; string, not the list of results.
+      (eshell-command-result-equal "list *.no" '("*.no")))))
+
+(ert-deftest em-glob-test/expand/explicitly-splice-results ()
+  "Test explicitly splicing globs works the same no matter the
+value of `eshell-glob-splice-results'."
+  (let ((eshell-prefer-lisp-functions t))
+    (dolist (eshell-glob-splice-results '(nil t))
+      (ert-info ((format "eshell-glob-splice-results: %s"
+                         eshell-glob-splice-results))
+        (with-fake-files '("a.el" "b.el" "c.txt")
+          (eshell-command-result-equal "list $@{listify *.el}"
+                                       '("a.el" "b.el"))
+          (eshell-command-result-equal "list $@{listify *.txt}"
+                                       '("c.txt"))
+          (eshell-command-result-equal "list $@{listify *.no}"
+                                       '("*.no")))))))
+
+(ert-deftest em-glob-test/expand/explicitly-listify-results ()
+  "Test explicitly listifying globs works the same no matter the
+value of `eshell-glob-splice-results'."
+  (let ((eshell-prefer-lisp-functions t))
+    (dolist (eshell-glob-splice-results '(nil t))
+      (ert-info ((format "eshell-glob-splice-results: %s"
+                         eshell-glob-splice-results))
+        (with-fake-files '("a.el" "b.el" "c.txt")
+          (eshell-command-result-equal "list ${listify *.el}"
+                                       '(("a.el" "b.el")))
+          (eshell-command-result-equal "list ${listify *.txt}"
+                                       '(("c.txt")))
+          (eshell-command-result-equal "list ${listify *.no}"
+                                       '(("*.no"))))))))
+
 (ert-deftest em-glob-test/match-any-string ()
   "Test that \"*\" pattern matches any string."
   (with-fake-files '("a.el" "b.el" "c.txt" "dir/a.el")
@@ -191,6 +252,9 @@ component ending in \"symlink\" is treated as a symbolic 
link."
   (with-fake-files '("foo.el" "bar.el")
     (should (equal (eshell-extended-glob "*.txt")
                    "*.txt"))
+    (let ((eshell-glob-splice-results t))
+      (should (equal (eshell-extended-glob "*.txt")
+                     '("*.txt"))))
     (let ((eshell-error-if-no-glob t))
       (should-error (eshell-extended-glob "*.txt")))))
 
diff --git a/test/lisp/eshell/em-hist-tests.el 
b/test/lisp/eshell/em-hist-tests.el
index 35ae6bdc239..0f143355115 100644
--- a/test/lisp/eshell/em-hist-tests.el
+++ b/test/lisp/eshell/em-hist-tests.el
@@ -22,8 +22,16 @@
 (require 'ert)
 (require 'ert-x)
 (require 'em-hist)
+(require 'eshell)
 
-(ert-deftest eshell-write-readonly-history ()
+(require 'eshell-tests-helpers
+         (expand-file-name "eshell-tests-helpers"
+                           (file-name-directory (or load-file-name
+                                                    default-directory))))
+
+;;; Tests:
+
+(ert-deftest em-hist-test/write-readonly-history ()
   "Test that having read-only strings in history is okay."
   (ert-with-temp-file histfile
     (let ((eshell-history-ring (make-ring 2)))
@@ -33,6 +41,39 @@
                    (propertize "echo bar" 'read-only t))
       (eshell-write-history histfile))))
 
+(ert-deftest em-hist-test/add-to-history/allow-dups ()
+  "Test adding to history, allowing dups."
+  (let ((eshell-hist-ignoredups nil))
+    (with-temp-eshell
+     (eshell-insert-command "echo hi")
+     (eshell-insert-command "echo bye")
+     (eshell-insert-command "echo bye")
+     (eshell-insert-command "echo hi")
+     (should (equal (ring-elements eshell-history-ring)
+                    '("echo hi" "echo bye" "echo bye" "echo hi"))))))
+
+(ert-deftest em-hist-test/add-to-history/no-consecutive-dups ()
+  "Test adding to history, ignoring consecutive dups."
+  (let ((eshell-hist-ignoredups t))
+    (with-temp-eshell
+     (eshell-insert-command "echo hi")
+     (eshell-insert-command "echo bye")
+     (eshell-insert-command "echo bye")
+     (eshell-insert-command "echo hi")
+     (should (equal (ring-elements eshell-history-ring)
+                    '("echo hi" "echo bye" "echo hi"))))))
+
+(ert-deftest em-hist-test/add-to-history/erase-dups ()
+  "Test adding to history, erasing any old dups."
+  (let ((eshell-hist-ignoredups 'erase))
+    (with-temp-eshell
+     (eshell-insert-command "echo hi")
+     (eshell-insert-command "echo bye")
+     (eshell-insert-command "echo bye")
+     (eshell-insert-command "echo hi")
+     (should (equal (ring-elements eshell-history-ring)
+                    '("echo hi" "echo bye"))))))
+
 (provide 'em-hist-test)
 
 ;;; em-hist-tests.el ends here
diff --git a/test/lisp/eshell/em-unix-tests.el 
b/test/lisp/eshell/em-unix-tests.el
new file mode 100644
index 00000000000..d7b6c55fe45
--- /dev/null
+++ b/test/lisp/eshell/em-unix-tests.el
@@ -0,0 +1,68 @@
+;;; em-unix-tests.el --- em-unix test suite  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for Eshell's implementation of various UNIX commands.
+
+;;; Code:
+
+(require 'ert)
+(require 'em-unix)
+
+(require 'eshell-tests-helpers
+         (expand-file-name "eshell-tests-helpers"
+                           (file-name-directory (or load-file-name
+                                                    default-directory))))
+
+;;; Tests:
+
+(ert-deftest em-unix-test/compile/interactive ()
+  "Check that `eshell/compile' opens a compilation buffer interactively."
+  (skip-unless (executable-find "echo"))
+  (with-temp-eshell
+   (eshell-match-command-output "compile echo hello"
+                                "#<buffer \\*compilation\\*>")
+   (with-current-buffer "*compilation*"
+     (forward-line 3)
+     (should (looking-at "echo hello")))))
+
+(ert-deftest em-unix-test/compile/noninteractive ()
+  "Check that `eshell/compile' writes to stdout noninteractively."
+  (skip-unless (executable-find "echo"))
+  (eshell-command-result-equal "compile echo hello"
+                               "hello\n"))
+
+(ert-deftest em-unix-test/compile/pipeline ()
+  "Check that `eshell/compile' writes to stdout from a pipeline."
+  (skip-unless (and (executable-find "echo")
+                    (executable-find "cat")))
+  (with-temp-eshell
+   (eshell-match-command-output "compile echo hello | *cat"
+                                "\\`hello\n")))
+
+(ert-deftest em-unix-test/compile/subcommand ()
+  "Check that `eshell/compile' writes to stdout from a subcommand."
+  (skip-unless (and (executable-find "echo")
+                    (executable-find "cat")))
+  (with-temp-eshell
+   (eshell-match-command-output "echo ${compile echo hello}"
+                                "\\`hello\n")))
+
+;; em-unix-tests.el ends here
diff --git a/test/lisp/eshell/esh-io-tests.el b/test/lisp/eshell/esh-io-tests.el
index ed350a9691c..ce80f3a8f08 100644
--- a/test/lisp/eshell/esh-io-tests.el
+++ b/test/lisp/eshell/esh-io-tests.el
@@ -31,6 +31,9 @@
 
 (defvar eshell-test-value nil)
 
+(defvar eshell-test-value-with-fun nil)
+(defun eshell-test-value-with-fun ())
+
 (defun eshell-test-file-string (file)
   "Return the contents of FILE as a string."
   (with-temp-buffer
@@ -117,6 +120,13 @@
      (eshell-insert-command "echo new >> #'eshell-test-value"))
     (should (equal eshell-test-value "oldnew"))))
 
+(ert-deftest esh-io-test/redirect-symbol/with-function-slot ()
+  "Check that redirecting to a symbol with function slot set works."
+  (let ((eshell-test-value-with-fun))
+    (with-temp-eshell
+     (eshell-insert-command "echo hi > #'eshell-test-value-with-fun"))
+    (should (equal eshell-test-value-with-fun "hi"))))
+
 (ert-deftest esh-io-test/redirect-marker ()
   "Check that redirecting to a marker works."
   (with-temp-buffer
diff --git a/test/lisp/eshell/esh-util-tests.el 
b/test/lisp/eshell/esh-util-tests.el
index 8585677e14e..fe4eb9f31dd 100644
--- a/test/lisp/eshell/esh-util-tests.el
+++ b/test/lisp/eshell/esh-util-tests.el
@@ -125,4 +125,35 @@
     (should (equal (eshell-convert-to-number "123") "123"))
     (should (equal (eshell-convert-to-number "1.23") "1.23"))))
 
+(ert-deftest esh-util-test/eshell-printable-size ()
+  (should (equal (eshell-printable-size (expt 2 16)) "65536"))
+  (should (equal (eshell-printable-size (expt 2 32)) "4294967296")))
+
+(ert-deftest esh-util-test/eshell-printable-size/zero ()
+  (should (equal (eshell-printable-size 0 1000 nil t) "0")))
+
+(ert-deftest esh-util-test/eshell-printable-size/terabyte ()
+  (should (equal (eshell-printable-size (1- (expt 2 40)) 1024 nil t) "1024G"))
+  (should (equal (eshell-printable-size (expt 2 40) 1024 nil t) "1T"))
+  (should (equal (eshell-printable-size (1- (expt 10 12)) 1000 nil t) "1000G"))
+  (should (equal (eshell-printable-size (expt 10 12) 1000 nil t) "1T")))
+
+(ert-deftest esh-util-test/eshell-printable-size/use-colors ()
+  (should (equal-including-properties
+           (eshell-printable-size (1- (expt 2 20)) 1024 nil t)
+           "1024k"))
+  (should (equal-including-properties
+           (eshell-printable-size (1- (expt 2 30)) 1024 nil t)
+           (propertize "1024M" 'face 'bold)))
+  (should (equal-including-properties
+           (eshell-printable-size (1- (expt 2 40)) 1024 nil t)
+           (propertize "1024G" 'face 'bold-italic))))
+
+(ert-deftest esh-util-test/eshell-printable-size/block-size ()
+  (should (equal (eshell-printable-size (1- (expt 2 20)) nil 4096) "256"))
+  (should (equal (eshell-printable-size (1- (expt 2 30)) nil 4096) "262144")))
+
+(ert-deftest esh-util-test/eshell-printable-size/human-readable-arg ()
+  (should-error (eshell-printable-size 0 999 nil t)))
+
 ;;; esh-util-tests.el ends here
diff --git a/test/lisp/eshell/esh-var-tests.el 
b/test/lisp/eshell/esh-var-tests.el
index 3e58fe749dd..ff646f5f977 100644
--- a/test/lisp/eshell/esh-var-tests.el
+++ b/test/lisp/eshell/esh-var-tests.el
@@ -766,6 +766,52 @@ it, since the setter is nil."
    (eshell-match-command-output "echo $INSIDE_EMACS[, 1]"
                                 "eshell")))
 
+(ert-deftest esh-var-test/pager-var/default ()
+  "Test that retrieving the default value of $PAGER works.
+This should be the value of `comint-pager' if non-nil, otherwise
+the value of the $PAGER env var."
+  (let ((comint-pager nil)
+        (process-environment (cons "PAGER=cat" process-environment)))
+    (eshell-command-result-equal "echo $PAGER" "cat")
+    (setq comint-pager "less")
+    (eshell-command-result-equal "echo $PAGER" "less")))
+
+(ert-deftest esh-var-test/pager-var/set ()
+  "Test that setting $PAGER in Eshell overrides the default value."
+  (let ((comint-pager nil)
+        (process-environment (cons "PAGER=cat" process-environment)))
+    (with-temp-eshell
+     (eshell-match-command-output "set PAGER bat" "bat")
+     (eshell-match-command-output "echo $PAGER" "bat"))
+    (setq comint-pager "less")
+    (with-temp-eshell
+     (eshell-match-command-output "set PAGER bat" "bat")
+     (eshell-match-command-output "echo $PAGER" "bat"))))
+
+(ert-deftest esh-var-test/pager-var/unset ()
+  "Test that unsetting $PAGER in Eshell overrides the default value."
+  (let ((comint-pager nil)
+        (process-environment (cons "PAGER=cat" process-environment)))
+    (with-temp-eshell
+     (eshell-insert-command "unset PAGER")
+     (eshell-match-command-output "echo $PAGER" "\\`\\'"))
+    (setq comint-pager "less")
+    (with-temp-eshell
+     (eshell-insert-command "unset PAGER")
+     (eshell-match-command-output "echo $PAGER" "\\`\\'"))))
+
+(ert-deftest esh-var-test/pager-var/set-locally ()
+  "Test setting $PAGER temporarily for a single command."
+  (let ((comint-pager nil)
+        (process-environment (cons "PAGER=cat" process-environment)))
+    (with-temp-eshell
+     (eshell-match-command-output "PAGER=bat env" "PAGER=bat\n")
+     (eshell-match-command-output "echo $PAGER" "cat"))
+    (setq comint-pager "less")
+    (with-temp-eshell
+     (eshell-match-command-output "PAGER=bat env" "PAGER=bat\n")
+     (eshell-match-command-output "echo $PAGER" "less"))))
+
 (ert-deftest esh-var-test/path-var/local-directory ()
   "Test using $PATH in a local directory."
   (let ((expected-path (string-join (eshell-get-path t) (path-separator))))
diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el
index 390f75cfbb9..46c9482ecf4 100644
--- a/test/lisp/eshell/eshell-tests.el
+++ b/test/lisp/eshell/eshell-tests.el
@@ -162,16 +162,13 @@ This test uses a pipeline for the command."
   "Test moving across command arguments"
   (with-temp-eshell
    (eshell-insert-command "echo $(+ 1 (- 4 3)) \"alpha beta\" file" 'ignore)
-   (let ((here (point)) begin valid)
+   (let ((end (point)) begin)
      (beginning-of-line)
      (setq begin (point))
      (eshell-forward-argument 4)
-     (setq valid (= here (point)))
+     (should (= end (point)))
      (eshell-backward-argument 4)
-     (prog1
-         (and valid (= begin (point)))
-       (beginning-of-line)
-       (delete-region (point) (point-max))))))
+     (should (= begin (point))))))
 
 (ert-deftest eshell-test/queue-input ()
   "Test queuing command input.
diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el
index f6c7be88b05..0e460009cea 100644
--- a/test/lisp/files-tests.el
+++ b/test/lisp/files-tests.el
@@ -1204,30 +1204,30 @@ unquoted file names."
     (let ((process-environment (cons "FOO=foo" process-environment))
           (nospecial-foo (files-tests--new-name nospecial "$FOO")))
       ;; The "/:" prevents substitution.
-      (equal (substitute-in-file-name nospecial-foo) nospecial-foo)))
+      (should (equal (substitute-in-file-name nospecial-foo) nospecial-foo))))
   (files-tests--with-temp-non-special-and-file-name-handler (tmpfile nospecial)
     (let ((process-environment (cons "FOO=foo" process-environment))
           (nospecial-foo (files-tests--new-name nospecial "$FOO")))
       ;; The "/:" prevents substitution.
-      (equal (substitute-in-file-name nospecial-foo) nospecial-foo))))
+      (should (equal (substitute-in-file-name nospecial-foo) nospecial-foo)))))
 
 (ert-deftest files-tests-file-name-non-special-temporary-file-directory ()
   (files-tests--with-temp-non-special (tmpdir nospecial-dir t)
     (let ((default-directory nospecial-dir))
-      (equal (temporary-file-directory) temporary-file-directory)))
+      (should (equal (temporary-file-directory) temporary-file-directory))))
   (files-tests--with-temp-non-special-and-file-name-handler
       (tmpdir nospecial-dir t)
     (let ((default-directory nospecial-dir))
-      (equal (temporary-file-directory) temporary-file-directory))))
+      (should (equal (temporary-file-directory) temporary-file-directory)))))
 
 (ert-deftest files-tests-file-name-non-special-unhandled-file-name-directory ()
   (files-tests--with-temp-non-special (tmpdir nospecial-dir t)
-    (equal (unhandled-file-name-directory nospecial-dir)
-           (file-name-as-directory tmpdir)))
+    (should (equal (unhandled-file-name-directory nospecial-dir)
+                   (file-name-as-directory tmpdir))))
   (files-tests--with-temp-non-special-and-file-name-handler
       (tmpdir nospecial-dir t)
-    (equal (unhandled-file-name-directory nospecial-dir)
-           (file-name-as-directory tmpdir))))
+    (should-not (equal (unhandled-file-name-directory nospecial-dir)
+                       (file-name-as-directory tmpdir)))))
 
 (ert-deftest files-tests-file-name-non-special-vc-registered ()
   (files-tests--with-temp-non-special (tmpfile nospecial)
diff --git a/test/lisp/help-fns-tests.el b/test/lisp/help-fns-tests.el
index 243a45ae6d2..56c521e765e 100644
--- a/test/lisp/help-fns-tests.el
+++ b/test/lisp/help-fns-tests.el
@@ -132,6 +132,12 @@ Return first line of the output of (describe-function-1 
FUNC)."
 
 
 ;;; Tests for describe-keymap
+
+(defvar-keymap help-fns-test-map
+  "a" 'test-cmd-a
+  "b" 'test-cmd-b
+  "c" 'test-cmd-c)
+
 (ert-deftest help-fns-test-find-keymap-name ()
   (should (equal (help-fns-find-keymap-name lisp-mode-map) 'lisp-mode-map))
   ;; Follow aliasing.
@@ -142,27 +148,32 @@ Return first line of the output of (describe-function-1 
FUNC)."
     (makunbound 'foo-test-map)))
 
 (ert-deftest help-fns-test-describe-keymap/symbol ()
-  (describe-keymap 'minibuffer-local-must-match-map)
+  (describe-keymap 'help-fns-test-map)
   (with-current-buffer "*Help*"
-    (should (looking-at "^minibuffer-local-must-match-map is"))))
+    (should (looking-at "^help-fns-test-map is"))
+    (should (re-search-forward (rx word-start "a" word-end
+                                   (+ blank)
+                                   word-start "test-cmd-a" word-end)
+                               nil t))))
 
 (ert-deftest help-fns-test-describe-keymap/value ()
-  (describe-keymap minibuffer-local-must-match-map)
+  (describe-keymap help-fns-test-map)
   (with-current-buffer "*Help*"
     (should (looking-at "\nKey"))))
 
 (ert-deftest help-fns-test-describe-keymap/not-keymap ()
   (should-error (describe-keymap nil))
-  (should-error (describe-keymap emacs-version)))
+  (should-error (describe-keymap emacs-version))
+  (should-error (describe-keymap 'some-undefined-variable-foobar)))
 
 (ert-deftest help-fns-test-describe-keymap/let-bound ()
-  (let ((foobar minibuffer-local-must-match-map))
+  (let ((foobar help-fns-test-map))
     (describe-keymap foobar)
     (with-current-buffer "*Help*"
       (should (looking-at "\nKey")))))
 
 (ert-deftest help-fns-test-describe-keymap/dynamically-bound-no-file ()
-  (setq help-fns-test--describe-keymap-foo minibuffer-local-must-match-map)
+  (setq help-fns-test--describe-keymap-foo help-fns-test-map)
   (describe-keymap 'help-fns-test--describe-keymap-foo)
   (with-current-buffer "*Help*"
     (should (looking-at "^help-fns-test--describe-keymap-foo is"))))
diff --git a/test/lisp/image/image-dired-util-tests.el 
b/test/lisp/image/image-dired-util-tests.el
index bd3d65bdd3a..1f3747a82b1 100644
--- a/test/lisp/image/image-dired-util-tests.el
+++ b/test/lisp/image/image-dired-util-tests.el
@@ -47,10 +47,11 @@
       (should (equal
                (file-name-directory (image-dired-thumb-name "foo.jpg"))
                (file-name-directory (image-dired-thumb-name "/tmp/foo.jpg"))))
-      (should (equal (file-name-nondirectory
-                      ;; The checksum is based on the file name.
-                      (image-dired-thumb-name "/some/path/foo.jpg"))
-                     "dc4e6f7068157023e7f2e8362d15bdd2e3ca89e4.jpg"))
+      (should
+       (let* ((test-fn "/some/path/foo.jpg")
+              (thumb-fn (image-dired-thumb-name test-fn)))
+         (equal (file-name-nondirectory thumb-fn)
+                (concat (sha1 (expand-file-name test-fn)) ".jpg"))))
       (should (equal (file-name-extension
                       (image-dired-thumb-name "foo.gif"))
                      "jpg")))))
@@ -62,8 +63,12 @@
     (should (equal
              (file-name-nondirectory (image-dired-thumb-name "foo.jpg"))
              (file-name-nondirectory (image-dired-thumb-name "/tmp/foo.jpg"))))
-    (should (equal (file-name-split (image-dired-thumb-name "/tmp/foo.jpg"))
-                   '("" "tmp" ".image-dired" "foo.jpg.thumb.jpg")))
+    ;; The cdr below avoids the system dependency in the car of the
+    ;; list returned by 'file-name-split': it's "" on Posix systems,
+    ;; but the drive letter on MS-Windows.
+    (should (equal (cdr (file-name-split
+                         (image-dired-thumb-name "/tmp/foo.jpg")))
+                   '("tmp" ".image-dired" "foo.jpg.thumb.jpg")))
     (should (equal (file-name-nondirectory
                     (image-dired-thumb-name "foo.jpg"))
                    "foo.jpg.thumb.jpg"))))
diff --git a/test/lisp/minibuffer-tests.el b/test/lisp/minibuffer-tests.el
index a67fc555772..ff58d35eb3e 100644
--- a/test/lisp/minibuffer-tests.el
+++ b/test/lisp/minibuffer-tests.el
@@ -139,7 +139,7 @@
 (defun test-completion-all-sorted-completions (base def history-var 
history-list)
   (with-temp-buffer
     (insert base)
-    (cl-letf (((symbol-function #'minibufferp) (lambda (&rest _) t)))
+    (cl-letf (((symbol-function #'minibufferp) #'always))
       (let ((completion-styles '(basic))
             (completion-category-defaults nil)
             (completion-category-overrides nil)
diff --git a/test/lisp/net/tramp-archive-tests.el 
b/test/lisp/net/tramp-archive-tests.el
index 5485b12f74f..9500ce0efca 100644
--- a/test/lisp/net/tramp-archive-tests.el
+++ b/test/lisp/net/tramp-archive-tests.el
@@ -895,11 +895,16 @@ This tests also `file-executable-p', `file-writable-p' 
and `set-file-modes'."
   (skip-unless (and (fboundp 'file-user-uid)
                     (fboundp 'file-group-gid)))
 
-  (let ((default-directory tramp-archive-test-archive))
-    ;; `file-user-uid' and `file-group-gid' exist since Emacs 30.1.
-    ;; We don't want to see compiler warnings for older Emacsen.
-    (should (integerp (with-no-warnings (file-user-uid))))
-    (should (integerp (with-no-warnings (file-group-gid))))))
+  ;; `file-user-uid' and `file-group-gid' exist since Emacs 30.1.
+  ;; We don't want to see compiler warnings for older Emacsen.
+  (let* ((default-directory tramp-archive-test-archive)
+        (uid (with-no-warnings (file-user-uid)))
+        (gid (with-no-warnings (file-group-gid))))
+    (should (integerp uid))
+    (should (integerp gid))
+    (let ((default-directory tramp-archive-test-file-archive))
+      (should (equal uid (with-no-warnings (file-user-uid))))
+      (should (equal gid (with-no-warnings (file-group-gid)))))))
 
 (ert-deftest tramp-archive-test48-auto-load ()
   "Check that `tramp-archive' autoloads properly."
@@ -988,6 +993,20 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
             code tae tramp-archive-test-file-archive
             (concat tramp-archive-test-archive "foo"))))))))))
 
+(ert-deftest tramp-archive-test49-without-remote-files ()
+  "Check that Tramp can be suppressed."
+  (skip-unless tramp-archive-enabled)
+
+  (should (file-exists-p tramp-archive-test-archive))
+  (should-not (without-remote-files (file-exists-p 
tramp-archive-test-archive)))
+  (should (file-exists-p tramp-archive-test-archive))
+
+  (inhibit-remote-files)
+  (should-not (file-exists-p tramp-archive-test-archive))
+  (tramp-register-file-name-handlers)
+  (setq tramp-mode t)
+  (should (file-exists-p tramp-archive-test-archive)))
+
 (ert-deftest tramp-archive-test99-libarchive-tests ()
   "Run tests of libarchive test files."
   :tags '(:expensive-test :unstable)
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index 16afc0f477a..50687dfe993 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -263,7 +263,6 @@ is greater than 10.
 `should-error' is not handled properly.  BODY shall not contain a timeout."
   (declare (indent 1) (debug (natnump body)))
   `(let* ((tramp-verbose (max (or ,verbose 0) (or tramp-verbose 0)))
-         (trace-buffer (tramp-trace-buffer-name tramp-test-vec))
          (debug-ignored-errors
           (append
            '("^make-symbolic-link not supported$"
@@ -2712,7 +2711,20 @@ This checks also `file-name-as-directory', 
`file-name-directory',
              :type 'file-already-exists)
            (should-error
             (write-region "foo" nil tmp-name nil nil nil 'excl)
-            :type 'file-already-exists))
+            :type 'file-already-exists)
+           (delete-file tmp-name)
+
+           ;; Check `buffer-file-coding-system'.  Bug#65022.
+           (with-temp-buffer
+             (setq buffer-file-name tmp-name)
+             (insert "foo")
+             (set-buffer-file-coding-system 'cp1251)
+             (let ((bfcs buffer-file-coding-system))
+               (should (buffer-modified-p))
+               (should (null (save-buffer)))
+               (should
+                 (eq (coding-system-get buffer-file-coding-system 
:mime-charset)
+                     (coding-system-get bfcs :mime-charset))))))
 
        ;; Cleanup.
        (ignore-errors (delete-file tmp-name))))))
@@ -3502,14 +3514,14 @@ This tests also `file-directory-p' and 
`file-accessible-directory-p'."
                        "tramp-test*" ert-remote-temporary-file-directory)))
              (goto-char (point-min))
              (should
-              (re-search-forward
+              (search-forward-regexp
                (rx
                 (literal
                  (file-relative-name
                   tmp-name1 ert-remote-temporary-file-directory)))))
              (goto-char (point-min))
              (should
-              (re-search-forward
+              (search-forward-regexp
                (rx
                 (literal
                  (file-relative-name
@@ -3524,14 +3536,14 @@ This tests also `file-directory-p' and 
`file-accessible-directory-p'."
                        "tramp-test*/*" ert-remote-temporary-file-directory)))
              (goto-char (point-min))
              (should
-              (re-search-forward
+              (search-forward-regexp
                (rx
                 (literal
                  (file-relative-name
                   tmp-name3 ert-remote-temporary-file-directory)))))
              (goto-char (point-min))
              (should
-              (re-search-forward
+              (search-forward-regexp
                (rx
                 (literal
                  (file-relative-name
@@ -3554,14 +3566,14 @@ This tests also `file-directory-p' and 
`file-accessible-directory-p'."
                        "tramp-test*/*" ert-remote-temporary-file-directory)))
              (goto-char (point-min))
              (should
-              (re-search-forward
+              (search-forward-regexp
                (rx
                 (literal
                  (file-relative-name
                   tmp-name3 ert-remote-temporary-file-directory)))))
              (goto-char (point-min))
              (should
-              (re-search-forward
+              (search-forward-regexp
                (rx
                 (literal
                  (file-relative-name
@@ -4980,10 +4992,10 @@ This tests also `make-symbolic-link', `file-truename' 
and `add-name-to-file'."
                      ;; We must remove leading `default-directory'.
                      (goto-char (point-min))
                      (let ((inhibit-read-only t))
-                       (while (re-search-forward "//" nil 'noerror)
+                       (while (search-forward-regexp "//" nil 'noerror)
                          (delete-region (line-beginning-position) (point))))
                      (goto-char (point-min))
-                     (re-search-forward
+                     (search-forward-regexp
                       (rx bol (0+ nonl)
                           (any "Pp") "ossible completions"
                           (0+ nonl) eol))
@@ -5095,7 +5107,8 @@ This tests also `make-symbolic-link', `file-truename' and 
`add-name-to-file'."
                    (if (bufferp destination) destination (current-buffer))
                  ;; "ls" could produce colorized output.
                  (goto-char (point-min))
-                 (while (re-search-forward ansi-color-control-seq-regexp nil t)
+                 (while (search-forward-regexp
+                         ansi-color-control-seq-regexp nil t)
                    (replace-match "" nil nil))
                  (should
                   (string-equal (if destination (format "%s\n" fnnd) "")
@@ -5109,7 +5122,8 @@ This tests also `make-symbolic-link', `file-truename' and 
`add-name-to-file'."
                    (if (bufferp destination) destination (current-buffer))
                  ;; "ls" could produce colorized output.
                  (goto-char (point-min))
-                 (while (re-search-forward ansi-color-control-seq-regexp nil t)
+                 (while (search-forward-regexp
+                         ansi-color-control-seq-regexp nil t)
                    (replace-match "" nil nil))
                  (should
                   (string-equal
@@ -5369,7 +5383,7 @@ If UNSTABLE is non-nil, the test is tagged as 
`:unstable'."
     (let ((default-directory ert-remote-temporary-file-directory)
          (tmp-name (tramp--test-make-temp-name nil quoted))
          kill-buffer-query-functions command proc)
-      (should-not (make-process))
+      (should-not (apply #'make-process nil)) ; Use `apply' to avoid warnings.
 
       ;; Simple process.
       (unwind-protect
@@ -5823,7 +5837,7 @@ INPUT, if non-nil, is a string sent to the process."
               (current-buffer))
              ;; "ls" could produce colorized output.
              (goto-char (point-min))
-             (while (re-search-forward ansi-color-control-seq-regexp nil t)
+             (while (search-forward-regexp ansi-color-control-seq-regexp nil t)
                (replace-match "" nil nil))
              (should
               (string-equal
@@ -7082,6 +7096,12 @@ This does not support external Emacs calls."
   (string-equal
    "mock" (file-remote-p ert-remote-temporary-file-directory 'method)))
 
+(defun tramp--test-openbsd-p ()
+  "Check, whether the remote host runs OpenBSD."
+  ;; We must refill the cache.  `file-truename' does it.
+  (file-truename ert-remote-temporary-file-directory)
+  (ignore-errors (tramp-check-remote-uname tramp-test-vec "OpenBSD")))
+
 (defun tramp--test-out-of-band-p ()
   "Check, whether an out-of-band method is used."
   (tramp-method-out-of-band-p tramp-test-vec 1))
@@ -7374,7 +7394,7 @@ This requires restrictions of file name syntax."
                    (should (zerop (process-file "printenv" nil t nil)))
                    (goto-char (point-min))
                    (should
-                    (re-search-forward
+                    (search-forward-regexp
                      (rx
                       bol (literal envvar)
                       "=" (literal (getenv envvar)) eol))))))))
@@ -7391,6 +7411,7 @@ This requires restrictions of file name syntax."
   (skip-unless (not (getenv "EMACS_HYDRA_CI"))) ; SLOW ~ 245s
   (skip-unless (not (tramp--test-rsync-p)))
   (skip-unless (not (tramp--test-rclone-p)))
+  (skip-unless (not (or (eq system-type 'darwin) (tramp--test-macos-p))))
 
   ;; Newlines, slashes and backslashes in file names are not
   ;; supported.  So we don't test.  And we don't test the tab
@@ -7402,6 +7423,7 @@ This requires restrictions of file name syntax."
          (cond ((or (tramp--test-ange-ftp-p)
                     (tramp--test-container-p)
                     (tramp--test-gvfs-p)
+                    (tramp--test-openbsd-p)
                     (tramp--test-rclone-p)
                     (tramp--test-sudoedit-p)
                     (tramp--test-windows-nt-or-smb-p))
@@ -7465,14 +7487,12 @@ This requires restrictions of file name syntax."
   (skip-unless (not (tramp--test-gdrive-p)))
   (skip-unless (not (tramp--test-crypt-p)))
   (skip-unless (not (tramp--test-rclone-p)))
+  (skip-unless (not (or (eq system-type 'darwin) (tramp--test-macos-p))))
 
-  (let* ((utf8 (if (and (eq system-type 'darwin)
-                       (memq 'utf-8-hfs (coding-system-list)))
-                  'utf-8-hfs 'utf-8))
-        (coding-system-for-read utf8)
-        (coding-system-for-write utf8)
-        (file-name-coding-system
-         (coding-system-change-eol-conversion utf8 'unix)))
+  (let ((coding-system-for-read 'utf-8)
+       (coding-system-for-write 'utf-8)
+       (file-name-coding-system
+        (coding-system-change-eol-conversion 'utf-8 'unix)))
     (apply
      #'tramp--test-check-files
      (append
@@ -7484,7 +7504,8 @@ This requires restrictions of file name syntax."
        "Автостопом по гала́ктике"
        ;; Use codepoints without a name.  See Bug#31272.
        ;; Works on some Android systems only.
-       (unless (tramp--test-adb-p) "™›šbung")
+       (unless (or (tramp--test-adb-p) (tramp--test-openbsd-p))
+        "™›šbung")
        ;; Use codepoints from Supplementary Multilingual Plane (U+10000
        ;; to U+1FFFF).
        "🌈🍒👋")
@@ -7847,7 +7868,7 @@ process sentinels.  They shall not disturb each other."
 
 (ert-deftest tramp-test47-read-password ()
   "Check Tramp password handling."
-  :tags '(:expensive-test)
+  :tags '(:expensive-test :unstable)
   (skip-unless (tramp--test-enabled))
   (skip-unless (tramp--test-mock-p))
   ;; Not all read commands understand argument "-s" or "-p".
@@ -8009,7 +8030,22 @@ process sentinels.  They shall not disturb each other."
        (mapconcat #'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test49-unload ()
+(ert-deftest tramp-test49-without-remote-files ()
+  "Check that Tramp can be suppressed."
+  (skip-unless (tramp--test-enabled))
+
+  (should (file-remote-p ert-remote-temporary-file-directory))
+  (should-not
+   (without-remote-files (file-remote-p ert-remote-temporary-file-directory)))
+  (should (file-remote-p ert-remote-temporary-file-directory))
+
+  (inhibit-remote-files)
+  (should-not (file-remote-p ert-remote-temporary-file-directory))
+  (tramp-register-file-name-handlers)
+  (setq tramp-mode t)
+  (should (file-remote-p ert-remote-temporary-file-directory)))
+
+(ert-deftest tramp-test50-unload ()
   "Check that Tramp and its subpackages unload completely.
 Since it unloads Tramp, it shall be the last test to run."
   :tags '(:expensive-test)
diff --git a/test/lisp/proced-tests.el b/test/lisp/proced-tests.el
index d53f25b00d8..44596f92490 100644
--- a/test/lisp/proced-tests.el
+++ b/test/lisp/proced-tests.el
@@ -1,6 +1,6 @@
 ;;; proced-tests.el --- Test suite for proced.el -*- lexical-binding: t -*-
 
-;; Copyright (C) 2022 Free Software Foundation, Inc.
+;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
 
 ;; This file is part of GNU Emacs.
 
@@ -18,6 +18,7 @@
 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
 ;;; Code:
+
 (require 'ert)
 (require 'proced)
 (require 'thingatpt)
@@ -44,6 +45,17 @@
   "Move to the column under ATTRIBUTE in the current proced buffer."
   (move-to-column (string-match attribute proced-header-line)))
 
+(defun proced--assert-process-valid-pid-refinement (pid)
+  "Fail unless the process at point could be present after a refinment using 
PID."
+  (proced--move-to-column "PID")
+  (let ((pid-equal (string= pid (word-at-point))))
+    (should
+     (or pid-equal
+         ;; Guard against the unlikely event a platform doesn't support PPID
+         (when (string-match "PPID" proced-header-line)
+           (proced--move-to-column "PPID")
+           (string= pid (word-at-point)))))))
+
 (ert-deftest proced-format-test ()
   (dolist (format '(short medium long verbose))
     (proced--within-buffer
@@ -75,22 +87,21 @@
 (ert-deftest proced-refine-test ()
   ;;(skip-unless (memq system-type '(gnu/linux gnu/kfreebsd darwin)))
   (proced--within-buffer
-   'medium
+   'verbose
    'user
    ;; When refining on PID for process A, a process is kept if and only
-   ;; if its PID are the same as process A, which more or less guarentees
-   ;; the refinement will remove some processes.
+   ;; if its PID is the same as process A, or its parent process is
+   ;; process A.
    (proced--move-to-column "PID")
    (let ((pid (word-at-point)))
      (proced-refine)
      (while (not (eobp))
-       (proced--move-to-column "PID")
-       (should (string= pid (word-at-point)))
+       (proced--assert-process-valid-pid-refinement pid)
        (forward-line)))))
 
 (ert-deftest proced-refine-with-update-test ()
   (proced--within-buffer
-   'medium
+   'verbose
    'user
    (proced--move-to-column "PID")
    (let ((pid (word-at-point)))
@@ -101,8 +112,7 @@
      ;; processes again, causing the test to fail.
      (proced-update)
      (while (not (eobp))
-       (proced--move-to-column "PID")
-       (should (string= pid (word-at-point)))
+       (proced--assert-process-valid-pid-refinement pid)
        (forward-line)))))
 
 (ert-deftest proced-update-preserves-pid-at-point-test ()
diff --git a/test/lisp/progmodes/cperl-mode-resources/sub-names.pl 
b/test/lisp/progmodes/cperl-mode-resources/sub-names.pl
new file mode 100644
index 00000000000..46d05b4dbd2
--- /dev/null
+++ b/test/lisp/progmodes/cperl-mode-resources/sub-names.pl
@@ -0,0 +1,25 @@
+use 5.038;
+use feature 'class';
+use warnings;
+no warnings 'experimental';
+
+class C {
+    # "method" is not yet understood by perl-mode, but it isn't
+    # relevant here: We can use "sub" because what matters is the
+    # name, which collides with a builtin.
+    sub m {
+       "m called"
+    }
+}
+
+say C->new->m;
+
+# This comment has a method name in it, and we don't want "method"
+# to be fontified as a keyword, nor "name" fontified as a name.
+
+__END__
+
+=head1 Test using the keywords POD
+
+This piece of POD has a method name in it, and we don't want "method"
+to be fontified as a keyword, nor "name" fontified as a name.
diff --git a/test/lisp/progmodes/cperl-mode-tests.el 
b/test/lisp/progmodes/cperl-mode-tests.el
index eaf228cb2e2..8f334245c64 100644
--- a/test/lisp/progmodes/cperl-mode-tests.el
+++ b/test/lisp/progmodes/cperl-mode-tests.el
@@ -256,6 +256,39 @@ These can occur as \"local\" aliases."
     (should (equal (get-text-property (point) 'face)
                    'font-lock-variable-name-face))))
 
+(ert-deftest cperl-test-fontify-sub-names ()
+    "Test fontification of subroutines named like builtins.
+On declaration, they should look like other used defined
+functions.  When called, they should not be fontified.  In
+comments and POD they should be fontified as POD."
+  (let ((file (ert-resource-file "sub-names.pl")))
+    (with-temp-buffer
+      (insert-file-contents file)
+      (goto-char (point-min))
+      (funcall cperl-test-mode)
+      (font-lock-ensure)
+      ;; The declaration
+      (search-forward-regexp "sub \\(m\\)")
+      (should (equal (get-text-property (match-beginning 1) 'face)
+                     'font-lock-function-name-face))
+      ;; calling as a method
+      (search-forward-regexp "C->new->\\(m\\)")
+      (should (equal (get-text-property (match-beginning 1) 'face)
+                     (if (equal cperl-test-mode 'perl-mode) nil
+                       'cperl-method-call)))
+      ;; POD
+      (search-forward-regexp "\\(method\\) \\(name\\)")
+      (should (equal (get-text-property (match-beginning 1) 'face)
+                     'font-lock-comment-face))
+      (should (equal (get-text-property (match-beginning 2) 'face)
+                     'font-lock-comment-face))
+      ;; comment
+      (search-forward-regexp "\\(method\\) \\(name\\)")
+      (should (equal (get-text-property (match-beginning 1) 'face)
+                     'font-lock-comment-face))
+      (should (equal (get-text-property (match-beginning 2) 'face)
+                     'font-lock-comment-face)))))
+
 (ert-deftest cperl-test-identify-heredoc ()
   "Test whether a construct containing \"<<\" followed by a
   bareword is properly identified for a here-document if
diff --git a/test/lisp/progmodes/python-tests.el 
b/test/lisp/progmodes/python-tests.el
index 54e32cbf07b..9f935f2748c 100644
--- a/test/lisp/progmodes/python-tests.el
+++ b/test/lisp/progmodes/python-tests.el
@@ -5973,9 +5973,9 @@ def func():
         else
 "
    (python-tests-look-at "else\n")
-    (should
-     (equal (list (python-tests-look-at "if (" -1 t))
-            (python-info-dedenter-opening-block-positions)))))
+   (should
+    (equal (list (python-tests-look-at "if (" -1 t))
+           (python-info-dedenter-opening-block-positions)))))
 
 (ert-deftest python-info-dedenter-opening-block-positions-7 ()
   "Test case blocks."
@@ -5993,9 +5993,9 @@ match a:
    (python-tests-look-at "case 2:")
    (should-not (python-info-dedenter-opening-block-positions))
    (python-tests-look-at "case 3:")
-   (equal (list (python-tests-look-at "case 2:" -1)
-                (python-tests-look-at "case 1:" -1 t))
-            (python-info-dedenter-opening-block-positions))))
+   (should (equal (list (python-tests-look-at "case 2:" -1 t)
+                        (python-tests-look-at "case 1:" -1 t))
+                  (python-info-dedenter-opening-block-positions)))))
 
 (ert-deftest python-info-dedenter-opening-block-message-1 ()
   "Test dedenters inside strings are ignored."
diff --git a/test/lisp/server-tests.el b/test/lisp/server-tests.el
index ffafa74925f..de1aa80c272 100644
--- a/test/lisp/server-tests.el
+++ b/test/lisp/server-tests.el
@@ -25,12 +25,18 @@
 
 (defconst server-tests/can-create-frames-p
   (and (not (memq system-type '(windows-nt ms-dos)))
-       (not (member (getenv "TERM") '("dumb" "" nil))))
+       (not (member (getenv "TERM") '("dumb" "" nil)))
+       (or (not (eq system-type 'cygwin))
+           (featurep 'gfilenotify)
+           (featurep 'dbus)
+           (featurep 'threads)))
   "Non-nil if we can create a new frame in the tests.
 Some tests below need to create new frames for the emacsclient.
 However, this doesn't work on all platforms.  In particular,
-MS-Windows fails to create frames from a batch Emacs session.  In
-cases like that, we just skip the test.")
+MS-Windows fails to create frames from a batch Emacs session.
+The same is true on Cygwin unless Emacs has at least one of the
+features gfilenotify, dbus, or threads (bug#65325).  In cases
+like that, we just skip the test.")
 
 (defconst server-tests/max-wait-time 5
   "The maximum time to wait in `server-tests/wait-until', in seconds.")
diff --git a/test/lisp/textmodes/conf-mode-tests.el 
b/test/lisp/textmodes/conf-mode-tests.el
index 5e21d2cfacb..677a6d35d66 100644
--- a/test/lisp/textmodes/conf-mode-tests.el
+++ b/test/lisp/textmodes/conf-mode-tests.el
@@ -93,12 +93,13 @@ x.2.y.1.z.2.zz =")
       (should (equal (face-at-point) 'font-lock-variable-name-face))
       (search-forward "val")
       (should-not (face-at-point)))
-    (while (re-search-forward "a-z" nil t)
+    (while (re-search-forward "[xyz]" nil t)
       (backward-char)
       (should (equal (face-at-point) 'font-lock-variable-name-face))
-      (re-search-forward "[0-0]" nil t)
-      (backward-char)
-      (should (equal (face-at-point) 'font-lock-constant-face)))))
+      (forward-char)
+      (when (re-search-forward "[0-9]" nil t)
+        (backward-char)
+        (should (equal (face-at-point) 'font-lock-constant-face))))))
 
 (ert-deftest conf-test-space-mode ()
   ;; From `conf-space-mode' docstring.
@@ -157,7 +158,6 @@ image/tiff                  tiff tif
     (should-not (face-at-point))))
 
 (ert-deftest conf-test-toml-mode ()
-  ;; From `conf-toml-mode' docstring.
   (with-temp-buffer
     (insert "[entry]
 value = \"some string\"")
@@ -173,6 +173,22 @@ value = \"some string\"")
     (search-forward "som")
     (should (equal (face-at-point) 'font-lock-string-face))))
 
+(ert-deftest conf-test-toml-mode/boolean ()
+  ;; https://toml.io/en/v1.0.0#boolean
+  (with-temp-buffer
+    (insert "[entry]
+a = true
+b = True")
+    (goto-char (point-min))
+    (conf-toml-mode)
+    (font-lock-mode)
+    (font-lock-ensure)
+    (search-forward "tru")
+    (should (equal (face-at-point) 'font-lock-keyword-face))
+    ;; Do not fontify upper-case "True".
+    (search-forward "Tru")
+    (should (equal (face-at-point) nil))))
+
 (ert-deftest conf-test-desktop-mode ()
   ;; From `conf-desktop-mode' dostring.
   (with-temp-buffer
diff --git a/test/lisp/uniquify-tests.el b/test/lisp/uniquify-tests.el
index abd61fa3504..38510c3bd77 100644
--- a/test/lisp/uniquify-tests.el
+++ b/test/lisp/uniquify-tests.el
@@ -22,6 +22,7 @@
 ;;; Code:
 
 (require 'ert)
+(require 'ert-x)
 
 (ert-deftest uniquify-basic ()
   (let (bufs old-names)
@@ -58,35 +59,35 @@
 
 (ert-deftest uniquify-dirs ()
   "Check strip-common-suffix and trailing-separator-p work together; bug#47132"
-  (let* ((root (make-temp-file "emacs-uniquify-tests" 'dir))
-         (a-path (file-name-concat root "a/x/y/dir"))
-         (b-path (file-name-concat root "b/x/y/dir")))
-    (make-directory a-path 'parents)
-    (make-directory b-path 'parents)
-    (let ((uniquify-buffer-name-style 'forward)
-          (uniquify-strip-common-suffix t)
-          (uniquify-trailing-separator-p nil))
-      (let ((bufs (list (find-file-noselect a-path)
-                       (find-file-noselect b-path))))
-        (should (equal (mapcar #'buffer-name bufs)
-                       '("a/dir" "b/dir")))
-        (mapc #'kill-buffer bufs)))
-    (let ((uniquify-buffer-name-style 'forward)
-          (uniquify-strip-common-suffix nil)
-          (uniquify-trailing-separator-p t))
-      (let ((bufs (list (find-file-noselect a-path)
-                       (find-file-noselect b-path))))
-        (should (equal (mapcar #'buffer-name bufs)
-                       '("a/x/y/dir/" "b/x/y/dir/")))
-        (mapc #'kill-buffer bufs)))
-    (let ((uniquify-buffer-name-style 'forward)
-          (uniquify-strip-common-suffix t)
-          (uniquify-trailing-separator-p t))
-      (let ((bufs (list (find-file-noselect a-path)
-                       (find-file-noselect b-path))))
-        (should (equal (mapcar #'buffer-name bufs)
-                       '("a/dir/" "b/dir/")))
-        (mapc #'kill-buffer bufs)))))
+  (ert-with-temp-directory root
+    (let ((a-path (file-name-concat root "a/x/y/dir"))
+          (b-path (file-name-concat root "b/x/y/dir")))
+      (make-directory a-path 'parents)
+      (make-directory b-path 'parents)
+      (let ((uniquify-buffer-name-style 'forward)
+            (uniquify-strip-common-suffix t)
+            (uniquify-trailing-separator-p nil))
+        (let ((bufs (list (find-file-noselect a-path)
+                          (find-file-noselect b-path))))
+          (should (equal (mapcar #'buffer-name bufs)
+                         '("a/dir" "b/dir")))
+          (mapc #'kill-buffer bufs)))
+      (let ((uniquify-buffer-name-style 'forward)
+            (uniquify-strip-common-suffix nil)
+            (uniquify-trailing-separator-p t))
+        (let ((bufs (list (find-file-noselect a-path)
+                          (find-file-noselect b-path))))
+          (should (equal (mapcar #'buffer-name bufs)
+                         '("a/x/y/dir/" "b/x/y/dir/")))
+          (mapc #'kill-buffer bufs)))
+      (let ((uniquify-buffer-name-style 'forward)
+            (uniquify-strip-common-suffix t)
+            (uniquify-trailing-separator-p t))
+        (let ((bufs (list (find-file-noselect a-path)
+                          (find-file-noselect b-path))))
+          (should (equal (mapcar #'buffer-name bufs)
+                         '("a/dir/" "b/dir/")))
+          (mapc #'kill-buffer bufs))))))
 
 (ert-deftest uniquify-rename-to-dir ()
   "Giving a buffer a name which matches a directory doesn't rename the buffer"
@@ -125,5 +126,25 @@ uniquify-trailing-separator-p is ignored"
     (should (equal (buffer-name) "| foo"))
     (kill-buffer)))
 
+(require 'project)
+(ert-deftest uniquify-project-transform ()
+  "`project-uniquify-dirname-transform' works"
+  (let ((uniquify-dirname-transform #'project-uniquify-dirname-transform)
+        (project-vc-name "foo1/bar")
+        bufs)
+    (save-excursion
+      (let ((default-directory (expand-file-name "test/" source-directory)))
+        (should (file-exists-p "../README"))
+        (push (find-file-noselect "../README") bufs)
+        (push (find-file-noselect "other/README") bufs)
+        (should (equal (mapcar #'buffer-name bufs)
+                       '("README<other>" "README<bar>")))
+        (push (find-file-noselect "foo2/bar/README") bufs)
+        (should (equal (mapcar #'buffer-name bufs)
+                       '("README<foo2/bar>" "README<other>"
+                         "README<foo1/bar>")))
+        (while bufs
+          (kill-buffer (pop bufs)))))))
+
 (provide 'uniquify-tests)
 ;;; uniquify-tests.el ends here
diff --git a/test/lisp/wid-edit-tests.el b/test/lisp/wid-edit-tests.el
index b379c7c91a8..ebfe729bc9a 100644
--- a/test/lisp/wid-edit-tests.el
+++ b/test/lisp/wid-edit-tests.el
@@ -349,4 +349,35 @@ return nil, even with a non-nil bubblep argument."
     (should-not (widget-apply widget :match "someundefinedcolorihope"))
     (should-not (widget-apply widget :match "#11223"))))
 
+(ert-deftest widget-test-alist-default-value-1 ()
+  "Test getting the default value for an alist widget with options."
+  (with-temp-buffer
+    (let ((w (widget-create '(alist :key-type string
+                                    :value-type integer
+                                    :options (("0" (integer)))))))
+      (should (equal '(("0" . 0)) (widget-default-get w))))))
+
+(ert-deftest widget-test-alist-default-value-2 ()
+  "Test getting the default value for an alist widget without :value."
+  (with-temp-buffer
+    (let ((w (widget-create '(alist :key-type string
+                                    :value-type integer))))
+      (should-not (widget-default-get w)))))
+
+(ert-deftest widget-test-alist-default-value-3 ()
+  "Test getting the default value for an alist widget with nil :value."
+  (with-temp-buffer
+    (let ((w (widget-create '(alist :key-type string
+                                    :value-type integer
+                                    :value nil))))
+      (should-not (widget-default-get w)))))
+
+(ert-deftest widget-test-alist-default-value-4 ()
+  "Test getting the default value for an alist widget with non-nil :value."
+  (with-temp-buffer
+    (let ((w (widget-create '(alist :key-type string
+                                    :value-type integer
+                                    :value (("1" . 1) ("2" . 2))))))
+      (should (equal '(("1" . 1) ("2" . 2)) (widget-default-get w))))))
+
 ;;; wid-edit-tests.el ends here
diff --git a/test/manual/image-tests.el b/test/manual/image-tests.el
index a359bb0f91f..eb5bf13e8b0 100644
--- a/test/manual/image-tests.el
+++ b/test/manual/image-tests.el
@@ -31,6 +31,8 @@
 
 ;;; Code:
 
+(require 'ert)
+
 (defmacro image-skip-unless (format &rest condition)
   `(skip-unless (and (and (display-images-p)
                           (image-type-available-p ,format))
@@ -41,9 +43,9 @@
                               source-directory))
     (jpeg . ,(expand-file-name "test/data/image/black.jpg"
                                source-directory))
-    (pbm . ,(find-image '((:file "splash.svg" :type svg))))
+    (svg . ,(find-image '((:file "splash.svg" :type svg))))
     (png . ,(find-image '((:file "splash.png" :type png))))
-    (svg . ,(find-image '((:file "splash.pbm" :type pbm))))
+    (pbm . ,(find-image '((:file "splash.pbm" :type pbm))))
     (tiff . ,(expand-file-name
               "nextstep/GNUstep/Emacs.base/Resources/emacs.tiff"
               source-directory))
@@ -80,6 +82,7 @@
 (image-tests-make-load-image-test 'xpm)
 
 (ert-deftest image-tests-load-image/svg-too-big ()
+  (image-skip-unless svg)
   (with-temp-buffer
     (let* ((max-image-size 0)
            (messages-buffer-name (buffer-name (current-buffer)))
@@ -95,6 +98,7 @@
       (should-not (string-match-p "error parsing" (buffer-string))))))
 
 (ert-deftest image-tests-load-image/svg-invalid ()
+  (image-skip-unless svg)
   (with-temp-buffer
     (let ((messages-buffer-name (buffer-name (current-buffer))))
       (with-temp-buffer
@@ -240,6 +244,8 @@
 
 (ert-deftest image-tests-image-metadata/gif ()
   (image-skip-unless 'gif
+                ;; FIXME: Why is this failing on macOS?
+                (not (eq system-type 'darwin))
                 (not (bound-and-true-p w32-use-native-image-API)))
   (should (memq 'delay
                 (image-metadata
@@ -268,7 +274,9 @@
                (create-image (cdr (assq 'tiff image-tests--images))))))
 
 (ert-deftest image-tests-image-metadata/webp ()
-  (image-skip-unless 'webp)
+  (image-skip-unless 'webp
+                ;; FIXME: Why is this failing on macOS?
+                (not (eq system-type 'darwin)))
   (should (memq 'delay
                 (image-metadata
                  (create-image (cdr (assq 'webp image-tests--images)))))))
diff --git a/test/lisp/eshell/em-hist-tests.el 
b/test/src/comp-resources/comp-test-funcs-dyn2.el
similarity index 51%
copy from test/lisp/eshell/em-hist-tests.el
copy to test/src/comp-resources/comp-test-funcs-dyn2.el
index 35ae6bdc239..3d70489d1ca 100644
--- a/test/lisp/eshell/em-hist-tests.el
+++ b/test/src/comp-resources/comp-test-funcs-dyn2.el
@@ -1,6 +1,8 @@
-;;; em-hist-tests.el --- em-hist test suite  -*- lexical-binding:t -*-
+;;; comp-test-funcs-dyn2.el -*- lexical-binding: nil; no-byte-compile: t; -*-
 
-;; Copyright (C) 2017-2023 Free Software Foundation, Inc.
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Author: Alan Mackenzie <acm@muc.de>
 
 ;; This file is part of GNU Emacs.
 
@@ -17,22 +19,13 @@
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-;;; Code:
-
-(require 'ert)
-(require 'ert-x)
-(require 'em-hist)
+;;; Commentary:
+;; Test the compilation of a function under dynamic binding.
 
-(ert-deftest eshell-write-readonly-history ()
-  "Test that having read-only strings in history is okay."
-  (ert-with-temp-file histfile
-    (let ((eshell-history-ring (make-ring 2)))
-      (ring-insert eshell-history-ring
-                   (propertize "echo foo" 'read-only t))
-      (ring-insert eshell-history-ring
-                   (propertize "echo bar" 'read-only t))
-      (eshell-write-history histfile))))
+;;; Code:
 
-(provide 'em-hist-test)
+(defun comp-tests-result-lambda ()
+  (lambda (bar) (car bar)))
 
-;;; em-hist-tests.el ends here
+(provide 'comp-test-funcs-dyn2)
+;;; comp-test-funcs-dyn2.el ends here.
diff --git a/test/src/comp-tests.el b/test/src/comp-tests.el
index ce7899d9d4c..4444ab61219 100644
--- a/test/src/comp-tests.el
+++ b/test/src/comp-tests.el
@@ -33,7 +33,8 @@
 
 (eval-and-compile
   (defconst comp-test-src (ert-resource-file "comp-test-funcs.el"))
-  (defconst comp-test-dyn-src (ert-resource-file "comp-test-funcs-dyn.el")))
+  (defconst comp-test-dyn-src (ert-resource-file "comp-test-funcs-dyn.el"))
+  (defconst comp-test-dyn-src2 (ert-resource-file "comp-test-funcs-dyn2.el")))
 
 (when (native-comp-available-p)
   (message "Compiling tests...")
@@ -44,6 +45,7 @@
 ;; names used in this file.
 (require 'comp-test-funcs comp-test-src)
 (require 'comp-test-dyn-funcs comp-test-dyn-src) ;Non-standard feature name!
+(require 'comp-test-funcs-dyn2 comp-test-dyn-src2)
 
 (defmacro comp-deftest (name args &rest docstring-and-body)
   "Define a test for the native compiler tagging it as :nativecomp."
@@ -62,6 +64,7 @@
 
 
 
+(defvar native-comp-eln-load-path)
 (ert-deftest comp-tests-bootstrap ()
   "Compile the compiler and load it to compile it-self.
 Check that the resulting binaries do not differ."
@@ -70,9 +73,11 @@ Check that the resulting binaries do not differ."
     :suffix "-comp-stage1.el"
     (ert-with-temp-file comp2-src
       :suffix "-comp-stage2.el"
-      (let* ((byte+native-compile t)     ; FIXME HACK
+      (let* ((byte+native-compile t)
+             (native-compile-target-directory
+              (car (last native-comp-eln-load-path)))
              (comp-src (expand-file-name "../../../lisp/emacs-lisp/comp.el"
-                                     (ert-resource-directory)))
+                                         (ert-resource-directory)))
              ;; Can't use debug symbols.
              (native-comp-debug 0))
         (copy-file comp-src comp1-src t)
@@ -1528,4 +1533,7 @@ folded."
           (equal (comp-mvar-typeset mvar)
                  comp-tests-cond-rw-expected-type))))))))
 
+(comp-deftest comp-tests-result-lambda ()
+  (native-compile 'comp-tests-result-lambda)
+  (should (eq (funcall (comp-tests-result-lambda) '(a . b)) 'a)))
 ;;; comp-tests.el ends here
diff --git a/test/src/data-tests.el b/test/src/data-tests.el
index 680fdd57d71..8167cccdd18 100644
--- a/test/src/data-tests.el
+++ b/test/src/data-tests.el
@@ -768,6 +768,31 @@ comparing the subr with a much slower Lisp implementation."
                          (default-value 'last-coding-system-used))
                    '(no-conversion bug34318)))))
 
+(defvar-local data-tests--bug65209 :default-value)
+
+(ert-deftest data-tests-make-local-bug65209 ()
+  (dolist (sym '(data-tests--bug65209   ;A normal always-local Lisp var.
+                 cursor-in-non-selected-windows)) ;Same but DEFVAR_PER_BUFFER.
+    ;; Note: For vars like `mode-name' that are *really* always buffer-local,
+    ;; this test isn't right because the `cl-progv' only binds the
+    ;; buffer-local value!
+    (let ((default (default-value sym))
+          vli vlo vgi vgo)
+      (with-temp-buffer
+        (cl-progv (list sym) '(:let-bound-value)
+          ;; While `setq' would not make the var buffer-local
+          ;; (because we'd be setq-ing the let-binding instead),
+          ;; `setq-local' definitely should.
+          (set (make-local-variable sym) :buffer-local-value)
+          (setq vgi (with-temp-buffer (symbol-value sym)))
+          (setq vli (symbol-value sym)))
+      (setq vgo (with-temp-buffer (symbol-value sym)))
+      (setq vlo (symbol-value sym)))
+      (should (equal (list vgo vgi vlo vli)
+                     (cons default
+                           '(:let-bound-value
+                             :buffer-local-value :buffer-local-value)))))))
+
 (ert-deftest data-tests-make_symbol_constant ()
   "Can't set variable marked with 'make_symbol_constant'."
   (should-error (setq most-positive-fixnum 1) :type 'setting-constant))
diff --git a/test/src/filelock-tests.el b/test/src/filelock-tests.el
index 1f055cfebc6..c5e77f70bb2 100644
--- a/test/src/filelock-tests.el
+++ b/test/src/filelock-tests.el
@@ -38,8 +38,12 @@ Create a test directory and a buffer whose 
`buffer-file-name' and
 Finally, delete the buffer and the test directory."
   (declare (debug (body)))
   `(ert-with-temp-directory temp-dir
-     (let ((name (concat (file-name-as-directory temp-dir)
-                         "userfile"))
+     (let ((name
+            ;; Use file-truename for when 'temporary-file-directory'
+            ;; is a symlink, to make sure 'buffer-file-name' is set
+            ;; below to a real existing file.
+            (file-truename (concat (file-name-as-directory temp-dir)
+                                   "userfile")))
            (create-lockfiles t))
        (with-temp-buffer
          (setq buffer-file-name name
@@ -184,7 +188,8 @@ the case)."
 
        ;; Just changing the file modification on disk doesn't hurt,
        ;; because file contents in buffer and on disk look equal.
-       (shell-command (format "touch %s" (buffer-file-name)))
+       (shell-command (format "touch %s"
+                              (shell-quote-argument (buffer-file-name))))
        (insert "bar")
        (when cl (filelock-tests--should-be-locked))
 
@@ -198,7 +203,8 @@ the case)."
        ;; Changing the file contents on disk hurts when buffer is
        ;; modified.  There shall be a query, which we answer.
        ;; *Messages* buffer is checked for prompt.
-       (shell-command (format "echo bar >>%s" (buffer-file-name)))
+       (shell-command (format "echo bar >>%s"
+                              (shell-quote-argument (buffer-file-name))))
        (cl-letf (((symbol-function 'read-char-choice)
                   (lambda (prompt &rest _) (message "%s" prompt) ?y)))
          (ert-with-message-capture captured-messages
diff --git a/test/src/keyboard-tests.el b/test/src/keyboard-tests.el
index 3393c1d9018..bbb9c19e2e7 100644
--- a/test/src/keyboard-tests.el
+++ b/test/src/keyboard-tests.el
@@ -23,6 +23,11 @@
 
 (ert-deftest keyboard-unread-command-events ()
   "Test `unread-command-events'."
+  ;; Avoid hang on Cygwin; see bug#65325.
+  (skip-unless (or (not (eq system-type 'cygwin))
+                   (featurep 'gfilenotify)
+                   (featurep 'dbus)
+                   (featurep 'threads)))
   (let ((unread-command-events nil))
     (should (equal (progn (push ?\C-a unread-command-events)
                           (read-event nil nil 1))
diff --git a/test/src/regex-emacs-tests.el b/test/src/regex-emacs-tests.el
index 08a93dbf30e..4e2c0f67a44 100644
--- a/test/src/regex-emacs-tests.el
+++ b/test/src/regex-emacs-tests.el
@@ -949,4 +949,20 @@ This evaluates the TESTS test cases from glibc."
     (should (equal (smatch "a\\=*b" "ab") 0))
     ))
 
+(ert-deftest regex-emacs-syntax-properties ()
+  ;; Verify absence of character class syntax property ghost matching bug.
+  (let ((re "\\s-[[:space:]]")
+        (s (concat "a"
+                (propertize "b" 'syntax-table '(0))  ; whitespace
+                "éz"))
+        (parse-sexp-lookup-properties t))
+    ;; Test matching in a string...
+    (should (equal (string-match re s) nil))
+    ;; ... and in a buffer.
+    (should (equal (with-temp-buffer
+                     (insert s)
+                     (goto-char (point-min))
+                     (re-search-forward re nil t))
+                   nil))))
+
 ;;; regex-emacs-tests.el ends here
diff --git a/test/src/syntax-tests.el b/test/src/syntax-tests.el
index 0ad3667c060..9e4740c17cc 100644
--- a/test/src/syntax-tests.el
+++ b/test/src/syntax-tests.el
@@ -518,7 +518,6 @@ the `parse-partial-sexp's are expected to stop.  See
         (modify-syntax-entry (unibyte-char-to-multibyte 128) "_" st)
         (set-syntax-table st)
         (should (equal (eval '(char-syntax 128) t) ?_))
-        (should (equal (funcall cs 128) ?_))))
-    (list (char-syntax 128) (funcall cs 128))))
+        (should (equal (funcall cs 128) ?_))))))
 
 ;;; syntax-tests.el ends here
diff --git a/test/src/treesit-tests.el b/test/src/treesit-tests.el
index 34540b463cd..65994ce608f 100644
--- a/test/src/treesit-tests.el
+++ b/test/src/treesit-tests.el
@@ -34,6 +34,7 @@
 (declare-function treesit-parser-buffer "treesit.c")
 (declare-function treesit-parser-language "treesit.c")
 
+(declare-function treesit-pattern-expand "treesit.c")
 (declare-function treesit-query-expand "treesit.c")
 (declare-function treesit-query-compile "treesit.c")
 (declare-function treesit-query-capture "treesit.c")



reply via email to

[Prev in Thread] Current Thread [Next in Thread]