emacs-diffs
[Top][All Lists]
Advanced

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

feature/android 2b87ab7b27 3/7: Update Android port


From: Po Lu
Subject: feature/android 2b87ab7b27 3/7: Update Android port
Date: Sat, 14 Jan 2023 09:48:54 -0500 (EST)

branch: feature/android
commit 2b87ab7b27163fbd7b6b64c5a44e26b0e692c00a
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Update Android port
    
    * 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 (EmacsContextMenu):
    New class.
    * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix
    bounds computation.
    * java/org/gnu/emacs/EmacsGC.java (markDirty): Set stroke width
    explicitly.
    * java/org/gnu/emacs/EmacsService.java (EmacsService)
    (getLocationOnScreen, nameKeysym): New functions.
    * java/org/gnu/emacs/EmacsView.java (EmacsView): Disable focus
    highlight.
    (onCreateContextMenu, popupMenu, cancelPopupMenu): New
    functions.
    * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): 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.
---
 java/Makefile.in                           |   2 +-
 java/debug.sh                              | 110 +++--
 java/org/gnu/emacs/EmacsActivity.java      |   2 +-
 java/org/gnu/emacs/EmacsContextMenu.java   | 213 +++++++++
 java/org/gnu/emacs/EmacsDrawRectangle.java |   2 +-
 java/org/gnu/emacs/EmacsGC.java            |   1 +
 java/org/gnu/emacs/EmacsService.java       |  43 +-
 java/org/gnu/emacs/EmacsView.java          |  47 ++
 java/org/gnu/emacs/EmacsWindow.java        | 161 ++++++-
 src/android.c                              | 169 ++++++-
 src/android.h                              |   6 +
 src/androidfns.c                           | 677 ++++++++++++++++++++++++++++-
 src/androidgui.h                           |  14 +-
 src/androidmenu.c                          | 293 +++++++++++++
 src/androidterm.c                          |  34 +-
 src/androidterm.h                          |   6 +
 src/emacs.c                                |   4 +
 xcompile/Makefile.in                       |  13 +-
 18 files changed, 1685 insertions(+), 112 deletions(-)

diff --git a/java/Makefile.in b/java/Makefile.in
index 05e61dede8..c539fb0f1f 100644
--- a/java/Makefile.in
+++ b/java/Makefile.in
@@ -168,4 +168,4 @@ clean:
        rm -rf install-temp
        find . -name '*.class' -delete
 
-maintainer-clean: clean
+maintainer-clean distclean bootstrap-clean: clean
diff --git a/java/debug.sh b/java/debug.sh
index 3e3e3d9c28..aa80aeeebc 100755
--- a/java/debug.sh
+++ b/java/debug.sh
@@ -31,6 +31,7 @@ gdb_port=5039
 jdb_port=64013
 jdb=no
 attach_existing=no
+gdbserver=
 
 while [ $# -gt 0 ]; do
     case "$1" in
@@ -41,6 +42,7 @@ while [ $# -gt 0 ]; do
                echo "You must specify an argument to --device"
                exit 1
            fi
+           shift
            ;;
        "--help" )
            echo "Usage: $progname [options] -- [gdb options]"
@@ -50,6 +52,7 @@ while [ $# -gt 0 ]; do
            echo "  --jdb-port PORT     run the JDB server on a specific port"
            echo "  --jdb               run JDB instead of GDB"
            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:"
@@ -62,9 +65,18 @@ while [ $# -gt 0 ]; do
        "--jdb" )
            jdb=yes
            ;;
+       "--gdbserver" )
+           shift
+           gdbserver=$1
+           ;;
        "--port" )
+           shift
            gdb_port=$1
            ;;
+       "--jdb-port" )
+           shift
+           jdb_port=$1
+           ;;
        "--attach-existing" )
            attach_existing=yes
            ;;
@@ -170,46 +182,71 @@ elif [ -z $package_pids ]; then
     exit 1
 fi
 
-# Start JDB to make the wait dialog disappear.
-echo "Attaching JDB to unblock the application."
-adb -s $device forward --remove-all
-adb -s $device forward "tcp:$jdb_port" "jdwp:$pid"
+# This isn't necessary when attaching gdb to an existing process.
+if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then
+    # Start JDB to make the wait dialog disappear.
+    echo "Attaching JDB to unblock the application."
+    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
+    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 \
+    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
+    if [ $jdb = "yes" ]; then
+       # Just start JDB and then exit
+       $jdb_command
+       exit 1
+    fi
 
-exec 4<> /tmp/file-descriptor-stamp
+    exec 4<> /tmp/file-descriptor-stamp
 
-# Now run JDB with IO redirected to file descriptor 4 in a subprocess.
-$jdb_command <&4 >&4 &
+    # Now run JDB with IO redirected to file descriptor 4 in a subprocess.
+    $jdb_command <&4 >&4 &
 
