1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-02-25 17:31:04 -08:00

Update Android port

* Makefile.in (java): Depend on info.
(MAKEFILE_NAME):
(config.status): Remove unneeded changes.
* configure.ac (BUILD_DETAILS, ANDROID_STUBIFY): Don't require a
C++ compiler on Android.
* java/AndroidManifest.xml: <EmacsActivity>: Set launchMode
appropriately.  <EmacsMultitaskActivity>: New activity.
* java/Makefile.in (CROSS_BINS): Add EmacsClient.
* java/org/gnu/emacs/EmacsActivity.java (EmacsActivity)
(onCreate): Use the window attachment manager.
* java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea)
(paintTo): Implement clip masks correctly.
* java/org/gnu/emacs/EmacsDrawRectangle.java (getRect, paintTo):
Fix damage tracking rectangles.
* java/org/gnu/emacs/EmacsFontDriver.java (FontSpec, toString):
New function.
(FontMetrics, EmacsFontDriver): Fix signature of textExtents.
* java/org/gnu/emacs/EmacsMultitaskActivity.java
(EmacsMultitaskActivity): New file.
* java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
functions sendFocusIn, sendFocusOut, sendWindowAction.
* java/org/gnu/emacs/EmacsPaintQueue.java (run): Fix clipping
handling.
* java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): Add
constructor for mutable pixmaps.
* java/org/gnu/emacs/EmacsSdk23FontDriver.java
(EmacsSdk23FontDriver): New file.
* java/org/gnu/emacs/EmacsSdk7FontDriver.java
(EmacsSdk7FontDriver, Sdk7Typeface, Sdk7FontEntity, Sdk7FontObject)
(checkMatch, hasChar, encodeChar): Implement text display and
fix font metrics semantics.

* java/org/gnu/emacs/EmacsService.java (EmacsService): Remove
availableChildren.
(getLibraryDirectory, onCreate): Pass pixel density to Emacs.
(clearArea): Fix arguments.  Switch to using the window
attachment manager.
* java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged)
(surfaceCreated): Flip buffers on surface attachment.
* java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers):
New argument FORCE.  Always swap if it is true.
(onKeyMultiple, onFocusChanged): New functions.

* java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, destroyHandle)
(run): Switch to using the window attachment manager.
* java/org/gnu/emacs/EmacsWindowAttachmentManager.java
(EmacsWindowAttachmentManager): New file.

* lisp/cus-edit.el (custom-button, custom-button-mouse)
(custom-button-pressed):
* lisp/faces.el (tool-bar): Define faces correctly on Android.
* src/android.c (struct android_emacs_pixmap): Add mutable
constructor.
(struct android_emacs_drawable): New structure.
(android_write_event): Check if event queue hasn't yet been
initialized.
(android_select): Set errno to EINTR if pselect fails.
(android_close): Remove unused debugging code.
(android_get_home_directory): New function.
(Java_org_gnu_emacs_EmacsNative_setEmacsParams): Set pixel
density and compute game path.
(android_init_emacs_drawable): New function.
(Java_org_gnu_emacs_EmacsNative_sendKeyPress): New argument
`unicode_char'.  Pass it in events.
(Java_org_gnu_emacs_EmacsNative_sendKeyRelease): Likewise.
(Java_org_gnu_emacs_EmacsNative_sendFocusIn)
(Java_org_gnu_emacs_EmacsNative_sendFocusOut)
(Java_org_gnu_emacs_EmacsNative_sendWindowAction): New
functions.
(android_resolve_handle): Export function.
(android_change_gc): Clear clip rects under the right
circumstances.  Set right clip mask field.
(android_create_pixmap_from_bitmap_data): Use correct alpha
channels.
(android_create_pixmap): Create mutable pixmap and avoid
redundant color array allocation.
(android_create_bitmap_from_data, android_create_image)
(android_destroy_image, android_put_pixel, android_get_pixel)
(android_get_image, android_put_image, faccessat): New
functions.

* src/android.h: Update prototypes.

* src/androidfns.c (android_default_font_parameter): Prefer
monospace to Droid Sans Mono.
* src/androidfont.c (struct android_emacs_font_driver): New
method `draw'.
(struct android_emacs_font_spec): New field `dpi'.
(struct androidfont_info): Add font metrics cache.
(android_init_font_driver, android_init_font_spec): Adjust
accordingly.
(androidfont_from_lisp, androidfont_from_java): Handle new
fields.
(androidfont_draw): Implement function.
(androidfont_open_font): Set pixel size correctly.
(androidfont_close_font): Free metrics cache.
(androidfont_cache_text_extents)
(androidfont_check_cached_extents): New functions.
(androidfont_text_extents): Cache glyph metrics somewhere for
future use.
(androidfont_list_family): Implement function.

* src/androidgui.h (enum android_event_type): New focus and
window action events.
(enum android_modifier_mask): New masks.
(struct android_key_event): New field `unicode_char'.
(ANDROID_IS_MODIFIER_KEY): Newmacro.
(struct android_focus_event, struct
android_window_action_event): New structs.
(union android_event): Add new fields.
(enum android_image_format, struct android_image): New enums and
structs.

* src/androidterm.c (android_android_to_emacs_modifiers)
(android_emacs_to_android_modifiers, android_lower_frame)
(android_raise_frame, android_new_focus_frame)
(android_focus_changed, android_detect_focus_change): New
functions.
(handle_one_android_event): Implement focus and key event
handling.
(android_frame_rehighlight): New function.
(android_frame_raise_lower): Implement accordingly.
(android_make_frame_invisible): Clear highlight_frame if
required.
(android_free_frame_resources): Clear x_focus_event_frame if
required.
(android_draw_fringe_bitmap, android_draw_image_foreground)
(android_draw_image_foreground_1)
(android_draw_image_glyph_string): Remove unnecessary code.
(android_create_terminal, android_term_init): Set the baud rate
to something sensible.
* src/androidterm.h (struct android_bitmap_record): Make
structure the same as on X.
(struct android_display_info): New focus tracking fields.
(struct android_output): Likewise.
* src/dispextern.h (struct image): Add ximg and mask_img on
Android.

