Update Android port
authorPo Lu <luangruo@yahoo.com>
Tue, 17 Jan 2023 14:10:43 +0000 (22:10 +0800)
committerPo Lu <luangruo@yahoo.com>
Tue, 17 Jan 2023 14:10:43 +0000 (22:10 +0800)
* 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.

24 files changed:
doc/emacs/android.texi
doc/emacs/emacs.texi
doc/lispref/commands.texi
etc/DEBUG
java/org/gnu/emacs/EmacsCopyArea.java
java/org/gnu/emacs/EmacsDialog.java [new file with mode: 0644]
java/org/gnu/emacs/EmacsDrawRectangle.java
java/org/gnu/emacs/EmacsFillRectangle.java
java/org/gnu/emacs/EmacsPixmap.java
java/org/gnu/emacs/EmacsView.java
java/org/gnu/emacs/EmacsWindow.java
src/android.c
src/android.h
src/androidmenu.c
src/androidterm.c
src/androidterm.h
src/keyboard.c
src/keyboard.h
src/process.c
src/sfnt.c
src/sfnt.h
src/sfntfont-android.c
src/sfntfont.c
xcompile/Makefile.in

index b5a91b0f98f8dd5f7a73a4fadcb2cd88afbaaadb..8806a2a2bf624990da205fb577a63ee9077e4bf9 100644 (file)
@@ -335,11 +335,10 @@ places fonts.  Emacs assumes there will always be a font named ``Droid
 Sans Mono'', and then defaults to using this font.  These fonts are
 then rendered by the @code{sfnt-android} font driver.
 
-When running on Android, Emacs currently lacks support for TrueType
-Container and 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 raising this limitation, please contact
-@email{emacs-devel@@gnu.org}.
+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
index 19a99d1deb2a62bd01b937267d49357430231803..d21d09bf920ecf1cf1b9ba74b0a56a7817f631b2 100644 (file)
@@ -223,6 +223,7 @@ 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.
 * Manifesto::           What's GNU?  Gnu's Not Unix!
 
@@ -1262,6 +1263,7 @@ Emacs and Android
 * Android Startup::     Starting up Emacs on Android.
 * Android File System:: The Android file system.
 * Android Environment:: Running Emacs under Android.
+* Android Fonts::      Font selection under Android.
 
 Emacs and Microsoft Windows/MS-DOS
 
index 14702ce6efa021f737b89d4cb699a2a4b8381939..6d1ce145fbfde9c8e30a6bfb518ef42966b54a69 100644 (file)
@@ -1999,6 +1999,13 @@ each point is represented by a cons of an arbitrary number identifying
 the point and a mouse position list (@pxref{Click Events}) specifying
 the position of the finger when the event occurred.
 
+In addition, @code{touchscreen-begin} events also have imaginary
+prefixes keys added by @code{read-key-sequence} when they originate on
+top of a special part of a frame or window.  @xref{Key Sequence
+Input}.  The reason the other touch screen events do not undergo this
+treatment is that they are rarely useful without being used in tandem
+from their corresponding @code{touchscreen-begin} events.
+
 @table @code
 @cindex @code{touchscreen-begin} event
 @item (touchscreen-begin @var{point})
@@ -3024,19 +3031,21 @@ 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
-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
-@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},
+@cindex touch screen events, in special parts of window or frame
+When mouse or @code{touch-screen-begin} 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 @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.
+@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:
index 124453939b143b52b2611b76b128b5b5f421f0ba..1cc575598bf738fd4a23f41ab78ec6e0e23b71d3 100644 (file)
--- a/etc/DEBUG
+++ b/etc/DEBUG
@@ -1123,6 +1123,15 @@ then placing a breakpoint on:
 
 will let you find the source of the crash.
 
