emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master 7df66b4762f 1/5: Better align Emacs window management with Androi


From: Po Lu
Subject: master 7df66b4762f 1/5: Better align Emacs window management with Android task lifecycles
Date: Wed, 3 Apr 2024 08:49:30 -0400 (EDT)

branch: master
commit 7df66b4762ff51e394b8db03dfffe888bdba0a67
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    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.
---
 java/org/gnu/emacs/EmacsActivity.java              |  28 +-
 java/org/gnu/emacs/EmacsMultitaskActivity.java     |  38 ++-
 java/org/gnu/emacs/EmacsService.java               |   2 +-
 java/org/gnu/emacs/EmacsView.java                  |  12 +-
 java/org/gnu/emacs/EmacsWindow.java                |  53 +--
 .../gnu/emacs/EmacsWindowAttachmentManager.java    | 211 ------------
 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;
 import android.widget.FrameLayout;
 
 public class EmacsActivity extends Activity
-  implements EmacsWindowAttachmentManager.WindowConsumer,
+  implements EmacsWindowManager.WindowConsumer,
   ViewTreeObserver.OnGlobalLayoutListener
 {
   public static final String TAG = "EmacsActivity";
@@ -218,7 +218,7 @@ public class EmacsActivity extends Activity
   }
 
   @Override
-  public final void
+  public void
   onCreate (Bundle savedInstanceState)
   {
     FrameLayout.LayoutParams params;
@@ -249,7 +249,7 @@ public class EmacsActivity extends Activity
     EmacsService.startEmacsService (this);
 
     /* Add this activity to the list of available activities.  */
-    EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
+    EmacsWindowManager.MANAGER.registerWindowConsumer (this);
 
     /* Start observing global layout changes between Jelly Bean and Q.
        This is required to restore the fullscreen state whenever the
@@ -326,16 +326,16 @@ public class EmacsActivity extends Activity
   public final void
   onDestroy ()
   {
-    EmacsWindowAttachmentManager manager;
-    boolean isMultitask;
+    EmacsWindowManager manager;
+    boolean isMultitask, reallyFinishing;
 
-    manager = EmacsWindowAttachmentManager.MANAGER;
+    manager = EmacsWindowManager.MANAGER;
 
     /* The activity will die shortly hereafter.  If there is a window
        attached, close it now.  */
     isMultitask = this instanceof EmacsMultitaskActivity;
-    manager.removeWindowConsumer (this, (isMultitask
-                                        || isReallyFinishing ()));
+    reallyFinishing = isReallyFinishing ();
+    manager.removeWindowConsumer (this, isMultitask || reallyFinishing);
     focusedActivities.remove (this);
     invalidateFocus (2);
 
@@ -383,7 +383,7 @@ public class EmacsActivity extends Activity
   {
     isPaused = true;
 
-    EmacsWindowAttachmentManager.MANAGER.noticeIconified (this);
+    EmacsWindowManager.MANAGER.noticeIconified (this);
     super.onPause ();
   }
 
@@ -394,7 +394,7 @@ public class EmacsActivity extends Activity
     isPaused = false;
     timeOfLastInteraction = 0;
 
-    EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this);
+    EmacsWindowManager.MANAGER.noticeDeiconified (this);
     super.onResume ();
   }
 
@@ -538,6 +538,14 @@ public class EmacsActivity extends Activity
 
     EmacsNative.sendNotificationAction (tag, action);
   }
+
+  @Override
+  public long
+  getAttachmentToken ()
+  {
+    return -1; /* This is overridden by EmacsMultitaskActivity.  */
+  }
+
 
 
   @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/>.  */
 
 package org.gnu.emacs;
 
-/* This class only exists because EmacsActivity is already defined as
-   an activity, and the system wants a new class in order to define a
-   new activity.  */
+import android.content.Intent;
+
+import android.os.Bundle;
+
+/* In large measure, this class only exists because EmacsActivity is
+   already defined as an activity, and the system requires that every
+   new activity be defined by a new class.  */
 
 public final class EmacsMultitaskActivity extends EmacsActivity
 {
-
-}
+  /* Token provided by the creator.  */
+  private long activityToken;
+
+  @Override
+  public final void
+  onCreate (Bundle savedInstanceState)
+  {
+    Intent intent;
+    String token;
+
+    intent = getIntent ();
+    token  = EmacsWindowManager.ACTIVITY_TOKEN;
+
+    if (intent != null)
+      activityToken = intent.getLongExtra (token, -2);
+
+    super.onCreate (savedInstanceState);
+  }
+
+  @Override
+  public final long
+  getAttachmentToken ()
+  {
+    return activityToken;
+  }
+};
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
 
     if (window == null)
       /* Just return all the windows without a parent.  */