-character=
-# Next, wait until the prompt is found.
-while read -n1 -u 4 character; do
-    if [ "$character" = ">" ]; then
-       echo "JDB attached successfully"
-       break;
-    fi
-done
+    character=
+    # Next, wait until the prompt is found.
+    while read -n1 -u 4 character; do
+       if [ "$character" = ">" ]; then
+           echo "JDB attached successfully"
+           break;
+       fi
+    done
+fi
+
+# See if gdbserver has to be uploaded
+if [ -z "$gdbserver" ]; then
+    gdbserver_bin=/system/bin/gdbserver
+else
+    gdbserver_bin=/data/local/tmp/gdbserver
+
+    # Upload the specified gdbserver binary to the device.
+    adb -s $device push "$gdbserver" "$gdbserver_bin"
+    adb -s $device shell chmod +x "$gdbserver_bin"
+fi
 
 # Now start gdbserver on the device asynchronously.
 
 echo "Attaching gdbserver to $pid on $device..."
 exec 5<> /tmp/file-descriptor-stamp
-adb -s $device shell run-as $package /system/bin/gdbserver --once \
-    "+debug.$package_uid.socket" --attach $pid >&5 &
+
+if [ -z "$gdbserver" ]; then
+    adb -s $device shell run-as $package $gdbserver_bin --once \
+       "+debug.$package_uid.socket" --attach $pid >&5 &
+    gdb_socket="localfilesystem:$app_data_dir/debug.$package_uid.socket"
+else
+    # Normally the program cannot access $gdbserver_bin when it is
+    # placed in /data/local/tmp.
+    adb -s $device shell $gdbserver_bin --once \
+       "+/data/local/tmp/debug.$package_uid.socket" \
+       --attach $pid >&5 &
+    gdb_socket="localfilesystem:/data/local/tmp/debug.$package_uid.socket"
+fi
 
 # Wait until gdbserver successfully runs.
 line=
@@ -227,16 +264,17 @@ while read -u 5 line; do
     esac
 done
 
-# Send EOF to JDB to make it go away.  This will also cause Android to
-# allow Emacs to continue executing.
-echo "Making JDB go away..."
-echo "exit" >&4
-read -u 4 line
-echo "JDB has gone away with $line"
+if [ "$attach_existing" != "yes" ]; then
+    # Send EOF to JDB to make it go away.  This will also cause
+    # Android to allow Emacs to continue executing.
+    echo "Making JDB go away..."
+    echo "exit" >&4
+    read -u 4 line
+    echo "JDB has gone away with $line"
+fi
 
 # Forward the gdb server port here.
-adb -s $device forward "tcp:$gdb_port" \
-    "localfilesystem:$app_data_dir/debug.$package_uid.socket"
+adb -s $device forward "tcp:$gdb_port" $gdb_socket
 if [ ! $? ]; then
     echo "Failed to forward $app_data_dir/debug.$package_uid.socket"
     echo "to $gdb_port!  Perhaps you need to specify a different port"
diff --git a/java/org/gnu/emacs/EmacsActivity.java 
b/java/org/gnu/emacs/EmacsActivity.java
index 2b66102484..4cd286d1e8 100644
--- a/java/org/gnu/emacs/EmacsActivity.java
+++ b/java/org/gnu/emacs/EmacsActivity.java
@@ -43,7 +43,7 @@ public class EmacsActivity extends Activity
   private FrameLayout layout;
 
   /* List of activities with focus.  */
-  private static List<EmacsActivity> focusedActivities;
+  public static List<EmacsActivity> focusedActivities;
 
   /* The currently focused window.  */
   public static EmacsWindow focusedWindow;