+If there is no `gdbserver' binary present on the device, then you can
+specify one to upload, like so:
+
+  ../java/debug.sh --gdbserver /path/to/gdbserver
+
+In addition, when Emacs runs as a 64-bit process on a system
+supporting both 64 and 32-bit binaries, you must specify the path to a
+64-bit gdbserver binary.
+
 \f
 This file is part of GNU Emacs.
 
index 00e817bb97dcc97a2508d7818ed890b8507fcdea..5d72a7860c833a54fa81830284a90b9ac6201656 100644 (file)
@@ -116,6 +116,7 @@ public class EmacsCopyArea
                                      src_x, src_y, width,
                                      height);
        canvas.drawBitmap (bitmap, null, rect, paint);
+       bitmap.recycle ();
       }
     else
       {
@@ -183,6 +184,9 @@ public class EmacsCopyArea
        paint.setXfermode (overAlu);
        canvas.drawBitmap (maskBitmap, null, maskRect, paint);
        gc.resetXfermode ();
+
+       /* Recycle this unused bitmap.  */
+       maskBitmap.recycle ();
       }
 
     canvas.restore ();
diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java
new file mode 100644 (file)
index 0000000..5bc8efa
--- /dev/null
@@ -0,0 +1,333 @@
+/* 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.DialogInterface;
+import android.content.Context;
+import android.util.Log;
+
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.FrameLayout;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/* Toolkit dialog implementation.  This object is built from JNI and
+   describes a single alert dialog.  Then, `inflate' turns it into
+   AlertDialog.  */
+
+public 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;
+
+  private 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)
+    {
+      Log.d (TAG, "onClicked " + this);
+
+      wasButtonClicked = true;
+      EmacsNative.sendContextMenu ((short) 0, id);
+      dismissDialog.dismiss ();
+    }
+
+    @Override
+    public void
+    onClick (DialogInterface dialog, int which)
+    {
+      Log.d (TAG, "onClicked " + this);
+
+      wasButtonClicked = true;
+      EmacsNative.sendContextMenu ((short) 0, id);
+    }
+  };
+
+  /* Create a popup dialog with the title TITLE and the text TEXT.
+     TITLE may be NULL.  */
+
+  public static EmacsDialog
+  createDialog (String title, String text)
+  {
+    EmacsDialog dialog;
+
+    dialog = new EmacsDialog ();
+    dialog.buttons = new ArrayList<EmacsButton> ();
+    dialog.title = title;
+    dialog.text = text;
+
+    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;
+    EmacsButton button;
+    LinearLayout layout;
+    Button buttonView;
+    ViewGroup.LayoutParams layoutParams;
+
+    size = buttons.size ();
+
+    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_NEUTRAL,
+                             button.name, button);
+           buttonView
+             = dialog.getButton (DialogInterface.BUTTON_NEUTRAL);
+           buttonView.setEnabled (button.enabled);
+         }
+
+       if (size >= 3)
+         {
+           button = buttons.get (2);
+           dialog.setButton (DialogInterface.BUTTON_NEGATIVE,
+                             button.name, button);
+         }
+      }
+    else
+      {
+       /* There are more than 4 buttons.  Add them all to a
+          LinearLayout.  */
+       layout = new LinearLayout (context);
+       layoutParams
+         = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT,
+                                          ViewGroup.LayoutParams.WRAP_CONTENT);
+
+       for (EmacsButton emacsButton : buttons)
+         {
+           buttonView = new Button (context);
+           buttonView.setText (emacsButton.name);
+           buttonView.setOnClickListener (emacsButton);
+           buttonView.setLayoutParams (layoutParams);
+           buttonView.setEnabled (emacsButton.enabled);
+           layout.addView (buttonView);
+         }
+
+       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);
+      }
+
+    return dialog;
+  }
+
+  /* Internal helper for display run on the main thread.  */
+
+  private boolean
+  display1 ()
+  {
+    EmacsActivity activity;
+    int size;
+    Button buttonView;
+    EmacsButton button;
+    AlertDialog dialog;
+
+    if (EmacsActivity.focusedActivities.isEmpty ())
+      return false;
+
+    activity = EmacsActivity.focusedActivities.get (0);
+    dialog = dismissDialog = toAlertDialog (activity);
+    dismissDialog.show ();
+
+    /* 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);
+           dialog.setButton (DialogInterface.BUTTON_NEUTRAL,
+                             button.name, button);
+           buttonView
+             = dialog.getButton (DialogInterface.BUTTON_NEUTRAL);
+           buttonView.setEnabled (button.enabled);
+         }
+
+       if (size >= 3)
+         {
+           button = buttons.get (2);
+           buttonView
+             = dialog.getButton (DialogInterface.BUTTON_NEGATIVE);
+           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 Holder<Boolean> rc;
+
+    rc = new Holder<Boolean> ();
+    runnable = new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         synchronized (this)
+           {
+             rc.thing = display1 ();
+             notify ();
+           }
+       }
+      };
+
+    synchronized (runnable)
+      {
+       EmacsService.SERVICE.runOnUiThread (runnable);
+
+       try
+         {
+           runnable.wait ();
+         }
+       catch (InterruptedException e)
+         {
+           EmacsNative.emacsAbort ();
+         }
+      }
+
+    return rc.thing;
+  }
+
+\f
+
+  @Override
+  public void
+  onDismiss (DialogInterface dialog)
+  {
+    Log.d (TAG, "onDismiss: " + this);
+
+    if (wasButtonClicked)
+      return;
+
+    EmacsNative.sendContextMenu ((short) 0, 0);
+  }
+};
index b42e9556e8c13b1eb0d62cdabb7a426871332654..c29d413f66e99a0d2730aa76ade8d1cf82da5dec 100644 (file)
@@ -36,10 +36,10 @@ public class EmacsDrawRectangle
     Paint maskPaint, paint;
     Canvas maskCanvas;
     Bitmap maskBitmap;
-    Rect rect;
     Rect maskRect, dstRect;
     Canvas canvas;
     Bitmap clipBitmap;
+    Rect clipRect;
 
     /* TODO implement stippling.  */
     if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
@@ -58,13 +58,29 @@ public class EmacsDrawRectangle
          canvas.clipRect (gc.real_clip_rects[i]);
       }
 
