aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
authorPo Lu2024-04-03 20:29:10 +0800
committerPo Lu2024-04-03 20:31:22 +0800
commit7df66b4762ff51e394b8db03dfffe888bdba0a67 (patch)
treec576b857afa236b95964cb28988b4aa2c3cabcc6 /java
parentfa9791fe6af2bbedf1f5bb3e7dd8879a0191ebf1 (diff)
downloademacs-7df66b4762ff51e394b8db03dfffe888bdba0a67.tar.gz
emacs-7df66b4762ff51e394b8db03dfffe888bdba0a67.zip
Better align Emacs window management with Android task lifecycles
* java/org/gnu/emacs/EmacsActivity.java (onCreate): Permit overriding by child classes. (onDestroy): Minor stylistic adjustments. (getAttachmentToken): New function. * java/org/gnu/emacs/EmacsMultitaskActivity.java (onCreate) (getAttachmentToken): New functions. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): <attachmentToken, preserve, previouslyAttached>: New variables. (onActivityDetached): Remove redundant isFinishing argument. (reparentTo): Reset the foregoing fields before registering with the window manager. * java/org/gnu/emacs/EmacsWindowManager.java (EmacsWindowManager): Rename from EmacsWindowAttachmentManager. (WindowConsumer): New function getAttachmentToken. (isWindowEligible): New function. (registerWindowConsumer, registerWindow, removeWindowConsumer) (detachWindow): Implement a new window management strategy on API 29 and subsequent releases where both varieties of toplevel window are permanently, except when reparented, bound to the activities to which they attach, and Emacs establishes at strategic junctures whether those activities remain present. (getTaskToken, pruneWindows): New functions.
Diffstat (limited to 'java')
-rw-r--r--java/org/gnu/emacs/EmacsActivity.java28
-rw-r--r--java/org/gnu/emacs/EmacsMultitaskActivity.java38
-rw-r--r--java/org/gnu/emacs/EmacsService.java2
-rw-r--r--java/org/gnu/emacs/EmacsView.java12
-rw-r--r--java/org/gnu/emacs/EmacsWindow.java53
-rw-r--r--java/org/gnu/emacs/EmacsWindowAttachmentManager.java211
-rw-r--r--java/org/gnu/emacs/EmacsWindowManager.java369
7 files changed, 458 insertions, 255 deletions
diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java
index e380b7bfc2a..a939641a752 100644
--- a/java/org/gnu/emacs/EmacsActivity.java
+++ b/java/org/gnu/emacs/EmacsActivity.java
@@ -50,7 +50,7 @@ import android.view.WindowInsetsController;
50import android.widget.FrameLayout; 50import android.widget.FrameLayout;
51 51
52public class EmacsActivity extends Activity 52public class EmacsActivity extends Activity
53 implements EmacsWindowAttachmentManager.WindowConsumer, 53 implements EmacsWindowManager.WindowConsumer,
54 ViewTreeObserver.OnGlobalLayoutListener 54 ViewTreeObserver.OnGlobalLayoutListener
55{ 55{
56 public static final String TAG = "EmacsActivity"; 56 public static final String TAG = "EmacsActivity";
@@ -218,7 +218,7 @@ public class EmacsActivity extends Activity
218 } 218 }
219 219
220 @Override 220 @Override
221 public final void 221 public void
222 onCreate (Bundle savedInstanceState) 222 onCreate (Bundle savedInstanceState)
223 { 223 {
224 FrameLayout.LayoutParams params; 224 FrameLayout.LayoutParams params;
@@ -249,7 +249,7 @@ public class EmacsActivity extends Activity
249 EmacsService.startEmacsService (this); 249 EmacsService.startEmacsService (this);
250 250
251 /* Add this activity to the list of available activities. */ 251 /* Add this activity to the list of available activities. */
252 EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this); 252 EmacsWindowManager.MANAGER.registerWindowConsumer (this);
253 253
254 /* Start observing global layout changes between Jelly Bean and Q. 254 /* Start observing global layout changes between Jelly Bean and Q.
255 This is required to restore the fullscreen state whenever the 255 This is required to restore the fullscreen state whenever the
@@ -326,16 +326,16 @@ public class EmacsActivity extends Activity
326 public final void 326 public final void
327 onDestroy () 327 onDestroy ()
328 { 328 {
329 EmacsWindowAttachmentManager manager; 329 EmacsWindowManager manager;
330 boolean isMultitask; 330 boolean isMultitask, reallyFinishing;
331 331
332 manager = EmacsWindowAttachmentManager.MANAGER; 332 manager = EmacsWindowManager.MANAGER;
333 333
334 /* The activity will die shortly hereafter. If there is a window 334 /* The activity will die shortly hereafter. If there is a window
335 attached, close it now. */ 335 attached, close it now. */
336 isMultitask = this instanceof EmacsMultitaskActivity; 336 isMultitask = this instanceof EmacsMultitaskActivity;
337 manager.removeWindowConsumer (this, (isMultitask 337 reallyFinishing = isReallyFinishing ();
338 || isReallyFinishing ())); 338 manager.removeWindowConsumer (this, isMultitask || reallyFinishing);
339 focusedActivities.remove (this); 339 focusedActivities.remove (this);
340 invalidateFocus (2); 340 invalidateFocus (2);
341 341
@@ -383,7 +383,7 @@ public class EmacsActivity extends Activity
383 { 383 {
384 isPaused = true; 384 isPaused = true;
385 385
386 EmacsWindowAttachmentManager.MANAGER.noticeIconified (this); 386 EmacsWindowManager.MANAGER.noticeIconified (this);
387 super.onPause (); 387 super.onPause ();
388 } 388 }
389 389
@@ -394,7 +394,7 @@ public class EmacsActivity extends Activity
394 isPaused = false; 394 isPaused = false;
395 timeOfLastInteraction = 0; 395 timeOfLastInteraction = 0;
396 396
397 EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); 397 EmacsWindowManager.MANAGER.noticeDeiconified (this);
398 super.onResume (); 398 super.onResume ();
399 } 399 }
400 400
@@ -538,6 +538,14 @@ public class EmacsActivity extends Activity
538 538
539 EmacsNative.sendNotificationAction (tag, action); 539 EmacsNative.sendNotificationAction (tag, action);
540 } 540 }
541
542 @Override
543 public long
544 getAttachmentToken ()
545 {
546 return -1; /* This is overridden by EmacsMultitaskActivity. */
547 }
548
541 549
542 550
543 @Override 551 @Override
diff --git a/java/org/gnu/emacs/EmacsMultitaskActivity.java b/java/org/gnu/emacs/EmacsMultitaskActivity.java
index 7229e34496e..10963ecfd3f 100644
--- a/java/org/gnu/emacs/EmacsMultitaskActivity.java
+++ b/java/org/gnu/emacs/EmacsMultitaskActivity.java
@@ -19,11 +19,39 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19 19
20package org.gnu.emacs; 20package org.gnu.emacs;
21 21
22/* This class only exists because EmacsActivity is already defined as 22import android.content.Intent;
23 an activity, and the system wants a new class in order to define a 23
24 new activity. */ 24import android.os.Bundle;
25
26/* In large measure, this class only exists because EmacsActivity is
27 already defined as an activity, and the system requires that every
28 new activity be defined by a new class. */
25 29
26public final class EmacsMultitaskActivity extends EmacsActivity 30public final class EmacsMultitaskActivity extends EmacsActivity
27{ 31{
28 32 /* Token provided by the creator. */
29} 33 private long activityToken;
34
35 @Override
36 public final void
37 onCreate (Bundle savedInstanceState)
38 {
39 Intent intent;
40 String token;
41
42 intent = getIntent ();
43 token = EmacsWindowManager.ACTIVITY_TOKEN;
44
45 if (intent != null)
46 activityToken = intent.getLongExtra (token, -2);
47
48 super.onCreate (savedInstanceState);
49 }
50
51 @Override
52 public final long
53 getAttachmentToken ()
54 {
55 return activityToken;
56 }
57};
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index 446cd26a3dd..171b427b05b 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -494,7 +494,7 @@ public final class EmacsService extends Service
494 494
495 if (window == null) 495 if (window == null)
496 /* Just return all the windows without a parent. */ 496 /* Just return all the windows without a parent. */
497 windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows (); 497 windowList = EmacsWindowManager.MANAGER.copyWindows ();
498 else 498 else
499 windowList = window.children; 499 windowList = window.children;
500 500
diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java
index 109208b2518..37aeded9938 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -511,6 +511,8 @@ public final class EmacsView extends ViewGroup
511 && !EmacsNative.shouldForwardMultimediaButtons ()) 511 && !EmacsNative.shouldForwardMultimediaButtons ())
512 return false; 512 return false;
513 513
514 Log.d (TAG, "onKeyDown: " + event.toString ());
515
514 window.onKeyDown (keyCode, event); 516 window.onKeyDown (keyCode, event);
515 return true; 517 return true;
516 } 518 }
@@ -708,12 +710,12 @@ public final class EmacsView extends ViewGroup
708 contextMenu = null; 710 contextMenu = null;
709 popupActive = false; 711 popupActive = false;
710 712
711 /* It is not possible to know with 100% certainty which activity 713 /* It is not possible to know with 100% certainty which activity is
712 is currently displaying the context menu. Loop through each 714 currently displaying the context menu. Loop over each activity
713 activity and call `closeContextMenu' instead. */ 715 and call `closeContextMenu' instead. */
714 716
715 for (EmacsWindowAttachmentManager.WindowConsumer consumer 717 for (EmacsWindowManager.WindowConsumer consumer
716 : EmacsWindowAttachmentManager.MANAGER.consumers) 718 : EmacsWindowManager.MANAGER.consumers)
717 { 719 {
718 if (consumer instanceof EmacsActivity) 720 if (consumer instanceof EmacsActivity)
719 ((EmacsActivity) consumer).closeContextMenu (); 721 ((EmacsActivity) consumer).closeContextMenu ();
diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java
index 2baede1d2d0..9b444c3f144 100644
--- a/java/org/gnu/emacs/EmacsWindow.java
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -112,7 +112,7 @@ public final class EmacsWindow extends EmacsHandleObject
112 private SparseArray<Coordinate> pointerMap; 112 private SparseArray<Coordinate> pointerMap;
113 113
114 /* The window consumer currently attached, if it exists. */ 114 /* The window consumer currently attached, if it exists. */
115 private EmacsWindowAttachmentManager.WindowConsumer attached; 115 private EmacsWindowManager.WindowConsumer attached;
116 116
117 /* The window background scratch GC. foreground is always the 117 /* The window background scratch GC. foreground is always the
118 window background. */ 118 window background. */
@@ -159,6 +159,16 @@ public final class EmacsWindow extends EmacsHandleObject
159 values are -1 if no drag and drop operation is under way. */ 159 values are -1 if no drag and drop operation is under way. */
160 private int dndXPosition, dndYPosition; 160 private int dndXPosition, dndYPosition;
161 161
162 /* Identifier binding this window to the activity created for it, or
163 -1 if the window should be attached to system-created activities
164 (i.e. the activity launched by the system at startup). Value is
165 meaningless under API level 29 and earlier. */
166 public long attachmentToken;
167
168 /* Whether this window should be preserved during window pruning,
169 and whether this window has previously been attached to a task. */
170 public boolean preserve, previouslyAttached;
171
162 public 172 public
163 EmacsWindow (short handle, final EmacsWindow parent, int x, int y, 173 EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
164 int width, int height, boolean overrideRedirect) 174 int width, int height, boolean overrideRedirect)
@@ -255,12 +265,12 @@ public final class EmacsWindow extends EmacsHandleObject
255 run () 265 run ()
256 { 266 {
257 ViewManager parent; 267 ViewManager parent;
258 EmacsWindowAttachmentManager manager; 268 EmacsWindowManager manager;
259 269
260 if (EmacsActivity.focusedWindow == EmacsWindow.this) 270 if (EmacsActivity.focusedWindow == EmacsWindow.this)
261 EmacsActivity.focusedWindow = null; 271 EmacsActivity.focusedWindow = null;
262 272
263 manager = EmacsWindowAttachmentManager.MANAGER; 273 manager = EmacsWindowManager.MANAGER;
264 view.setVisibility (View.GONE); 274 view.setVisibility (View.GONE);
265 275
266 /* If the window manager is set, use that instead. */ 276 /* If the window manager is set, use that instead. */
@@ -281,12 +291,12 @@ public final class EmacsWindow extends EmacsHandleObject
281 } 291 }
282 292
283 public void 293 public void
284 setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer) 294 setConsumer (EmacsWindowManager.WindowConsumer consumer)
285 { 295 {
286 attached = consumer; 296 attached = consumer;
287 } 297 }
288 298
289 public EmacsWindowAttachmentManager.WindowConsumer 299 public EmacsWindowManager.WindowConsumer
290 getAttachedConsumer () 300 getAttachedConsumer ()
291 { 301 {
292 return attached; 302 return attached;
@@ -420,7 +430,7 @@ public final class EmacsWindow extends EmacsHandleObject
420 public void 430 public void
421 run () 431 run ()
422 { 432 {
423 EmacsWindowAttachmentManager manager; 433 EmacsWindowManager manager;
424 WindowManager windowManager; 434 WindowManager windowManager;
425 Activity ctx; 435 Activity ctx;
426 Object tem; 436 Object tem;
@@ -431,7 +441,7 @@ public final class EmacsWindow extends EmacsHandleObject
431 441
432 if (!overrideRedirect) 442 if (!overrideRedirect)
433 { 443 {
434 manager = EmacsWindowAttachmentManager.MANAGER; 444 manager = EmacsWindowManager.MANAGER;
435 445
436 /* If parent is the root window, notice that there are new 446 /* If parent is the root window, notice that there are new
437 children available for interested activities to pick 447 children available for interested activities to pick
@@ -527,9 +537,9 @@ public final class EmacsWindow extends EmacsHandleObject
527 public void 537 public void
528 run () 538 run ()
529 { 539 {
530 EmacsWindowAttachmentManager manager; 540 EmacsWindowManager manager;
531 541
532 manager = EmacsWindowAttachmentManager.MANAGER; 542 manager = EmacsWindowManager.MANAGER;
533 543
534 view.setVisibility (View.GONE); 544 view.setVisibility (View.GONE);
535 545
@@ -809,20 +819,13 @@ public final class EmacsWindow extends EmacsHandleObject
809 EmacsActivity.invalidateFocus (gainFocus ? 6 : 5); 819 EmacsActivity.invalidateFocus (gainFocus ? 6 : 5);
810 } 820 }
811 821
812 /* Notice that the activity has been detached or destroyed. 822 /* Notice that the activity (or its task) has been detached or
813 823 destroyed by explicit user action. */
814 ISFINISHING is set if the activity is not the main activity, or
815 if the activity was not destroyed in response to explicit user
816 action. */
817 824
818 public void 825 public void
819 onActivityDetached (boolean isFinishing) 826 onActivityDetached ()
820 { 827 {
821 /* Destroy the associated frame when the activity is detached in 828 EmacsNative.sendWindowAction (this.handle, 0);
822 response to explicit user action. */
823
824 if (isFinishing)
825 EmacsNative.sendWindowAction (this.handle, 0);
826 } 829 }
827 830
828 831
@@ -1312,13 +1315,17 @@ public final class EmacsWindow extends EmacsHandleObject
1312 public void 1315 public void
1313 run () 1316 run ()
1314 { 1317 {
1315 EmacsWindowAttachmentManager manager; 1318 EmacsWindowManager manager;
1316 ViewManager parent; 1319 ViewManager parent;
1317 1320
1318 /* First, detach this window if necessary. */ 1321 /* First, detach this window if necessary. */
1319 manager = EmacsWindowAttachmentManager.MANAGER; 1322 manager = EmacsWindowManager.MANAGER;
1320 manager.detachWindow (EmacsWindow.this); 1323 manager.detachWindow (EmacsWindow.this);
1321 1324
1325 /* Reset window management state. */
1326 previouslyAttached = false;
1327 attachmentToken = false;
1328
1322 /* Also unparent this view. */ 1329 /* Also unparent this view. */
1323 1330
1324 /* If the window manager is set, use that instead. */ 1331 /* If the window manager is set, use that instead. */
@@ -1858,7 +1865,7 @@ public final class EmacsWindow extends EmacsHandleObject
1858 public void 1865 public void
1859 recreateActivity () 1866 recreateActivity ()
1860 { 1867 {
1861 final EmacsWindowAttachmentManager.WindowConsumer attached; 1868 final EmacsWindowManager.WindowConsumer attached;
1862 1869
1863 attached = this.attached; 1870 attached = this.attached;
1864 1871
diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
deleted file mode 100644
index aae4e2ee49b..00000000000
--- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+++ /dev/null
@@ -1,211 +0,0 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2023-2024 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.ArrayList;
23import java.util.List;
24
25import android.app.ActivityOptions;
26import android.content.Intent;
27import android.os.Build;
28import android.util.Log;
29
30/* Code to paper over the differences in lifecycles between
31 "activities" and windows. There are four interfaces to an instance
32 of this class:
33
34 registerWindowConsumer (WindowConsumer)
35 registerWindow (EmacsWindow)
36 removeWindowConsumer (WindowConsumer)
37 removeWindow (EmacsWindow)
38
39 A WindowConsumer is expected to allow an EmacsWindow to be attached
40 to it, and be created or destroyed.
41
42 Every time a window is created, registerWindow checks the list of
43 window consumers. If a consumer exists and does not currently have
44 a window of its own attached, it gets the new window. Otherwise,
45 the window attachment manager starts a new consumer.
46
47 Every time a consumer is registered, registerWindowConsumer checks
48 the list of available windows. If a window exists and is not
49 currently attached to a consumer, then the consumer gets it.
50
51 Finally, every time a window is removed, the consumer is
52 destroyed. */
53
54public final class EmacsWindowAttachmentManager
55{
56 private final static String TAG = "EmacsWindowAttachmentManager";
57
58 /* The single window attachment manager ``object''. */
59 public static final EmacsWindowAttachmentManager MANAGER;
60
61 static
62 {
63 MANAGER = new EmacsWindowAttachmentManager ();
64 };
65
66 public interface WindowConsumer
67 {
68 public void attachWindow (EmacsWindow window);
69 public EmacsWindow getAttachedWindow ();
70 public void detachWindow ();
71 public void destroy ();
72 };
73
74 /* List of currently attached window consumers. */
75 public List<WindowConsumer> consumers;
76
77 /* List of currently attached windows. */
78 public List<EmacsWindow> windows;
79
80 public
81 EmacsWindowAttachmentManager ()
82 {
83 consumers = new ArrayList<WindowConsumer> ();
84 windows = new ArrayList<EmacsWindow> ();
85 }
86
87 public void
88 registerWindowConsumer (WindowConsumer consumer)
89 {
90 consumers.add (consumer);
91
92 for (EmacsWindow window : windows)
93 {
94 if (window.getAttachedConsumer () == null)
95 {
96 consumer.attachWindow (window);
97 return;
98 }
99 }
100
101 EmacsNative.sendWindowAction ((short) 0, 0);
102 }
103
104 public synchronized void
105 registerWindow (EmacsWindow window)
106 {
107 Intent intent;
108 ActivityOptions options;
109
110 if (windows.contains (window))
111 /* The window is already registered. */
112 return;
113
114 windows.add (window);
115
116 for (WindowConsumer consumer : consumers)
117 {
118 if (consumer.getAttachedWindow () == null)
119 {
120 consumer.attachWindow (window);
121 return;
122 }
123 }
124
125 intent = new Intent (EmacsService.SERVICE,
126 EmacsMultitaskActivity.class);
127
128 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
129 | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
130
131 /* Intent.FLAG_ACTIVITY_NEW_DOCUMENT is lamentably unavailable on
132 older systems than Lolipop. */
133 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
134 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
135
136 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
137 EmacsService.SERVICE.startActivity (intent);
138 else
139 {
140 /* Specify the desired window size. */
141 options = ActivityOptions.makeBasic ();
142 options.setLaunchBounds (window.getGeometry ());
143 EmacsService.SERVICE.startActivity (intent,
144 options.toBundle ());
145 }
146 }
147
148 public void
149 removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
150 {
151 EmacsWindow window;
152
153 window = consumer.getAttachedWindow ();
154
155 if (window != null)
156 {
157 consumer.detachWindow ();
158 window.onActivityDetached (isFinishing);
159 }
160
161 consumers.remove (consumer);
162 }
163
164 public synchronized void
165 detachWindow (EmacsWindow window)
166 {
167 WindowConsumer consumer;
168
169 if (window.getAttachedConsumer () != null)
170 {
171 consumer = window.getAttachedConsumer ();
172
173 consumers.remove (consumer);
174 consumer.destroy ();
175 }
176
177 windows.remove (window);
178 }
179
180 public void
181 noticeIconified (WindowConsumer consumer)
182 {
183 EmacsWindow window;
184
185 /* If a window is attached, send the appropriate iconification
186 events. */
187 window = consumer.getAttachedWindow ();
188
189 if (window != null)
190 window.noticeIconified ();
191 }
192
193 public void
194 noticeDeiconified (WindowConsumer consumer)
195 {
196 EmacsWindow window;
197
198 /* If a window is attached, send the appropriate iconification
199 events. */
200 window = consumer.getAttachedWindow ();
201
202 if (window != null)
203 window.noticeDeiconified ();
204 }
205
206 public synchronized List<EmacsWindow>
207 copyWindows ()
208 {
209 return new ArrayList<EmacsWindow> (windows);
210 }
211};
diff --git a/java/org/gnu/emacs/EmacsWindowManager.java b/java/org/gnu/emacs/EmacsWindowManager.java
new file mode 100644
index 00000000000..fb4ef6344b2
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsWindowManager.java
@@ -0,0 +1,369 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2023-2024 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.ArrayList;
23import java.util.List;
24
25import android.app.ActivityManager.AppTask;
26import android.app.ActivityManager;
27import android.app.ActivityOptions;
28import android.app.TaskInfo;
29
30import android.content.Context;
31import android.content.Intent;
32
33import android.os.Build;
34
35import android.util.Log;
36
37/* Code to paper over the differences in lifecycles between
38 "activities" and windows.
39
40 Four of the five interfaces to be implemented by an instance of this
41 class are relevant on all versions of Android:
42
43 registerWindowConsumer (WindowConsumer)
44 registerWindow (EmacsWindow)
45 removeWindowConsumer (WindowConsumer)
46 removeWindow (EmacsWindow)
47
48 A WindowConsumer is expected to allow an EmacsWindow to be attached
49 to it, and be created or destroyed.
50
51 Whenever a window is created, registerWindow examines the list of
52 window consumers. If a consumer exists and does not currently have a
53 window of its own attached, it gets the new window, while otherwise,
54 the window attachment manager starts a new consumer. Whenever a
55 consumer is registered, registerWindowConsumer checks the list of
56 available windows. If a window exists and is not currently attached
57 to a consumer, then the consumer gets it. Finally, every time a
58 window is removed, the consumer is destroyed.
59
60 getAttachmentToken ()
61
62 should return a token uniquely identifying a consumer, which, on API
63 29 and up, enables attributing the tasks of activities to the windows
64 for which they were created, and with that, consistent interaction
65 between user-visible window state and their underlying frames. */
66
67public final class EmacsWindowManager
68{
69 private static final String TAG = "EmacsWindowManager";
70 public final static String ACTIVITY_TOKEN = "emacs:activity_token";
71
72 /* The single window attachment manager ``object''. */
73 public static final EmacsWindowManager MANAGER;
74
75 /* Monotonically increasing counter from which multitasking activity
76 tokens are produced. */
77 private static long nextActivityToken;
78
79 /* The ActivityManager. */
80 private ActivityManager activityManager;
81
82 static
83 {
84 MANAGER = new EmacsWindowManager ();
85 };
86
87 public interface WindowConsumer
88 {
89 public void attachWindow (EmacsWindow window);
90 public EmacsWindow getAttachedWindow ();
91 public void detachWindow ();
92 public void destroy ();
93 public long getAttachmentToken ();
94 };
95
96 /* List of currently attached window consumers. */
97 public List<WindowConsumer> consumers;
98
99 /* List of currently attached windows. */
100 public List<EmacsWindow> windows;
101
102 public
103 EmacsWindowManager ()
104 {
105 consumers = new ArrayList<WindowConsumer> ();
106 windows = new ArrayList<EmacsWindow> ();
107 }
108
109
110
111
112 /* Return whether the provided WINDOW should be attached to the window
113 consumer CONSUMER. */
114
115 public static boolean
116 isWindowEligible (WindowConsumer consumer, EmacsWindow window)
117 {
118 return (/* The window has yet to be bound. */
119 window.attachmentToken == 0
120 /* Or has already been bound to CONSUMER. */
121 || (window.attachmentToken
122 == consumer.getAttachmentToken ()));
123 }
124
125
126
127 public synchronized void
128 registerWindowConsumer (WindowConsumer consumer)
129 {
130 consumers.add (consumer);
131 pruneWindows ();
132
133 for (EmacsWindow window : windows)
134 {
135 if (window.getAttachedConsumer () == null
136 /* Don't attach this window to CONSUMER if incompatible. */
137 && isWindowEligible (consumer, window))
138 {
139 /* Permantly bind this window to the consumer. */
140 window.attachmentToken = consumer.getAttachmentToken ();
141 window.previouslyAttached = true;
142 consumer.attachWindow (window);
143 return;
144 }
145 }
146
147 EmacsNative.sendWindowAction ((short) 0, 0);
148 }
149
150 public synchronized void
151 registerWindow (EmacsWindow window)
152 {
153 Intent intent;
154 ActivityOptions options;
155 long token;
156
157 if (windows.contains (window))
158 /* The window is already registered. */
159 return;
160
161 windows.add (window);
162
163 for (WindowConsumer consumer : consumers)
164 {
165 if (consumer.getAttachedWindow () == null
166 && isWindowEligible (consumer, window))
167 {
168 /* Permantly bind this window to the consumer. */
169 window.attachmentToken = consumer.getAttachmentToken ();
170 window.previouslyAttached = true;
171 consumer.attachWindow (window);
172 return;
173 }
174 }
175
176 intent = new Intent (EmacsService.SERVICE,
177 EmacsMultitaskActivity.class);
178
179 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
180 | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
181
182 /* Intent.FLAG_ACTIVITY_NEW_DOCUMENT is lamentably unavailable on
183 older systems than Lolipop. */
184 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
185 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
186
187 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
188 EmacsService.SERVICE.startActivity (intent);
189 else
190 {
191 /* Specify the desired window size. */
192 options = ActivityOptions.makeBasic ();
193 options.setLaunchBounds (window.getGeometry ());
194
195 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
196 /* Bind this window to the activity in advance, i.e., before
197 its creation, so that its ID will be recorded in the
198 RecentTasks list. */
199 token = ++nextActivityToken;
200 else
201 /* APIs required for linking activities to windows are not
202 available in earlier Android versions. */
203 token = -2;
204
205 window.attachmentToken = token;
206 intent.putExtra (ACTIVITY_TOKEN, token);
207 EmacsService.SERVICE.startActivity (intent, options.toBundle ());
208 }
209
210 pruneWindows ();
211 }
212
213 public synchronized void
214 removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
215 {
216 EmacsWindow window;
217
218 window = consumer.getAttachedWindow ();
219
220 if (window != null)
221 {
222 consumer.detachWindow ();
223
224 /* Though pruneWindows will likely remove the same windows, call
225 onActivityDetached anyway if isFinishing is set, as in
226 obscure circumstances pruneWindows will not remove frames
227 bound to the system-started task. */
228 if (isFinishing)
229 window.onActivityDetached ();
230 }
231
232 pruneWindows ();
233 consumers.remove (consumer);
234 }
235
236 public synchronized void
237 detachWindow (EmacsWindow window)
238 {
239 WindowConsumer consumer;
240
241 if (window.getAttachedConsumer () != null)
242 {
243 consumer = window.getAttachedConsumer ();
244
245 consumers.remove (consumer);
246 consumer.destroy ();
247 }
248
249 pruneWindows ();
250 windows.remove (window);
251 }
252
253 public void
254 noticeIconified (WindowConsumer consumer)
255 {
256 EmacsWindow window;
257
258 /* If a window is attached, send the appropriate iconification
259 events. */
260 window = consumer.getAttachedWindow ();
261
262 if (window != null)
263 window.noticeIconified ();
264 }
265
266 public void
267 noticeDeiconified (WindowConsumer consumer)
268 {
269 EmacsWindow window;
270
271 /* If a window is attached, send the appropriate iconification
272 events. */
273 window = consumer.getAttachedWindow ();
274
275 if (window != null)
276 window.noticeDeiconified ();
277 }
278
279 public synchronized List<EmacsWindow>
280 copyWindows ()
281 {
282 return new ArrayList<EmacsWindow> (windows);
283 }
284
285
286
287 /* Return the activity token specified in the intent giving rise to
288 TASK, or 0 if absent. */
289
290 private static long
291 getTaskToken (AppTask task)
292 {
293 TaskInfo info;
294
295 info = (TaskInfo) task.getTaskInfo ();
296 return (info.baseIntent != null
297 ? info.baseIntent.getLongExtra (ACTIVITY_TOKEN,
298 -1l)
299 : 0);
300 }
301
302 /* Iterate over each of Emacs's tasks and remove remaining registered
303 windows whose tasks no longer exist. This function should be
304 called upon any event that could plausibly indicate changes in the
305 task list or as to window management. */
306
307 private synchronized void
308 pruneWindows ()
309 {
310 Object object;
311 List<AppTask> appTasks;
312 long taskToken;
313 boolean set;
314
315 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
316 || EmacsService.SERVICE == null)
317 return;
318
319 if (activityManager == null)
320 {
321 object
322 = EmacsService.SERVICE.getSystemService (Context.ACTIVITY_SERVICE);
323 activityManager = (ActivityManager) object;
324 }
325
326 appTasks = activityManager.getAppTasks ();
327
328 /* Clear the preserve flag on all toplevel windows. */
329
330 for (EmacsWindow window : windows)
331 window.preserve = false;
332
333 for (AppTask task : appTasks)
334 {
335 taskToken = getTaskToken (task);
336 set = false;
337
338 if (taskToken == 0)
339 continue;
340
341 /* Search for a window with this token. */
342 for (EmacsWindow window : windows)
343 {
344 if (window.attachmentToken == taskToken)
345 {
346 window.preserve = true;
347 set = true;
348 }
349 }
350
351 if (!set)
352 task.finishAndRemoveTask ();
353 }
354
355 /* Now remove toplevel windows without activity tasks. */
356
357 for (EmacsWindow window : windows)
358 {
359 if (window.preserve
360 /* This is not the initial window. */
361 || (window.attachmentToken < 1)
362 /* Nor has it never been attached. */
363 || !window.previouslyAttached)
364 continue;
365
366 window.onActivityDetached ();
367 }
368 }
369};