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 . */ 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 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 . */ - -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 consumers; - - /* List of currently attached windows. */ - public List windows; - - public - EmacsWindowAttachmentManager () - { - consumers = new ArrayList (); - windows = new ArrayList (); - } - - 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 - copyWindows () - { - return new ArrayList (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 . */ + +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 consumers; + + /* List of currently attached windows. */ + public List windows; + + public + EmacsWindowManager () + { + consumers = new ArrayList (); + windows = new ArrayList (); + } + + + + + /* 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 + copyWindows () + { + return new ArrayList (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 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 (); + } + } +};