-    paint = gc.gcPaint;
-    rect = new Rect (x, y, x + width, y + height);
+    /* Clip to the clipRect because some versions of Android draw an
+       overly wide line.  */
+    clipRect = new Rect (x, y, x + width + 1,
+                        y + height + 1);
+    canvas.clipRect (clipRect);
 
-    paint.setStyle (Paint.Style.STROKE);
+    paint = gc.gcPaint;
 
     if (gc.clip_mask == null)
-      canvas.drawRect (rect, paint);
+      {
+       /* canvas.drawRect just doesn't work on Android, producing
+          different results on various devices.  Do a 5 point
+          PolyLine instead.  */
+       canvas.drawLine ((float) x, (float) y, (float) x + width,
+                        (float) y, paint);
+       canvas.drawLine ((float) x + width, (float) y,
+                        (float) x + width, (float) y + height,
+                        paint);
+       canvas.drawLine ((float) x + width, (float) y + height,
+                        (float) x, (float) y + height, paint);
+       canvas.drawLine ((float) x, (float) y + height,
+                        (float) x, (float) y, paint);
+      }
     else
       {
        /* Drawing with a clip mask involves calculating the
@@ -116,10 +132,12 @@ public class EmacsDrawRectangle
        /* Finally, draw the mask bitmap to the destination.  */
        paint.setXfermode (null);
        canvas.drawBitmap (maskBitmap, null, maskRect, paint);
+
+       /* Recycle this unused bitmap.  */
+       maskBitmap.recycle ();
       }
 
     canvas.restore ();
-    drawable.damageRect (new Rect (x, y, x + width + 1,
-                                  y + height + 1));
+    drawable.damageRect (clipRect);
   }
 }
index b733b417d6bb39ceb84051955351556a8ae17778..7cc55d3db96282c9d96440abfa2c14c9a7ea97b5 100644 (file)
@@ -115,6 +115,9 @@ public class EmacsFillRectangle
        /* Finally, draw the mask bitmap to the destination.  */
        paint.setXfermode (null);
        canvas.drawBitmap (maskBitmap, null, maskRect, paint);
+
+       /* Recycle this unused bitmap.  */
+       maskBitmap.recycle ();
       }
 
     canvas.restore ();
index 15452f007c47bc04ae231f74c031058bf920cd36..85931c2abd41a4086106972086aa843ec53d8fc9 100644 (file)
@@ -25,6 +25,8 @@ import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 
+import android.os.Build;
+
 /* Drawable backed by bitmap.  */
 
 public class EmacsPixmap extends EmacsHandleObject
@@ -123,4 +125,25 @@ public class EmacsPixmap extends EmacsHandleObject
   {
     return bitmap;
   }
+
+  @Override
+  public void
+  destroyHandle ()
+  {
+    boolean needCollect;
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+      needCollect = (bitmap.getByteCount ()
+                    >= 1024 * 512);
+    else
+      needCollect = (bitmap.getAllocationByteCount ()
+                    >= 1024 * 512);
+
+    bitmap.recycle ();
+    bitmap = null;
+
+    /* Collect the bitmap storage if the bitmap is big.  */
+    if (needCollect)
+      Runtime.getRuntime ().gc ();
+  }
 };
index 445d8ffa0236492b363831edbff44b48c154cc68..6137fd74a7f6234400d591b1dcab11f4c68a8582 100644 (file)
@@ -70,9 +70,9 @@ public class EmacsView extends ViewGroup
      event regardless of what changed.  */
   public boolean mustReportLayout;
 