* src/emacs.c (android_emacs_init): Fix argc sorting iteration.

* src/fileio.c (user_homedir):
(get_homedir): Implement correctly on Android.

* src/font.h (PT_PER_INCH): Define correctly on Android.

* src/fringe.c (X, swap_nibble, init_fringe_bitmap): Swap fringe
bitmaps correctly on Android.

* src/image.c (GET_PIXEL, image_create_bitmap_from_data)
(image_create_bitmap_from_file, free_bitmap_record)
(image_unget_x_image_or_dc, struct image_type)
(prepare_image_for_display, image_clear_image_1)
(image_size_in_bytes, x_check_image_size)
(x_create_x_image_and_pixmap, x_destroy_x_image)
(image_check_image_size, image_create_x_image_and_pixmap_1)
(image_destroy_x_image, gui_put_x_image, image_put_x_image)
(image_get_x_image, image_unget_x_image)
(Create_Pixmap_From_Bitmap_Data, image_pixmap_draw_cross)
(MaskForeground, image_types, syms_of_image): Implement all of
the above on Android in terms of an API very similar to X.

* src/keyboard.c (FUNCTION_KEY_OFFSET, lispy_function_keys):
Define on Android to something sensible.

* src/lread.c (build_load_history): Fix problem.
This commit is contained in:
Po Lu 2023-01-02 21:38:19 +08:00
parent fd074f3133
commit a32963e11f
36 changed files with 2435 additions and 693 deletions

View file

@ -32,65 +32,114 @@ import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
public class EmacsActivity extends Activity
implements EmacsWindowAttachmentManager.WindowConsumer
{
public static final String TAG = "EmacsActivity";
/* List of all activities that do not have an associated
EmacsWindow. */
public static List<EmacsActivity> availableActivities;
/* The currently attached EmacsWindow, or null if none. */
private EmacsWindow window;
/* The frame layout associated with the activity. */
private FrameLayout layout;
/* List of activities with focus. */
private static List<EmacsActivity> focusedActivities;
/* The currently focused window. */
public static EmacsWindow focusedWindow;
static
{
/* Set up the list of available activities. */
availableActivities = new ArrayList<EmacsActivity> ();
focusedActivities = new ArrayList<EmacsActivity> ();
};
public static void
invalidateFocus1 (EmacsWindow window)
{
if (window.view.isFocused ())
focusedWindow = window;
for (EmacsWindow child : window.children)
invalidateFocus1 (window);
}
public static void
invalidateFocus ()
{
EmacsWindow oldFocus;
/* Walk through each focused activity and assign the window focus
to the bottom-most focused window within. Record the old focus
as well. */
oldFocus = focusedWindow;
focusedWindow = null;
for (EmacsActivity activity : focusedActivities)
{
if (activity.window != null)
invalidateFocus1 (activity.window);
}
/* Send focus in- and out- events to the previous and current
focus. */
if (oldFocus != null)
EmacsNative.sendFocusOut (oldFocus.handle,
System.currentTimeMillis ());
if (focusedWindow != null)
EmacsNative.sendFocusIn (focusedWindow.handle,
System.currentTimeMillis ());
}
@Override
public void
attachChild (EmacsWindow child)
detachWindow ()
{
if (window == null)
Log.w (TAG, "detachWindow called, but there is no window");
else
{
/* Clear the window's pointer to this activity and remove the
window's view. */
window.setConsumer (null);
layout.removeView (window.view);
window = null;
invalidateFocus ();
}
}
@Override
public void
attachWindow (EmacsWindow child)
{
if (window != null)
throw new IllegalStateException ("trying to attach window when one"
+ " already exists");
/* Record and attach the view. */
window = child;
layout.addView (window.view);
child.setConsumer (this);
/* Remove the objects from the lists of what is available. */
EmacsService.availableChildren.remove (child);
availableActivities.remove (this);
/* Now set child->activity. */
child.setActivity (this);
/* Invalidate the focus. */
invalidateFocus ();
}
/* Make this activity available for future windows to attach
again. */
@Override
public void
makeAvailable ()
destroy ()
{
window = null;
finish ();
}
for (EmacsWindow iterWindow
: EmacsService.availableChildren)
{
synchronized (iterWindow)
{
if (!iterWindow.isDestroyed ())
attachChild (iterWindow);
return;
}
}
availableActivities.add (this);
@Override
public EmacsWindow
getAttachedWindow ()
{
return window;
}
@Override
@ -109,38 +158,38 @@ public class EmacsActivity extends Activity
/* Set it as the content view. */
setContentView (layout);
/* Make the activity available before starting the
service. */
makeAvailable ();
if (EmacsService.SERVICE == null)
/* Start the Emacs service now. */
startService (new Intent (this, EmacsService.class));
/* Add this activity to the list of available activities. */
EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
super.onCreate (savedInstanceState);
}
@Override
public void
onStop ()
onDestroy ()
{
/* The activity is no longer visible. If there is a window
attached, detach it. */
/* The activity will die shortly hereafter. If there is a window
attached, close it now. */
Log.d (TAG, "onDestroy " + this);
EmacsWindowAttachmentManager.MANAGER.removeWindowConsumer (this);
focusedActivities.remove (this);
invalidateFocus ();
super.onDestroy ();
}
if (window != null)
{
layout.removeView (window.view);
@Override
public void
onWindowFocusChanged (boolean isFocused)
{
if (isFocused && !focusedActivities.contains (this))
focusedActivities.add (this);
else
focusedActivities.remove (this);
/* Notice that the window is already available too. But do
not call noticeAvailableChild; that might assign it to some
other activity, which behaves badly. */
EmacsService.availableChildren.add (window);
window = null;
}
/* Finally, remove this activity from the list of available
activities. */
availableActivities.remove (this);
super.onStop ();
invalidateFocus ();
}
};

View file