diff --git a/java/org/gnu/emacs/EmacsContextMenu.java 
b/java/org/gnu/emacs/EmacsContextMenu.java
new file mode 100644
index 0000000000..8d7ae08b25
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsContextMenu.java
@@ -0,0 +1,213 @@
+/* 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.Bundle;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import android.widget.PopupMenu;
+
+/* 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 class EmacsContextMenu
+{
+  private class Item
+  {
+    public int itemID;
+    public String itemName;
+    public EmacsContextMenu subMenu;
+    public boolean isEnabled;
+  };
+
+  public List<Item> menuItems;
+  public String title;
+  private EmacsContextMenu parent;
+
+  /* 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.menuItems = new ArrayList<Item> ();
+    menu.title = title;
+
+    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.  */
+
+  public void
+  addItem (int itemID, String itemName, boolean isEnabled)
+  {
+    Item item;
+
+    item = new Item ();
+    item.itemID = itemID;
+    item.itemName = itemName;
+    item.isEnabled = isEnabled;
+
+    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 title)
+  {
+    EmacsContextMenu submenu;
+    Item item;
+
+    item = new Item ();
+    item.itemID = 0;
+    item.itemName = itemName;
+    item.subMenu = createContextMenu (title);
+    item.subMenu.parent = this;
+
+    menuItems.add (item);
+    return item.subMenu;
+  }
+
+  /* Add the contents of this menu to MENU.  */
+
+  private void
+  inflateMenuItems (Menu menu)
+  {
+    Intent intent;
+    MenuItem menuItem;
+    Menu submenu;
+
+    for (Item item : menuItems)
+      {
+       if (item.subMenu != null)
+         {
+           /* This is a submenu.  Create the submenu and add the
+              contents of the menu to it.  */
+           submenu = menu.addSubMenu (item.itemName);
+           inflateMenuItems (submenu);
+         }
+       else
+         {
+           menuItem = menu.add (item.itemName);
+
+           /* If the item ID is zero, then disable the item.  */
+           if (item.itemID == 0 || !item.isEnabled)
+             menuItem.setEnabled (false);
+         }
+      }
+  }
+
+  /* Enter the items in this context menu to MENU.  Create each menu
+     item with an Intent containing a Bundle, where the key
+     "emacs:menu_item_hi" maps to the high 16 bits of the
+     corresponding item ID, and the key "emacs:menu_item_low" maps to
+     the low 16 bits of the item ID.  */
+
+  public void
+  expandTo (Menu menu)
+  {
+    inflateMenuItems (menu);
+  }
+
+  /* Return the parent or NULL.  */
+
+  public EmacsContextMenu
+  parent ()
+  {
+    return parent;
+  }
+
+  /* Like display, but does the actual work and runs in the main
+     thread.  */
+
+  private boolean
+  display1 (EmacsWindow window, int xPosition, int yPosition)
+  {
+    return window.view.popupMenu (this, xPosition, yPosition);
+  }
+
+  /* Display this context menu on WINDOW, at xPosition and
+     yPosition.  */
+
+  public boolean
+  display (final EmacsWindow window, final int xPosition,
+          final int yPosition)
+  {
+    Runnable runnable;
+    final Holder<Boolean> rc;
+
+    rc = new Holder<Boolean> ();
+
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             rc.thing = display1 (window, xPosition, yPosition);
+             notify ();
+           }
+       }
+      };
+
+    try
+      {
+       runnable.wait ();
+      }
+    catch (InterruptedException e)
+      {
+       EmacsNative.emacsAbort ();
+      }
+
+    return rc.thing;
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java 
b/java/org/gnu/emacs/EmacsDrawRectangle.java
index b42e9556e8..84ff498847 100644
--- a/java/org/gnu/emacs/EmacsDrawRectangle.java
+++ b/java/org/gnu/emacs/EmacsDrawRectangle.java
@@ -59,7 +59,7 @@ public class EmacsDrawRectangle
       }
 
     paint = gc.gcPaint;
-    rect = new Rect (x, y, x + width, y + height);
+    rect = new Rect (x + 1, y + 1, x + width, y + height);
 
     paint.setStyle (Paint.Style.STROKE);
 
diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java
index caa5c91edd..c579625f3f 100644
--- a/java/org/gnu/emacs/EmacsGC.java
+++ b/java/org/gnu/emacs/EmacsGC.java
@@ -93,6 +93,7 @@ public class EmacsGC extends EmacsHandleObject
     else
       real_clip_rects = clip_rects;
 
+    gcPaint.setStrokeWidth (1f);
     gcPaint.setColor (foreground | 0xff000000);
     gcPaint.setXfermode (function == GC_XOR
                         ? xorAlu : srcInAlu);
diff --git a/java/org/gnu/emacs/EmacsService.java 
b/java/org/gnu/emacs/EmacsService.java
index c008300dd3..f935b63fa0 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -29,6 +29,7 @@ import android.graphics.Point;
 
 import android.view.View;
 import android.view.InputDevice;
+import android.view.KeyEvent;
 
 import android.annotation.TargetApi;
 import android.app.Service;
@@ -150,13 +151,13 @@ public class EmacsService extends Service
   /* Functions from here on must only be called from the Emacs
      thread.  */
 
-  void
+  public void
   runOnUiThread (Runnable runnable)
   {
     handler.post (runnable);
   }
 
-  EmacsView
+  public EmacsView
   getEmacsView (final EmacsWindow window, final int visibility,
                final boolean isFocusedByDefault)
   {
@@ -196,6 +197,38 @@ public class EmacsService extends Service
     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 ();
+           }
+       }
+      };
+
+    synchronized (runnable)
+      {
+       runOnUiThread (runnable);
+
+       try
+         {
+           runnable.wait ();
+         }
+       catch (InterruptedException e)
+         {
+           EmacsNative.emacsAbort ();
+         }
+      }
+  }
+
   public void
   fillRectangle (EmacsDrawable drawable, EmacsGC gc,
                 int x, int y, int width, int height)
@@ -368,4 +401,10 @@ public class EmacsService extends Service
 
     return false;
   }
+
+  public String
+  nameKeysym (int keysym)
+  {
+    return KeyEvent.keyCodeToString (keysym);
+  }
 };
diff --git a/java/org/gnu/emacs/EmacsView.java 
b/java/org/gnu/emacs/EmacsView.java
index 41acabab97..1391f630be 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -21,6 +21,7 @@ package org.gnu.emacs;
 
 import android.content.res.ColorStateList;
 
+import android.view.ContextMenu;
 import android.view.View;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -73,6 +74,12 @@ public class EmacsView extends ViewGroup
      next call to getBitmap.  */
   private Rect bitmapDirty;
 