-  /* If non-null, whether or not bitmaps must be recreated upon the
-     next call to getBitmap.  */
-  private Rect bitmapDirty;
+  /* 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;
@@ -80,6 +80,9 @@ public class EmacsView extends ViewGroup
   /* The current context menu.  */
   private EmacsContextMenu contextMenu;
 
+  /* The last measured width and height.  */
+  private int measuredWidth, measuredHeight;
+
   public
   EmacsView (EmacsWindow window)
   {
@@ -116,13 +119,27 @@ public class EmacsView extends ViewGroup
   {
     Bitmap oldBitmap;
 
+    if (measuredWidth == 0 || measuredHeight == 0)
+      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 front and back buffer bitmaps.  */
     bitmap
-      = Bitmap.createBitmap (bitmapDirty.width (),
-                            bitmapDirty.height (),
+      = Bitmap.createBitmap (measuredWidth,
+                            measuredHeight,
                             Bitmap.Config.ARGB_8888);
     bitmap.eraseColor (0xffffffff);
 
@@ -133,23 +150,27 @@ public class EmacsView extends ViewGroup
     if (oldBitmap != null)
       canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ());
 
-    bitmapDirty = null;
+    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 (Rect rect)
+  explicitlyDirtyBitmap ()
   {
-    if (bitmapDirty == null
-       && (bitmap == null
-           || rect.width () != bitmap.getWidth ()
-           || rect.height () != bitmap.getHeight ()))
-      bitmapDirty = rect;
+    bitmapDirty = true;
   }
 
   public synchronized Bitmap
   getBitmap ()
   {
-    if (bitmapDirty != null)
+    if (bitmapDirty || bitmap == null)
       handleDirtyBitmap ();
 
     return bitmap;
@@ -158,7 +179,7 @@ public class EmacsView extends ViewGroup
   public synchronized Canvas
   getCanvas ()
   {
-    if (bitmapDirty != null)
+    if (bitmapDirty || bitmap == null)
       handleDirtyBitmap ();
 
     return canvas;
@@ -196,8 +217,12 @@ public class EmacsView extends ViewGroup
     super.setMeasuredDimension (width, height);
   }
 
+  /* 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 synchronized void
+  protected void
   onLayout (boolean changed, int left, int top, int right,
            int bottom)
   {
@@ -213,12 +238,13 @@ public class EmacsView extends ViewGroup
        window.viewLayout (left, top, right, bottom);
       }
 
-    if (changed
-       /* Check that a change has really happened.  */
-       && (bitmapDirty == null
-           || bitmapDirty.width () != right - left
-           || bitmapDirty.height () != bottom - top))
-      bitmapDirty = new Rect (left, top, right, bottom);
+    measuredWidth = right - left;
+    measuredHeight = bottom - top;
+
+    /* Dirty the back buffer.  */
+
+    if (changed)
+      explicitlyDirtyBitmap ();
 
     for (i = 0; i < count; ++i)
       {
@@ -472,4 +498,20 @@ public class EmacsView extends ViewGroup
     contextMenu = null;
     popupActive = false;
   }
+
+  @Override
+  public synchronized void
+  onDetachedFromWindow ()
+  {
+    synchronized (this)
+      {
+       /* Recycle the bitmap and call GC.  */
+       bitmap.recycle ();
+       bitmap = null;
+       canvas = null;
+
+       /* Collect the bitmap storage; it could be large.  */
+       Runtime.getRuntime ().gc ();
+      }
+  }
 };
index 7181bc89fea7bb404dc56ab48d30f8b5ab476cbe..f5b50f11f1408d8af20a313362fae043661eb919 100644 (file)
@@ -260,7 +260,7 @@ public class EmacsWindow extends EmacsHandleObject
   {
     /* This is necessary because otherwise subsequent drawing on the
        Emacs thread may be lost.  */
-    view.explicitlyDirtyBitmap (rect);
+    view.explicitlyDirtyBitmap ();
 
     EmacsService.SERVICE.runOnUiThread (new Runnable () {
        @Override
index 9b15ea9f15ab866b8b8581f083abb99946c6ee3d..cfb79045c0b37973f762f20bdbcd2e48a9e92432 100644 (file)
@@ -26,6 +26,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <semaphore.h>
 #include <dlfcn.h>
 #include <errno.h>
+#include <math.h>
 
 #include <sys/stat.h>
 #include <sys/mman.h>
@@ -225,7 +226,6 @@ static fd_set *volatile android_pselect_readfds;
 static fd_set *volatile android_pselect_writefds;
 static fd_set *volatile android_pselect_exceptfds;
 static struct timespec *volatile android_pselect_timeout;
-static const sigset_t *volatile android_pselect_sigset;
 
 /* Value of pselect.  */
 static int android_pselect_rc;
@@ -242,8 +242,8 @@ static sem_t android_pselect_sem, android_pselect_start_sem;
 static void *
 android_run_select_thread (void *data)
 {
-  sigset_t signals, sigset;
-  int rc;
+  sigset_t signals, waitset;
+  int rc, sig;
 
   sigfillset (&signals);
 
@@ -253,6 +253,8 @@ android_run_select_thread (void *data)
                         strerror (errno));
 
   sigdelset (&signals, SIGUSR1);
+  sigemptyset (&waitset);
+  sigaddset (&waitset, SIGUSR1);
 
   while (true)
     {
@@ -262,35 +264,33 @@ android_run_select_thread (void *data)
 
       /* Get the select lock and call pselect.  */
       pthread_mutex_lock (&event_queue.select_mutex);
-
-      /* Make sure SIGUSR1 can always wake pselect up.  */
-      if (android_pselect_sigset)
-       {
-         sigset = *android_pselect_sigset;
-         sigdelset (&sigset, SIGUSR1);
-         android_pselect_sigset = &sigset;
-       }
-      else
-       android_pselect_sigset = &signals;
-
       rc = pselect (android_pselect_nfds,
                    android_pselect_readfds,
                    android_pselect_writefds,
                    android_pselect_exceptfds,
                    android_pselect_timeout,
-                   android_pselect_sigset);
+                   &signals);
       android_pselect_rc = rc;
       pthread_mutex_unlock (&event_queue.select_mutex);
 
+      /* Signal the main thread that there is now data to read.
+         It is ok to signal this condition variable without holding
+         the event queue lock, because android_select will always
+         wait for this to complete before returning.  */
+      android_pselect_completed = true;
+      pthread_cond_signal (&event_queue.read_var);
+
+      if (rc != -1 || errno != EINTR)
+       /* 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);
-
-      pthread_mutex_lock (&event_queue.mutex);
-      android_pselect_completed = true;
-      pthread_cond_signal (&event_queue.read_var);
-      pthread_mutex_unlock (&event_queue.mutex);
     }
 }
 
@@ -445,8 +445,7 @@ android_write_event (union android_event *event)
 
 int
 android_select (int nfds, fd_set *readfds, fd_set *writefds,
-               fd_set *exceptfds, struct timespec *timeout,
-               const sigset_t *sigset)
+               fd_set *exceptfds, struct timespec *timeout)
 {
   int nfds_return;
 
@@ -467,7 +466,6 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
   android_pselect_writefds = writefds;
   android_pselect_exceptfds = exceptfds;
   android_pselect_timeout = timeout;
-  android_pselect_sigset = sigset;
   pthread_mutex_unlock (&event_queue.select_mutex);
 
   /* Release the select thread.  */
@@ -3725,6 +3723,24 @@ android_build_string (Lisp_Object text)
   return string;
 }
 
+/* Do the same, except TEXT is constant string data.  */
+
+jstring
+android_build_jstring (const char *text)
+{
+  jstring string;
+
+  string = (*android_java_env)->NewStringUTF (android_java_env,
+                                             text);
+  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.  */
 
index e68e0a51fbfc8812b7e55a7da64523881b6b4b55..036e6d266fd394bab2aefd4925c1c40db7ea561a 100644 (file)
@@ -48,7 +48,7 @@ extern int ANDROID_EXPORT android_emacs_init (int, char **);
 #ifndef ANDROID_STUBIFY
 
 extern int android_select (int, fd_set *, fd_set *, fd_set *,
-                          struct timespec *, const sigset_t *);
+                          struct timespec *);
 
 extern bool android_file_access_p (const char *, int);
 extern int android_open (const char *, int, int);
@@ -86,6 +86,7 @@ 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 jstring android_build_jstring (const char *);
 extern void android_exception_check (void);
 
 extern void android_get_keysym_name (int, char *, size_t);
index 6fb4963174b3992b9d7970378c6ff91e516445d4..f65b5d3ffd12354d86da65c56817e5279c5b7204 100644 (file)
@@ -168,11 +168,17 @@ android_dismiss_menu (void *pointer)
 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)
     {
@@ -181,11 +187,11 @@ android_process_events_for_menu (int *id)
 
       /* Process pending signals.  */
       process_pending_signals ();
-
-      /* Maybe quit.  */
-      maybe_quit ();
     }
 
+  /* Restore the input block.  */
+  interrupt_input_blocked = blocked;
+
   /* Return the ID.  */
   *id = x_display_list->menu_event_id;
 }
@@ -420,9 +426,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
 
   /* Next, process events waiting for something to be selected.  */
   popup_activated_flag = 1;
-  unblock_input ();
   android_process_events_for_menu (&id);
-  block_input ();
 
   if (!id)
     /* This means no menu item was selected.  */
@@ -498,8 +502,6 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
        }
     }
 
