diff options
Diffstat (limited to 'java')
| -rw-r--r-- | java/org/gnu/emacs/EmacsActivity.java | 28 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsMultitaskActivity.java | 38 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsService.java | 2 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsView.java | 12 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsWindow.java | 53 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsWindowAttachmentManager.java | 211 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsWindowManager.java | 369 |
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; | |||
| 50 | import android.widget.FrameLayout; | 50 | import android.widget.FrameLayout; |
| 51 | 51 | ||
| 52 | public class EmacsActivity extends Activity | 52 | public 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 | ||
| 20 | package org.gnu.emacs; | 20 | package org.gnu.emacs; |
| 21 | 21 | ||
| 22 | /* This class only exists because EmacsActivity is already defined as | 22 | import android.content.Intent; |
| 23 | an activity, and the system wants a new class in order to define a | 23 | |
| 24 | new activity. */ | 24 | import 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 | ||
| 26 | public final class EmacsMultitaskActivity extends EmacsActivity | 30 | public 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 | |||
| 3 | Copyright (C) 2023-2024 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.ArrayList; | ||
| 23 | import java.util.List; | ||
| 24 | |||
| 25 | import android.app.ActivityOptions; | ||
| 26 | import android.content.Intent; | ||
| 27 | import android.os.Build; | ||
| 28 | import 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 | |||
| 54 | public 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 | |||
| 3 | Copyright (C) 2023-2024 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.ArrayList; | ||
| 23 | import java.util.List; | ||
| 24 | |||
| 25 | import android.app.ActivityManager.AppTask; | ||
| 26 | import android.app.ActivityManager; | ||
| 27 | import android.app.ActivityOptions; | ||
| 28 | import android.app.TaskInfo; | ||
| 29 | |||
| 30 | import android.content.Context; | ||
| 31 | import android.content.Intent; | ||
| 32 | |||
| 33 | import android.os.Build; | ||
| 34 | |||
| 35 | import 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 | |||
| 67 | public 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 | }; | ||