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     Log.d (TAG, ("onPrimaryClipChanged: "
    75                  + monitoredClipboardChangedCount
    76                  + " " + clipboardChangedCount));
    77 
    78     /* Increment monitoredClipboardChangeCount.  If it is now greater
    79        than clipboardChangedCount, then Emacs no longer owns the
    80        clipboard.  */
    81     monitoredClipboardChangedCount++;
    82 
    83     if (monitoredClipboardChangedCount > clipboardChangedCount)
    84       {
    85         ownsClipboard = false;
    86 
    87         /* Reset both values back to 0.  */
    88         monitoredClipboardChangedCount = 0;
    89         clipboardChangedCount = 0;
    90       }
    91   }
    92 
    93   /* Set the clipboard text to CLIPBOARD, a string in UTF-8
    94      encoding.  */
    95 
    96   @Override
    97   public synchronized void
    98   setClipboard (byte[] bytes)
    99   {
   100     ClipData data;
   101     String string;
   102 
   103     try
   104       {
   105         string = new String (bytes, "UTF-8");
   106         data = ClipData.newPlainText ("Emacs", string);
   107         manager.setPrimaryClip (data);
   108         ownsClipboard = true;
   109 
   110         /* onPrimaryClipChanged will be called again.  Use this
   111            variable to keep track of how many times the clipboard has
   112            been changed.  */
   113         ++clipboardChangedCount;
   114       }
   115     catch (UnsupportedEncodingException exception)
   116       {
   117         Log.w (TAG, "setClipboard: " + exception);
   118       }
   119   }
   120 
   121   /* Return whether or not Emacs owns the clipboard.  Value is 1 if
   122      Emacs does, 0 if Emacs does not, and -1 if that information is
   123      unavailable.  */
   124 
   125   @Override
   126   public synchronized int
   127   ownsClipboard ()
   128   {
   129     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
   130       return -1;
   131 
   132     return ownsClipboard ? 1 : 0;
   133   }
   134 
   135   /* Return whether or not clipboard content currently exists.  */
   136 
   137   @Override
   138   public boolean
   139   clipboardExists ()
   140   {
   141     return manager.hasPrimaryClip ();
   142   }
   143 
   144   /* Return the current content of the clipboard, as plain text, or
   145      NULL if no content is available.  */
   146 
   147   @Override
   148   public byte[]
   149   getClipboard ()
   150   {
   151     ClipData clip;
   152     CharSequence text;
   153     Context context;
   154 
   155     clip = manager.getPrimaryClip ();
   156 
   157     if (clip == null || clip.getItemCount () < 1)
   158       return null;
   159 
   160     context = EmacsService.SERVICE;
   161 
   162     try
   163       {
   164         text = clip.getItemAt (0).coerceToText (context);
   165         return text.toString ().getBytes ("UTF-8");
   166       }
   167     catch (UnsupportedEncodingException exception)
   168       {
   169         Log.w (TAG, "getClipboard: " + exception);
   170       }
   171 
   172     return null;
   173   }
   174 
   175   /* Return an array of targets currently provided by the
   176      clipboard, or NULL if there are none.  */
   177 
   178   @Override
   179   public byte[][]
   180   getClipboardTargets ()
   181   {
   182     ClipData clip;
   183     ClipDescription description;
   184     byte[][] typeArray;
   185     int i;
   186 
   187     /* N.B. that Android calls the clipboard the ``primary clip''; it
   188        is not related to the X primary selection.  */
   189     clip = manager.getPrimaryClip ();
   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     Log.d (TAG, "getClipboardData: "+ mimeType);
   241 
   242     /* Now obtain the clipboard data and the data corresponding to
   243        that MIME type.  */
   244 
   245     data = manager.getPrimaryClip ();
   246 
   247     if (data.getItemCount () < 1)
   248       return null;
   249 
   250     try
   251       {
   252         uri = data.getItemAt (0).getUri ();
   253 
   254         if (uri == null)
   255           return null;
   256 
   257         Log.d (TAG, "getClipboardData: "+ uri);
   258 
   259         /* Now open the file descriptor.  */
   260         assetFd = resolver.openTypedAssetFileDescriptor (uri, mimeType,
   261                                                          null);
   262 
   263         /* Duplicate the file descriptor.  */
   264         fd = assetFd.getParcelFileDescriptor ().getFd ();
   265         fd = EmacsNative.dup (fd);
   266 
   267         /* Return the relevant information.  */
   268         value = new long[] { fd, assetFd.getStartOffset (),
   269                              assetFd.getLength (), };
   270 
   271         /* Close the original offset.  */
   272         assetFd.close ();
   273 
   274         Log.d (TAG, "getClipboardData: "+ value);
   275       }
   276     catch (FileNotFoundException e)
   277       {
   278         return null;
   279       }
   280     catch (IOException e)
   281       {
   282         return null;
   283       }
   284 
   285     /* Don't return value if the file descriptor couldn't be
   286        created.  */
   287 
   288     return fd != -1 ? value : null;
   289   }
   290 };

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