-  Fprint (tem, Qexternal_debugging_output);
-
   unblock_input ();
   return unbind_to (count, tem);
 
@@ -508,6 +510,229 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
   return unbind_to (count, Qnil);
 }
 
+\f
+
+/* 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;)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;
+
+  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);
+  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
@@ -531,6 +756,7 @@ init_androidmenu (void)
 {
 #ifndef ANDROID_STUBIFY
   android_init_emacs_context_menu ();
+  android_init_emacs_dialog ();
 #endif
 }
 
index cc2da279bb3afbc2c781fcda0aa9d262d1bfe7f2..f19cee5b11bf447ac62d217b495dc24c5114fca1 100644 (file)
@@ -496,10 +496,17 @@ android_update_tools (struct frame *f, struct input_event *ie)
   /* Build the list of active touches.  */
   for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points;
        touchpoint; touchpoint = touchpoint->next)
-    ie->arg = Fcons (list3i (touchpoint->x,
-                            touchpoint->y,
-                            touchpoint->tool_id),
-                    ie->arg);
+    {
+      /* 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
@@ -951,6 +958,59 @@ handle_one_android_event (struct android_display_info *dpyinfo,
       touchpoint->next = FRAME_OUTPUT_DATA (any)->touch_points;
       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))
+               {
+                 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;
@@ -970,9 +1030,10 @@ handle_one_android_event (struct android_display_info *dpyinfo,
 
       touchpoint = android_find_tool (any, event->touch.pointer_id);
 
-      /* If it doesn't exist, skip processing this event.  */
+      /* If it doesn't exist or has been grabbed by the tool bar, skip
+        processing this event.  */
 
-      if (!touchpoint)
+      if (!touchpoint || touchpoint->tool_bar_p)
        goto OTHER;
 
       /* Otherwise, update the position and send the update event.  */
@@ -999,8 +1060,27 @@ handle_one_android_event (struct android_display_info *dpyinfo,
              *last = touchpoint->next;
 
              /* The tool was unlinked.  Free it and generate the
-                appropriate Emacs event.  */
+                appropriate Emacs event (assuming that it was not
+                grabbed by the tool bar).  */
              xfree (touchpoint);
+
+             if (touchpoint->tool_bar_p)
+               {
+                 /* 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;
+               }
+
              inev.ie.kind = TOUCHSCREEN_END_EVENT;
              inev.ie.timestamp = event->touch.time;
 
@@ -1227,6 +1307,9 @@ android_frame_up_to_date (struct frame *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 ();
 }
 
@@ -1513,7 +1596,7 @@ android_wait_for_event (struct frame *f, int eventtype)
        break;
 
       tmo = timespec_sub (tmo_at, time_now);
-      if (android_select (0, NULL, NULL, NULL, &tmo, NULL) == 0)
+      if (android_select (0, NULL, NULL, NULL, &tmo) == 0)
         break; /* Timeout */
     }
 
@@ -4061,6 +4144,7 @@ android_create_terminal (struct android_display_info *dpyinfo)
   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
index 9aa098771963a8b8952fbd01bdc3ecf402f25d8d..c0f862e35fb6ae819b323cb38262cdd438b8cba3 100644 (file)
@@ -148,6 +148,9 @@ struct android_touch_point
 
   /* 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
@@ -410,6 +413,9 @@ extern void android_finalize_font_entity (struct font_entity *);
 
 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);
 
@@ -417,6 +423,7 @@ extern void syms_of_androidmenu (void);
 
 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);
 
index 990b5307f142edf17b7b334a62e2930ac1905229..834049b496a62441bba80fdb7c0a4b1c1bc6ceda 100644 (file)
@@ -10380,7 +10380,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));
@@ -12185,7 +12185,9 @@ 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)},
 };
 
 static Lisp_Object
@@ -12895,6 +12897,7 @@ See also `pre-command-hook'.  */);
          "display-monitors-changed-functions");
 
   DEFSYM (Qcoding, "coding");
