diff options
| author | Po Lu | 2023-01-17 22:10:43 +0800 |
|---|---|---|
| committer | Po Lu | 2023-01-17 22:10:43 +0800 |
| commit | 1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f (patch) | |
| tree | d6c709e513882f5d430a98508e631cc503469fab /java | |
| parent | 356249d9faf2b454879ff30f06d97beb97fb9a36 (diff) | |
| download | emacs-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.java | 4 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsDialog.java | 333 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsDrawRectangle.java | 32 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsFillRectangle.java | 3 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsPixmap.java | 23 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsView.java | 84 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsWindow.java | 2 |
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 | |||
| 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.util.List; | ||
| 23 | import java.util.ArrayList; | ||
| 24 | |||
| 25 | import android.app.AlertDialog; | ||
| 26 | import android.content.DialogInterface; | ||
| 27 | import android.content.Context; | ||
| 28 | import android.util.Log; | ||
| 29 | |||
| 30 | import android.widget.Button; | ||
| 31 | import android.widget.LinearLayout; | ||
| 32 | import android.widget.FrameLayout; | ||
| 33 | |||
| 34 | import android.view.View; | ||
| 35 | import 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 | |||
| 41 | public 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; | |||
| 25 | import android.graphics.Canvas; | 25 | import android.graphics.Canvas; |
| 26 | import android.graphics.Rect; | 26 | import android.graphics.Rect; |
| 27 | 27 | ||
| 28 | import android.os.Build; | ||
| 29 | |||
| 28 | /* Drawable backed by bitmap. */ | 30 | /* Drawable backed by bitmap. */ |
| 29 | 31 | ||
| 30 | public class EmacsPixmap extends EmacsHandleObject | 32 | public 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 |