+  /* Whether or not a popup is active.  */
+  private boolean popupActive;
+
+  /* The current context menu.  */
+  private EmacsContextMenu contextMenu;
+
   public
   EmacsView (EmacsWindow window)
   {
@@ -98,6 +105,10 @@ public class EmacsView extends ViewGroup
     /* Get rid of the foreground and background tint.  */
     setBackgroundTintList (null);
     setForegroundTintList (null);
+
+    /* Get rid of the default focus highlight.  */
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
+      setDefaultFocusHighlightEnabled (false);
   }
 
   private void
@@ -423,4 +434,40 @@ public class EmacsView extends ViewGroup
     removeView (surfaceView);
     addView (surfaceView, 0);
   }
+
+  @Override
+  protected void
+  onCreateContextMenu (ContextMenu menu)
+  {
+    if (contextMenu == null)
+      return;
+
+    contextMenu.expandTo (menu);
+  }
+
+  public boolean
+  popupMenu (EmacsContextMenu menu, int xPosition,
+            int yPosition)
+  {
+    if (popupActive)
+      return false;
+
+    contextMenu = menu;
+
+    /* On API 21 or later, use showContextMenu (float, float).  */
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP)
+      return showContextMenu ((float) xPosition, (float) yPosition);
+    else
+      return showContextMenu ();
+  }
+
+  public void
+  cancelPopupMenu ()
+  {
+    if (!popupActive)
+      throw new IllegalStateException ("cancelPopupMenu called without"
+                                      + " popupActive set");
+
+    contextMenu = null;
+  }
 };
diff --git a/java/org/gnu/emacs/EmacsWindow.java 
b/java/org/gnu/emacs/EmacsWindow.java
index 1f8596dba5..6effa79d1a 100644
--- a/java/org/gnu/emacs/EmacsWindow.java
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -24,16 +24,22 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.HashMap;
 
+import android.content.Context;
+
 import android.graphics.Rect;
 import android.graphics.Canvas;
 import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.graphics.PixelFormat;
 
 import android.view.View;
+import android.view.ViewManager;
 import android.view.ViewGroup;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.InputDevice;
+import android.view.WindowManager;
 
 import android.content.Intent;
 import android.util.Log;
@@ -110,9 +116,17 @@ public class EmacsWindow extends EmacsHandleObject
      not the window should be focusable.  */
   private boolean dontFocusOnMap, dontAcceptFocus;
 
+  /* 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;
+
   public
   EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
-              int width, int height)
+              int width, int height, boolean overrideRedirect)
   {
     super (handle);
 
@@ -124,6 +138,7 @@ public class EmacsWindow extends EmacsHandleObject
     view = EmacsService.SERVICE.getEmacsView (this, View.GONE,
                                              parent == null);
     this.parent = parent;
+    this.overrideRedirect = overrideRedirect;
 
     /* Create the list of children.  */
     children = new ArrayList<EmacsWindow> ();
@@ -180,7 +195,7 @@ public class EmacsWindow extends EmacsHandleObject
        public void
        run ()
        {
-         View parent;
+         ViewManager parent;
          EmacsWindowAttachmentManager manager;
 
          if (EmacsActivity.focusedWindow == EmacsWindow.this)
@@ -189,10 +204,15 @@ public class EmacsWindow extends EmacsHandleObject
          manager = EmacsWindowAttachmentManager.MANAGER;
          view.setVisibility (View.GONE);
 
-         parent = (View) view.getParent ();
+         /* If the window manager is set, use that instead.  */
+         if (windowManager != null)
+           parent = windowManager;
+         else
+           parent = (ViewManager) view.getParent ();
+         windowManager = null;
 
          if (parent != null)
-           ((ViewGroup) parent).removeView (view);
+           parent.removeView (view);
 
          manager.detachWindow (EmacsWindow.this);
        }
@@ -247,6 +267,10 @@ public class EmacsWindow extends EmacsHandleObject
        public void
        run ()
        {
+         if (overrideRedirect)
+           /* Set the layout parameters again.  */
+           view.setLayoutParams (getWindowLayoutParams ());
+
          view.mustReportLayout = true;
          view.requestLayout ();
        }
@@ -284,6 +308,39 @@ public class EmacsWindow extends EmacsHandleObject
       }
   }
 
+  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 void
   mapWindow ()
   {
@@ -300,20 +357,60 @@ public class EmacsWindow extends EmacsHandleObject
            run ()
            {
              EmacsWindowAttachmentManager manager;
+             WindowManager windowManager;
+             Context ctx;
+             Object tem;
+             WindowManager.LayoutParams params;
 
              /* Make the view visible, first of all.  */
              view.setVisibility (View.VISIBLE);
 
-             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 ();
+             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
+                   {
+                     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);
+                   }
+               }
            }
          });
       }
@@ -355,6 +452,11 @@ public class EmacsWindow extends EmacsHandleObject
 
          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);
