aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
authorPo Lu2023-01-17 22:10:43 +0800
committerPo Lu2023-01-17 22:10:43 +0800
commit1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f (patch)
treed6c709e513882f5d430a98508e631cc503469fab /java
parent356249d9faf2b454879ff30f06d97beb97fb9a36 (diff)
downloademacs-1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f.tar.gz
emacs-1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f.zip
Update Android port
* doc/emacs/android.texi (Android Fonts): Document that TTC format fonts are now supported. * doc/emacs/emacs.texi (Top): Fix menus. * doc/lispref/commands.texi (Touchscreen Events) (Key Sequence Input): Document changes to touchscreen events. * etc/DEBUG: Describe how to debug 64 bit binaries on Android. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog): New class. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Use 5 point PolyLine like X, because Android behaves like Postscript on some devices and X elsewhere. * java/org/gnu/emacs/EmacsFillRectangle.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsPixmap.java (destroyHandle): Explicitly recycle bitmap and GC if it is big. * java/org/gnu/emacs/EmacsView.java (EmacsView): Make `bitmapDirty' a boolean. (handleDirtyBitmap): Reimplement in terms of that boolean. Explicitly recycle old bitmap and GC. (onLayout): Fix lock up. (onDetachedFromWindow): Recycle bitmap and GC. * java/org/gnu/emacs/EmacsWindow.java (requestViewLayout): Update call to explicitlyDirtyBitmap. * src/android.c (android_run_select_thread, android_select): Really fix android_select. (android_build_jstring): New function. * src/android.h: Update prototypes. * src/androidmenu.c (android_process_events_for_menu): Totally unblock input before process_pending_signals. (android_menu_show): Remove redundant unblock_input and debugging code. (struct android_emacs_dialog, android_init_emacs_dialog) (android_dialog_show, android_popup_dialog, init_androidmenu): Implement popup dialogs on Android. * src/androidterm.c (android_update_tools) (handle_one_android_event, android_frame_up_to_date): Allow tapping tool bar items. (android_create_terminal): Add dialog hook. (android_wait_for_event): Adjust call to android_select. * src/androidterm.h (struct android_touch_point): New field `tool_bar_p'. * src/keyboard.c (read_key_sequence, head_table) (syms_of_keyboard): Prefix touchscreen events with posn. * src/keyboard.h (EVENT_HEAD): Handle touchscreen events. * src/process.c (wait_reading_process_output): Adjust call to android_select. * src/sfnt.c (sfnt_read_table_directory): If the first long turns out to be ttcf, return -1. (sfnt_read_ttc_header): New function. (main): Test TTC support. * src/sfnt.h (struct sfnt_ttc_header): New structure. (enum sfnt_ttc_tag): New enum. * src/sfntfont-android.c (struct sfntfont_android_scanline_buffer): New structure. (GET_SCANLINE_BUFFER): New macro. Try to avoid so much malloc upon accessing the scanline buffer. (sfntfont_android_put_glyphs): Do not use SAFE_ALLOCA to allocate the scaline buffer. (Fandroid_enumerate_fonts): Enumerate ttc fonts too. * src/sfntfont.c (struct sfnt_font_desc): New field `offset'. (sfnt_enum_font_1): Split out enumeration code from sfnt_enum_font. (sfnt_enum_font): Read TTC tables and enumerate each font therein. (sfntfont_open): Seek to the offset specified. * xcompile/Makefile.in (maintainer-clean): Fix depends here.
Diffstat (limited to 'java')
-rw-r--r--java/org/gnu/emacs/EmacsCopyArea.java4
-rw-r--r--java/org/gnu/emacs/EmacsDialog.java333
-rw-r--r--java/org/gnu/emacs/EmacsDrawRectangle.java32
-rw-r--r--java/org/gnu/emacs/EmacsFillRectangle.java3
-rw-r--r--java/org/gnu/emacs/EmacsPixmap.java23
-rw-r--r--java/org/gnu/emacs/EmacsView.java84
-rw-r--r--java/org/gnu/emacs/EmacsWindow.java2
7 files changed, 452 insertions, 29 deletions
diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java
index 00e817bb97d..5d72a7860c8 100644
--- a/java/org/gnu/emacs/EmacsCopyArea.java
+++ b/java/org/gnu/emacs/EmacsCopyArea.java
@@ -116,6 +116,7 @@ public class EmacsCopyArea
116 src_x, src_y, width, 116 src_x, src_y, width,
117 height); 117 height);
118 canvas.drawBitmap (bitmap, null, rect, paint); 118 canvas.drawBitmap (bitmap, null, rect, paint);
119 bitmap.recycle ();
119 } 120 }
120 else 121 else
121 { 122 {
@@ -183,6 +184,9 @@ public class EmacsCopyArea
183 paint.setXfermode (overAlu); 184 paint.setXfermode (overAlu);
184 canvas.drawBitmap (maskBitmap, null, maskRect, paint); 185 canvas.drawBitmap (maskBitmap, null, maskRect, paint);
185 gc.resetXfermode (); 186 gc.resetXfermode ();
187
188 /* Recycle this unused bitmap. */
189 maskBitmap.recycle ();
186 } 190 }
187 191
188 canvas.restore (); 192 canvas.restore ();
diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java
new file mode 100644
index 00000000000..5bc8efa5978
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDialog.java
@@ -0,0 +1,333 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2023 Free Software Foundation, Inc.
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or (at
10your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19
20package org.gnu.emacs;
21
22import java.util.List;
23import java.util.ArrayList;
24
25import android.app.AlertDialog;
26import android.content.DialogInterface;
27import android.content.Context;
28import android.util.Log;
29
30import android.widget.Button;
31import android.widget.LinearLayout;
32import android.widget.FrameLayout;
33
34import android.view.View;
35import android.view.ViewGroup;
36
37/* Toolkit dialog implementation. This object is built from JNI and
38 describes a single alert dialog. Then, `inflate' turns it into
39 AlertDialog. */
40
41public class EmacsDialog implements DialogInterface.OnDismissListener
42{
43 private static final String TAG = "EmacsDialog";
44
45 /* List of buttons in this dialog. */
46 private List<EmacsButton> buttons;
47
48 /* Dialog title. */
49 private String title;
50
51 /* Dialog text. */
52 private String text;
53
54 /* Whether or not a selection has already been made. */
55 private boolean wasButtonClicked;
56
57 /* Dialog to dismiss after click. */
58 private AlertDialog dismissDialog;
59
60 private class EmacsButton implements View.OnClickListener,
61 DialogInterface.OnClickListener
62 {
63 /* Name of this button. */
64 public String name;
65
66 /* ID of this button. */
67 public int id;
68
69 /* Whether or not the button is enabled. */
70 public boolean enabled;
71
72 @Override
73 public void
74 onClick (View view)
75 {
76 Log.d (TAG, "onClicked " + this);
77
78 wasButtonClicked = true;
79 EmacsNative.sendContextMenu ((short) 0, id);
80 dismissDialog.dismiss ();
81 }
82
83 @Override
84 public void
85 onClick (DialogInterface dialog, int which)
86 {
87 Log.d (TAG, "onClicked " + this);
88
89 wasButtonClicked = true;
90 EmacsNative.sendContextMenu ((short) 0, id);
91 }
92 };
93
94 /* Create a popup dialog with the title TITLE and the text TEXT.
95 TITLE may be NULL. */
96
97 public static EmacsDialog
98 createDialog (String title, String text)
99 {
100 EmacsDialog dialog;
101
102 dialog = new EmacsDialog ();
103 dialog.buttons = new ArrayList<EmacsButton> ();
104 dialog.title = title;
105 dialog.text = text;
106
107 return dialog;
108 }
109
110 /* Add a button named NAME, with the identifier ID. If DISABLE,
111 disable the button. */
112
113 public void
114 addButton (String name, int id, boolean disable)
115 {
116 EmacsButton button;
117
118 button = new EmacsButton ();
119 button.name = name;
120 button.id = id;
121 button.enabled = !disable;
122 buttons.add (button);
123 }
124
125 /* Turn this dialog into an AlertDialog for the specified
126 CONTEXT.
127
128 Upon a button being selected, the dialog will send an
129 ANDROID_CONTEXT_MENU event with the id of that button.
130
131 Upon the dialog being dismissed, an ANDROID_CONTEXT_MENU event
132 will be sent with an id of 0. */
133
134 public AlertDialog
135 toAlertDialog (Context context)
136 {
137 AlertDialog dialog;
138 int size;
139 EmacsButton button;
140 LinearLayout layout;
141 Button buttonView;
142 ViewGroup.LayoutParams layoutParams;
143
144 size = buttons.size ();
145
146 if (size <= 3)
147 {
148 dialog = new AlertDialog.Builder (context).create ();
149 dialog.setMessage (text);
150 dialog.setCancelable (true);
151 dialog.setOnDismissListener (this);
152
153 if (title != null)
154 dialog.setTitle (title);
155
156 /* There are less than 4 buttons. Add the buttons the way
157 Android intends them to be added. */
158
159 if (size >= 1)
160 {
161 button = buttons.get (0);
162 dialog.setButton (DialogInterface.BUTTON_POSITIVE,
163 button.name, button);
164 }
165
166 if (size >= 2)
167 {
168 button = buttons.get (1);
169 dialog.setButton (DialogInterface.BUTTON_NEUTRAL,
170 button.name, button);
171 buttonView
172 = dialog.getButton (DialogInterface.BUTTON_NEUTRAL);
173 buttonView.setEnabled (button.enabled);
174 }
175
176 if (size >= 3)
177 {
178 button = buttons.get (2);
179 dialog.setButton (DialogInterface.BUTTON_NEGATIVE,
180 button.name, button);
181 }
182 }
183 else
184 {
185 /* There are more than 4 buttons. Add them all to a
186 LinearLayout. */
187 layout = new LinearLayout (context);
188 layoutParams
189 = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT,
190 ViewGroup.LayoutParams.WRAP_CONTENT);
191
192 for (EmacsButton emacsButton : buttons)
193 {
194 buttonView = new Button (context);
195 buttonView.setText (emacsButton.name);
196 buttonView.setOnClickListener (emacsButton);
197 buttonView.setLayoutParams (layoutParams);
198 buttonView.setEnabled (emacsButton.enabled);
199 layout.addView (buttonView);
200 }
201
202 layoutParams
203 = new FrameLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT,
204 ViewGroup.LayoutParams.WRAP_CONTENT);
205 layout.setLayoutParams (layoutParams);
206
207 /* Add that layout to the dialog's custom view.
208
209 android.R.id.custom is documented to work. But looking it
210 up returns NULL, so setView must be used instead. */
211
212 dialog = new AlertDialog.Builder (context).setView (layout).create ();
213 dialog.setMessage (text);
214 dialog.setCancelable (true);
215 dialog.setOnDismissListener (this);
216
217 if (title != null)
218 dialog.setTitle (title);
219 }
220
221 return dialog;
222 }
223
224 /* Internal helper for display run on the main thread. */
225
226 private boolean
227 display1 ()
228 {
229 EmacsActivity activity;
230 int size;
231 Button buttonView;
232 EmacsButton button;
233 AlertDialog dialog;
234
235 if (EmacsActivity.focusedActivities.isEmpty ())
236 return false;
237
238 activity = EmacsActivity.focusedActivities.get (0);
239 dialog = dismissDialog = toAlertDialog (activity);
240 dismissDialog.show ();
241
242 /* If there are less than four buttons, then they must be
243 individually enabled or disabled after the dialog is
244 displayed. */
245 size = buttons.size ();
246
247 if (size <= 3)
248 {
249 if (size >= 1)
250 {
251 button = buttons.get (0);
252 buttonView
253 = dialog.getButton (DialogInterface.BUTTON_POSITIVE);
254 buttonView.setEnabled (button.enabled);
255 }
256
257 if (size >= 2)
258 {
259 button = buttons.get (1);
260 dialog.setButton (DialogInterface.BUTTON_NEUTRAL,
261 button.name, button);
262 buttonView
263 = dialog.getButton (DialogInterface.BUTTON_NEUTRAL);
264 buttonView.setEnabled (button.enabled);
265 }
266
267 if (size >= 3)
268 {
269 button = buttons.get (2);
270 buttonView
271 = dialog.getButton (DialogInterface.BUTTON_NEGATIVE);
272 buttonView.setEnabled (button.enabled);
273 }
274 }
275
276 return true;
277 }
278
279 /* Display this dialog for a suitable activity.
280 Value is false if the dialog could not be displayed,
281 and true otherwise. */
282
283 public boolean
284 display ()
285 {
286 Runnable runnable;
287 final Holder<Boolean> rc;
288
289 rc = new Holder<Boolean> ();
290 runnable = new Runnable () {
291 @Override
292 public void
293 run ()
294 {
295 synchronized (this)
296 {
297 rc.thing = display1 ();
298 notify ();
299 }
300 }
301 };
302
303 synchronized (runnable)
304 {
305 EmacsService.SERVICE.runOnUiThread (runnable);
306
307 try
308 {
309 runnable.wait ();
310 }
311 catch (InterruptedException e)
312 {
313 EmacsNative.emacsAbort ();
314 }
315 }
316
317 return rc.thing;
318 }
319
320
321
322 @Override
323 public void
324 onDismiss (DialogInterface dialog)
325 {
326 Log.d (TAG, "onDismiss: " + this);
327
328 if (wasButtonClicked)
329 return;
330
331 EmacsNative.sendContextMenu ((short) 0, 0);
332 }
333};
diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java
index b42e9556e8c..c29d413f66e 100644
--- a/java/org/gnu/emacs/EmacsDrawRectangle.java
+++ b/java/org/gnu/emacs/EmacsDrawRectangle.java
@@ -36,10 +36,10 @@ public class EmacsDrawRectangle
36 Paint maskPaint, paint; 36 Paint maskPaint, paint;
37 Canvas maskCanvas; 37 Canvas maskCanvas;
38 Bitmap maskBitmap; 38 Bitmap maskBitmap;
39 Rect rect;
40 Rect maskRect, dstRect; 39 Rect maskRect, dstRect;
41 Canvas canvas; 40 Canvas canvas;
42 Bitmap clipBitmap; 41 Bitmap clipBitmap;
42 Rect clipRect;
43 43
44 /* TODO implement stippling. */ 44 /* TODO implement stippling. */
45 if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) 45 if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
@@ -58,13 +58,29 @@ public class EmacsDrawRectangle
58 canvas.clipRect (gc.real_clip_rects[i]); 58 canvas.clipRect (gc.real_clip_rects[i]);
59 } 59 }
60 60
61 paint = gc.gcPaint; 61 /* Clip to the clipRect because some versions of Android draw an
62 rect = new Rect (x, y, x + width, y + height); 62 overly wide line. */
63 clipRect = new Rect (x, y, x + width + 1,
64 y + height + 1);
65 canvas.clipRect (clipRect);
63 66
64 paint.setStyle (Paint.Style.STROKE); 67 paint = gc.gcPaint;
65 68
66 if (gc.clip_mask == null) 69 if (gc.clip_mask == null)
67 canvas.drawRect (rect, paint); 70 {
71 /* canvas.drawRect just doesn't work on Android, producing
72 different results on various devices. Do a 5 point
73 PolyLine instead. */
74 canvas.drawLine ((float) x, (float) y, (float) x + width,
75 (float) y, paint);
76 canvas.drawLine ((float) x + width, (float) y,
77 (float) x + width, (float) y + height,
78 paint);
79 canvas.drawLine ((float) x + width, (float) y + height,
80 (float) x, (float) y + height, paint);
81 canvas.drawLine ((float) x, (float) y + height,
82 (float) x, (float) y, paint);
83 }
68 else 84 else
69 { 85 {
70 /* Drawing with a clip mask involves calculating the 86 /* Drawing with a clip mask involves calculating the
@@ -116,10 +132,12 @@ public class EmacsDrawRectangle
116 /* Finally, draw the mask bitmap to the destination. */ 132 /* Finally, draw the mask bitmap to the destination. */
117 paint.setXfermode (null); 133 paint.setXfermode (null);
118 canvas.drawBitmap (maskBitmap, null, maskRect, paint); 134 canvas.drawBitmap (maskBitmap, null, maskRect, paint);
135
136 /* Recycle this unused bitmap. */
137 maskBitmap.recycle ();
119 } 138 }
120 139
121 canvas.restore (); 140 canvas.restore ();
122 drawable.damageRect (new Rect (x, y, x + width + 1, 141 drawable.damageRect (clipRect);
123 y + height + 1));
124 } 142 }
125} 143}
diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java
index b733b417d6b..7cc55d3db96 100644
--- a/java/org/gnu/emacs/EmacsFillRectangle.java
+++ b/java/org/gnu/emacs/EmacsFillRectangle.java
@@ -115,6 +115,9 @@ public class EmacsFillRectangle
115 /* Finally, draw the mask bitmap to the destination. */ 115 /* Finally, draw the mask bitmap to the destination. */
116 paint.setXfermode (null); 116 paint.setXfermode (null);
117 canvas.drawBitmap (maskBitmap, null, maskRect, paint); 117 canvas.drawBitmap (maskBitmap, null, maskRect, paint);
118
119 /* Recycle this unused bitmap. */
120 maskBitmap.recycle ();
118 } 121 }
119 122
120 canvas.restore (); 123 canvas.restore ();
diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java
index 15452f007c4..85931c2abd4 100644
--- a/java/org/gnu/emacs/EmacsPixmap.java
+++ b/java/org/gnu/emacs/EmacsPixmap.java
@@ -25,6 +25,8 @@ import android.graphics.Bitmap;
25import android.graphics.Canvas; 25import android.graphics.Canvas;
26import android.graphics.Rect; 26import android.graphics.Rect;
27 27
28import android.os.Build;
29
28/* Drawable backed by bitmap. */ 30/* Drawable backed by bitmap. */
29 31
30public class EmacsPixmap extends EmacsHandleObject 32public class EmacsPixmap extends EmacsHandleObject
@@ -123,4 +125,25 @@ public class EmacsPixmap extends EmacsHandleObject
123 { 125 {
124 return bitmap; 126 return bitmap;
125 } 127 }
128
129 @Override
130 public void
131 destroyHandle ()
132 {
133 boolean needCollect;
134
135 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
136 needCollect = (bitmap.getByteCount ()
137 >= 1024 * 512);
138 else
139 needCollect = (bitmap.getAllocationByteCount ()
140 >= 1024 * 512);
141
142 bitmap.recycle ();
143 bitmap = null;
144
145 /* Collect the bitmap storage if the bitmap is big. */
146 if (needCollect)
147 Runtime.getRuntime ().gc ();
148 }
126}; 149};
diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java
index 445d8ffa023..6137fd74a7f 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -70,9 +70,9 @@ public class EmacsView extends ViewGroup
70 event regardless of what changed. */ 70 event regardless of what changed. */
71 public boolean mustReportLayout; 71 public boolean mustReportLayout;
72 72
73 /* If non-null, whether or not bitmaps must be recreated upon the 73 /* Whether or not bitmaps must be recreated upon the next call to
74 next call to getBitmap. */ 74 getBitmap. */
75 private Rect bitmapDirty; 75 private boolean bitmapDirty;
76 76
77 /* Whether or not a popup is active. */ 77 /* Whether or not a popup is active. */
78 private boolean popupActive; 78 private boolean popupActive;
@@ -80,6 +80,9 @@ public class EmacsView extends ViewGroup
80 /* The current context menu. */ 80 /* The current context menu. */
81 private EmacsContextMenu contextMenu; 81 private EmacsContextMenu contextMenu;
82 82
83 /* The last measured width and height. */
84 private int measuredWidth, measuredHeight;
85
83 public 86 public
84 EmacsView (EmacsWindow window) 87 EmacsView (EmacsWindow window)
85 { 88 {
@@ -116,13 +119,27 @@ public class EmacsView extends ViewGroup
116 { 119 {
117 Bitmap oldBitmap; 120 Bitmap oldBitmap;
118 121
122 if (measuredWidth == 0 || measuredHeight == 0)
123 return;
124
125 /* If bitmap is the same width and height as the measured width
126 and height, there is no need to do anything. Avoid allocating
127 the extra bitmap. */
128 if (bitmap != null
129 && (bitmap.getWidth () == measuredWidth
130 && bitmap.getHeight () == measuredHeight))
131 {
132 bitmapDirty = false;
133 return;
134 }
135
119 /* Save the old bitmap. */ 136 /* Save the old bitmap. */
120 oldBitmap = bitmap; 137 oldBitmap = bitmap;
121 138
122 /* Recreate the front and back buffer bitmaps. */ 139 /* Recreate the front and back buffer bitmaps. */
123 bitmap 140 bitmap
124 = Bitmap.createBitmap (bitmapDirty.width (), 141 = Bitmap.createBitmap (measuredWidth,
125 bitmapDirty.height (), 142 measuredHeight,
126 Bitmap.Config.ARGB_8888); 143 Bitmap.Config.ARGB_8888);
127 bitmap.eraseColor (0xffffffff); 144 bitmap.eraseColor (0xffffffff);
128 145
@@ -133,23 +150,27 @@ public class EmacsView extends ViewGroup
133 if (oldBitmap != null) 150 if (oldBitmap != null)
134 canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ()); 151 canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ());
135 152
136 bitmapDirty = null; 153 bitmapDirty = false;
154
155 /* Explicitly free the old bitmap's memory. */
156 if (oldBitmap != null)
157 oldBitmap.recycle ();
158
159 /* Some Android versions still don't free the bitmap until the
160 next GC. */
161 Runtime.getRuntime ().gc ();
137 } 162 }
138 163
139 public synchronized void 164 public synchronized void
140 explicitlyDirtyBitmap (Rect rect) 165 explicitlyDirtyBitmap ()
141 { 166 {
142 if (bitmapDirty == null 167 bitmapDirty = true;
143 && (bitmap == null
144 || rect.width () != bitmap.getWidth ()
145 || rect.height () != bitmap.getHeight ()))
146 bitmapDirty = rect;
147 } 168 }
148 169
149 public synchronized Bitmap 170 public synchronized Bitmap
150 getBitmap () 171 getBitmap ()
151 { 172 {
152 if (bitmapDirty != null) 173 if (bitmapDirty || bitmap == null)
153 handleDirtyBitmap (); 174 handleDirtyBitmap ();
154 175
155 return bitmap; 176 return bitmap;
@@ -158,7 +179,7 @@ public class EmacsView extends ViewGroup
158 public synchronized Canvas 179 public synchronized Canvas
159 getCanvas () 180 getCanvas ()
160 { 181 {
161 if (bitmapDirty != null) 182 if (bitmapDirty || bitmap == null)
162 handleDirtyBitmap (); 183 handleDirtyBitmap ();
163 184
164 return canvas; 185 return canvas;
@@ -196,8 +217,12 @@ public class EmacsView extends ViewGroup
196 super.setMeasuredDimension (width, height); 217 super.setMeasuredDimension (width, height);
197 } 218 }
198 219
220 /* Note that the monitor lock for the window must never be held from
221 within the lock for the view, because the window also locks the
222 other way around. */
223
199 @Override 224 @Override
200 protected synchronized void 225 protected void
201 onLayout (boolean changed, int left, int top, int right, 226 onLayout (boolean changed, int left, int top, int right,
202 int bottom) 227 int bottom)
203 { 228 {
@@ -213,12 +238,13 @@ public class EmacsView extends ViewGroup
213 window.viewLayout (left, top, right, bottom); 238 window.viewLayout (left, top, right, bottom);
214 } 239 }
215 240
216 if (changed 241 measuredWidth = right - left;
217 /* Check that a change has really happened. */ 242 measuredHeight = bottom - top;
218 && (bitmapDirty == null 243
219 || bitmapDirty.width () != right - left 244 /* Dirty the back buffer. */
220 || bitmapDirty.height () != bottom - top)) 245
221 bitmapDirty = new Rect (left, top, right, bottom); 246 if (changed)
247 explicitlyDirtyBitmap ();
222 248
223 for (i = 0; i < count; ++i) 249 for (i = 0; i < count; ++i)
224 { 250 {
@@ -472,4 +498,20 @@ public class EmacsView extends ViewGroup
472 contextMenu = null; 498 contextMenu = null;
473 popupActive = false; 499 popupActive = false;
474 } 500 }
501
502 @Override
503 public synchronized void
504 onDetachedFromWindow ()
505 {
506 synchronized (this)
507 {
508 /* Recycle the bitmap and call GC. */
509 bitmap.recycle ();
510 bitmap = null;
511 canvas = null;
512
513 /* Collect the bitmap storage; it could be large. */
514 Runtime.getRuntime ().gc ();
515 }
516 }
475}; 517};
diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java
index 7181bc89fea..f5b50f11f14 100644
--- a/java/org/gnu/emacs/EmacsWindow.java
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -260,7 +260,7 @@ public class EmacsWindow extends EmacsHandleObject
260 { 260 {
261 /* This is necessary because otherwise subsequent drawing on the 261 /* This is necessary because otherwise subsequent drawing on the
262 Emacs thread may be lost. */ 262 Emacs thread may be lost. */
263 view.explicitlyDirtyBitmap (rect); 263 view.explicitlyDirtyBitmap ();
264 264
265 EmacsService.SERVICE.runOnUiThread (new Runnable () { 265 EmacsService.SERVICE.runOnUiThread (new Runnable () {
266 @Override 266 @Override