root/java/org/gnu/emacs/EmacsWindow.java

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

DEFINITIONS

This source file includes following definitions.
  1. changeWindowBackground
  2. getGeometry
  3. destroyHandle
  4. setConsumer
  5. getAttachedConsumer
  6. viewLayout
  7. requestViewLayout
  8. resizeWindow
  9. moveWindow
  10. getWindowLayoutParams
  11. findSuitableActivityContext
  12. mapWindow
  13. unmapWindow
  14. lockCanvas
  15. damageRect
  16. swapBuffers
  17. clearWindow
  18. clearArea
  19. getBitmap
  20. getEventUnicodeChar
  21. saveUnicodeString
  22. eventModifiers
  23. onKeyDown
  24. onKeyUp
  25. onFocusChanged
  26. onActivityDetached
  27. whatButtonWasIt
  28. buttonForEvent
  29. figureChange
  30. motionEventModifiers
  31. motionEvent
  32. onTouchEvent
  33. onGenericMotionEvent
  34. reparentTo
  35. makeInputFocus
  36. raise
  37. lower
  38. getWindowGeometry
  39. noticeIconified
  40. noticeDeiconified
  41. setDontAcceptFocus
  42. setDontFocusOnMap
  43. getDontFocusOnMap
  44. translateCoordinates
  45. toggleOnScreenKeyboard
  46. lookupString
  47. setFullscreen
  48. defineCursor
  49. notifyContentRectPosition

     1 /* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 package org.gnu.emacs;
    21 
    22 import java.lang.IllegalStateException;
    23 import java.util.ArrayList;
    24 import java.util.List;
    25 import java.util.HashMap;
    26 import java.util.LinkedHashMap;
    27 import java.util.Map;
    28 
    29 import android.content.Context;
    30 
    31 import android.graphics.Rect;
    32 import android.graphics.Canvas;
    33 import android.graphics.Bitmap;
    34 import android.graphics.PixelFormat;
    35 
    36 import android.view.View;
    37 import android.view.ViewManager;
    38 import android.view.Gravity;
    39 import android.view.KeyEvent;
    40 import android.view.MotionEvent;
    41 import android.view.InputDevice;
    42 import android.view.WindowManager;
    43 
    44 import android.util.Log;
    45 
    46 import android.os.Build;
    47 
    48 /* This defines a window, which is a handle.  Windows represent a
    49    rectangular subset of the screen with their own contents.
    50 
    51    Windows either have a parent window, in which case their views are
    52    attached to the parent's view, or are "floating", in which case
    53    their views are attached to the parent activity (if any), else
    54    nothing.
    55 
    56    Views are also drawables, meaning they can accept drawing
    57    requests.  */
    58 
    59 public final class EmacsWindow extends EmacsHandleObject
    60   implements EmacsDrawable
    61 {
    62   private static final String TAG = "EmacsWindow";
    63 
    64   private static class Coordinate
    65   {
    66     /* Integral coordinate.  */
    67     int x, y;
    68 
    69     /* Button associated with the coordinate, or 0 if it is a touch
    70        event.  */
    71     int button;
    72 
    73     /* Pointer ID associated with the coordinate.  */
    74     int id;
    75 
    76     public
    77     Coordinate (int x, int y, int button, int id)
    78     {
    79       this.x = x;
    80       this.y = y;
    81       this.button = button;
    82       this.id = id;
    83     }
    84   };
    85 
    86   /* The view associated with the window.  */
    87   public EmacsView view;
    88 
    89   /* The geometry of the window.  */
    90   private Rect rect;
    91 
    92   /* The parent window, or null if it is the root window.  */
    93   public EmacsWindow parent;
    94 
    95   /* List of all children in stacking order.  This must be kept
    96      consistent with their Z order!  */
    97   public ArrayList<EmacsWindow> children;
    98 
    99   /* Map between pointer identifiers and last known position.  Used to
   100      compute which pointer changed upon a touch event.  */
   101   private HashMap<Integer, Coordinate> pointerMap;
   102 
   103   /* The window consumer currently attached, if it exists.  */
   104   private EmacsWindowAttachmentManager.WindowConsumer attached;
   105 
   106   /* The window background scratch GC.  foreground is always the
   107      window background.  */
   108   private EmacsGC scratchGC;
   109 
   110   /* The button state and keyboard modifier mask at the time of the
   111      last button press or release event.  */
   112   public int lastButtonState;
   113 
   114   /* Whether or not the window is mapped.  */
   115   private volatile boolean isMapped;
   116 
   117   /* Whether or not to ask for focus upon being mapped.  */
   118   private boolean dontFocusOnMap;
   119 
   120   /* Whether or not the window is override-redirect.  An
   121      override-redirect window always has its own system window.  */
   122   private boolean overrideRedirect;
   123 
   124   /* The window manager that is the parent of this window.  NULL if
   125      there is no such window manager.  */
   126   private WindowManager windowManager;
   127 
   128   /* The time of the last KEYCODE_VOLUME_DOWN release.  This is used
   129      to quit Emacs upon two rapid clicks of the volume down
   130      button.  */
   131   private long lastVolumeButtonRelease;
   132 
   133   /* Linked list of character strings which were recently sent as
   134      events.  */
   135   public LinkedHashMap<Integer, String> eventStrings;
   136 
   137   /* Whether or not this window is fullscreen.  */
   138   public boolean fullscreen;
   139 
   140   /* The window background pixel.  This is used by EmacsView when
   141      creating new bitmaps.  */
   142   public volatile int background;
   143 
   144   /* The position of this window relative to the root window.  */
   145   public int xPosition, yPosition;
   146 
   147   public
   148   EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
   149                int width, int height, boolean overrideRedirect)
   150   {
   151     super (handle);
   152 
   153     rect = new Rect (x, y, x + width, y + height);
   154     pointerMap = new HashMap<Integer, Coordinate> ();
   155 
   156     /* Create the view from the context's UI thread.  The window is
   157        unmapped, so the view is GONE.  */
   158     view = EmacsService.SERVICE.getEmacsView (this, View.GONE,
   159                                               parent == null);
   160     this.parent = parent;
   161     this.overrideRedirect = overrideRedirect;
   162 
   163     /* Create the list of children.  */
   164     children = new ArrayList<EmacsWindow> ();
   165 
   166     if (parent != null)
   167       {
   168         parent.children.add (this);
   169         EmacsService.SERVICE.runOnUiThread (new Runnable () {
   170             @Override
   171             public void
   172             run ()
   173             {
   174               parent.view.addView (view);
   175             }
   176           });
   177       }
   178 
   179     scratchGC = new EmacsGC ((short) 0);
   180 
   181     /* Create the map of input method-committed strings.  Keep at most
   182        ten strings in the map.  */
   183 
   184     eventStrings
   185       = new LinkedHashMap<Integer, String> () {
   186           @Override
   187           protected boolean
   188           removeEldestEntry (Map.Entry<Integer, String> entry)
   189           {
   190             return size () > 10;
   191           }
   192         };
   193   }
   194 
   195   public void
   196   changeWindowBackground (int pixel)
   197   {
   198     /* scratchGC is used as the argument to a FillRectangles req.  */
   199     scratchGC.foreground = pixel;
   200     scratchGC.markDirty (false);
   201 
   202     /* Make the background known to the view as well.  */
   203     background = pixel;
   204   }
   205 
   206   public synchronized Rect
   207   getGeometry ()
   208   {
   209     return new Rect (rect);
   210   }
   211 
   212   @Override
   213   public synchronized void
   214   destroyHandle () throws IllegalStateException
   215   {
   216     if (parent != null)
   217       parent.children.remove (this);
   218 
   219     EmacsActivity.invalidateFocus ();
   220 
   221     if (!children.isEmpty ())
   222       throw new IllegalStateException ("Trying to destroy window with "
   223                                        + "children!");
   224 
   225     /* Remove the view from its parent and make it invisible.  */
   226     EmacsService.SERVICE.runOnUiThread (new Runnable () {
   227         public void
   228         run ()
   229         {
   230           ViewManager parent;
   231           EmacsWindowAttachmentManager manager;
   232 
   233           if (EmacsActivity.focusedWindow == EmacsWindow.this)
   234             EmacsActivity.focusedWindow = null;
   235 
   236           manager = EmacsWindowAttachmentManager.MANAGER;
   237           view.setVisibility (View.GONE);
   238 
   239           /* If the window manager is set, use that instead.  */
   240           if (windowManager != null)
   241             parent = windowManager;
   242           else
   243             parent = (ViewManager) view.getParent ();
   244           windowManager = null;
   245 
   246           if (parent != null)
   247             parent.removeView (view);
   248 
   249           manager.detachWindow (EmacsWindow.this);
   250         }
   251       });
   252 
   253     super.destroyHandle ();
   254   }
   255 
   256   public void
   257   setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer)
   258   {
   259     attached = consumer;
   260   }
   261 
   262   public EmacsWindowAttachmentManager.WindowConsumer
   263   getAttachedConsumer ()
   264   {
   265     return attached;
   266   }
   267 
   268   public synchronized long
   269   viewLayout (int left, int top, int right, int bottom)
   270   {
   271     int rectWidth, rectHeight;
   272 
   273     rect.left = left;
   274     rect.top = top;
   275     rect.right = right;
   276     rect.bottom = bottom;
   277 
   278     rectWidth = right - left;
   279     rectHeight = bottom - top;
   280 
   281     /* If parent is null, use xPosition and yPosition instead of the
   282        geometry rectangle positions.  */
   283 
   284     if (parent == null)
   285       {
   286         left = xPosition;
   287         top = yPosition;
   288       }
   289 
   290     return EmacsNative.sendConfigureNotify (this.handle,
   291                                             System.currentTimeMillis (),
   292                                             left, top, rectWidth,
   293                                             rectHeight);
   294   }
   295 
   296   public void
   297   requestViewLayout ()
   298   {
   299     view.explicitlyDirtyBitmap ();
   300 
   301     EmacsService.SERVICE.runOnUiThread (new Runnable () {
   302         @Override
   303         public void
   304         run ()
   305         {
   306           if (overrideRedirect)
   307             /* Set the layout parameters again.  */
   308             view.setLayoutParams (getWindowLayoutParams ());
   309 
   310           view.mustReportLayout = true;
   311           view.requestLayout ();
   312         }
   313       });
   314   }
   315 
   316   public synchronized void
   317   resizeWindow (int width, int height)
   318   {
   319     rect.right = rect.left + width;
   320     rect.bottom = rect.top + height;
   321 
   322     requestViewLayout ();
   323   }
   324 
   325   public synchronized void
   326   moveWindow (int x, int y)
   327   {
   328     int width, height;
   329 
   330     width = rect.width ();
   331     height = rect.height ();
   332 
   333     rect.left = x;
   334     rect.top = y;
   335     rect.right = x + width;
   336     rect.bottom = y + height;
   337 
   338     requestViewLayout ();
   339   }
   340 
   341   private WindowManager.LayoutParams
   342   getWindowLayoutParams ()
   343   {
   344     WindowManager.LayoutParams params;
   345     int flags, type;
   346     Rect rect;
   347 
   348     flags = 0;
   349     rect = getGeometry ();
   350     flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
   351     flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
   352     type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
   353 
   354     params
   355       = new WindowManager.LayoutParams (rect.width (), rect.height (),
   356                                         rect.left, rect.top,
   357                                         type, flags,
   358                                         PixelFormat.RGBA_8888);
   359     params.gravity = Gravity.TOP | Gravity.LEFT;
   360     return params;
   361   }
   362 
   363   private Context
   364   findSuitableActivityContext ()
   365   {
   366     /* Find a recently focused activity.  */
   367     if (!EmacsActivity.focusedActivities.isEmpty ())
   368       return EmacsActivity.focusedActivities.get (0);
   369 
   370     /* Return the service context, which probably won't work.  */
   371     return EmacsService.SERVICE;
   372   }
   373 
   374   public synchronized void
   375   mapWindow ()
   376   {
   377     final int width, height;
   378 
   379     if (isMapped)
   380       return;
   381 
   382     isMapped = true;
   383     width = rect.width ();
   384     height = rect.height ();
   385 
   386     if (parent == null)
   387       {
   388         EmacsService.SERVICE.runOnUiThread (new Runnable () {
   389             @Override
   390             public void
   391             run ()
   392             {
   393               EmacsWindowAttachmentManager manager;
   394               WindowManager windowManager;
   395               Context ctx;
   396               Object tem;
   397               WindowManager.LayoutParams params;
   398 
   399               /* Make the view visible, first of all.  */
   400               view.setVisibility (View.VISIBLE);
   401 
   402               if (!overrideRedirect)
   403                 {
   404                   manager = EmacsWindowAttachmentManager.MANAGER;
   405 
   406                   /* If parent is the root window, notice that there are new
   407                      children available for interested activites to pick
   408                      up.  */
   409                   manager.registerWindow (EmacsWindow.this);
   410 
   411                   if (!getDontFocusOnMap ())
   412                     /* Eventually this should check no-focus-on-map.  */
   413                     view.requestFocus ();
   414                 }
   415               else
   416                 {
   417                   /* But if the window is an override-redirect window,
   418                      then:
   419 
   420                      - Find an activity that is currently active.
   421 
   422                      - Map the window as a panel on top of that
   423                        activity using the system window manager.  */
   424 
   425                   ctx = findSuitableActivityContext ();
   426                   tem = ctx.getSystemService (Context.WINDOW_SERVICE);
   427                   windowManager = (WindowManager) tem;
   428 
   429                   /* Calculate layout parameters.  */
   430                   params = getWindowLayoutParams ();
   431                   view.setLayoutParams (params);
   432 
   433                   /* Attach the view.  */
   434                   try
   435                     {
   436                       view.prepareForLayout (width, height);
   437                       windowManager.addView (view, params);
   438 
   439                       /* Record the window manager being used in the
   440                          EmacsWindow object.  */
   441                       EmacsWindow.this.windowManager = windowManager;
   442                     }
   443                   catch (Exception e)
   444                     {
   445                       Log.w (TAG,
   446                              "failed to attach override-redirect window, " + e);
   447                     }
   448                 }
   449             }
   450           });
   451       }
   452     else
   453       {
   454         /* Do the same thing as above, but don't register this
   455            window.  */
   456         EmacsService.SERVICE.runOnUiThread (new Runnable () {
   457             @Override
   458             public void
   459             run ()
   460             {
   461               /* Prior to mapping the view, set its measuredWidth and
   462                  measuredHeight to some reasonable value, in order to
   463                  avoid excessive bitmap dirtying.  */
   464 
   465               view.prepareForLayout (width, height);
   466               view.setVisibility (View.VISIBLE);
   467 
   468               if (!getDontFocusOnMap ())
   469                 view.requestFocus ();
   470             }
   471           });
   472       }
   473   }
   474 
   475   public synchronized void
   476   unmapWindow ()
   477   {
   478     if (!isMapped)
   479       return;
   480 
   481     isMapped = false;
   482 
   483     view.post (new Runnable () {
   484         @Override
   485         public void
   486         run ()
   487         {
   488           EmacsWindowAttachmentManager manager;
   489 
   490           manager = EmacsWindowAttachmentManager.MANAGER;
   491 
   492           view.setVisibility (View.GONE);
   493 
   494           /* Detach the view from the window manager if possible.  */
   495           if (windowManager != null)
   496             windowManager.removeView (view);
   497           windowManager = null;
   498 
   499           /* Now that the window is unmapped, unregister it as
   500              well.  */
   501           manager.detachWindow (EmacsWindow.this);
   502         }
   503       });
   504   }
   505 
   506   @Override
   507   public Canvas
   508   lockCanvas (EmacsGC gc)
   509   {
   510     return view.getCanvas (gc);
   511   }
   512 
   513   @Override
   514   public void
   515   damageRect (Rect damageRect)
   516   {
   517     view.damageRect (damageRect);
   518   }
   519 
   520   public void
   521   swapBuffers ()
   522   {
   523     view.swapBuffers ();
   524   }
   525 
   526   public void
   527   clearWindow ()
   528   {
   529     EmacsService.SERVICE.fillRectangle (this, scratchGC,
   530                                         0, 0, rect.width (),
   531                                         rect.height ());
   532   }
   533 
   534   public void
   535   clearArea (int x, int y, int width, int height)
   536   {
   537     EmacsService.SERVICE.fillRectangle (this, scratchGC,
   538                                         x, y, width, height);
   539   }
   540 
   541   @Override
   542   public Bitmap
   543   getBitmap ()
   544   {
   545     return view.getBitmap ();
   546   }
   547 
   548   /* event.getCharacters is used because older input methods still
   549      require it.  */
   550   @SuppressWarnings ("deprecation")
   551   public int
   552   getEventUnicodeChar (KeyEvent event, int state)
   553   {
   554     String characters;
   555 
   556     if (event.getUnicodeChar (state) != 0)
   557       return event.getUnicodeChar (state);
   558 
   559     characters = event.getCharacters ();
   560 
   561     if (characters != null && characters.length () == 1)
   562       return characters.charAt (0);
   563 
   564     return characters == null ? 0 : -1;
   565   }
   566 
   567   public void
   568   saveUnicodeString (int serial, String string)
   569   {
   570     eventStrings.put (serial, string);
   571   }
   572 
   573 
   574 
   575   /* Return the modifier mask associated with the specified keyboard
   576      input EVENT.  Replace bits corresponding to Left or Right keys
   577      with their corresponding general modifier bits.  */
   578 
   579   private int
   580   eventModifiers (KeyEvent event)
   581   {
   582     int state;
   583 
   584     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
   585       state = event.getModifiers ();
   586     else
   587       {
   588         /* Replace this with getMetaState and manual
   589            normalization.  */
   590         state = event.getMetaState ();
   591 
   592         /* Normalize the state by setting the generic modifier bit if
   593            either a left or right modifier is pressed.  */
   594 
   595         if ((state & KeyEvent.META_ALT_LEFT_ON) != 0
   596             || (state & KeyEvent.META_ALT_RIGHT_ON) != 0)
   597           state |= KeyEvent.META_ALT_MASK;
   598 
   599         if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0
   600             || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0)
   601           state |= KeyEvent.META_CTRL_MASK;
   602       }
   603 
   604     return state;
   605   }
   606 
   607   /* event.getCharacters is used because older input methods still
   608      require it.  */
   609   @SuppressWarnings ("deprecation")
   610   public void
   611   onKeyDown (int keyCode, KeyEvent event)
   612   {
   613     int state, state_1;
   614     long serial;
   615     String characters;
   616 
   617     state = eventModifiers (event);
   618 
   619     /* Ignore meta-state understood by Emacs for now, or key presses
   620        such as Ctrl+C and Meta+C will not be recognized as an ASCII
   621        key press event.  */
   622 
   623     state_1
   624       = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK
   625                   | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK);
   626 
   627     synchronized (eventStrings)
   628       {
   629         serial
   630           = EmacsNative.sendKeyPress (this.handle,
   631                                       event.getEventTime (),
   632                                       state, keyCode,
   633                                       getEventUnicodeChar (event,
   634                                                            state_1));
   635 
   636         characters = event.getCharacters ();
   637 
   638         if (characters != null && characters.length () > 1)
   639           saveUnicodeString ((int) serial, characters);
   640       }
   641   }
   642 
   643   public void
   644   onKeyUp (int keyCode, KeyEvent event)
   645   {
   646     int state, state_1;
   647     long time;
   648 
   649     /* Compute the event's modifier mask.  */
   650     state = eventModifiers (event);
   651 
   652     /* Ignore meta-state understood by Emacs for now, or key presses
   653        such as Ctrl+C and Meta+C will not be recognized as an ASCII
   654        key press event.  */
   655 
   656     state_1
   657       = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK
   658                   | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK);
   659 
   660     EmacsNative.sendKeyRelease (this.handle,
   661                                 event.getEventTime (),
   662                                 state, keyCode,
   663                                 getEventUnicodeChar (event,
   664                                                      state_1));
   665 
   666     if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
   667       {
   668         /* Check if this volume down press should quit Emacs.
   669            Most Android devices have no physical keyboard, so it
   670            is unreasonably hard to press C-g.  */
   671 
   672         time = event.getEventTime ();
   673 
   674         if (time - lastVolumeButtonRelease < 350)
   675           EmacsNative.quit ();
   676 
   677         lastVolumeButtonRelease = time;
   678       }
   679   }
   680 
   681   public void
   682   onFocusChanged (boolean gainFocus)
   683   {
   684     EmacsActivity.invalidateFocus ();
   685   }
   686 
   687   /* Notice that the activity has been detached or destroyed.
   688 
   689      ISFINISHING is set if the activity is not the main activity, or
   690      if the activity was not destroyed in response to explicit user
   691      action.  */
   692 
   693   public void
   694   onActivityDetached (boolean isFinishing)
   695   {
   696     /* Destroy the associated frame when the activity is detached in
   697        response to explicit user action.  */
   698 
   699     if (isFinishing)
   700       EmacsNative.sendWindowAction (this.handle, 0);
   701   }
   702 
   703 
   704 
   705   /* Mouse and touch event handling.
   706 
   707      Android does not conceptually distinguish between mouse events
   708      (those coming from a device whose movement affects the on-screen
   709      pointer image) and touch screen events.  Each click or touch
   710      starts a single pointer gesture sequence, and subsequent motion
   711      of the device will result in updates being reported relative to
   712      that sequence until the mouse button or touch is released.
   713 
   714      When a touch, click, or pointer motion takes place, several kinds
   715      of event can be sent:
   716 
   717      ACTION_DOWN or ACTION_POINTER_DOWN is sent with a new coordinate
   718      and an associated ``pointer ID'' identifying the event and its
   719      gesture sequence when a click or touch takes place.  Emacs is
   720      responsible for recording both the position and pointer ID of
   721      this click for the purpose of determining future changes to its
   722      position.
   723 
   724      ACTION_UP or ACTION_POINTER_UP is sent with a pointer ID when the
   725      click associated with a previous ACTION_DOWN event is released.
   726 
   727      ACTION_CANCEL (or ACTION_POINTER_UP with FLAG_CANCELED) is sent
   728      if a similar situation transpires: the window system has chosen
   729      to grab the click, and future changes to its position will no
   730      longer be reported to Emacs.
   731 
   732      ACTION_MOVE is sent if a coordinate tied to a click that has not
   733      been released changes.  Emacs processes this event by comparing
   734      each of the coordinates within the event with its recollection of
   735      those contained within prior ACTION_DOWN and ACTION_MOVE events;
   736      the pointer ID of the differing coordinate is then reported
   737      within a touch or pointer motion event along with its new
   738      position.
   739 
   740      The events described above are all sent for both touch and mouse
   741      click events.  Determining whether an ACTION_DOWN event is
   742      associated with a button event is performed by inspecting the
   743      mouse button state associated with that event.  If it contains
   744      any mouse buttons that were not contained in the button state at
   745      the time of the last ACTION_DOWN or ACTION_UP event, the
   746      coordinate contained within is assumed to be a mouse click,
   747      leading to it and associated motion or ACTION_UP events being
   748      reported as mouse button or motion events.  Otherwise, those
   749      events are reported as touch screen events, with the touch ID set
   750      to the pointer ID.
   751 
   752      In addition to the events illustrated above, Android also sends
   753      several other types of event upon select types of activity from a
   754      mouse device:
   755 
   756      ACTION_HOVER_MOVE is sent with the coordinate of the mouse
   757      pointer if it moves above a frame prior to any click taking
   758      place.  Emacs sends a mouse motion event containing the
   759      coordinate.
   760 
   761      ACTION_HOVER_ENTER and ACTION_HOVER_LEAVE are respectively sent
   762      when the mouse pointer enters and leaves a frame.  Moreover,
   763      ACTION_HOVER_LEAVE events are sent immediately before an
   764      ACTION_DOWN event associated with a mouse click.  These
   765      extraneous events are distinct in that their button states always
   766      contain an additional button compared to the button state
   767      recorded at the time of the last ACTION_UP event.
   768 
   769      On Android 6.0 and later, ACTION_BUTTON_PRESS is sent with the
   770      coordinate of the mouse pointer if a mouse click occurs,
   771      alongside a ACTION_DOWN event.  ACTION_BUTTON_RELEASE is sent
   772      with the same information upon a mouse click being released, also
   773      accompanying an ACTION_UP event.
   774 
   775      However, both types of button events are implemented in a buggy
   776      fashion and cannot be used to report button events.  */
   777 
   778   /* Look through the button state to determine what button EVENT was
   779      generated from.  DOWN is true if EVENT is a button press event,
   780      false otherwise.  Value is the X number of the button.  */
   781 
   782   private int
   783   whatButtonWasIt (MotionEvent event, boolean down)
   784   {
   785     int eventState, notIn;
   786 
   787     /* Obtain the new button state.  */
   788     eventState = event.getButtonState ();
   789 
   790     /* Compute which button is now set or no longer set.  */
   791 
   792     notIn = (down ? eventState & ~lastButtonState
   793              : lastButtonState & ~eventState);
   794 
   795     if ((notIn & (MotionEvent.BUTTON_PRIMARY
   796                   | MotionEvent.BUTTON_SECONDARY
   797                   | MotionEvent.BUTTON_TERTIARY)) == 0)
   798       /* No buttons have been pressed, so this is a touch event.  */
   799       return 0;
   800 
   801     if ((notIn & MotionEvent.BUTTON_PRIMARY) != 0)
   802       return 1;
   803 
   804     if ((notIn & MotionEvent.BUTTON_SECONDARY) != 0)
   805       return 3;
   806 
   807     if ((notIn & MotionEvent.BUTTON_TERTIARY) != 0)
   808       return 2;
   809 
   810     /* Buttons 4, 5, 6 and 7 are actually scroll wheels under X.
   811        Thus, report additional buttons starting at 8.  */
   812 
   813     if ((notIn & MotionEvent.BUTTON_BACK) != 0)
   814       return 8;
   815 
   816     if ((notIn & MotionEvent.BUTTON_FORWARD) != 0)
   817       return 9;
   818 
   819     /* Report stylus events as touch screen events.  */
   820 
   821     if ((notIn & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0)
   822       return 0;
   823 
   824     if ((notIn & MotionEvent.BUTTON_STYLUS_SECONDARY) != 0)
   825       return 0;
   826 
   827     /* Not a real value.  */
   828     return 11;
   829   }
   830 
   831   /* Return the mouse button associated with the specified ACTION_DOWN
   832      or ACTION_POINTER_DOWN EVENT.
   833 
   834      Value is 0 if no mouse button was pressed, or the X number of
   835      that mouse button.  */
   836 
   837   private int
   838   buttonForEvent (MotionEvent event)
   839   {
   840     /* ICS and earlier don't support true mouse button events, so
   841        treat all down events as touch screen events.  */
   842 
   843     if (Build.VERSION.SDK_INT
   844         < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
   845       return 0;
   846 
   847     return whatButtonWasIt (event, true);
   848   }
   849 
   850   /* Return the coordinate object associated with the specified
   851      EVENT, or null if it is not known.  */
   852 
   853   private Coordinate
   854   figureChange (MotionEvent event)
   855   {
   856     int i, truncatedX, truncatedY, pointerIndex, pointerID, count;
   857     Coordinate coordinate;
   858 
   859     /* Initialize this variable now.  */
   860     coordinate = null;
   861 
   862     switch (event.getActionMasked ())
   863       {
   864       case MotionEvent.ACTION_DOWN:
   865         /* Primary pointer pressed with index 0.  */
   866 
   867         pointerID = event.getPointerId (0);
   868         coordinate = new Coordinate ((int) event.getX (0),
   869                                      (int) event.getY (0),
   870                                      buttonForEvent (event),
   871                                      pointerID);
   872         pointerMap.put (pointerID, coordinate);
   873         break;
   874 
   875       case MotionEvent.ACTION_UP:
   876       case MotionEvent.ACTION_CANCEL:
   877         /* Primary pointer released with index 0.  */
   878         pointerID = event.getPointerId (0);
   879         coordinate = pointerMap.remove (pointerID);
   880         break;
   881 
   882       case MotionEvent.ACTION_POINTER_DOWN:
   883         /* New pointer.  Find the pointer ID from the index and place
   884            it in the map.  */
   885         pointerIndex = event.getActionIndex ();
   886         pointerID = event.getPointerId (pointerIndex);
   887         coordinate = new Coordinate ((int) event.getX (0),
   888                                      (int) event.getY (0),
   889                                      buttonForEvent (event),
   890                                      pointerID);
   891         pointerMap.put (pointerID, coordinate);
   892         break;
   893 
   894       case MotionEvent.ACTION_POINTER_UP:
   895         /* Pointer removed.  Remove it from the map.  */
   896         pointerIndex = event.getActionIndex ();
   897         pointerID = event.getPointerId (pointerIndex);
   898         coordinate = pointerMap.remove (pointerID);
   899         break;
   900 
   901       default:
   902 
   903         /* Loop through each pointer in the event.  */
   904 
   905         count = event.getPointerCount ();
   906         for (i = 0; i < count; ++i)
   907           {
   908             pointerID = event.getPointerId (i);
   909 
   910             /* Look up that pointer in the map.  */
   911             coordinate = pointerMap.get (pointerID);
   912 
   913             if (coordinate != null)
   914               {
   915                 /* See if coordinates have changed.  */
   916                 truncatedX = (int) event.getX (i);
   917                 truncatedY = (int) event.getY (i);
   918 
   919                 if (truncatedX != coordinate.x
   920                     || truncatedY != coordinate.y)
   921                   {
   922                     /* The pointer changed.  Update the coordinate and
   923                        break out of the loop.  */
   924                     coordinate.x = truncatedX;
   925                     coordinate.y = truncatedY;
   926 
   927                     break;
   928                   }
   929               }
   930           }
   931 
   932         /* Set coordinate to NULL if the loop failed to find any
   933            matching pointer.  */
   934 
   935         if (i == count)
   936           coordinate = null;
   937       }
   938 
   939     /* Return the pointer ID.  */
   940     return coordinate;
   941   }
   942 
   943   /* Return the modifier mask associated with the specified motion
   944      EVENT.  Replace bits corresponding to Left or Right keys with
   945      their corresponding general modifier bits.  */
   946 
   947   private int
   948   motionEventModifiers (MotionEvent event)
   949   {
   950     int state;
   951 
   952     state = event.getMetaState ();
   953 
   954     /* Normalize the state by setting the generic modifier bit if
   955        either a left or right modifier is pressed.  */
   956 
   957     if ((state & KeyEvent.META_ALT_LEFT_ON) != 0
   958         || (state & KeyEvent.META_ALT_RIGHT_ON) != 0)
   959       state |= KeyEvent.META_ALT_MASK;
   960 
   961     if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0
   962         || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0)
   963       state |= KeyEvent.META_CTRL_MASK;
   964 
   965     return state;
   966   }
   967 
   968   /* Process a single ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_UP,
   969      ACTION_POINTER_UP, ACTION_CANCEL, or ACTION_MOVE event.
   970 
   971      Ascertain which coordinate changed and send an appropriate mouse
   972      or touch screen event.  */
   973 
   974   private void
   975   motionEvent (MotionEvent event)
   976   {
   977     Coordinate coordinate;
   978     int modifiers;
   979     long time;
   980 
   981     /* Find data associated with this event's pointer.  Namely, its
   982        current location, whether or not a change has taken place, and
   983        whether or not it is a button event.  */
   984 
   985     coordinate = figureChange (event);
   986 
   987     if (coordinate == null)
   988       return;
   989 
   990     time = event.getEventTime ();
   991 
   992     if (coordinate.button != 0)
   993       {
   994         /* This event is tied to a mouse click, so report mouse motion
   995            and button events.  */
   996 
   997         modifiers = motionEventModifiers (event);
   998 
   999         switch (event.getAction ())
  1000           {
  1001           case MotionEvent.ACTION_POINTER_DOWN:
  1002           case MotionEvent.ACTION_DOWN:
  1003             EmacsNative.sendButtonPress (this.handle, coordinate.x,
  1004                                          coordinate.y, time, modifiers,
  1005                                          coordinate.button);
  1006             break;
  1007 
  1008           case MotionEvent.ACTION_POINTER_UP:
  1009           case MotionEvent.ACTION_UP:
  1010           case MotionEvent.ACTION_CANCEL:
  1011             EmacsNative.sendButtonRelease (this.handle, coordinate.x,
  1012                                            coordinate.y, time, modifiers,
  1013                                            coordinate.button);
  1014             break;
  1015 
  1016           case MotionEvent.ACTION_MOVE:
  1017             EmacsNative.sendMotionNotify (this.handle, coordinate.x,
  1018                                           coordinate.y, time);
  1019             break;
  1020           }
  1021       }
  1022     else
  1023       {
  1024         /* This event is a touch event, and the touch ID is the
  1025            pointer ID.  */
  1026 
  1027         switch (event.getActionMasked ())
  1028           {
  1029           case MotionEvent.ACTION_DOWN:
  1030           case MotionEvent.ACTION_POINTER_DOWN:
  1031             /* Touch down event.  */
  1032             EmacsNative.sendTouchDown (this.handle, coordinate.x,
  1033                                        coordinate.y, time,
  1034                                        coordinate.id, 0);
  1035             break;
  1036 
  1037           case MotionEvent.ACTION_UP:
  1038           case MotionEvent.ACTION_POINTER_UP:
  1039             /* Touch up event.  */
  1040             EmacsNative.sendTouchUp (this.handle, coordinate.x,
  1041                                      coordinate.y, time,
  1042                                      coordinate.id, 0);
  1043             break;
  1044 
  1045           case MotionEvent.ACTION_CANCEL:
  1046             /* Touch sequence cancellation event.  */
  1047             EmacsNative.sendTouchUp (this.handle, coordinate.x,
  1048                                      coordinate.y, time,
  1049                                      coordinate.id,
  1050                                      1 /* ANDROID_TOUCH_SEQUENCE_CANCELED */);
  1051             break;
  1052 
  1053           case MotionEvent.ACTION_MOVE:
  1054             /* Pointer motion event.  */
  1055             EmacsNative.sendTouchMove (this.handle, coordinate.x,
  1056                                        coordinate.y, time,
  1057                                        coordinate.id, 0);
  1058             break;
  1059           }
  1060       }
  1061 
  1062     if (Build.VERSION.SDK_INT
  1063         < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  1064       return;
  1065 
  1066     /* Now update the button state.  */
  1067     lastButtonState = event.getButtonState ();
  1068     return;
  1069   }
  1070 
  1071   public boolean
  1072   onTouchEvent (MotionEvent event)
  1073   {
  1074     switch (event.getActionMasked ())
  1075       {
  1076       case MotionEvent.ACTION_DOWN:
  1077       case MotionEvent.ACTION_POINTER_DOWN:
  1078       case MotionEvent.ACTION_UP:
  1079       case MotionEvent.ACTION_POINTER_UP:
  1080       case MotionEvent.ACTION_CANCEL:
  1081       case MotionEvent.ACTION_MOVE:
  1082         motionEvent (event);
  1083         return true;
  1084       }
  1085 
  1086     return false;
  1087   }
  1088 
  1089   public boolean
  1090   onGenericMotionEvent (MotionEvent event)
  1091   {
  1092     switch (event.getAction ())
  1093       {
  1094       case MotionEvent.ACTION_HOVER_ENTER:
  1095         EmacsNative.sendEnterNotify (this.handle, (int) event.getX (),
  1096                                      (int) event.getY (),
  1097                                      event.getEventTime ());
  1098         return true;
  1099 
  1100       case MotionEvent.ACTION_HOVER_MOVE:
  1101         EmacsNative.sendMotionNotify (this.handle, (int) event.getX (),
  1102                                       (int) event.getY (),
  1103                                       event.getEventTime ());
  1104         return true;
  1105 
  1106       case MotionEvent.ACTION_HOVER_EXIT:
  1107 
  1108         /* If the exit event comes from a button press, its button
  1109            state will have extra bits compared to the last known
  1110            button state.  Since the exit event will interfere with
  1111            tool bar button presses, ignore such splurious events.  */
  1112 
  1113         if ((event.getButtonState () & ~lastButtonState) == 0)
  1114           EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (),
  1115                                        (int) event.getY (),
  1116                                        event.getEventTime ());
  1117 
  1118         return true;
  1119 
  1120       case MotionEvent.ACTION_DOWN:
  1121       case MotionEvent.ACTION_POINTER_DOWN:
  1122       case MotionEvent.ACTION_UP:
  1123       case MotionEvent.ACTION_POINTER_UP:
  1124       case MotionEvent.ACTION_CANCEL:
  1125       case MotionEvent.ACTION_MOVE:
  1126         /* MotionEvents may either be sent to onGenericMotionEvent or
  1127            onTouchEvent depending on if Android thinks it is a mouse
  1128            event or not, but we detect them ourselves.  */
  1129         motionEvent (event);
  1130         return true;
  1131 
  1132       case MotionEvent.ACTION_SCROLL:
  1133         /* Send a scroll event with the specified deltas.  */
  1134         EmacsNative.sendWheel (this.handle, (int) event.getX (),
  1135                                (int) event.getY (),
  1136                                event.getEventTime (),
  1137                                motionEventModifiers (event),
  1138                                event.getAxisValue (MotionEvent.AXIS_HSCROLL),
  1139                                event.getAxisValue (MotionEvent.AXIS_VSCROLL));
  1140         return true;
  1141       }
  1142 
  1143     return false;
  1144   }
  1145 
  1146 
  1147 
  1148   public synchronized void
  1149   reparentTo (final EmacsWindow otherWindow, int x, int y)
  1150   {
  1151     int width, height;
  1152 
  1153     /* Reparent this window to the other window.  */
  1154 
  1155     if (parent != null)
  1156       parent.children.remove (this);
  1157 
  1158     if (otherWindow != null)
  1159       otherWindow.children.add (this);
  1160 
  1161     parent = otherWindow;
  1162 
  1163     /* Move this window to the new location.  */
  1164     width = rect.width ();
  1165     height = rect.height ();
  1166     rect.left = x;
  1167     rect.top = y;
  1168     rect.right = x + width;
  1169     rect.bottom = y + height;
  1170 
  1171     /* Now do the work necessary on the UI thread to reparent the
  1172        window.  */
  1173     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1174         @Override
  1175         public void
  1176         run ()
  1177         {
  1178           EmacsWindowAttachmentManager manager;
  1179           ViewManager parent;
  1180 
  1181           /* First, detach this window if necessary.  */
  1182           manager = EmacsWindowAttachmentManager.MANAGER;
  1183           manager.detachWindow (EmacsWindow.this);
  1184 
  1185           /* Also unparent this view.  */
  1186 
  1187           /* If the window manager is set, use that instead.  */
  1188           if (windowManager != null)
  1189             parent = windowManager;
  1190           else
  1191             parent = (ViewManager) view.getParent ();
  1192           windowManager = null;
  1193 
  1194           if (parent != null)
  1195             parent.removeView (view);
  1196 
  1197           /* Next, either add this window as a child of the new
  1198              parent's view, or make it available again.  */
  1199           if (otherWindow != null)
  1200             otherWindow.view.addView (view);
  1201           else if (EmacsWindow.this.isMapped)
  1202             manager.registerWindow (EmacsWindow.this);
  1203 
  1204           /* Request relayout.  */
  1205           view.requestLayout ();
  1206         }
  1207       });
  1208   }
  1209 
  1210   public void
  1211   makeInputFocus (long time)
  1212   {
  1213     /* TIME is currently ignored.  Request the input focus now.  */
  1214 
  1215     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1216         @Override
  1217         public void
  1218         run ()
  1219         {
  1220           view.requestFocus ();
  1221         }
  1222       });
  1223   }
  1224 
  1225   public synchronized void
  1226   raise ()
  1227   {
  1228     /* This does nothing here.  */
  1229     if (parent == null)
  1230       return;
  1231 
  1232     /* Remove and add this view again.  */
  1233     parent.children.remove (this);
  1234     parent.children.add (this);
  1235 
  1236     /* Request a relayout.  */
  1237     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1238         @Override
  1239         public void
  1240         run ()
  1241         {
  1242           view.raise ();
  1243         }
  1244       });
  1245   }
  1246 
  1247   public synchronized void
  1248   lower ()
  1249   {
  1250     /* This does nothing here.  */
  1251     if (parent == null)
  1252       return;
  1253 
  1254     /* Remove and add this view again.  */
  1255     parent.children.remove (this);
  1256     parent.children.add (this);
  1257 
  1258     /* Request a relayout.  */
  1259     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1260         @Override
  1261         public void
  1262         run ()
  1263         {
  1264           view.lower ();
  1265         }
  1266       });
  1267   }
  1268 
  1269   public synchronized int[]
  1270   getWindowGeometry ()
  1271   {
  1272     int[] array;
  1273 
  1274     array = new int[4];
  1275 
  1276     array[0] = parent != null ? rect.left : xPosition;
  1277     array[1] = parent != null ? rect.top : yPosition;
  1278     array[2] = rect.width ();
  1279     array[3] = rect.height ();
  1280 
  1281     return array;
  1282   }
  1283 
  1284   public void
  1285   noticeIconified ()
  1286   {
  1287     EmacsNative.sendIconified (this.handle);
  1288   }
  1289 
  1290   public void
  1291   noticeDeiconified ()
  1292   {
  1293     EmacsNative.sendDeiconified (this.handle);
  1294   }
  1295 
  1296   public synchronized void
  1297   setDontAcceptFocus (final boolean dontAcceptFocus)
  1298   {
  1299     /* Update the view's focus state.  */
  1300     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1301         @Override
  1302         public void
  1303         run ()
  1304         {
  1305           view.setFocusable (!dontAcceptFocus);
  1306           view.setFocusableInTouchMode (!dontAcceptFocus);
  1307         }
  1308       });
  1309   }
  1310 
  1311   public synchronized void
  1312   setDontFocusOnMap (final boolean dontFocusOnMap)
  1313   {
  1314     this.dontFocusOnMap = dontFocusOnMap;
  1315   }
  1316 
  1317   public synchronized boolean
  1318   getDontFocusOnMap ()
  1319   {
  1320     return dontFocusOnMap;
  1321   }
  1322 
  1323   public int[]
  1324   translateCoordinates (int x, int y)
  1325   {
  1326     int[] array;
  1327 
  1328     /* This is supposed to translate coordinates to the root
  1329        window.  */
  1330     array = new int[2];
  1331     EmacsService.SERVICE.getLocationOnScreen (view, array);
  1332 
  1333     /* Now, the coordinates of the view should be in array.  Offset X
  1334        and Y by them.  */
  1335     array[0] += x;
  1336     array[1] += y;
  1337 
  1338     /* Return the resulting coordinates.  */
  1339     return array;
  1340   }
  1341 
  1342   public void
  1343   toggleOnScreenKeyboard (final boolean on)
  1344   {
  1345     /* Even though InputMethodManager functions are thread safe,
  1346        `showOnScreenKeyboard' etc must be called from the UI thread in
  1347        order to avoid deadlocks if the calls happen in tandem with a
  1348        call to a synchronizing function within
  1349        `onCreateInputConnection'.  */
  1350 
  1351     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1352         @Override
  1353         public void
  1354         run ()
  1355         {
  1356           if (on)
  1357             view.showOnScreenKeyboard ();
  1358           else
  1359             view.hideOnScreenKeyboard ();         
  1360         }
  1361       });
  1362   }
  1363 
  1364   public String
  1365   lookupString (int eventSerial)
  1366   {
  1367     String any;
  1368 
  1369     synchronized (eventStrings)
  1370       {
  1371         any = eventStrings.remove (eventSerial);
  1372       }
  1373 
  1374     return any;
  1375   }
  1376 
  1377   public void
  1378   setFullscreen (final boolean isFullscreen)
  1379   {
  1380     EmacsService.SERVICE.runOnUiThread (new Runnable () {
  1381         @Override
  1382         public void
  1383         run ()
  1384         {
  1385           EmacsActivity activity;
  1386           Object tem;
  1387 
  1388           fullscreen = isFullscreen;
  1389           tem = getAttachedConsumer ();
  1390 
  1391           if (tem != null)
  1392             {
  1393               activity = (EmacsActivity) tem;
  1394               activity.syncFullscreenWith (EmacsWindow.this);
  1395             }
  1396         }
  1397       });
  1398   }
  1399 
  1400   public void
  1401   defineCursor (final EmacsCursor cursor)
  1402   {
  1403     /* Don't post this message if pointer icons aren't supported.  */
  1404 
  1405     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
  1406       view.post (new Runnable () {
  1407           @Override
  1408           public void
  1409           run ()
  1410           {
  1411             if (cursor != null)
  1412               view.setPointerIcon (cursor.icon);
  1413             else
  1414               view.setPointerIcon (null);
  1415           }
  1416         });
  1417   }
  1418 
  1419   public synchronized void
  1420   notifyContentRectPosition (int xPosition, int yPosition)
  1421   {
  1422     Rect geometry;
  1423 
  1424     /* Ignore these notifications if not a child of the root
  1425        window.  */
  1426     if (parent != null)
  1427       return;
  1428 
  1429     /* xPosition and yPosition are the position of this window
  1430        relative to the screen.  Set them and request a ConfigureNotify
  1431        event.  */
  1432 
  1433     if (this.xPosition != xPosition
  1434         || this.yPosition != yPosition)
  1435       {
  1436         this.xPosition = xPosition;
  1437         this.yPosition = yPosition;
  1438 
  1439         EmacsNative.sendConfigureNotify (this.handle,
  1440                                          System.currentTimeMillis (),
  1441                                          xPosition, yPosition,
  1442                                          rect.width (), rect.height ());
  1443       }
  1444   }
  1445 };

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