@@ -756,17 +858,23 @@ public class EmacsWindow extends EmacsHandleObject
        run ()
        {
          EmacsWindowAttachmentManager manager;
-         View parent;
+         ViewManager parent;
 
          /* First, detach this window if necessary.  */
          manager = EmacsWindowAttachmentManager.MANAGER;
          manager.detachWindow (EmacsWindow.this);
 
          /* Also unparent this view.  */
-         parent = (View) view.getParent ();
+
+         /* If the window manager is set, use that instead.  */
+         if (windowManager != null)
+           parent = windowManager;
+         else
+           parent = (ViewManager) view.getParent ();
+         windowManager = null;
 
          if (parent != null)
-           ((ViewGroup) parent).removeView (view);
+           parent.removeView (view);
 
          /* Next, either add this window as a child of the new
             parent's view, or make it available again.  */
@@ -899,4 +1007,23 @@ public class EmacsWindow extends EmacsHandleObject
   {
     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;
+  }
 };
diff --git a/src/android.c b/src/android.c
index fba43129ee..e4022501f9 100644
--- a/src/android.c
+++ b/src/android.c
@@ -87,6 +87,7 @@ struct android_emacs_service
   jmethodID get_screen_width;
   jmethodID get_screen_height;
   jmethodID detect_mouse;
+  jmethodID name_keysym;
 };
 
 struct android_emacs_pixmap
@@ -229,14 +230,14 @@ static volatile bool android_pselect_completed;
 /* The global event queue.  */
 static struct android_event_queue event_queue;
 
-/* Semaphore used to signal select completion.  */
-static sem_t android_pselect_sem;
+/* Semaphores used to signal select completion and start.  */
+static sem_t android_pselect_sem, android_pselect_start_sem;
 
 static void *
 android_run_select_thread (void *data)
 {
   sigset_t signals;
-  int sig, rc;
+  int rc;
 
   sigfillset (&signals);
 
@@ -245,23 +246,10 @@ android_run_select_thread (void *data)
                         "pthread_sigmask: %s",
                         strerror (errno));
 
-  sigemptyset (&signals);
-  sigaddset (&signals, SIGUSR1);
-
-  if (pthread_sigmask (SIG_UNBLOCK, &signals, NULL))
-    __android_log_print (ANDROID_LOG_FATAL, __func__,
-                        "pthread_sigmask: %s",
-                        strerror (errno));
-
-  sigemptyset (&signals);
-  sigaddset (&signals, SIGUSR2);
-
   while (true)
     {
-      /* Keep waiting for SIGUSR2, ignoring EINTR in the meantime.  */
-
-      while (sigwait (&signals, &sig))
-       /* Spin.  */;
+      /* Wait for the thread to be released.  */
+      sem_wait (&android_pselect_start_sem);
 
       /* Get the select lock and call pselect.  */
       pthread_mutex_lock (&event_queue.select_mutex);
@@ -322,6 +310,7 @@ android_init_events (void)
                         strerror (errno));
 
   sem_init (&android_pselect_sem, 0, 0);
+  sem_init (&android_pselect_start_sem, 0, 0);
 
   event_queue.events.next = &event_queue.events;
   event_queue.events.last = &event_queue.events;
@@ -444,7 +433,9 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
   android_pselect_sigset = sigset;
   pthread_mutex_unlock (&event_queue.select_mutex);
 
-  pthread_kill (event_queue.select_thread, SIGUSR2);
+  /* Release the select thread.  */
+  sem_post (&android_pselect_start_sem);
+
   pthread_cond_wait (&event_queue.read_var, &event_queue.mutex);
 
   /* Interrupt the select thread now, in case it's still in
@@ -1058,6 +1049,7 @@ android_init_emacs_service (void)
   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;");
 #undef FIND_METHOD
 }
 
@@ -1678,6 +1670,7 @@ android_create_window (android_window parent, int x, int 
y,
   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);
 
@@ -1695,7 +1688,8 @@ android_create_window (android_window parent, int x, int 
y,
 
       constructor
        = (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
-                                           
"(SLorg/gnu/emacs/EmacsWindow;IIII)V");
+                                           "(SLorg/gnu/emacs/EmacsWindow;"
+                                           "IIIIZ)V");
       assert (constructor != NULL);
 
       old = class;
@@ -1707,10 +1701,17 @@ android_create_window (android_window parent, int x, 
int y,
        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);
+                                          (jint) width, (jint) height,
+                                          (jboolean) override_redirect);
   if (!object)
     {
       (*android_java_env)->ExceptionClear (android_java_env);
@@ -3212,6 +3213,66 @@ android_get_geometry (android_window handle,
   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 = android_lookup_method ("org/gnu/emacs/EmacsWindow",
+                                 "translateCoordinates",
+                                 "(II)[I");
+  coordinates
+    = (*android_java_env)->CallObjectMethod (android_java_env,
+                                            window, method,
+                                            (jint) x, (jint) y);
+
+  if (!coordinates)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      memory_full (0);
+    }
+
+  /* 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);
+  *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);
+}
+
 
 
 /* Low level drawing primitives.  */
@@ -3384,6 +3445,30 @@ android_set_dont_accept_focus (android_window handle,
                                       (jboolean) no_accept_focus);
 }
 