+  DEFSYM (Qtouchscreen, "touchscreen");
 
   Fset (Qecho_area_clear_hook, Qnil);
 
index 3f86a8e03adda15fa908c39d8a15a0611e3d67f1..26eecd48b002c96609280b68dc934a33c151689f 100644 (file)
@@ -395,8 +395,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.  */
index 111e0c80e430012bd42d576c9ceeb8e68b025874..651b5fa035b032f437ac59a2fee89dc14a44ae4c 100644 (file)
@@ -5689,7 +5689,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd,
 #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
          nfds = android_select (max_desc + 1,
                                 &Available, (check_write ? &Writeok : 0),
-                                NULL, &timeout, NULL);
+                                NULL, &timeout);
 #else
 
          /* Non-macOS HAVE_GLIB builds call thread_select in
index ee74ba0fefe100b212c24235ea35d5fdf2dc9c7b..6d58798c599a630ec3372a6b744eb29b5eb759f2 100644 (file)
@@ -129,8 +129,12 @@ _sfnt_swap32 (uint32_t *value)
 #define sfnt_swap32(what) (_sfnt_swap32 ((uint32_t *) (what)))
 
 /* Read the table directory from the file FD.  FD must currently be at
-   the start of the file, and must be seekable.  Return the table
-   directory upon success, else NULL.  */
+   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)
@@ -147,11 +151,34 @@ sfnt_read_table_directory (int fd)
 
   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);
