root/java/org/gnu/emacs/EmacsActivity.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. invalidateFocus1
  2. invalidateFocus
  3. detachWindow
  4. attachWindow
  5. destroy
  6. getAttachedWindow
  7. onCreate
  8. onGlobalLayout
  9. onDestroy
  10. onWindowFocusChanged
  11. onPause
  12. onResume
  13. onContextMenuClosed
  14. syncFullscreenWith
  15. onAttachedToWindow
  16. onActivityResult

     1 /* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 package org.gnu.emacs;
    21 
    22 import java.lang.IllegalStateException;
    23 import java.util.List;
    24 import java.util.ArrayList;
    25 
    26 import android.app.Activity;
    27 
    28 import android.content.ContentResolver;
    29 import android.content.Context;
    30 import android.content.Intent;
    31 
    32 import android.os.Build;
    33 import android.os.Bundle;
    34 
    35 import android.util.Log;
    36 
    37 import android.net.Uri;
    38 
    39 import android.view.Menu;
    40 import android.view.View;
    41 import android.view.ViewTreeObserver;
    42 import android.view.Window;
    43 import android.view.WindowInsets;
    44 import android.view.WindowInsetsController;
    45 
    46 import android.widget.FrameLayout;
    47 
    48 public class EmacsActivity extends Activity
    49   implements EmacsWindowAttachmentManager.WindowConsumer,
    50   ViewTreeObserver.OnGlobalLayoutListener
    51 {
    52   public static final String TAG = "EmacsActivity";
    53 
    54   /* ID for URIs from a granted document tree.  */
    55   public static final int ACCEPT_DOCUMENT_TREE = 1;
    56 
    57   /* The currently attached EmacsWindow, or null if none.  */
    58   private EmacsWindow window;
    59 
    60   /* The frame layout associated with the activity.  */
    61   private FrameLayout layout;
    62 
    63   /* List of activities with focus.  */
    64   public static final List<EmacsActivity> focusedActivities;
    65 
    66   /* The last activity to have been focused.  */
    67   public static EmacsActivity lastFocusedActivity;
    68 
    69   /* The currently focused window.  */
    70   public static EmacsWindow focusedWindow;
    71 
    72   /* Whether or not this activity is paused.  */
    73   private boolean isPaused;
    74 
    75   /* Whether or not this activity is fullscreen.  */
    76   private boolean isFullscreen;
    77 
    78   /* The last context menu to be closed.  */
    79   private static Menu lastClosedMenu;
    80 
    81   static
    82   {
    83     focusedActivities = new ArrayList<EmacsActivity> ();
    84   };
    85 
    86   public static void
    87   invalidateFocus1 (EmacsWindow window)
    88   {
    89     if (window.view.isFocused ())
    90       focusedWindow = window;
    91 
    92     for (EmacsWindow child : window.children)
    93       invalidateFocus1 (child);
    94   }
    95 
    96   public static void
    97   invalidateFocus ()
    98   {
    99     EmacsWindow oldFocus;
   100 
   101     /* Walk through each focused activity and assign the window focus
   102        to the bottom-most focused window within.  Record the old focus
   103        as well.  */
   104     oldFocus = focusedWindow;
   105     focusedWindow = null;
   106 
   107     for (EmacsActivity activity : focusedActivities)
   108       {
   109         if (activity.window != null)
   110           invalidateFocus1 (activity.window);
   111       }
   112 
   113     /* Send focus in- and out- events to the previous and current
   114        focus.  */
   115 
   116     if (oldFocus != null)
   117       EmacsNative.sendFocusOut (oldFocus.handle,
   118                                 System.currentTimeMillis ());
   119 
   120     if (focusedWindow != null)
   121       EmacsNative.sendFocusIn (focusedWindow.handle,
   122                                System.currentTimeMillis ());
   123   }
   124 
   125   @Override
   126   public final void
   127   detachWindow ()
   128   {
   129     syncFullscreenWith (null);
   130 
   131     if (window == null)
   132       Log.w (TAG, "detachWindow called, but there is no window");
   133     else
   134       {
   135         /* Clear the window's pointer to this activity and remove the
   136            window's view.  */
   137         window.setConsumer (null);
   138 
   139         /* The window can't be iconified any longer.  */
   140         window.noticeDeiconified ();
   141         layout.removeView (window.view);
   142         window = null;
   143 
   144         invalidateFocus ();
   145       }
   146   }
   147 
   148   @Override
   149   public final void
   150   attachWindow (EmacsWindow child)
   151   {
   152     Log.d (TAG, "attachWindow: " + child);
   153 
   154     if (window != null)
   155       throw new IllegalStateException ("trying to attach window when one"
   156                                        + " already exists");
   157 
   158     syncFullscreenWith (child);
   159 
   160     /* Record and attach the view.  */
   161 
   162     window = child;
   163     layout.addView (window.view);
   164     child.setConsumer (this);
   165 
   166     /* If the window isn't no-focus-on-map, focus its view.  */
   167     if (!child.getDontFocusOnMap ())
   168       window.view.requestFocus ();
   169 
   170     /* If the activity is iconified, send that to the window.  */
   171     if (isPaused)
   172       window.noticeIconified ();
   173 
   174     /* Invalidate the focus.  */
   175     invalidateFocus ();
   176   }
   177 
   178   @Override
   179   public final void
   180   destroy ()
   181   {
   182     finish ();
   183   }
   184 
   185   @Override
   186   public final EmacsWindow
   187   getAttachedWindow ()
   188   {
   189     return window;
   190   }
   191 
   192   @Override
   193   public final void
   194   onCreate (Bundle savedInstanceState)
   195   {
   196     FrameLayout.LayoutParams params;
   197     Intent intent;
   198     View decorView;
   199     ViewTreeObserver observer;
   200     int matchParent;
   201 
   202     /* See if Emacs should be started with any extra arguments, such
   203        as `--quick'.  */
   204     intent = getIntent ();
   205     EmacsService.extraStartupArgument
   206       = intent.getStringExtra ("org.gnu.emacs.STARTUP_ARGUMENT");
   207 
   208     matchParent = FrameLayout.LayoutParams.MATCH_PARENT;
   209     params
   210       = new FrameLayout.LayoutParams (matchParent,
   211                                       matchParent);
   212 
   213     /* Make the frame layout.  */
   214     layout = new FrameLayout (this);
   215     layout.setLayoutParams (params);
   216 
   217     /* Set it as the content view.  */
   218     setContentView (layout);
   219 
   220     /* Maybe start the Emacs service if necessary.  */
   221     EmacsService.startEmacsService (this);
   222 
   223     /* Add this activity to the list of available activities.  */
   224     EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
   225 
   226     /* Start observing global layout changes between Jelly Bean and Q.
   227        This is required to restore the fullscreen state whenever the
   228        on screen keyboard is displayed, as there is otherwise no way
   229        to determine when the on screen keyboard becomes visible.  */
   230 
   231     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
   232         && Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
   233       {
   234         decorView = getWindow ().getDecorView ();
   235         observer = decorView.getViewTreeObserver ();
   236         observer.addOnGlobalLayoutListener (this);
   237       }
   238 
   239     super.onCreate (savedInstanceState);
   240   }
   241 
   242   @Override
   243   public final void
   244   onGlobalLayout ()
   245   {
   246     syncFullscreenWith (window);
   247   }
   248 
   249   @Override
   250   public final void
   251   onDestroy ()
   252   {
   253     EmacsWindowAttachmentManager manager;
   254     boolean isMultitask;
   255 
   256     manager = EmacsWindowAttachmentManager.MANAGER;
   257 
   258     /* The activity will die shortly hereafter.  If there is a window
   259        attached, close it now.  */
   260     Log.d (TAG, "onDestroy " + this);
   261     isMultitask = this instanceof EmacsMultitaskActivity;
   262     manager.removeWindowConsumer (this, isMultitask || isFinishing ());
   263     focusedActivities.remove (this);
   264     invalidateFocus ();
   265 
   266     /* Remove this activity from the static field, lest it leak.  */
   267     if (lastFocusedActivity == this)
   268       lastFocusedActivity = null;
   269 
   270     super.onDestroy ();
   271   }
   272 
   273   @Override
   274   public final void
   275   onWindowFocusChanged (boolean isFocused)
   276   {
   277     Log.d (TAG, ("onWindowFocusChanged: "
   278                  + (isFocused ? "YES" : "NO")));
   279 
   280     if (isFocused && !focusedActivities.contains (this))
   281       {
   282         focusedActivities.add (this);
   283         lastFocusedActivity = this;
   284 
   285         /* Update the window insets as the focus change may have
   286            changed the window insets as well, and the system does not
   287            automatically restore visibility flags.  */
   288 
   289         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
   290             && Build.VERSION.SDK_INT < Build.VERSION_CODES.R
   291             && isFullscreen)
   292           syncFullscreenWith (window);
   293       }
   294     else
   295       focusedActivities.remove (this);
   296 
   297     invalidateFocus ();
   298   }
   299 
   300   @Override
   301   public final void
   302   onPause ()
   303   {
   304     isPaused = true;
   305 
   306     EmacsWindowAttachmentManager.MANAGER.noticeIconified (this);
   307     super.onPause ();
   308   }
   309 
   310   @Override
   311   public final void
   312   onResume ()
   313   {
   314     isPaused = false;
   315 
   316     EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this);
   317     super.onResume ();
   318   }
   319 
   320   @Override
   321   public final void
   322   onContextMenuClosed (Menu menu)
   323   {
   324     int serial;
   325 
   326     Log.d (TAG, "onContextMenuClosed: " + menu);
   327 
   328     /* See the comment inside onMenuItemClick.  */
   329 
   330     if (((EmacsContextMenu.wasSubmenuSelected == -2)
   331          || (EmacsContextMenu.wasSubmenuSelected >= 0
   332              && ((System.currentTimeMillis ()
   333                   - EmacsContextMenu.wasSubmenuSelected)
   334                  <= 300)))
   335         || menu == lastClosedMenu)
   336       {
   337         EmacsContextMenu.wasSubmenuSelected = -1;
   338         lastClosedMenu = menu;
   339         return;
   340       }
   341 
   342     /* lastClosedMenu is set because Android apparently calls this
   343        function twice.  */
   344 
   345     lastClosedMenu = null;
   346 
   347     /* Send a context menu event given that no menu item has already
   348        been selected.  */
   349     if (!EmacsContextMenu.itemAlreadySelected)
   350       {
   351         serial = EmacsContextMenu.lastMenuEventSerial;
   352         EmacsNative.sendContextMenu ((short) 0, 0,
   353                                      serial);
   354       }
   355 
   356     super.onContextMenuClosed (menu);
   357   }
   358 
   359   @SuppressWarnings ("deprecation")
   360   public final void
   361   syncFullscreenWith (EmacsWindow emacsWindow)
   362   {
   363     WindowInsetsController controller;
   364     Window window;
   365     int behavior, flags;
   366     View view;
   367 
   368     if (emacsWindow != null)
   369       isFullscreen = emacsWindow.fullscreen;
   370     else
   371       isFullscreen = false;
   372 
   373     /* On Android 11 or later, use the window insets controller to
   374        control whether or not the view is fullscreen.  */
   375 
   376     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
   377       {
   378         window = getWindow ();
   379 
   380         /* If there is no attached window, return immediately.  */
   381         if (window == null)
   382           return;
   383 
   384         behavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
   385         controller = window.getInsetsController ();
   386         controller.setSystemBarsBehavior (behavior);
   387 
   388         if (isFullscreen)
   389           controller.hide (WindowInsets.Type.statusBars ()
   390                            | WindowInsets.Type.navigationBars ());
   391         else
   392           controller.show (WindowInsets.Type.statusBars ()
   393                            | WindowInsets.Type.navigationBars ());
   394 
   395         return;
   396       }
   397 
   398     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
   399       {
   400         /* On Android 4.1 or later, use `setSystemUiVisibility'.  */
   401 
   402         window = getWindow ();
   403 
   404         if (window == null)
   405           return;
   406 
   407         view = window.getDecorView ();
   408 
   409         if (isFullscreen)
   410           {
   411             flags = 0;
   412             flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
   413 
   414             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
   415               {
   416                 /* These flags means that Emacs will be full screen as
   417                    long as the state flag is set.  */
   418                 flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
   419                 flags |= View.SYSTEM_UI_FLAG_IMMERSIVE;
   420                 flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
   421               }
   422 
   423             /* Apply the given flags.  */
   424             view.setSystemUiVisibility (flags);
   425           }
   426         else
   427           view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_VISIBLE);
   428       }
   429   }
   430 
   431   @Override
   432   public final void
   433   onAttachedToWindow ()
   434   {
   435     super.onAttachedToWindow ();
   436 
   437     /* Update the window insets.  */
   438     syncFullscreenWith (window);
   439   }
   440 
   441 
   442 
   443   @Override
   444   public final void
   445   onActivityResult (int requestCode, int resultCode, Intent data)
   446   {
   447     ContentResolver resolver;
   448     Uri uri;
   449     int flags;
   450 
   451     switch (requestCode)
   452       {
   453       case ACCEPT_DOCUMENT_TREE:
   454 
   455         /* A document granted through
   456            EmacsService.requestDirectoryAccess.  */
   457 
   458         if (resultCode == RESULT_OK)
   459           {
   460             resolver = getContentResolver ();
   461             uri = data.getData ();
   462             flags = (Intent.FLAG_GRANT_READ_URI_PERMISSION
   463                      | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
   464 
   465             try
   466               {
   467                 if (uri != null)
   468                   resolver.takePersistableUriPermission (uri, flags);
   469               }
   470             catch (Exception exception)
   471               {
   472                 /* Permission to access URI might've been revoked in
   473                    between selecting the file and this callback being
   474                    invoked.  Don't crash in such cases.  */
   475               }
   476           }
   477 
   478         break;
   479       }
   480   }
   481 };

/* [<][>][^][v][top][bottom][index][help] */