+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 ();
+  strncpy (name_return, buffer, size - 1);
+
+  (*android_java_env)->ReleaseStringUTFChars (android_java_env,
+                                             (jstring) string,
+                                             buffer);
+  ANDROID_DELETE_LOCAL_REF (string);
+}
+
 
 
 #undef faccessat
@@ -3525,6 +3610,48 @@ emacs_abort (void)
   abort ();
 }
 
+
+
+/* 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;
+
+  encoded = ENCODE_UTF_8 (text);
+
+  /* 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.  The only consequence of passing an actual
+     UTF-8 string is that NUL bytes cannot be represented, which is
+     not really of consequence.  */
+  string = (*android_java_env)->NewStringUTF (android_java_env,
+                                             SSDATA (encoded));
+  if (!string)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      memory_full (0);
+    }
+
+  return string;
+}
+
+/* Check for JNI exceptions and call memory_full in that
+   situation.  */
+
+void
+android_exception_check (void)
+{
+  if ((*android_java_env)->ExceptionCheck (android_java_env))
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+      memory_full (0);
+    }
+}
+
 #else /* ANDROID_STUBIFY */
 
 /* X emulation functions for Android.  */
diff --git a/src/android.h b/src/android.h
index af1e42ec37..98f2494e9a 100644
--- a/src/android.h
+++ b/src/android.h
@@ -35,6 +35,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <android/bitmap.h>
 
 #include "androidgui.h"
+#include "lisp.h"
 #endif
 
 /* This must be used in every symbol declaration to export it to the
@@ -84,6 +85,11 @@ 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 jstring android_build_string (Lisp_Object);
+extern void android_exception_check (void);
+
+extern void android_get_keysym_name (int, char *, size_t);
+
 
 
 /* Directory listing emulation.  */
diff --git a/src/androidfns.c b/src/androidfns.c
index 459e407b90..ab136bc272 100644
--- a/src/androidfns.c
+++ b/src/androidfns.c
@@ -25,12 +25,36 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include "androidterm.h"
 #include "blockinput.h"
 #include "keyboard.h"
+#include "buffer.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 *
@@ -180,6 +204,9 @@ android_set_parent_frame (struct frame *f, Lisp_Object 
new_value,
 
       fset_parent_frame (f, new_value);
     }
+
+  /* Update the fullscreen frame parameter as well.  */
+  FRAME_TERMINAL (f)->fullscreen_hook (f);
 }
 
 void
@@ -858,13 +885,13 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
   gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0),
                          NULL, NULL, RES_TYPE_NUMBER);
 
-  /* gui_default_parameter (f, parms, Qvertical_scroll_bars, */
-  /*                        Qleft, */
-  /*                        "verticalScrollBars", "ScrollBars", */
-  /*                        RES_TYPE_SYMBOL); */
-  /* gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil, */
-  /*                        "horizontalScrollBars", "ScrollBars", */
-  /*                        RES_TYPE_SYMBOL);  TODO */
+  gui_default_parameter (f, parms, Qvertical_scroll_bars,
+                         Qleft,
+                         "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"),
@@ -893,7 +920,7 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
   android_default_scroll_bar_color_parameter (f, parms, Qscroll_bar_background,
                                              "scrollBarBackground",
                                              "ScrollBarBackground", false);
-#endif /* TODO */
+#endif
 
   /* Init faces before gui_default_parameter is called for the
      scroll-bar-width parameter because otherwise we end up in
@@ -974,12 +1001,16 @@ DEFUN ("x-create-frame", Fx_create_frame, 
Sx_create_frame,
                          "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.  */
+#if 0
   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);
+#endif
   gui_default_parameter (f, parms, Qalpha, Qnil,
                          "alpha", "Alpha", RES_TYPE_NUMBER);
   gui_default_parameter (f, parms, Qalpha_background, Qnil,
@@ -1009,8 +1040,9 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
 
   /* 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.  */
-  gui_default_parameter (f, parms, Qfullscreen, Qnil,
+     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
@@ -1661,6 +1693,391 @@ DEFUN ("x-display-list", Fx_display_list, 
Sx_display_list, 0, 0, 0,
   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;
+
+    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));
+  }
+
+  /* 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;
+
+    attrs.override_redirect = true;
+    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,
@@ -1670,8 +2087,214 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
   error ("Android cross-compilation stub called!");
   return Qnil;
 #else
-  /* TODO tooltips */
-  return Qnil;
+  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 ();
+
+  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;
+
+ 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
 }
 
@@ -1683,7 +2306,7 @@ DEFUN ("x-hide-tip", Fx_hide_tip, Sx_hide_tip, 0, 0, 0,
   error ("Android cross-compilation stub called!");
   return Qnil;
 #else
-  return Qnil;
+  return android_hide_tip (true);
 #endif
 }
 
@@ -2112,6 +2735,17 @@ syms_of_androidfns (void)
     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;
+
   /* Functions defined.  */
   defsubr (&Sx_create_frame);
   defsubr (&Sxw_color_defined_p);