@ -32,19 +32,22 @@ public class EmacsCopyArea implements EmacsPaintReq
private int src_x, src_y, dest_x, dest_y, width, height;
private EmacsDrawable destination, source;
private EmacsGC immutableGC;
private static Xfermode xorAlu, srcInAlu;
private static Xfermode xorAlu, srcInAlu, overAlu;
static
{
overAlu = new PorterDuffXfermode (Mode.SRC_OVER);
xorAlu = new PorterDuffXfermode (Mode.XOR);
srcInAlu = new PorterDuffXfermode (Mode.SRC_IN);
};
public
EmacsCopyArea (EmacsDrawable destination, EmacsDrawable source,
EmacsCopyArea (EmacsDrawable source, EmacsDrawable destination,
int src_x, int src_y, int width, int height,
int dest_x, int dest_y, EmacsGC immutableGC)
{
Bitmap bitmap;
this.destination = destination;
this.source = source;
this.src_x = src_x;
@ -71,6 +74,16 @@ public class EmacsCopyArea implements EmacsPaintReq
return destination;
}
private void
insetRectBy (Rect rect, int left, int top, int right,
int bottom)
{
rect.left += left;
rect.top += top;
rect.right -= right;
rect.bottom -= bottom;
}
@Override
public EmacsGC
getGC ()
@ -86,16 +99,45 @@ public class EmacsCopyArea implements EmacsPaintReq
Bitmap bitmap;
Paint maskPaint;
Canvas maskCanvas;
Bitmap maskBitmap;
Rect rect, srcRect;
Bitmap srcBitmap, maskBitmap, clipBitmap;
Rect rect, maskRect, srcRect, dstRect, maskDestRect;
boolean needFill;
/* TODO implement stippling. */
if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
return;
alu = immutableGC.function;
/* A copy must be created or drawBitmap could end up overwriting
itself. */
srcBitmap = source.getBitmap ();
/* If srcBitmap is out of bounds, then adjust the source rectangle
to be within bounds. Note that tiling on windows with
backgrounds is unimplemented. */
if (src_x < 0)
{
width += src_x;
dest_x -= src_x;
src_x = 0;
}
if (src_y < 0)
{
height += src_y;
dest_y -= src_y;
src_y = 0;
}
if (src_x + width > srcBitmap.getWidth ())
width = srcBitmap.getWidth () - src_x;
if (src_y + height > srcBitmap.getHeight ())
height = srcBitmap.getHeight () - src_y;
rect = getRect ();
bitmap = source.getBitmap ();
if (alu == EmacsGC.GC_COPY)
paint.setXfermode (null);
@ -103,29 +145,76 @@ public class EmacsCopyArea implements EmacsPaintReq
paint.setXfermode (xorAlu);
if (immutableGC.clip_mask == null)
canvas.drawBitmap (bitmap, new Rect (src_x, src_y,
src_x + width,
src_y + height),
rect, paint);
{
bitmap = Bitmap.createBitmap (srcBitmap,
src_x, src_y, width,
height);
canvas.drawBitmap (bitmap, null, rect, paint);
}
else
{
maskPaint = new Paint ();
srcRect = new Rect (0, 0, rect.width (),
rect.height ());
maskBitmap
= immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888,
true);
/* Drawing with a clip mask involves calculating the
intersection of the clip mask with the dst rect, and
extrapolating the corresponding part of the src rect. */
clipBitmap = immutableGC.clip_mask.bitmap;
dstRect = new Rect (dest_x, dest_y,
dest_x + width,
dest_y + height);
maskRect = new Rect (immutableGC.clip_x_origin,
immutableGC.clip_y_origin,
(immutableGC.clip_x_origin
+ clipBitmap.getWidth ()),
(immutableGC.clip_y_origin
+ clipBitmap.getHeight ()));
clipBitmap = immutableGC.clip_mask.bitmap;
if (maskBitmap == null)
if (!maskRect.setIntersect (dstRect, maskRect))
/* There is no intersection between the clip mask and the
dest rect. */
return;
maskPaint.setXfermode (srcInAlu);
/* Now figure out which part of the source corresponds to
maskRect and return it relative to srcBitmap. */
srcRect = new Rect (src_x, src_y, src_x + width,
src_y + height);
insetRectBy (srcRect, maskRect.left - dstRect.left,
maskRect.top - dstRect.top,
maskRect.right - dstRect.right,
maskRect.bottom - dstRect.bottom);
/* Finally, create a temporary bitmap that is the size of
maskRect. */
maskBitmap
= Bitmap.createBitmap (maskRect.width (), maskRect.height (),
Bitmap.Config.ARGB_8888);
/* Draw the mask onto the maskBitmap. */
maskCanvas = new Canvas (maskBitmap);
maskCanvas.drawBitmap (bitmap, new Rect (src_x, src_y,
src_x + width,
src_y + height),
srcRect, maskPaint);
canvas.drawBitmap (maskBitmap, srcRect, rect, paint);
maskRect.offset (-immutableGC.clip_x_origin,
-immutableGC.clip_y_origin);
maskCanvas.drawBitmap (immutableGC.clip_mask.bitmap,
maskRect, new Rect (0, 0,
maskRect.width (),
maskRect.height ()),
paint);
maskRect.offset (immutableGC.clip_x_origin,
immutableGC.clip_y_origin);
/* Set the transfer mode to SRC_IN to preserve only the parts
of the source that overlap with the mask. */
maskPaint = new Paint ();
maskPaint.setXfermode (srcInAlu);
/* Draw the source. */
maskDestRect = new Rect (0, 0, srcRect.width (),
srcRect.height ());
maskCanvas.drawBitmap (srcBitmap, srcRect, maskDestRect,
maskPaint);
/* Finally, draw the mask bitmap to the destination. */
paint.setXfermode (overAlu);
canvas.drawBitmap (maskBitmap, null, maskRect, paint);
}
}
}

View file

@ -57,7 +57,10 @@ public class EmacsDrawRectangle implements EmacsPaintReq
public Rect
getRect ()
{
return new Rect (x, y, x + width, y + height);
/* Canvas.drawRect actually behaves exactly like PolyRectangle wrt
to where the lines are placed, so extend the width and height
by 1 in the damage rectangle. */
return new Rect (x, y, x + width + 1, y + height + 1);
}
@Override
@ -89,9 +92,10 @@ public class EmacsDrawRectangle implements EmacsPaintReq
return;
alu = immutableGC.function;
rect = getRect ();
rect = new Rect (x, y, x + width, y + height);
paint.setStyle (Paint.Style.STROKE);
paint.setStrokeWidth (1);
if (alu == EmacsGC.GC_COPY)
paint.setXfermode (null);

