root/java/org/gnu/emacs/EmacsSdk11Clipboard.java

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

DEFINITIONS

This source file includes following definitions.
  1. onPrimaryClipChanged
  2. setClipboard
  3. ownsClipboard
  4. clipboardExists
  5. getClipboard
  6. getClipboardTargets
  7. getClipboardData

     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 android.content.ClipboardManager;
    23 import android.content.Context;
    24 import android.content.ContentResolver;
    25 import android.content.ClipData;
    26 import android.content.ClipDescription;
    27 
    28 import android.content.res.AssetFileDescriptor;
    29 
    30 import android.net.Uri;
    31 
    32 import android.util.Log;
    33 
    34 import android.os.Build;
    35 
    36 import java.io.FileNotFoundException;
    37 import java.io.IOException;
    38 import java.io.UnsupportedEncodingException;
    39 
    40 /* This class implements EmacsClipboard for Android 3.0 and later
    41    systems.  */
    42 
    43 public final class EmacsSdk11Clipboard extends EmacsClipboard
    44   implements ClipboardManager.OnPrimaryClipChangedListener
    45 {
    46   private static final String TAG = "EmacsSdk11Clipboard";
    47   private ClipboardManager manager;
    48   private boolean ownsClipboard;
    49   private int clipboardChangedCount;
    50   private int monitoredClipboardChangedCount;
    51   private ContentResolver resolver;
    52 
    53   public
    54   EmacsSdk11Clipboard ()
    55   {
    56     manager = EmacsService.SERVICE.getClipboardManager ();
    57 
    58     /* The system forbids Emacs from reading clipboard data in the
    59        background under Android 10 or later.  */
    60 
    61     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
    62       manager.addPrimaryClipChangedListener (this);
    63 
    64     /* Now obtain the content resolver used to open file
    65        descriptors.  */
    66 
    67     resolver = EmacsService.SERVICE.getContentResolver ();
    68   }
    69 
    70   @Override
    71   public synchronized void
    72   onPrimaryClipChanged ()
    73   {
    74     /* Increment monitoredClipboardChangeCount.  If it is now greater
    75        than clipboardChangedCount, then Emacs no longer owns the
    76        clipboard.  */
    77     monitoredClipboardChangedCount++;
    78 
    79     if (monitoredClipboardChangedCount > clipboardChangedCount)
    80       {
    81         ownsClipboard = false;
    82 
    83         /* Reset both values back to 0.  */
    84         monitoredClipboardChangedCount = 0;
    85         clipboardChangedCount = 0;
    86       }
    87   }
    88 
    89   /* Set the clipboard text to CLIPBOARD, a string in UTF-8
    90      encoding.  */
    91 
    92   @Override
    93   public synchronized void
    94   setClipboard (byte[] bytes)
    95   {
    96     ClipData data;
    97     String string;
    98 
    99     try
   100       {
   101         string = new String (bytes, "UTF-8");
   102         data = ClipData.newPlainText ("Emacs", string);
   103         manager.setPrimaryClip (data);
   104         ownsClipboard = true;
   105 
   106         /* onPrimaryClipChanged will be called again.  Use this
   107            variable to keep track of how many times the clipboard has
   108            been changed.  */
   109         ++clipboardChangedCount;
   110       }
   111     catch (UnsupportedEncodingException exception)
   112       {
   113         Log.w (TAG, "setClipboard: " + exception);
   114       }
   115   }
   116 
   117   /* Return whether or not Emacs owns the clipboard.  Value is 1 if
   118      Emacs does, 0 if Emacs does not, and -1 if that information is
   119      unavailable.  */
   120 
   121   @Override
   122   public synchronized int
   123   ownsClipboard ()
   124   {
   125     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
   126       return -1;
   127 
   128     return ownsClipboard ? 1 : 0;
   129   }
   130 
   131   /* Return whether or not clipboard content currently exists.  */
   132 
   133   @Override
   134   public boolean
   135   clipboardExists ()
   136   {
   137     return manager.hasPrimaryClip ();
   138   }
   139 
   140   /* Return the current content of the clipboard, as plain text, or
   141      NULL if no content is available.  */
   142 
   143   @Override
   144   public byte[]
   145   getClipboard ()
   146   {
   147     ClipData clip;
   148     CharSequence text;
   149     Context context;
   150 
   151     clip = manager.getPrimaryClip ();
   152 
   153     if (clip == null || clip.getItemCount () < 1)
   154       return null;
   155 
   156     context = EmacsService.SERVICE;
   157 
   158     try
   159       {
   160         text = clip.getItemAt (0).coerceToText (context);
   161         return text.toString ().getBytes ("UTF-8");
   162       }
   163     catch (UnsupportedEncodingException exception)
   164       {
   165         Log.w (TAG, "getClipboard: " + exception);
   166       }
   167 
   168     return null;
   169   }
   170 
   171   /* Return an array of targets currently provided by the
   172      clipboard, or NULL if there are none.  */
   173 
   174   @Override
   175   public byte[][]
   176   getClipboardTargets ()
   177   {
   178     ClipData clip;
   179     ClipDescription description;
   180     byte[][] typeArray;
   181     int i;
   182 
   183     /* N.B. that Android calls the clipboard the ``primary clip''; it
   184        is not related to the X primary selection.  */
   185     clip = manager.getPrimaryClip ();
   186 
   187     if (clip == null)
   188       return null;
   189 
   190     description = clip.getDescription ();
   191     i = description.getMimeTypeCount ();
   192     typeArray = new byte[i][i];
   193 
   194     try
   195       {
   196         for (i = 0; i < description.getMimeTypeCount (); ++i)
   197           typeArray[i] = description.getMimeType (i).getBytes ("UTF-8");
   198       }
   199     catch (UnsupportedEncodingException exception)
   200       {
   201         return null;
   202       }
   203 
   204     return typeArray;
   205   }
   206 
   207   /* Return the clipboard data for the given target, or NULL if it
   208      does not exist.
   209 
   210      Value is normally an array of three longs: the file descriptor,
   211      the start offset of the data, and its length; length may be
   212      AssetFileDescriptor.UNKOWN_LENGTH, meaning that the data extends
   213      from that offset to the end of the file.
   214 
   215      Do not use this function to open text targets; use `getClipboard'
   216      for that instead, as it will handle selection data consisting
   217      solely of a URI.  */
   218 
   219   @Override
   220   public long[]
   221   getClipboardData (byte[] target)
   222   {
   223     ClipData data;
   224     String mimeType;
   225     int fd;
   226     AssetFileDescriptor assetFd;
   227     Uri uri;
   228     long[] value;
   229 
   230     /* Decode the target given by Emacs.  */
   231     try
   232       {
   233         mimeType = new String (target, "UTF-8");
   234       }
   235     catch (UnsupportedEncodingException exception)
   236       {
   237         return null;
   238       }
   239 
   240     /* Now obtain the clipboard data and the data corresponding to
   241        that MIME type.  */
   242 
   243     data = manager.getPrimaryClip ();
   244 
   245     if (data == null || data.getItemCount () < 1)
   246       return null;
   247 
   248     try
   249       {
   250         uri = data.getItemAt (0).getUri ();
   251 
   252         if (uri == null)
   253           return null;
   254 
   255         /* Now open the file descriptor.  */
   256         assetFd = resolver.openTypedAssetFileDescriptor (uri, mimeType,
   257                                                          null);
   258 
   259         /* Duplicate the file descriptor.  */
   260         fd = assetFd.getParcelFileDescriptor ().getFd ();
   261         fd = EmacsNative.dup (fd);
   262 
   263         /* Return the relevant information.  */
   264         value = new long[] { fd, assetFd.getStartOffset (),
   265                              assetFd.getLength (), };
   266 
   267         /* Close the original offset.  */
   268         assetFd.close ();
   269       }
   270     catch (FileNotFoundException e)
   271       {
   272         return null;
   273       }
   274     catch (IOException e)
   275       {
   276         return null;
   277       }
   278 
   279     /* Don't return value if the file descriptor couldn't be
   280        created.  */
   281 
   282     return fd != -1 ? value : null;
   283   }
   284 };

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