1 /** 2 * Copyright (c) 2010, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemService; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.os.Handler; 24 import android.os.RemoteException; 25 import android.os.ServiceManager; 26 import android.os.ServiceManager.ServiceNotFoundException; 27 28 import java.util.ArrayList; 29 import java.util.Objects; 30 31 /** 32 * Interface to the clipboard service, for placing and retrieving text in 33 * the global clipboard. 34 * 35 * <p> 36 * The ClipboardManager API itself is very simple: it consists of methods 37 * to atomically get and set the current primary clipboard data. That data 38 * is expressed as a {@link ClipData} object, which defines the protocol 39 * for data exchange between applications. 40 * 41 * <div class="special reference"> 42 * <h3>Developer Guides</h3> 43 * <p>For more information about using the clipboard framework, read the 44 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a> 45 * developer guide.</p> 46 * </div> 47 */ 48 @SystemService(Context.CLIPBOARD_SERVICE) 49 public class ClipboardManager extends android.text.ClipboardManager { 50 private final Context mContext; 51 private final Handler mHandler; 52 private final IClipboard mService; 53 54 private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners 55 = new ArrayList<OnPrimaryClipChangedListener>(); 56 57 private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener 58 = new IOnPrimaryClipChangedListener.Stub() { 59 @Override 60 public void dispatchPrimaryClipChanged() { 61 mHandler.post(() -> { 62 reportPrimaryClipChanged(); 63 }); 64 } 65 }; 66 67 /** 68 * Defines a listener callback that is invoked when the primary clip on the clipboard changes. 69 * Objects that want to register a listener call 70 * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener) 71 * addPrimaryClipChangedListener()} with an 72 * object that implements OnPrimaryClipChangedListener. 73 * 74 */ 75 public interface OnPrimaryClipChangedListener { 76 77 /** 78 * Callback that is invoked by {@link android.content.ClipboardManager} when the primary 79 * clip changes. 80 */ onPrimaryClipChanged()81 void onPrimaryClipChanged(); 82 } 83 84 /** {@hide} */ 85 @UnsupportedAppUsage ClipboardManager(Context context, Handler handler)86 public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException { 87 mContext = context; 88 mHandler = handler; 89 mService = IClipboard.Stub.asInterface( 90 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE)); 91 } 92 93 /** 94 * Sets the current primary clip on the clipboard. This is the clip that 95 * is involved in normal cut and paste operations. 96 * 97 * @param clip The clipped data item to set. 98 * @see #getPrimaryClip() 99 * @see #clearPrimaryClip() 100 */ setPrimaryClip(@onNull ClipData clip)101 public void setPrimaryClip(@NonNull ClipData clip) { 102 try { 103 Objects.requireNonNull(clip); 104 clip.prepareToLeaveProcess(true); 105 mService.setPrimaryClip(clip, mContext.getOpPackageName(), mContext.getUserId()); 106 } catch (RemoteException e) { 107 throw e.rethrowFromSystemServer(); 108 } 109 } 110 111 /** 112 * Clears any current primary clip on the clipboard. 113 * 114 * @see #setPrimaryClip(ClipData) 115 */ clearPrimaryClip()116 public void clearPrimaryClip() { 117 try { 118 mService.clearPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); 119 } catch (RemoteException e) { 120 throw e.rethrowFromSystemServer(); 121 } 122 } 123 124 /** 125 * Returns the current primary clip on the clipboard. 126 * 127 * <em>If the application is not the default IME or does not have input focus this return 128 * {@code null}.</em> 129 * 130 * @see #setPrimaryClip(ClipData) 131 */ getPrimaryClip()132 public @Nullable ClipData getPrimaryClip() { 133 try { 134 return mService.getPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); 135 } catch (RemoteException e) { 136 throw e.rethrowFromSystemServer(); 137 } 138 } 139 140 /** 141 * Returns a description of the current primary clip on the clipboard 142 * but not a copy of its data. 143 * 144 * <em>If the application is not the default IME or does not have input focus this return 145 * {@code null}.</em> 146 * 147 * @see #setPrimaryClip(ClipData) 148 */ getPrimaryClipDescription()149 public @Nullable ClipDescription getPrimaryClipDescription() { 150 try { 151 return mService.getPrimaryClipDescription(mContext.getOpPackageName(), 152 mContext.getUserId()); 153 } catch (RemoteException e) { 154 throw e.rethrowFromSystemServer(); 155 } 156 } 157 158 /** 159 * Returns true if there is currently a primary clip on the clipboard. 160 * 161 * <em>If the application is not the default IME or the does not have input focus this will 162 * return {@code false}.</em> 163 */ hasPrimaryClip()164 public boolean hasPrimaryClip() { 165 try { 166 return mService.hasPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); 167 } catch (RemoteException e) { 168 throw e.rethrowFromSystemServer(); 169 } 170 } 171 addPrimaryClipChangedListener(OnPrimaryClipChangedListener what)172 public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) { 173 synchronized (mPrimaryClipChangedListeners) { 174 if (mPrimaryClipChangedListeners.isEmpty()) { 175 try { 176 mService.addPrimaryClipChangedListener( 177 mPrimaryClipChangedServiceListener, mContext.getOpPackageName(), 178 mContext.getUserId()); 179 } catch (RemoteException e) { 180 throw e.rethrowFromSystemServer(); 181 } 182 } 183 mPrimaryClipChangedListeners.add(what); 184 } 185 } 186 removePrimaryClipChangedListener(OnPrimaryClipChangedListener what)187 public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) { 188 synchronized (mPrimaryClipChangedListeners) { 189 mPrimaryClipChangedListeners.remove(what); 190 if (mPrimaryClipChangedListeners.isEmpty()) { 191 try { 192 mService.removePrimaryClipChangedListener( 193 mPrimaryClipChangedServiceListener, mContext.getOpPackageName(), 194 mContext.getUserId()); 195 } catch (RemoteException e) { 196 throw e.rethrowFromSystemServer(); 197 } 198 } 199 } 200 } 201 202 /** 203 * @deprecated Use {@link #getPrimaryClip()} instead. This retrieves 204 * the primary clip and tries to coerce it to a string. 205 */ 206 @Deprecated getText()207 public CharSequence getText() { 208 ClipData clip = getPrimaryClip(); 209 if (clip != null && clip.getItemCount() > 0) { 210 return clip.getItemAt(0).coerceToText(mContext); 211 } 212 return null; 213 } 214 215 /** 216 * @deprecated Use {@link #setPrimaryClip(ClipData)} instead. This 217 * creates a ClippedItem holding the given text and sets it as the 218 * primary clip. It has no label or icon. 219 */ 220 @Deprecated setText(CharSequence text)221 public void setText(CharSequence text) { 222 setPrimaryClip(ClipData.newPlainText(null, text)); 223 } 224 225 /** 226 * @deprecated Use {@link #hasPrimaryClip()} instead. 227 */ 228 @Deprecated hasText()229 public boolean hasText() { 230 try { 231 return mService.hasClipboardText(mContext.getOpPackageName(), mContext.getUserId()); 232 } catch (RemoteException e) { 233 throw e.rethrowFromSystemServer(); 234 } 235 } 236 237 @UnsupportedAppUsage reportPrimaryClipChanged()238 void reportPrimaryClipChanged() { 239 Object[] listeners; 240 241 synchronized (mPrimaryClipChangedListeners) { 242 final int N = mPrimaryClipChangedListeners.size(); 243 if (N <= 0) { 244 return; 245 } 246 listeners = mPrimaryClipChangedListeners.toArray(); 247 } 248 249 for (int i=0; i<listeners.length; i++) { 250 ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged(); 251 } 252 } 253 } 254