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.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SystemApi; 24 import android.annotation.SystemService; 25 import android.annotation.TestApi; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.os.Handler; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.ServiceManager.ServiceNotFoundException; 31 32 import java.util.ArrayList; 33 import java.util.Objects; 34 35 /** 36 * Interface to the clipboard service, for placing and retrieving text in 37 * the global clipboard. 38 * 39 * <p> 40 * The ClipboardManager API itself is very simple: it consists of methods 41 * to atomically get and set the current primary clipboard data. That data 42 * is expressed as a {@link ClipData} object, which defines the protocol 43 * for data exchange between applications. 44 * 45 * <div class="special reference"> 46 * <h3>Developer Guides</h3> 47 * <p>For more information about using the clipboard framework, read the 48 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a> 49 * developer guide.</p> 50 * </div> 51 */ 52 @SystemService(Context.CLIPBOARD_SERVICE) 53 public class ClipboardManager extends android.text.ClipboardManager { 54 55 /** 56 * DeviceConfig property, within the clipboard namespace, that determines whether notifications 57 * are shown when an app accesses clipboard. This may be overridden by a user-controlled 58 * setting. 59 * 60 * @hide 61 */ 62 public static final String DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS = 63 "show_access_notifications"; 64 65 /** 66 * Default value for the DeviceConfig property that determines whether notifications are shown 67 * when an app accesses clipboard. 68 * 69 * @hide 70 */ 71 public static final boolean DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true; 72 73 private final Context mContext; 74 private final Handler mHandler; 75 private final IClipboard mService; 76 77 private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners 78 = new ArrayList<OnPrimaryClipChangedListener>(); 79 80 private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener 81 = new IOnPrimaryClipChangedListener.Stub() { 82 @Override 83 public void dispatchPrimaryClipChanged() { 84 mHandler.post(() -> { 85 reportPrimaryClipChanged(); 86 }); 87 } 88 }; 89 90 /** 91 * Defines a listener callback that is invoked when the primary clip on the clipboard changes. 92 * Objects that want to register a listener call 93 * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener) 94 * addPrimaryClipChangedListener()} with an 95 * object that implements OnPrimaryClipChangedListener. 96 * 97 */ 98 public interface OnPrimaryClipChangedListener { 99 100 /** 101 * Callback that is invoked by {@link android.content.ClipboardManager} when the primary 102 * clip changes. 103 * 104 * <p>This is called when the result of {@link ClipDescription#getClassificationStatus()} 105 * changes, as well as when new clip data is set. So in cases where text classification is 106 * performed, this callback may be invoked multiple times for the same clip. 107 */ onPrimaryClipChanged()108 void onPrimaryClipChanged(); 109 } 110 111 /** {@hide} */ 112 @UnsupportedAppUsage ClipboardManager(Context context, Handler handler)113 public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException { 114 mContext = context; 115 mHandler = handler; 116 mService = IClipboard.Stub.asInterface( 117 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE)); 118 } 119 120 /** 121 * Sets the current primary clip on the clipboard. This is the clip that 122 * is involved in normal cut and paste operations. 123 * 124 * @param clip The clipped data item to set. 125 * @see #getPrimaryClip() 126 * @see #clearPrimaryClip() 127 */ setPrimaryClip(@onNull ClipData clip)128 public void setPrimaryClip(@NonNull ClipData clip) { 129 try { 130 Objects.requireNonNull(clip); 131 clip.prepareToLeaveProcess(true); 132 mService.setPrimaryClip(clip, mContext.getOpPackageName(), mContext.getUserId()); 133 } catch (RemoteException e) { 134 throw e.rethrowFromSystemServer(); 135 } 136 } 137 138 /** 139 * Sets the current primary clip on the clipboard, attributed to the specified {@code 140 * sourcePackage}. The primary clip is the clip that is involved in normal cut and paste 141 * operations. 142 * 143 * @param clip The clipped data item to set. 144 * @param sourcePackage The package name of the app that is the source of the clip data. 145 * @throws IllegalArgumentException if the clip is null or contains no items. 146 * 147 * @hide 148 */ 149 @SystemApi 150 @RequiresPermission(Manifest.permission.SET_CLIP_SOURCE) setPrimaryClipAsPackage(@onNull ClipData clip, @NonNull String sourcePackage)151 public void setPrimaryClipAsPackage(@NonNull ClipData clip, @NonNull String sourcePackage) { 152 try { 153 Objects.requireNonNull(clip); 154 Objects.requireNonNull(sourcePackage); 155 clip.prepareToLeaveProcess(true); 156 mService.setPrimaryClipAsPackage( 157 clip, mContext.getOpPackageName(), mContext.getUserId(), sourcePackage); 158 } catch (RemoteException e) { 159 throw e.rethrowFromSystemServer(); 160 } 161 } 162 163 /** 164 * Clears any current primary clip on the clipboard. 165 * 166 * @see #setPrimaryClip(ClipData) 167 */ clearPrimaryClip()168 public void clearPrimaryClip() { 169 try { 170 mService.clearPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); 171 } catch (RemoteException e) { 172 throw e.rethrowFromSystemServer(); 173 } 174 } 175 176 /** 177 * Returns the current primary clip on the clipboard. 178 * 179 * <em>If the application is not the default IME or does not have input focus this return 180 * {@code null}.</em> 181 * 182 * @see #setPrimaryClip(ClipData) 183 */ getPrimaryClip()184 public @Nullable ClipData getPrimaryClip() { 185 try { 186 return mService.getPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); 187 } catch (RemoteException e) { 188 throw e.rethrowFromSystemServer(); 189 } 190 } 191 192 /** 193 * Returns a description of the current primary clip on the clipboard 194 * but not a copy of its data. 195 * 196 * <em>If the application is not the default IME or does not have input focus this return 197 * {@code null}.</em> 198 * 199 * @see #setPrimaryClip(ClipData) 200 */ getPrimaryClipDescription()201 public @Nullable ClipDescription getPrimaryClipDescription() { 202 try { 203 return mService.getPrimaryClipDescription(mContext.getOpPackageName(), 204 mContext.getUserId()); 205 } catch (RemoteException e) { 206 throw e.rethrowFromSystemServer(); 207 } 208 } 209 210 /** 211 * Returns true if there is currently a primary clip on the clipboard. 212 * 213 * <em>If the application is not the default IME or the does not have input focus this will 214 * return {@code false}.</em> 215 */ hasPrimaryClip()216 public boolean hasPrimaryClip() { 217 try { 218 return mService.hasPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); 219 } catch (RemoteException e) { 220 throw e.rethrowFromSystemServer(); 221 } 222 } 223 addPrimaryClipChangedListener(OnPrimaryClipChangedListener what)224 public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) { 225 synchronized (mPrimaryClipChangedListeners) { 226 if (mPrimaryClipChangedListeners.isEmpty()) { 227 try { 228 mService.addPrimaryClipChangedListener( 229 mPrimaryClipChangedServiceListener, mContext.getOpPackageName(), 230 mContext.getUserId()); 231 } catch (RemoteException e) { 232 throw e.rethrowFromSystemServer(); 233 } 234 } 235 mPrimaryClipChangedListeners.add(what); 236 } 237 } 238 removePrimaryClipChangedListener(OnPrimaryClipChangedListener what)239 public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) { 240 synchronized (mPrimaryClipChangedListeners) { 241 mPrimaryClipChangedListeners.remove(what); 242 if (mPrimaryClipChangedListeners.isEmpty()) { 243 try { 244 mService.removePrimaryClipChangedListener( 245 mPrimaryClipChangedServiceListener, mContext.getOpPackageName(), 246 mContext.getUserId()); 247 } catch (RemoteException e) { 248 throw e.rethrowFromSystemServer(); 249 } 250 } 251 } 252 } 253 254 /** 255 * @deprecated Use {@link #getPrimaryClip()} instead. This retrieves 256 * the primary clip and tries to coerce it to a string. 257 */ 258 @Deprecated getText()259 public CharSequence getText() { 260 ClipData clip = getPrimaryClip(); 261 if (clip != null && clip.getItemCount() > 0) { 262 return clip.getItemAt(0).coerceToText(mContext); 263 } 264 return null; 265 } 266 267 /** 268 * @deprecated Use {@link #setPrimaryClip(ClipData)} instead. This 269 * creates a ClippedItem holding the given text and sets it as the 270 * primary clip. It has no label or icon. 271 */ 272 @Deprecated setText(CharSequence text)273 public void setText(CharSequence text) { 274 setPrimaryClip(ClipData.newPlainText(null, text)); 275 } 276 277 /** 278 * @deprecated Use {@link #hasPrimaryClip()} instead. 279 */ 280 @Deprecated hasText()281 public boolean hasText() { 282 try { 283 return mService.hasClipboardText(mContext.getOpPackageName(), mContext.getUserId()); 284 } catch (RemoteException e) { 285 throw e.rethrowFromSystemServer(); 286 } 287 } 288 289 /** 290 * Returns the package name of the source of the current primary clip, or null if there is no 291 * primary clip or if a source is not available. 292 * 293 * @hide 294 */ 295 @TestApi 296 @Nullable 297 @RequiresPermission(Manifest.permission.SET_CLIP_SOURCE) getPrimaryClipSource()298 public String getPrimaryClipSource() { 299 try { 300 return mService.getPrimaryClipSource(mContext.getOpPackageName(), mContext.getUserId()); 301 } catch (RemoteException e) { 302 throw e.rethrowFromSystemServer(); 303 } 304 } 305 306 @UnsupportedAppUsage reportPrimaryClipChanged()307 void reportPrimaryClipChanged() { 308 Object[] listeners; 309 310 synchronized (mPrimaryClipChangedListeners) { 311 final int N = mPrimaryClipChangedListeners.size(); 312 if (N <= 0) { 313 return; 314 } 315 listeners = mPrimaryClipChangedListeners.toArray(); 316 } 317 318 for (int i=0; i<listeners.length; i++) { 319 ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged(); 320 } 321 } 322 } 323