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 android.view.View;
23
24 import android.os.Build;
25
26 import android.graphics.Bitmap;
27 import android.graphics.Canvas;
28 import android.graphics.Rect;
29 import android.graphics.Paint;
30
31 import java.lang.ref.WeakReference;
32
33 /* This originally extended SurfaceView. However, doing so proved to
34 be too slow, and Android's surface view keeps up to three of its
35 own back buffers, which use too much memory (up to 96 MB for a
36 single frame.) */
37
38 public final class EmacsSurfaceView extends View
39 {
40 private static final String TAG = "EmacsSurfaceView";
41
42 /* The complete buffer contents at the time of the last draw. */
43 private Bitmap frontBuffer;
44
45 /* Whether frontBuffer has been updated since the last call to
46 `onDraw'. */
47 private boolean bitmapChanged;
48
49 /* Canvas representing the front buffer. */
50 private Canvas bitmapCanvas;
51
52 /* Reference to the last bitmap copied to the front buffer. */
53 private WeakReference<Bitmap> bitmap;
54
55 /* Paint objects used on the main and UI threads, respectively. */
56 private static final Paint bitmapPaint, uiThreadPaint;
57
58 static
59 {
60 /* Create two different Paint objects; one is used on the main
61 thread for buffer swaps, while the other is used from the UI
62 thread in `onDraw'. This is necessary because Paint objects
63 are not thread-safe, even if their uses are interlocked. */
64
65 bitmapPaint = new Paint ();
66 uiThreadPaint = new Paint ();
67 };
68
69 public
70 EmacsSurfaceView (EmacsView view)
71 {
72 super (view.getContext ());
73
74 this.bitmap = new WeakReference<Bitmap> (null);
75 }
76
77 private void
78 copyToFrontBuffer (Bitmap bitmap, Rect damageRect)
79 {
80 EmacsService.checkEmacsThread ();
81
82 if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O
83 && Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1
84 && Build.VERSION.SDK_INT != Build.VERSION_CODES.N_MR1
85 && Build.VERSION.SDK_INT != Build.VERSION_CODES.N)
86 {
87 /* If `drawBitmap' can safely be used while a bitmap is locked
88 by another thread, continue here... */
89
90 if (damageRect != null)
91 bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect,
92 bitmapPaint);
93 else
94 bitmapCanvas.drawBitmap (bitmap, 0f, 0f, bitmapPaint);
95 }
96 else
97 {
98 /* But if it can not, as on Android 7.0 through 8.1, then use
99 a replacement function. */
100
101 if (damageRect != null)
102 EmacsNative.blitRect (bitmap, frontBuffer,
103 damageRect.left,
104 damageRect.top,
105 damageRect.right,
106 damageRect.bottom);
107 else
108 EmacsNative.blitRect (bitmap, frontBuffer, 0, 0,
109 bitmap.getWidth (),
110 bitmap.getHeight ());
111 }
112
113 /* See the large comment inside `onDraw'. */
114 bitmapChanged = true;
115 }
116
117 private void
118 reconfigureFrontBuffer (Bitmap bitmap)
119 {
120 /* First, remove the old front buffer. */
121
122 if (frontBuffer != null)
123 {
124 frontBuffer.recycle ();
125 frontBuffer = null;
126 bitmapCanvas = null;
127 }
128
129 this.bitmap = new WeakReference<Bitmap> (bitmap);
130
131 /* Next, create the new front buffer if necessary. */
132
133 if (bitmap != null && frontBuffer == null)
134 {
135 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
136 frontBuffer = Bitmap.createBitmap (bitmap.getWidth (),
137 bitmap.getHeight (),
138 Bitmap.Config.ARGB_8888,
139 false);
140 else
141 frontBuffer = Bitmap.createBitmap (bitmap.getWidth (),
142 bitmap.getHeight (),
143 Bitmap.Config.ARGB_8888);
144
145 bitmapCanvas = new Canvas (frontBuffer);
146
147 /* And copy over the bitmap contents. */
148 copyToFrontBuffer (bitmap, null);
149 }
150 else if (bitmap != null)
151 /* Just copy over the bitmap contents. */
152 copyToFrontBuffer (bitmap, null);
153 }
154
155 public synchronized void
156 setBitmap (Bitmap bitmap, Rect damageRect)
157 {
158 if (bitmap != this.bitmap.get ())
159 reconfigureFrontBuffer (bitmap);
160 else if (bitmap != null)
161 copyToFrontBuffer (bitmap, damageRect);
162
163 if (bitmap != null)
164 {
165 /* In newer versions of Android, the invalid rectangle is
166 supposedly internally calculated by the system. How that
167 is done is unknown, but calling `invalidateRect' is now
168 deprecated.
169
170 Fortunately, nobody has deprecated the version of
171 `postInvalidate' that accepts a dirty rectangle. */
172
173 if (damageRect != null)
174 postInvalidate (damageRect.left, damageRect.top,
175 damageRect.right, damageRect.bottom);
176 else
177 postInvalidate ();
178 }
179 }
180
181 @Override
182 public synchronized void
183 onDraw (Canvas canvas)
184 {
185 /* Paint the view's bitmap; the bitmap might be recycled right
186 now. */
187
188 if (frontBuffer != null)
189 {
190 /* The first time the bitmap is drawn after a buffer swap,
191 mark its contents as having changed. This increments the
192 ``generation ID'' used by Android to avoid uploading buffer
193 textures for unchanged bitmaps.
194
195 When a buffer swap takes place, the bitmap is initially
196 updated from the Emacs thread, resulting in the generation
197 ID being increased. If the render thread is texturizing
198 the bitmap while the swap takes place, it might record the
199 generation ID after the update for a texture containing the
200 contents of the bitmap prior to the swap, leaving the
201 texture tied to the bitmap partially updated.
202
203 Android never calls `onDraw' if the render thread is still
204 processing the bitmap. Update the generation ID here to
205 ensure that a new texture will be uploaded if the bitmap
206 has changed.
207
208 Uploading the bitmap contents to the GPU uses an excessive
209 amount of memory, as the entire bitmap is placed into the
210 graphics command queue, but this memory is actually shared
211 among all other applications and reclaimed by the system
212 when necessary. */
213
214 if (bitmapChanged)
215 {
216 EmacsNative.notifyPixelsChanged (frontBuffer);
217 bitmapChanged = false;
218 }
219
220 canvas.drawBitmap (frontBuffer, 0f, 0f, uiThreadPaint);
221 }
222 }
223 };