-      windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows ();
+      windowList = EmacsWindowManager.MANAGER.copyWindows ();
     else
       windowList = window.children;
 
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
        && !EmacsNative.shouldForwardMultimediaButtons ())
       return false;
 
+    Log.d (TAG, "onKeyDown: " + event.toString ());
+
     window.onKeyDown (keyCode, event);
     return true;
   }
@@ -708,12 +710,12 @@ public final class EmacsView extends ViewGroup
     contextMenu = null;
     popupActive = false;
 
-    /* It is not possible to know with 100% certainty which activity
-       is currently displaying the context menu.  Loop through each
-       activity and call `closeContextMenu' instead.  */
+    /* It is not possible to know with 100% certainty which activity is
+       currently displaying the context menu.  Loop over each activity
+       and call `closeContextMenu' instead.  */
 
-    for (EmacsWindowAttachmentManager.WindowConsumer consumer
-          : EmacsWindowAttachmentManager.MANAGER.consumers)
+    for (EmacsWindowManager.WindowConsumer consumer
+          : EmacsWindowManager.MANAGER.consumers)
       {
        if (consumer instanceof EmacsActivity)
          ((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
   private SparseArray<Coordinate> pointerMap;
 
   /* The window consumer currently attached, if it exists.  */
-  private EmacsWindowAttachmentManager.WindowConsumer attached;
+  private EmacsWindowManager.WindowConsumer attached;
 
   /* The window background scratch GC.  foreground is always the
      window background.  */
@@ -159,6 +159,16 @@ public final class EmacsWindow extends EmacsHandleObject
      values are -1 if no drag and drop operation is under way.  */
   private int dndXPosition, dndYPosition;
 
+  /* Identifier binding this window to the activity created for it, or
+     -1 if the window should be attached to system-created activities
+     (i.e. the activity launched by the system at startup).  Value is
+     meaningless under API level 29 and earlier.  */
+  public long attachmentToken;
+
+  /* Whether this window should be preserved during window pruning,
+     and whether this window has previously been attached to a task.  */
+  public boolean preserve, previouslyAttached;
+
   public
   EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
               int width, int height, boolean overrideRedirect)
@@ -255,12 +265,12 @@ public final class EmacsWindow extends EmacsHandleObject
        run ()
        {
          ViewManager parent;
-         EmacsWindowAttachmentManager manager;
+         EmacsWindowManager manager;
 
          if (EmacsActivity.focusedWindow == EmacsWindow.this)
            EmacsActivity.focusedWindow = null;
 
-         manager = EmacsWindowAttachmentManager.MANAGER;
+         manager = EmacsWindowManager.MANAGER;
          view.setVisibility (View.GONE);
 
          /* If the window manager is set, use that instead.  */
@@ -281,12 +291,12 @@ public final class EmacsWindow extends EmacsHandleObject
   }
 
   public void
-  setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer)
+  setConsumer (EmacsWindowManager.WindowConsumer consumer)
   {
     attached = consumer;
   }
 
-  public EmacsWindowAttachmentManager.WindowConsumer
+  public EmacsWindowManager.WindowConsumer
   getAttachedConsumer ()
   {
     return attached;
@@ -420,7 +430,7 @@ public final class EmacsWindow extends EmacsHandleObject
            public void
            run ()
            {
-             EmacsWindowAttachmentManager manager;
+             EmacsWindowManager manager;
              WindowManager windowManager;
              Activity ctx;
              Object tem;
@@ -431,7 +441,7 @@ public final class EmacsWindow extends EmacsHandleObject
 
              if (!overrideRedirect)
                {
-                 manager = EmacsWindowAttachmentManager.MANAGER;
+                 manager = EmacsWindowManager.MANAGER;
 
                  /* If parent is the root window, notice that there are new
                     children available for interested activities to pick
@@ -527,9 +537,9 @@ public final class EmacsWindow extends EmacsHandleObject
        public void
        run ()
        {
-         EmacsWindowAttachmentManager manager;
+         EmacsWindowManager manager;
 
-         manager = EmacsWindowAttachmentManager.MANAGER;
+         manager = EmacsWindowManager.MANAGER;
 
          view.setVisibility (View.GONE);
 
@@ -809,20 +819,13 @@ public final class EmacsWindow extends EmacsHandleObject
     EmacsActivity.invalidateFocus (gainFocus ? 6 : 5);
   }
 
-  /* Notice that the activity has been detached or destroyed.
-
-     ISFINISHING is set if the activity is not the main activity, or
-     if the activity was not destroyed in response to explicit user
-     action.  */
+  /* Notice that the activity (or its task) has been detached or
+     destroyed by explicit user action.  */
 
   public void
-  onActivityDetached (boolean isFinishing)
+  onActivityDetached ()
   {
-    /* Destroy the associated frame when the activity is detached in
-       response to explicit user action.  */
-
-    if (isFinishing)
-      EmacsNative.sendWindowAction (this.handle, 0);
+    EmacsNative.sendWindowAction (this.handle, 0);
   }
 
 
@@ -1312,13 +1315,17 @@ public final class EmacsWindow extends EmacsHandleObject
        public void
        run ()
        {
-         EmacsWindowAttachmentManager manager;
+         EmacsWindowManager manager;
          ViewManager parent;
 
          /* First, detach this window if necessary.  */
-         manager = EmacsWindowAttachmentManager.MANAGER;
+         manager = EmacsWindowManager.MANAGER;
          manager.detachWindow (EmacsWindow.this);
 
+         /* Reset window management state.  */
+         previouslyAttached = false;
+         attachmentToken = false;
+
          /* Also unparent this view.  */
 
          /* If the window manager is set, use that instead.  */
@@ -1858,7 +1865,7 @@ public final class EmacsWindow extends EmacsHandleObject
   public void
   recreateActivity ()
   {
-    final EmacsWindowAttachmentManager.WindowConsumer attached;
+    final EmacsWindowManager.WindowConsumer attached;
 
     attached = this.attached;
 
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 @@
-/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
-
-Copyright (C) 2023-2024 Free Software Foundation, Inc.
-
-This file is part of GNU Emacs.
-
-GNU Emacs is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or (at
-your option) any later version.
-
-GNU Emacs is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
-
-package org.gnu.emacs;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.os.Build;
-import android.util.Log;
-
-/* Code to paper over the differences in lifecycles between
-   "activities" and windows.  There are four interfaces to an instance
-   of this class:
-
-     registerWindowConsumer (WindowConsumer)
-     registerWindow (EmacsWindow)
-     removeWindowConsumer (WindowConsumer)
-     removeWindow (EmacsWindow)
-
-   A WindowConsumer is expected to allow an EmacsWindow to be attached
-   to it, and be created or destroyed.
-
-   Every time a window is created, registerWindow checks the list of
-   window consumers.  If a consumer exists and does not currently have
-   a window of its own attached, it gets the new window.  Otherwise,
-   the window attachment manager starts a new consumer.
-
-   Every time a consumer is registered, registerWindowConsumer checks
-   the list of available windows.  If a window exists and is not
-   currently attached to a consumer, then the consumer gets it.
-
-   Finally, every time a window is removed, the consumer is
-   destroyed.  */
-
-public final class EmacsWindowAttachmentManager
-{
-  private final static String TAG = "EmacsWindowAttachmentManager";
-
-  /* The single window attachment manager ``object''.  */
-  public static final EmacsWindowAttachmentManager MANAGER;
-
-  static
-  {
-    MANAGER = new EmacsWindowAttachmentManager ();
-  };
-
-  public interface WindowConsumer
-  {
-    public void attachWindow (EmacsWindow window);
-    public EmacsWindow getAttachedWindow ();
-    public void detachWindow ();
-    public void destroy ();
-  };
-
-  /* List of currently attached window consumers.  */
-  public List<WindowConsumer> consumers;
-
-  /* List of currently attached windows.  */
-  public List<EmacsWindow> windows;
-
-  public
-  EmacsWindowAttachmentManager ()
-  {
-    consumers = new ArrayList<WindowConsumer> ();
-    windows = new ArrayList<EmacsWindow> ();
-  }
-
-  public void
-  registerWindowConsumer (WindowConsumer consumer)
-  {
-    consumers.add (consumer);
-
-    for (EmacsWindow window : windows)
-      {
-       if (window.getAttachedConsumer () == null)
-         {
-           consumer.attachWindow (window);
-           return;
-         }
-      }
-
-    EmacsNative.sendWindowAction ((short) 0, 0);
-  }
-
-  public synchronized void
-  registerWindow (EmacsWindow window)
-  {
-    Intent intent;
-    ActivityOptions options;
-
-    if (windows.contains (window))
-      /* The window is already registered.  */
-      return;
-
-    windows.add (window);
-
-    for (WindowConsumer consumer : consumers)
-      {
-       if (consumer.getAttachedWindow () == null)
-         {
-           consumer.attachWindow (window);
-           return;
-         }
-      }
-
-    intent = new Intent (EmacsService.SERVICE,
-                        EmacsMultitaskActivity.class);
-
-    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-
-    /* Intent.FLAG_ACTIVITY_NEW_DOCUMENT is lamentably unavailable on
-       older systems than Lolipop.  */
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
-      intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
-
-    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
-      EmacsService.SERVICE.startActivity (intent);
-    else
-      {
-       /* Specify the desired window size.  */
-       options = ActivityOptions.makeBasic ();
-       options.setLaunchBounds (window.getGeometry ());
-       EmacsService.SERVICE.startActivity (intent,
-                                           options.toBundle ());
-      }
-  }
-
-  public void
-  removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
-  {
-    EmacsWindow window;
-
-    window = consumer.getAttachedWindow ();
-
-    if (window != null)
-      {
-       consumer.detachWindow ();
-       window.onActivityDetached (isFinishing);
-      }
-
-    consumers.remove (consumer);
-  }
-
-  public synchronized void
-  detachWindow (EmacsWindow window)
-  {
-    WindowConsumer consumer;
-
-    if (window.getAttachedConsumer () != null)
-      {
-       consumer = window.getAttachedConsumer ();
-
-       consumers.remove (consumer);
-       consumer.destroy ();
-      }
-
-    windows.remove (window);
-  }
-
-  public void
-  noticeIconified (WindowConsumer consumer)
-  {
-    EmacsWindow window;
-
-    /* If a window is attached, send the appropriate iconification
-       events.  */
-    window = consumer.getAttachedWindow ();
-
-    if (window != null)
-      window.noticeIconified ();
-  }
-
-  public void
-  noticeDeiconified (WindowConsumer consumer)
-  {
-    EmacsWindow window;
-
-    /* If a window is attached, send the appropriate iconification
-       events.  */
-    window = consumer.getAttachedWindow ();
-
-    if (window != null)
-      window.noticeDeiconified ();
-  }
-
-  public synchronized List<EmacsWindow>
-  copyWindows ()
-  {
-    return new ArrayList<EmacsWindow> (windows);
-  }
-};
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 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.ActivityManager.AppTask;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.TaskInfo;
+
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.Build;
+
+import android.util.Log;
+
+/* Code to paper over the differences in lifecycles between
+   "activities" and windows.
+
+   Four of the five interfaces to be implemented by an instance of this
+   class are relevant on all versions of Android:
+
+     registerWindowConsumer (WindowConsumer)
+     registerWindow (EmacsWindow)
+     removeWindowConsumer (WindowConsumer)
+     removeWindow (EmacsWindow)
+
+   A WindowConsumer is expected to allow an EmacsWindow to be attached
+   to it, and be created or destroyed.
+
+   Whenever a window is created, registerWindow examines the list of
+   window consumers.  If a consumer exists and does not currently have a
+   window of its own attached, it gets the new window, while otherwise,
+   the window attachment manager starts a new consumer.  Whenever a
+   consumer is registered, registerWindowConsumer checks the list of
+   available windows.  If a window exists and is not currently attached
+   to a consumer, then the consumer gets it.  Finally, every time a
+   window is removed, the consumer is destroyed.
+
+     getAttachmentToken ()
+
+   should return a token uniquely identifying a consumer, which, on API
+   29 and up, enables attributing the tasks of activities to the windows
+   for which they were created, and with that, consistent interaction
+   between user-visible window state and their underlying frames.  */
+
+public final class EmacsWindowManager
+{
+  private static final String TAG = "EmacsWindowManager";
+  public  final static String ACTIVITY_TOKEN = "emacs:activity_token";
+
+  /* The single window attachment manager ``object''.  */
+  public static final EmacsWindowManager MANAGER;
+
+  /* Monotonically increasing counter from which multitasking activity
+     tokens are produced.  */
+  private static long nextActivityToken;
+
+  /* The ActivityManager.  */
+  private ActivityManager activityManager;
+
+  static
+  {
+    MANAGER = new EmacsWindowManager ();
+  };
+
+  public interface WindowConsumer
+  {
+    public void attachWindow (EmacsWindow window);
+    public EmacsWindow getAttachedWindow ();
+    public void detachWindow ();
+    public void destroy ();
+    public long getAttachmentToken ();
+  };
+
+  /* List of currently attached window consumers.  */
+  public List<WindowConsumer> consumers;
+
+  /* List of currently attached windows.  */
+  public List<EmacsWindow> windows;
+
+  public
+  EmacsWindowManager ()
+  {
+    consumers = new ArrayList<WindowConsumer> ();
+    windows = new ArrayList<EmacsWindow> ();
+  }
+
+
+
+
+  /* Return whether the provided WINDOW should be attached to the window
+     consumer CONSUMER.  */
+
+  public static boolean
+  isWindowEligible (WindowConsumer consumer, EmacsWindow window)
+  {
+    return (/* The window has yet to be bound.  */
+           window.attachmentToken == 0
+           /* Or has already been bound to CONSUMER.  */
+           || (window.attachmentToken
+               == consumer.getAttachmentToken ()));
+  }
+
+
+
+  public synchronized void
+  registerWindowConsumer (WindowConsumer consumer)
+  {
+    consumers.add (consumer);
+    pruneWindows ();
+
+    for (EmacsWindow window : windows)
+      {
+       if (window.getAttachedConsumer () == null
+           /* Don't attach this window to CONSUMER if incompatible.  */
+           && isWindowEligible (consumer, window))
+         {
+           /* Permantly bind this window to the consumer.  */
+           window.attachmentToken = consumer.getAttachmentToken ();
+           window.previouslyAttached = true;
+           consumer.attachWindow (window);
+           return;
+         }
+      }
+
+    EmacsNative.sendWindowAction ((short) 0, 0);
+  }
+
+  public synchronized void
+  registerWindow (EmacsWindow window)
+  {
+    Intent intent;
+    ActivityOptions options;
+    long token;
+
+    if (windows.contains (window))
+      /* The window is already registered.  */
+      return;
+
+    windows.add (window);
+
+    for (WindowConsumer consumer : consumers)
+      {
+       if (consumer.getAttachedWindow () == null
+           && isWindowEligible (consumer, window))
+         {
+           /* Permantly bind this window to the consumer.  */
+           window.attachmentToken = consumer.getAttachmentToken ();
+           window.previouslyAttached = true;
+           consumer.attachWindow (window);
+           return;
+         }
+      }
+
+    intent = new Intent (EmacsService.SERVICE,
+                        EmacsMultitaskActivity.class);
+
+    intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+
+    /* Intent.FLAG_ACTIVITY_NEW_DOCUMENT is lamentably unavailable on
+       older systems than Lolipop.  */
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+      intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+      EmacsService.SERVICE.startActivity (intent);
+    else
+      {
+       /* Specify the desired window size.  */
+       options = ActivityOptions.makeBasic ();
+       options.setLaunchBounds (window.getGeometry ());
+
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+         /* Bind this window to the activity in advance, i.e., before
+            its creation, so that its ID will be recorded in the
+            RecentTasks list.  */
+         token = ++nextActivityToken;
+       else
+         /* APIs required for linking activities to windows are not
+            available in earlier Android versions.  */
+         token = -2;
+
+       window.attachmentToken = token;
+       intent.putExtra (ACTIVITY_TOKEN, token);
+       EmacsService.SERVICE.startActivity (intent, options.toBundle ());
+      }
+
+    pruneWindows ();
+  }
+
+  public synchronized void
+  removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
+  {
+    EmacsWindow window;
+
+    window = consumer.getAttachedWindow ();
+
+    if (window != null)
+      {
+       consumer.detachWindow ();
+
+       /* Though pruneWindows will likely remove the same windows, call
+          onActivityDetached anyway if isFinishing is set, as in
+          obscure circumstances pruneWindows will not remove frames
+          bound to the system-started task.  */
+       if (isFinishing)
+         window.onActivityDetached ();
+      }
+
+    pruneWindows ();
+    consumers.remove (consumer);
+  }
+
+  public synchronized void
+  detachWindow (EmacsWindow window)
+  {
+    WindowConsumer consumer;
+
+    if (window.getAttachedConsumer () != null)
+      {
+       consumer = window.getAttachedConsumer ();
+
+       consumers.remove (consumer);
+       consumer.destroy ();
+      }
+
+    pruneWindows ();
+    windows.remove (window);
+  }
+
+  public void
+  noticeIconified (WindowConsumer consumer)
+  {
+    EmacsWindow window;
+
+    /* If a window is attached, send the appropriate iconification
+       events.  */
+    window = consumer.getAttachedWindow ();
+
+    if (window != null)
+      window.noticeIconified ();
+  }
+
+  public void
+  noticeDeiconified (WindowConsumer consumer)
+  {
+    EmacsWindow window;
+
+    /* If a window is attached, send the appropriate iconification
+       events.  */
+    window = consumer.getAttachedWindow ();
+
+    if (window != null)
+      window.noticeDeiconified ();
+  }
+
+  public synchronized List<EmacsWindow>
+  copyWindows ()
+  {
+    return new ArrayList<EmacsWindow> (windows);
+  }
+
+
+
+  /* Return the activity token specified in the intent giving rise to
+     TASK, or 0 if absent.  */
+
+  private static long
+  getTaskToken (AppTask task)
+  {
+    TaskInfo info;
+
+    info = (TaskInfo) task.getTaskInfo ();
+    return (info.baseIntent != null
+           ? info.baseIntent.getLongExtra (ACTIVITY_TOKEN,
+                                           -1l)
+           : 0);
+  }
+
+  /* Iterate over each of Emacs's tasks and remove remaining registered
+     windows whose tasks no longer exist.  This function should be
+     called upon any event that could plausibly indicate changes in the
+     task list or as to window management.  */
+
+  private synchronized void
+  pruneWindows ()
+  {
+    Object object;
+    List<AppTask> appTasks;
+    long taskToken;
+    boolean set;
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
+       || EmacsService.SERVICE == null)
+      return;
+
+    if (activityManager == null)
+      {
+       object
+         = EmacsService.SERVICE.getSystemService (Context.ACTIVITY_SERVICE);
+       activityManager = (ActivityManager) object;
+      }
+
+    appTasks = activityManager.getAppTasks ();
+
+    /* Clear the preserve flag on all toplevel windows.  */
+
+    for (EmacsWindow window : windows)
+      window.preserve = false;
+
+    for (AppTask task : appTasks)
+      {
+       taskToken = getTaskToken (task);
+       set = false;
+
+       if (taskToken == 0)
+         continue;
+
+       /* Search for a window with this token.  */
+       for (EmacsWindow window : windows)
+         {
+           if (window.attachmentToken == taskToken)
+             {
+               window.preserve = true;
+               set = true;
+             }
+         }
+
+       if (!set)
+         task.finishAndRemoveTask ();
+      }
+
+    /* Now remove toplevel windows without activity tasks.  */
+
+    for (EmacsWindow window : windows)
+      {
+       if (window.preserve
+           /* This is not the initial window.  */
+           || (window.attachmentToken < 1)
+           /* Nor has it never been attached.  */
+           || !window.previouslyAttached)
+         continue;
+
+       window.onActivityDetached ();
+      }
+  }
+};



reply via email to

[Prev in Thread] Current Thread [Next in Thread]