View file

@ -21,6 +21,8 @@ package org.gnu.emacs;
import java.util.List;
import android.os.Build;
public abstract class EmacsFontDriver
{
/* Font weights. */
@ -75,6 +77,7 @@ public abstract class EmacsFontDriver
public Integer size;
public Integer spacing;
public Integer avgwidth;
public Integer dpi;
@Override
public String
@ -88,7 +91,8 @@ public abstract class EmacsFontDriver
+ " weight: " + weight
+ " slant: " + slant
+ " spacing: " + spacing
+ " avgwidth: " + avgwidth);
+ " avgwidth: " + avgwidth
+ " dpi: " + dpi);
}
};
@ -99,6 +103,17 @@ public abstract class EmacsFontDriver
public short width;
public short ascent;
public short descent;
@Override
public String
toString ()
{
return ("lbearing " + lbearing
+ " rbearing " + rbearing
+ " width " + width
+ " ascent " + ascent
+ " descent " + descent);
}
}
public class FontEntity extends FontSpec
@ -139,12 +154,19 @@ public abstract class EmacsFontDriver
public abstract FontObject openFont (FontEntity fontEntity, int pixelSize);
public abstract int hasChar (FontSpec font, char charCode);
public abstract void textExtents (FontObject font, int code[],
FontMetrics fontMetrics[]);
FontMetrics fontMetrics);
public abstract int encodeChar (FontObject fontObject, char charCode);
public abstract int draw (FontObject fontObject, EmacsGC gc,
EmacsDrawable drawable, int[] chars,
int x, int y, int backgroundWidth,
boolean withBackground);
public static EmacsFontDriver
createFontDriver ()
{
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M)
return new EmacsSdk23FontDriver ();
return new EmacsSdk7FontDriver ();
}
};

View file

@ -0,0 +1,25 @@
/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
Copyright (C) 2023 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
package org.gnu.emacs;
public class EmacsMultitaskActivity extends EmacsActivity
{
}

View file

@ -38,10 +38,15 @@ public class EmacsNative
libDir must be the package's data storage location for native
libraries. It is used as PATH.
pixelDensityX and pixelDensityY are the DPI values that will be
used by Emacs.
emacsService must be the emacsService singleton. */
public static native void setEmacsParams (AssetManager assetManager,
String filesDir,
String libDir,
float pixelDensityX,
float pixelDensityY,
EmacsService emacsService);
/* Initialize Emacs with the argument array ARGV. Each argument
@ -59,11 +64,20 @@ public class EmacsNative
/* Send an ANDROID_KEY_PRESS event. */
public static native void sendKeyPress (short window, long time, int state,
int keyCode);
int keyCode, int unicodeChar);
/* Send an ANDROID_KEY_RELEASE event. */
public static native void sendKeyRelease (short window, long time, int state,
int keyRelease);
int keyCode, int unicodeChar);
/* Send an ANDROID_FOCUS_IN event. */
public static native void sendFocusIn (short window, long time);
/* Send an ANDROID_FOCUS_OUT event. */
public static native void sendFocusOut (short window, long time);
/* Send an ANDROID_WINDOW_ACTION event. */
public static native void sendWindowAction (short window, int action);
static
{

View file

@ -47,7 +47,7 @@ public class EmacsPaintQueue
{
EmacsDrawable drawable, last;
Canvas canvas;
EmacsGC gc, lastGC;
EmacsGC gc;
int i;
Paint paint;
Rect rect, offsetRect, copyRect;
@ -60,45 +60,34 @@ public class EmacsPaintQueue
for (EmacsPaintReq req : paintOperations)
{
drawable = req.getDrawable ();
synchronized (req)
{
/* Ignore graphics requests for drawables that have been
destroyed. */
if (drawable.isDestroyed ())
continue;
}
canvas = drawable.lockCanvas ();
if (canvas == null)
/* No canvas is currently available. */
continue;
lastGC = gc;
gc = req.getGC ();
rect = req.getRect ();
drawable.damageRect (rect);
if (gc.clip_rects == null)
{
/* No clipping is applied. Just draw and continue. */
canvas.save ();
req.paintTo (canvas, paint, gc);
canvas.restore ();
drawable.damageRect (rect);
continue;
}
if (gc.clip_rects != null && gc.clip_rects.length > 0)
{
canvas.save ();
if (gc.clip_rects.length == 1)
{
/* There is only a single clip rect, which is simple
enough. */
canvas.save ();
canvas.clipRect (gc.clip_rects[0]);
req.paintTo (canvas, paint, gc);
canvas.restore ();
}
else
{
@ -122,9 +111,6 @@ public class EmacsPaintQueue
}
}
}
drawable.damageRect (rect);
canvas.restore ();
}
}
}

View file

@ -69,6 +69,35 @@ public class EmacsPixmap extends EmacsHandleObject
this.depth = depth;
}
public
EmacsPixmap (short handle, int width, int height, int depth)
{
super (handle);
if (depth != 1 && depth != 24)
throw new IllegalArgumentException ("Invalid depth specified"
+ " for pixmap: " + depth);
switch (depth)
{
case 1:
bitmap = Bitmap.createBitmap (width, height,
Bitmap.Config.ALPHA_8,
false);
break;
case 24:
bitmap = Bitmap.createBitmap (width, height,
Bitmap.Config.ARGB_8888,
false);
break;
}
this.width = width;
this.height = height;
this.depth = depth;
}
@Override
public Canvas
lockCanvas ()

View file