@@ -4183,6 +4210,101 @@ sfnt_find_metadata (struct sfnt_meta_table *meta,
 
 \f
 
+/* 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;
+}
+
+\f
+
 #ifdef TEST
 
 struct sfnt_test_dcontext
@@ -4397,6 +4519,7 @@ main (int argc, char **argv)
   unsigned char *string;
   struct sfnt_name_record record;
   struct sfnt_meta_table *meta;
+  struct sfnt_ttc_header *ttc;
 
   if (argc != 2)
     return 1;
@@ -4406,8 +4529,41 @@ main (int argc, char **argv)
   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)
     {
       close (fd);
@@ -4432,9 +4588,9 @@ main (int argc, char **argv)
   for (i = 0; i < table->num_subtables; ++i)
     {
       fprintf (stderr, "Found cmap table %"PRIu32": %p\n",
-              subtables[i].offset, data);
+              subtables[i].offset, data[i]);
 
-      if (data)
+      if (data[i])
        fprintf (stderr, "  format: %"PRIu16"\n",
                 data[i]->format);
     }
@@ -4552,7 +4708,7 @@ main (int argc, char **argv)
       if (scanf ("%d %"SCNu32"", &i, &character) == EOF)
        break;
 
-      if (i >= table->num_subtables)
+      if (i < 0 || i >= table->num_subtables)
        {
          printf ("table out of range\n");
          continue;
@@ -4699,6 +4855,7 @@ main (int argc, char **argv)
   xfree (hmtx);
   xfree (name);
   xfree (meta);
+  xfree (ttc);
 
   return 0;
 }
index 91d7b261cb09e2e4ec2445ecbda9fa74035025f2..fe6b6ec3dd79907cb72c65fa0481ddae381bd79b 100644 (file)
@@ -875,6 +875,44 @@ enum sfnt_meta_data_tag
 
 \f
 
+/* 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,
+  };
+
+\f
+
 #define SFNT_CEIL_FIXED(fixed)                 \
   (!((fixed) & 0177777) ? (fixed)              \
    : ((fixed) + 0200000) & 037777600000)
@@ -960,5 +998,7 @@ 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);
+
 #endif /* TEST */
 #endif /* _SFNT_H_ */
index cddb3fd40f31db689a1b5dad7c86268bf5dd0f69..1b01a4d9be4fd3a23a6970138a01b10436df2b5d 100644 (file)
@@ -31,6 +31,17 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 #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.  */
 const char *system_font_directories[] =
   {
@@ -40,6 +51,49 @@ const char *system_font_directories[] =
 /* 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;
+
+\f
+
+/* Return a temporary buffer for storing scan lines.
+   Set BUFFER to the buffer upon success.  */
+
+#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);
+
 \f
 
 /* Scale each of the four packed bytes in P in the low 16 bits of P by
@@ -205,8 +259,6 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from,
   back_pixel &= ~0x00ff00ff;
   back_pixel |= rb >> 16 | rb << 16 | 0xff000000;
 
-  USE_SAFE_ALLOCA;
-
   prepare_face_for_display (s->f, s->face);
 
   /* Build the scanline buffer.  Figure out the bounds of the
@@ -259,7 +311,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from,
   /* Allocate enough to hold text_rectangle.height, aligned to 8
      bytes.  Then fill it with the background.  */
   stride = (text_rectangle.width * sizeof *buffer) + 7 & ~7;
-  SAFE_NALLOCA (buffer, text_rectangle.height, stride);
+  GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride);
   memset (buffer, 0, text_rectangle.height * stride);
 
   if (with_background)
@@ -327,10 +379,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from,
   /* If locking the bitmap fails, just discard the data that was
      allocated.  */
   if (!bitmap_data)
-    {
-      SAFE_FREE ();
-      return;
-    }
+    return;
 
   /* Loop over each clip rect in the GC.  */
   eassert (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);
@@ -366,8 +415,33 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from,
   android_damage_window (FRAME_ANDROID_DRAWABLE (s->f),
                         &text_rectangle);
 
-  /* Release the temporary scanline buffer.  */
-  SAFE_FREE ();
+#undef MAX_ALLOCA
+}
+
+\f
+
+/* 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;
 }
 
 \f
@@ -437,10 +511,11 @@ loaded before character sets are made available.  */)
 
       while ((dirent = readdir (dir)))
        {
-         /* If it contains (not ends with!) with .ttf, then enumerate
-            it.  */
+         /* If it contains (not ends with!) with .ttf or .ttc, then
+            enumerate it.  */
 
-         if (strstr (dirent->d_name, ".ttf"))
+         if (strstr (dirent->d_name, ".ttf")
+             || strstr (dirent->d_name, ".ttc"))
            {
              sprintf (name, "%s/%s", system_font_directories[i],
                       dirent->d_name);
index 56977622211cd5b32c58f104c620a3d5b9664895..e2d18517fcbd3c542b811c62f379e00c21ec3908 100644 (file)
@@ -77,6 +77,9 @@ struct sfnt_font_desc
   /* 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;
 };
 
 /* List of fonts.  */
@@ -426,15 +429,17 @@ sfnt_parse_style (Lisp_Object style_name, struct sfnt_font_desc *desc)
     }
 }
 