@@ -2139,4 +2773,21 @@ syms_of_androidfns (void)
   defsubr (&Sx_show_tip);
   defsubr (&Sx_hide_tip);
   defsubr (&Sandroid_detect_mouse);
+
+#ifndef ANDROID_STUBIFY
+  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
 }
diff --git a/src/androidgui.h b/src/androidgui.h
index 422e72408c..8450a1f637 100644
--- a/src/androidgui.h
+++ b/src/androidgui.h
@@ -80,13 +80,18 @@ enum android_fill_style
 
 enum android_window_value_mask
   {
-    ANDROID_CW_BACK_PIXEL = (1 << 1),
+    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
@@ -260,7 +265,7 @@ struct android_key_event
   ((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) == 63 || (key) == 115)
 
 struct android_configure_event
 {
@@ -478,6 +483,11 @@ extern int android_query_tree (android_window, 
android_window *,
 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 *);
 
 #endif
 
diff --git a/src/androidmenu.c b/src/androidmenu.c
index 6f6e4ca8de..0f0c6f4ef1 100644
--- a/src/androidmenu.c
+++ b/src/androidmenu.c
@@ -21,6 +21,10 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 #include "lisp.h"
 #include "androidterm.h"
+#include "android.h"
+#include "blockinput.h"
+#include "keyboard.h"
+#include "menu.h"
 
 #ifndef ANDROID_STUBIFY
 
@@ -35,4 +39,293 @@ 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;
+};
+
+/* 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;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;II)Z");
+
+#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);
+}
+
+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, temp;
+  size_t i;
+  Lisp_Object pane_name, prefix;
+  const char *pane_string;
+  specpdl_ref count, count1;
+  Lisp_Object item_name, enable, def;
+  jmethodID method;
+  jobject store;
+  bool rc;
+  jobject window;
+
+  count = SPECPDL_INDEX ();
+
+  block_input ();
+
+  /* Push the first local frame.  */
+  android_push_local_frame ();
+
+  /* Push the first local frame for the context menu.  */
+  title_string = (!NILP (title)
+                 ? (jobject) android_build_string (title)
+                 : NULL);
+  method = menu_class.create_context_menu;
+  current_context_menu = context_menu
+    = (*android_java_env)->CallStaticObjectMethod (android_java_env,
+                                                  menu_class.class,
+                                                  method,
+                                                  title_string);
+
+  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;
+
+  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;
+       }
+      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;
+
+         eassert (current_context_menu);
+       }
+      else if (EQ (AREF (menu_items, i), Qquote))
+       i += 1;
+      else if (EQ (AREF (menu_items, i), Qt))
+       {
+         /* 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);
+
+         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);
+
+         /* 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);
+             store = current_context_menu;
+             current_context_menu
+               = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                        current_context_menu,
+                                                        menu_class.add_submenu,
+                                                        title_string);
+             android_exception_check ();
+
+             if (store != context_menu)
+               ANDROID_DELETE_LOCAL_REF (store);
+
+             if (title_string)
+               ANDROID_DELETE_LOCAL_REF (title_string);
+           }
+         else if (NILP (def) && menu_separator_name_p (SSDATA (item_name)))
+           /* Ignore this separator item.  */
+           ;
+         else
+           {
+             /* Add this menu item with the appropriate state.  */
+
+             title_string = (!NILP (item_name)
+                             ? android_build_string (item_name)
+                             : NULL);
+             (*android_java_env)->CallVoidMethod (android_java_env,
+                                                  current_context_menu,
+                                                  menu_class.add_item,
+                                                  (jint) 1,
+                                                  title_string,
+                                                  (jboolean) !NILP (enable));
+             android_exception_check ();
+
+             if (title_string)
+               ANDROID_DELETE_LOCAL_REF (title_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,
+                                              window, (jint) x,
+                                              (jint) y);
+  android_exception_check ();
+
+  if (!rc)
+    /* This means displaying the menu failed.  */
+    goto finish;
+
+#if 0
+  record_unwind_protect_ptr (android_dismiss_menu, &context_menu);
+
+  /* Otherwise, loop waiting for the menu event to arrive.  */
+  android_process_events_for_menu (&id);
+
+  if (!id)
+    /* This means no menu item was selected.  */
+    goto finish;
+
+#endif
+
+ finish:
+  unblock_input ();
+  return unbind_to (count, Qnil);
+}
+
+#endif
+
+void
+init_androidmenu (void)
+{
+#ifndef ANDROID_STUBIFY
+  android_init_emacs_context_menu ();
 #endif
+}
diff --git a/src/androidterm.c b/src/androidterm.c
index a832531249..002d39af70 100644
--- a/src/androidterm.c
+++ b/src/androidterm.c
@@ -559,6 +559,9 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
       f = android_window_to_frame (dpyinfo,
                                   configureEvent.xconfigure.window);
 
+      if (!f)
+       goto OTHER;
+
       int width = configureEvent.xconfigure.width;
       int height = configureEvent.xconfigure.height;
 
@@ -884,10 +887,6 @@ handle_one_android_event (struct android_display_info 
*dpyinfo,
                inev.ie.arg = tab_bar_arg;
            }
        }