@ -0,0 +1,43 @@
/* Font backend for Android terminals. -*- c-file-style: "GNU" -*-
Copyright (C) 2023 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
package org.gnu.emacs;
import android.graphics.Paint;
public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver
{
@Override
public int
hasChar (FontSpec font, char charCode)
{
Sdk7FontObject fontObject;
Paint paint;
if (font instanceof Sdk7FontObject)
{
fontObject = (Sdk7FontObject) font;
paint = fontObject.typeface.typefacePaint;
}
else
paint = ((Sdk7FontEntity) font).typeface.typefacePaint;
return paint.hasGlyph (String.valueOf (charCode)) ? 1 : 0;
}
};

View file

@ -28,16 +28,19 @@ import java.util.List;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Canvas;
import android.util.Log;
import android.os.Build;
public class EmacsSdk7FontDriver extends EmacsFontDriver
{
private static final String TOFU_STRING = "\uDB3F\uDFFD";
private static final String EM_STRING = "m";
private static final String TAG = "EmacsSdk7FontDriver";
private class Sdk7Typeface
protected class Sdk7Typeface
{
/* The typeface and paint. */
public Typeface typeface;
@ -57,7 +60,10 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
width = UNSPECIFIED;
spacing = PROPORTIONAL;
this.typeface = typeface;
typefacePaint = new Paint ();
typefacePaint.setAntiAlias (true);
typefacePaint.setTypeface (typeface);
/* For the calls to measureText below. */
@ -160,7 +166,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
}
};
private class Sdk7FontEntity extends FontEntity
protected class Sdk7FontEntity extends FontEntity
{
/* The typeface. */
public Sdk7Typeface typeface;
@ -177,19 +183,17 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
slant = typeface.slant;
spacing = typeface.spacing;
width = typeface.width;
dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
this.typeface = typeface;
}
};
private class Sdk7FontObject extends FontObject
protected class Sdk7FontObject extends FontObject
{
/* The typeface. */
public Sdk7Typeface typeface;
/* The text size. */
public int pixelSize;
public
Sdk7FontObject (Sdk7Typeface typeface, int pixelSize)
{
@ -205,6 +209,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
slant = typeface.slant;
spacing = typeface.spacing;
width = typeface.width;
dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
/* Compute the ascent and descent. */
typeface.typefacePaint.setTextSize (pixelSize);
@ -238,6 +243,93 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
}
};
private class Sdk7DrawString implements EmacsPaintReq
{
private boolean drawBackground;
private Sdk7FontObject fontObject;
private char[] chars;
private EmacsGC immutableGC;
private EmacsDrawable drawable;
private Rect rect;
private int originX, originY;
public
Sdk7DrawString (Sdk7FontObject fontObject, char[] chars,
EmacsGC immutableGC, EmacsDrawable drawable,
boolean drawBackground, Rect rect,
int originX, int originY)
{
this.fontObject = fontObject;
this.chars = chars;
this.immutableGC = immutableGC;
this.drawable = drawable;
this.drawBackground = drawBackground;
this.rect = rect;
this.originX = originX;
this.originY = originY;
}
@Override
public EmacsDrawable
getDrawable ()
{
return drawable;
}
@Override
public EmacsGC
getGC ()
{
return immutableGC;
}
@Override
public void
paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC)
{
int scratch;
paint.setStyle (Paint.Style.FILL);
if (drawBackground)
{
paint.setColor (immutableGC.background | 0xff000000);
canvas.drawRect (rect, paint);
}
paint.setTextSize (fontObject.pixelSize);
paint.setColor (immutableGC.foreground | 0xff000000);
paint.setTypeface (fontObject.typeface.typeface);
paint.setAntiAlias (true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
/* Disable hinting as that leads to displayed text not
matching the computed metrics. */
paint.setHinting (Paint.HINTING_OFF);
canvas.drawText (chars, 0, chars.length, originX, originY, paint);
paint.setAntiAlias (false);
}
@Override
public Rect
getRect ()
{
Rect rect;
rect = new Rect ();
fontObject.typeface.typefacePaint.setTextSize (fontObject.pixelSize);
fontObject.typeface.typefacePaint.getTextBounds (chars, 0, chars.length,
rect);
/* Add the background rect to the damage as well. */
rect.union (this.rect);
return rect;
}
};
private String[] fontFamilyList;
private Sdk7Typeface[] typefaceList;
private Sdk7Typeface fallbackTypeface;
@ -252,7 +344,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
systemFontsDirectory = new File ("/system/fonts");
fontFamilyList = systemFontsDirectory.list ();
typefaceList = new Sdk7Typeface[fontFamilyList.length];
typefaceList = new Sdk7Typeface[fontFamilyList.length + 3];
/* It would be nice to avoid opening each and every font upon
startup. But that doesn't seem to be possible on
@ -267,8 +359,18 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
typeface);
}
/* Initialize the default monospace and serif typefaces. */
fallbackTypeface = new Sdk7Typeface ("monospace",
Typeface.MONOSPACE);
typefaceList[fontFamilyList.length] = fallbackTypeface;
fallbackTypeface = new Sdk7Typeface ("Monospace",
Typeface.MONOSPACE);
typefaceList[fontFamilyList.length + 1] = fallbackTypeface;
fallbackTypeface = new Sdk7Typeface ("Sans Serif",
Typeface.DEFAULT);
typefaceList[fontFamilyList.length + 2] = fallbackTypeface;
}
private boolean
@ -278,11 +380,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
&& !fontSpec.family.equals (typeface.familyName))
return false;
if (fontSpec.adstyle != null
&& !fontSpec.adstyle.isEmpty ())
/* return false; */;
if (fontSpec.slant != null
&& !fontSpec.weight.equals (typeface.weight))
return false;
@ -393,7 +490,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (),
rect1);
paint.getTextBounds ("" + charCode, 0, 1, rect2);
return rect1.equals (rect2) ? 1 : 0;
return rect1.equals (rect2) ? 0 : 1;
}
private void
@ -434,21 +531,47 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
@Override
public void
textExtents (FontObject font, int code[], FontMetrics fontMetrics[])
textExtents (FontObject font, int code[], FontMetrics fontMetrics)
{
int i;
Paint paintCache;
Rect boundsCache;
Sdk7FontObject fontObject;
char[] text;
float width;
fontObject = (Sdk7FontObject) font;
paintCache = fontObject.typeface.typefacePaint;
paintCache.setTextSize (fontObject.pixelSize);
boundsCache = new Rect ();
for (i = 0; i < code.length; ++i)
textExtents1 ((Sdk7FontObject) font, code[i], fontMetrics[i],
if (code.length == 0)
{
fontMetrics.lbearing = 0;
fontMetrics.rbearing = 0;
fontMetrics.ascent = 0;
fontMetrics.descent = 0;
fontMetrics.width = 0;
}
else if (code.length == 1)
textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics,
paintCache, boundsCache);
else
{
text = new char[code.length];
for (i = 0; i < code.length; ++i)
text[i] = (char) code[i];
paintCache.getTextBounds (text, 0, 1, boundsCache);
width = paintCache.measureText (text, 0, code.length);
fontMetrics.lbearing = (short) boundsCache.left;
fontMetrics.rbearing = (short) boundsCache.right;
fontMetrics.ascent = (short) -boundsCache.top;
fontMetrics.descent = (short) boundsCache.bottom;
fontMetrics.width = (short) Math.round (width);
}
}
@Override
@ -457,4 +580,37 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver
{
return charCode;
}
@Override
public int
draw (FontObject fontObject, EmacsGC gc, EmacsDrawable drawable,
int[] chars, int x, int y, int backgroundWidth,
boolean withBackground)
{
Rect backgroundRect;
Sdk7FontObject sdk7FontObject;
Sdk7DrawString op;
char[] charsArray;
int i;
sdk7FontObject = (Sdk7FontObject) fontObject;
charsArray = new char[chars.length];
for (i = 0; i < chars.length; ++i)
charsArray[i] = (char) chars[i];
backgroundRect = new Rect ();
backgroundRect.top = y - sdk7FontObject.ascent;
backgroundRect.left = x;
backgroundRect.right = x + backgroundWidth;
backgroundRect.bottom = y + sdk7FontObject.descent;
op = new Sdk7DrawString (sdk7FontObject, charsArray,
gc.immutableGC (), drawable,
withBackground,
backgroundRect, x, y);
EmacsService.SERVICE.appendPaintOperation (op);
return 1;
}
};