-/* Enumerate the font FILE into the list of system fonts.  Return 1 if
-   it could not be enumerated, 0 otherwise.  */
+/* 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.  */
 
-int
-sfnt_enum_font (const char *file)
+static int
+sfnt_enum_font_1 (int fd, const char *file,
+                 struct sfnt_offset_subtable *subtables,
+                 off_t offset)
 {
   struct sfnt_font_desc *desc;
-  int fd;
-  struct sfnt_offset_subtable *subtables;
   struct sfnt_head_table *head;
   struct sfnt_name_table *name;
   struct sfnt_meta_table *meta;
@@ -444,18 +449,7 @@ sfnt_enum_font (const char *file)
   desc = xzalloc (sizeof *desc + strlen (file) + 1);
   desc->path = (char *) (desc + 1);
   memcpy (desc->path, file, strlen (file) + 1);
-
-  /* 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)
-    goto bail0;
+  desc->offset = offset;
 
   /* Check that this is a TrueType font.  */
   if (subtables->scaler_type != SFNT_SCALER_TRUE
@@ -511,8 +505,6 @@ sfnt_enum_font (const char *file)
   xfree (meta);
   xfree (name);
   xfree (head);
-  xfree (subtables);
-  emacs_close (fd);
   return 0;
 
  bail3:
@@ -521,11 +513,84 @@ sfnt_enum_font (const char *file)
  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.
+
+   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)
+           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:
-  xfree (desc);
   return 1;
 }
 
@@ -1730,6 +1795,12 @@ sfntfont_open (struct frame *f, Lisp_Object font_entity,
   if (fd == -1)
     goto bail;
 
+  /* Seek to the offset specified.  */
+
+  if (desc->offset
+      && lseek (fd, desc->offset, SEEK_SET) != desc->offset)
+    goto bail;
+
   /* Read the offset subtable.  */
   subtable = sfnt_read_table_directory (fd);
 
index 9f817bb4c530c98542e0d1f81b76103a239c65bd..ca3af4a9586edb42e6a53e7c2320d96b3572fe43 100644 (file)
@@ -170,7 +170,7 @@ distclean bootstrap-clean: clean
 # Just in case.
        rm -rf lib/Makefile lib/gnulib.mk
 
-maintainer-clean: clean
+maintainer-clean: distclean bootstrap-clean
        if [ -e lib/Makefile ]; then            \
          make -C lib maintainer-clean;         \
        fi