-      else
-       {
-         /* TODO: scroll bars */
-       }
 
       if (event->type == ANDROID_BUTTON_PRESS)
        {
@@ -1451,7 +1450,12 @@ android_make_frame_visible_invisible (struct frame *f, 
bool visible)
 static void
 android_fullscreen_hook (struct frame *f)
 {
-  /* TODO */
+  /* Explicitly setting fullscreen is not supported on Android.  */
+
+  if (!FRAME_PARENT_FRAME (f))
+    store_frame_param (f, Qfullscreen, Qmaximized);
+  else
+    store_frame_param (f, Qfullscreen, Qnil);
 }
 
 void
@@ -2360,7 +2364,7 @@ android_draw_box_rect (struct glyph_string *s,
 
   /* Top.  */
   android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
-                         left_x, right_x - left_x + 1, hwidth);
+                         top_y, right_x - left_x + 1, hwidth);
 
   /* Left.  */
   if (left_p)
@@ -3958,7 +3962,14 @@ frame_set_mouse_pixel_position (struct frame *f, int 
pix_x, int pix_y)
 char *
 get_keysym_name (int keysym)
 {
-  return (char *) "UNKNOWN KEYSYM";
+  static char buffer[64];
+
+#ifndef ANDROID_STUBIFY
+  android_get_keysym_name (keysym, buffer, 64);
+#else
+  emacs_abort ();
+#endif
+  return buffer;
 }
 
 
@@ -4009,20 +4020,13 @@ android_create_terminal (struct android_display_info 
*dpyinfo)
   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; XXX */
+  terminal->menu_show_hook = android_menu_show;
   terminal->change_tab_bar_height_hook = android_change_tab_bar_height;
   terminal->change_tool_bar_height_hook = android_change_tool_bar_height;
-  /* terminal->set_vertical_scroll_bar_hook */
-  /*   = android_set_vertical_scroll_bar; */
-  /* terminal->set_horizontal_scroll_bar_hook */
-  /*   = android_set_horizontal_scroll_bar; */
   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->condemn_scroll_bars_hook = android_condemn_scroll_bars; */
-  /* terminal->redeem_scroll_bars_hook = android_redeem_scroll_bars; */
-  /* terminal->judge_scroll_bars_hook = android_judge_scroll_bars; */
   terminal->free_pixmap = android_free_pixmap_hook;
   terminal->delete_frame_hook = android_delete_frame;
   terminal->delete_terminal_hook = android_delete_terminal;
diff --git a/src/androidterm.h b/src/androidterm.h
index ebde15c40a..e83e32a585 100644
--- a/src/androidterm.h
+++ b/src/androidterm.h
@@ -402,6 +402,12 @@ extern void syms_of_androidfont (void);
 
 extern void android_finalize_font_entity (struct font_entity *);
 
+/* Defined in androidmenu.c.  */
+
+extern Lisp_Object android_menu_show (struct frame *, int, int, int,
+                                     Lisp_Object, const char **);
+extern void init_androidmenu (void);
+
 /* Defined in sfntfont-android.c.  */
 
 extern const struct font_driver android_sfntfont_driver;
diff --git a/src/emacs.c b/src/emacs.c
index e3f6c7d66f..8f5be53aad 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -2499,6 +2499,10 @@ 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_sfntfont ();
diff --git a/xcompile/Makefile.in b/xcompile/Makefile.in
index ed2b10b91a..9f817bb4c5 100644
--- a/xcompile/Makefile.in
+++ b/xcompile/Makefile.in
@@ -110,7 +110,7 @@ lib/libgnu.a: src/verbose.mk config.status $(LIB_DEPS) 
$(PRE_BUILD_DEPS)
        +make -C lib libgnu.a
 
 src/Makefile src/config.h &: $(top_builddir)/src/config.h.android      \
-  $(top_builddir)/src/Makefile.android $(PRE_BUILD_DEPS)
+  $(top_builddir)/src/Makefile.android
        mkdir -p src src/deps
 # Copy config.h to src/
        cp -f -p $(top_builddir)/src/config.h.android src/config.h
@@ -155,13 +155,20 @@ $(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ 
lib/libgnu.a      \
 # Finally, go into lib-src and make everything being built
        +make -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin)))
 
-.PHONY: clean maintainer-clean
+.PHONY: clean maintainer-clean distclean
 clean:
        rm -rf $(CLEAN_SUBDIRS) *.bak sys
        if [ -e lib/Makefile ]; then    \
          make -C lib clean;            \
        fi
-       rm -rf lib/gnulib.mk lib/Makefile lib/config.h
+       rm -rf lib/config.h
+
+distclean bootstrap-clean: clean
+       if [ -e lib/Makefile ]; then            \
+         make -C lib distclean;                \
+       fi
+# Just in case.
+       rm -rf lib/Makefile lib/gnulib.mk
 
 maintainer-clean: clean
        if [ -e lib/Makefile ]; then            \



reply via email to

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