View file

@ -28,6 +28,8 @@ import android.graphics.Canvas;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.view.View;
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Context;
@ -37,7 +39,9 @@ import android.os.Build;
import android.os.Looper;
import android.os.IBinder;
import android.os.Handler;
import android.util.Log;
import android.util.DisplayMetrics;
class Holder<T>
{
@ -57,14 +61,8 @@ public class EmacsService extends Service
private Handler handler;
private EmacsPaintQueue paintQueue;
/* List of all EmacsWindows that are available to attach to an
activity. */
public static List<EmacsWindow> availableChildren;
static
{
availableChildren = new ArrayList<EmacsWindow> ();
};
/* Display metrics used by font backends. */
public DisplayMetrics metrics;
@Override
public int
@ -88,7 +86,7 @@ public class EmacsService extends Service
Context context;
context = getApplicationContext ();
apiLevel = android.os.Build.VERSION.SDK_INT;
apiLevel = Build.VERSION.SDK_INT;
if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
return context.getApplicationInfo().nativeLibraryDir;
@ -105,11 +103,16 @@ public class EmacsService extends Service
AssetManager manager;
Context app_context;
String filesDir, libDir;
double pixelDensityX;
double pixelDensityY;
SERVICE = this;
handler = new Handler (Looper.getMainLooper ());
manager = getAssets ();
app_context = getApplicationContext ();
metrics = getResources ().getDisplayMetrics ();
pixelDensityX = metrics.xdpi;
pixelDensityY = metrics.ydpi;
try
{
@ -122,6 +125,8 @@ public class EmacsService extends Service
+ " and libDir = " + libDir);
EmacsNative.setEmacsParams (manager, filesDir, libDir,
(float) pixelDensityX,
(float) pixelDensityY,
this);
/* Start the thread that runs Emacs. */
@ -147,7 +152,8 @@ public class EmacsService extends Service
}
EmacsView
getEmacsView (final EmacsWindow window)
getEmacsView (final EmacsWindow window, final int visibility,
final boolean isFocusedByDefault)
{
Runnable runnable;
final Holder<EmacsView> view;
@ -161,6 +167,8 @@ public class EmacsService extends Service
synchronized (this)
{
view.thing = new EmacsView (window);
view.thing.setVisibility (visibility);
view.thing.setFocusedByDefault (isFocusedByDefault);
notify ();
}
}
@ -183,48 +191,6 @@ public class EmacsService extends Service
return view.thing;
}
/* Notice that a child of the root window named WINDOW is now
available for attachment to a specific activity. */
public void
noticeAvailableChild (final EmacsWindow window)
{
Log.d (TAG, "A new child is available: " + window);
handler.post (new Runnable () {
public void
run ()
{
for (EmacsActivity activity
: EmacsActivity.availableActivities)
{
/* TODO: check if the activity matches. */
activity.attachChild (window);
break;
}
/* Nope, wait for an activity to become available. */
availableChildren.add (window);
}
});
}
/* Notice that a child of the root window named WINDOW has been
destroyed. */
public void
noticeChildDestroyed (final EmacsWindow child)
{
handler.post (new Runnable () {
@Override
public void
run ()
{
availableChildren.remove (child);
}
});
}
/* X drawing operations. These are quite primitive operations. The
drawing queue is kept on the Emacs thread, but is periodically
flushed to the application thread, upon buffers swaps and once it
@ -311,11 +277,6 @@ public class EmacsService extends Service
ensurePaintQueue ();
if (gc.clip_rects != null && gc.clip_rects.length >= 1)
android.util.Log.d ("drawRectangle",
gc.clip_rects[0].toString ()
+ " " + gc.toString ());
req = new EmacsDrawRectangle (drawable, x, y,
width, height,
gc.immutableGC ());
@ -381,4 +342,12 @@ public class EmacsService extends Service
{
window.clearArea (x, y, width, height);
}
public void
appendPaintOperation (EmacsPaintReq op)
{
ensurePaintQueue ();
paintQueue.appendPaintOperation (op);
checkFlush ();
}
};

View file

@ -22,6 +22,8 @@ package org.gnu.emacs;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.os.Build;
import android.graphics.Canvas;
import android.graphics.Rect;
@ -40,7 +42,9 @@ public class EmacsSurfaceView extends SurfaceView
surfaceChanged (SurfaceHolder holder, int format,
int width, int height)
{
/* Force a buffer swap now to get the contents of the Emacs
view on screen. */
view.swapBuffers (true);
}
@Override
@ -51,7 +55,7 @@ public class EmacsSurfaceView extends SurfaceView
/* Force a buffer swap now to get the contents of the Emacs
view on screen. */
view.swapBuffers ();
view.swapBuffers (true);
}
@Override
@ -72,12 +76,37 @@ public class EmacsSurfaceView extends SurfaceView
public Canvas
lockCanvas (Rect damage)
{
return getHolder ().lockCanvas (damage);
SurfaceHolder holder;
holder = getHolder ();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
damage.setEmpty ();
return holder.lockHardwareCanvas ();
}
return holder.lockCanvas (damage);
}
/* This method is only used during debugging when it seems damage
isn't working correctly. */
public Canvas
lockCanvas ()
{
SurfaceHolder holder;
holder = getHolder ();
return holder.lockCanvas ();
}
public void
unlockCanvasAndPost (Canvas canvas)
{
getHolder ().unlockCanvasAndPost (canvas);
SurfaceHolder holder;
holder = getHolder ();
holder.unlockCanvasAndPost (canvas);
}
};

View file

@ -19,17 +19,20 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
package org.gnu.emacs;
import android.content.res.ColorStateList;
import android.view.View;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Paint;
import android.util.Log;
import android.os.Build;
import android.util.Log;
/* This is an Android view which has a back and front buffer. When
swapBuffers is called, the back buffer is swapped to the front
@ -70,10 +73,11 @@ public class EmacsView extends ViewGroup
this.damageRegion = new Region ();
this.paint = new Paint ();
setFocusable (true);
setFocusableInTouchMode (true);
/* Create the surface view. */
this.surfaceView = new EmacsSurfaceView (this);
setFocusable (FOCUSABLE);
addView (this.surfaceView);
}
@ -162,7 +166,7 @@ public class EmacsView extends ViewGroup
}
public void
swapBuffers ()
swapBuffers (boolean force)
{
Bitmap back;
Canvas canvas;
@ -185,14 +189,25 @@ public class EmacsView extends ViewGroup
if (canvas == null)
return;
/* Copy from the back buffer to the canvas. */
canvas.drawBitmap (bitmap, damageRect, damageRect, paint);
/* Copy from the back buffer to the canvas. If damageRect was
made empty, then draw the entire back buffer. */
if (damageRect.isEmpty ())
canvas.drawBitmap (bitmap, 0f, 0f, paint);
else
canvas.drawBitmap (bitmap, damageRect, damageRect, paint);
/* Unlock the canvas and clear the damage. */
surfaceView.unlockCanvasAndPost (canvas);
damageRegion.setEmpty ();
}
public void
swapBuffers ()
{
swapBuffers (false);
}
@Override
public boolean
onKeyDown (int keyCode, KeyEvent event)
@ -201,6 +216,14 @@ public class EmacsView extends ViewGroup
return true;
}
@Override
public boolean
onKeyMultiple (int keyCode, int repeatCount, KeyEvent event)
{
window.onKeyDown (keyCode, event);
return true;
}
@Override
public boolean
onKeyUp (int keyCode, KeyEvent event)
@ -208,4 +231,14 @@ public class EmacsView extends ViewGroup
window.onKeyUp (keyCode, event);
return true;
}
@Override
public void
onFocusChanged (boolean gainFocus, int direction,
Rect previouslyFocusedRect)
{
window.onFocusChanged (gainFocus);
super.onFocusChanged (gainFocus, direction,
previouslyFocusedRect);
}
};

View file

@ -32,6 +32,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.KeyEvent;
import android.content.Intent;
/* This defines a window, which is a handle. Windows represent a
rectangular subset of the screen with their own contents.
@ -57,10 +59,10 @@ public class EmacsWindow extends EmacsHandleObject
/* List of all children in stacking order. This must be kept
consistent! */
private ArrayList<EmacsWindow> children;
public ArrayList<EmacsWindow> children;
/* The EmacsActivity currently attached, if it exists. */
private EmacsActivity attached;
/* The window consumer currently attached, if it exists. */
private EmacsWindowAttachmentManager.WindowConsumer attached;
/* The window background scratch GC. foreground is always the
window background. */
@ -74,35 +76,44 @@ public class EmacsWindow extends EmacsHandleObject
rect = new Rect (x, y, x + width, y + height);
/* Create the view from the context's UI thread. */
view = EmacsService.SERVICE.getEmacsView (this);
/* Create the view from the context's UI thread. The window is
unmapped, so the view is GONE. */
view = EmacsService.SERVICE.getEmacsView (this, View.GONE,
parent == null);
this.parent = parent;
/* Create the list of children. */
children = new ArrayList<EmacsWindow> ();
/* The window is unmapped by default. */
view.setVisibility (View.GONE);
/* If parent is the root window, notice that there are new
children available for interested activites to pick up. */
if (parent == null)
EmacsService.SERVICE.noticeAvailableChild (this);
else
if (parent != null)
{
/* Otherwise, directly add this window as a child of that
window's view. */
synchronized (parent)
{
parent.children.add (this);
parent.view.post (new Runnable () {
@Override
public void
run ()
{
parent.view.addView (view);
}
});
}
parent.children.add (this);
parent.view.post (new Runnable () {
@Override
public void
run ()
{
parent.view.addView (view);
}
});
}
else
EmacsService.SERVICE.runOnUiThread (new Runnable () {
@Override
public void
run ()
{
EmacsWindowAttachmentManager manager;
manager = EmacsWindowAttachmentManager.MANAGER;
/* If parent is the root window, notice that there are new
children available for interested activites to pick
up. */
manager.registerWindow (EmacsWindow.this);
}
});
scratchGC = new EmacsGC ((short) 0);
}
@ -129,28 +140,35 @@ public class EmacsWindow extends EmacsHandleObject
public void
destroyHandle () throws IllegalStateException
{
synchronized (this)
{
if (!children.isEmpty ())
throw new IllegalStateException ("Trying to destroy window with "
+ "children!");
}
if (parent != null)
parent.children.remove (this);
/* Notice that the child has been destroyed. */
EmacsService.SERVICE.noticeChildDestroyed (this);
EmacsActivity.invalidateFocus ();
if (!children.isEmpty ())
throw new IllegalStateException ("Trying to destroy window with "
+ "children!");
/* Remove the view from its parent and make it invisible. */
view.post (new Runnable () {
public void
run ()
{
View parent;
EmacsWindowAttachmentManager manager;
if (EmacsActivity.focusedWindow == EmacsWindow.this)
EmacsActivity.focusedWindow = null;
manager = EmacsWindowAttachmentManager.MANAGER;
view.setVisibility (View.GONE);
if (view.getParent () != null)
((ViewGroup) view.getParent ()).removeView (view);
parent = (View) view.getParent ();
if (attached != null)
attached.makeAvailable ();
if (parent != null && attached == null)
((ViewGroup) parent).removeView (view);
manager.detachWindow (EmacsWindow.this);
}
});
@ -158,12 +176,15 @@ public class EmacsWindow extends EmacsHandleObject
}
public void
setActivity (EmacsActivity activity)
setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer)
{
synchronized (this)
{
activity = activity;
}
attached = consumer;
}
public EmacsWindowAttachmentManager.WindowConsumer
getAttachedConsumer ()
{
return attached;
}
public void
@ -233,7 +254,10 @@ public class EmacsWindow extends EmacsHandleObject
public void
run ()
{
view.setVisibility (View.VISIBLE);
/* Eventually this should check no-focus-on-map. */
view.requestFocus ();
}
});
}
@ -319,18 +343,47 @@ public class EmacsWindow extends EmacsHandleObject
public void
onKeyDown (int keyCode, KeyEvent event)
{
int state;
state = event.getModifiers ();
state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
EmacsNative.sendKeyPress (this.handle,
event.getEventTime (),
event.getModifiers (),
keyCode);
keyCode,
/* Ignore meta-state understood by Emacs
for now, or Ctrl+C will not be
recognized as an ASCII key press
event. */
event.getUnicodeChar (state));
}
public void
onKeyUp (int keyCode, KeyEvent event)
{
int state;
state = event.getModifiers ();
state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
EmacsNative.sendKeyRelease (this.handle,
event.getEventTime (),
event.getModifiers (),
keyCode);
keyCode,
event.getUnicodeChar (state));
}
public void
onFocusChanged (boolean gainFocus)
{
EmacsActivity.invalidateFocus ();
}
public void
onActivityDetached ()
{
/* Destroy the associated frame when the activity is detached. */
EmacsNative.sendWindowAction (this.handle, 0);
}
};

View file

@ -0,0 +1,166 @@
/* 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.LinkedList;
import java.util.List;
import android.content.Intent;
import android.util.Log;
/* Code to paper over the differences in lifecycles between
"activities" and windows. There are four interfaces to an instance
of this class:
registerWindowConsumer (WindowConsumer)
registerWindow (EmacsWindow)
removeWindowConsumer (WindowConsumer)
removeWindow (EmacsWindow)
A WindowConsumer is expected to allow an EmacsWindow to be attached
to it, and be created or destroyed.
Every time a window is created, registerWindow checks the list of
window consumers. If a consumer exists and does not currently have
a window of its own attached, it gets the new window. Otherwise,
the window attachment manager starts a new consumer.
Every time a consumer is registered, registerWindowConsumer checks
the list of available windows. If a window exists and is not
currently attached to a consumer, then the consumer gets it.
Finally, every time a window is removed, the consumer is
destroyed. */
public class EmacsWindowAttachmentManager
{
public static EmacsWindowAttachmentManager MANAGER;
private final static String TAG = "EmacsWindowAttachmentManager";
static
{
MANAGER = new EmacsWindowAttachmentManager ();
};
public interface WindowConsumer
{
public void attachWindow (EmacsWindow window);
public EmacsWindow getAttachedWindow ();
public void detachWindow ();
public void destroy ();
};
private List<WindowConsumer> consumers;
private List<EmacsWindow> windows;
public
EmacsWindowAttachmentManager ()
{
consumers = new LinkedList<WindowConsumer> ();
windows = new LinkedList<EmacsWindow> ();
}
public void
registerWindowConsumer (WindowConsumer consumer)
{
Log.d (TAG, "registerWindowConsumer " + consumer);
consumers.add (consumer);
for (EmacsWindow window : windows)
{
if (window.getAttachedConsumer () == null)
{
Log.d (TAG, "registerWindowConsumer: attaching " + window);
consumer.attachWindow (window);
return;
}
}
Log.d (TAG, "registerWindowConsumer: sendWindowAction 0, 0");
EmacsNative.sendWindowAction ((short) 0, 0);
}
public void
registerWindow (EmacsWindow window)
{
Intent intent;
Log.d (TAG, "registerWindow " + window);
windows.add (window);
for (WindowConsumer consumer : consumers)
{
if (consumer.getAttachedWindow () == null)
{
Log.d (TAG, "registerWindow: attaching " + consumer);
consumer.attachWindow (window);
return;
}
}
intent = new Intent (EmacsService.SERVICE,
EmacsMultitaskActivity.class);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
EmacsService.SERVICE.startActivity (intent);
Log.d (TAG, "registerWindow: startActivity");
}
public void
removeWindowConsumer (WindowConsumer consumer)
{
EmacsWindow window;
Log.d (TAG, "removeWindowConsumer " + consumer);
window = consumer.getAttachedWindow ();
if (window != null)
{
Log.d (TAG, "removeWindowConsumer: detaching " + window);
consumer.detachWindow ();
window.onActivityDetached ();
}
Log.d (TAG, "removeWindowConsumer: removing " + consumer);
consumers.remove (consumer);
}
public void
detachWindow (EmacsWindow window)
{
WindowConsumer consumer;
Log.d (TAG, "detachWindow " + window);
if (window.getAttachedConsumer () != null)
{
consumer = window.getAttachedConsumer ();
Log.d (TAG, "detachWindow: removing" + consumer);
consumers.remove (consumer);
consumer.destroy ();
}
}
};