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 /** 74 * DeviceConfig property, within the clipboard namespace, that determines whether VirtualDevices 75 * are allowed to have siloed Clipboards for the apps running on them. If false, then clipboard 76 * access is blocked entirely for apps running on VirtualDevices. 77 * 78 * @hide 79 */ 80 public static final String DEVICE_CONFIG_ALLOW_VIRTUALDEVICE_SILOS = 81 "allow_virtualdevice_silos"; 82 83 /** 84 * Default value for the DEVICE_CONFIG_ALLOW_VIRTUALDEVICE_SILOS property. 85 * 86 * @hide 87 */ 88 public static final boolean DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS = true; 89 90 private final Context mContext; 91 private final Handler mHandler; 92 private final IClipboard mService; 93 94 private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners 95 = new ArrayList<OnPrimaryClipChangedListener>(); 96 97 private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener 98 = new IOnPrimaryClipChangedListener.Stub() { 99 @Override 100 public void dispatchPrimaryClipChanged() { 101 mHandler.post(() -> { 102 reportPrimaryClipChanged(); 103 }); 104 } 105 }; 106 107 /** 108 * Defines a listener callback that is invoked when the primary clip on the clipboard changes. 109 * Objects that want to register a listener call 110 * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener) 111 * addPrimaryClipChangedListener()} with an 112 * object that implements OnPrimaryClipChangedListener. 113 * 114 */ 115 public interface OnPrimaryClipChangedListener { 116 117 /** 118 * Callback that is invoked by {@link android.content.ClipboardManager} when the primary 119 * clip changes. 120 * 121 * <p>This is called when the result of {@link ClipDescription#getClassificationStatus()} 122 * changes, as well as when new clip data is set. So in cases where text classification is 123 * performed, this callback may be invoked multiple times for the same clip. 124 */ onPrimaryClipChanged()125 void onPrimaryClipChanged(); 126 } 127 128 /** {@hide} */ 129 @UnsupportedAppUsage ClipboardManager(Context context, Handler handler)130 public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException { 131 mContext = context; 132 mHandler = handler; 133 mService = IClipboard.Stub.asInterface( 134 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE)); 135 } 136 137 /** 138 * Determine if the Clipboard Access Notifications are enabled 139 * 140 * @return true if notifications are enabled, false otherwise. 141 * 142 * @hide 143 */ 144 @SystemApi 145 @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION) areClipboardAccessNotificationsEnabled()146 public boolean areClipboardAccessNotificationsEnabled() { 147 try { 148 return mService.areClipboardAccessNotificationsEnabledForUser(mContext.getUserId()); 149 } catch (RemoteException e) { 150 throw e.rethrowFromSystemServer(); 151 } 152 } 153 154 /** 155 * 156 * Set the enable state of the Clipboard Access Notifications 157 * @param enable Whether to enable notifications 158 * @hide 159 */ 160 @SystemApi 161 @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION) setClipboardAccessNotificationsEnabled(boolean enable)162 public void setClipboardAccessNotificationsEnabled(boolean enable) { 163 try { 164 mService.setClipboardAccessNotificationsEnabledForUser(enable, mContext.getUserId()); 165 } catch (RemoteException e) { 166 throw e.rethrowFromSystemServer(); 167 } 168 } 169 170 /** 171 * Sets the current primary clip on the clipboard. This is the clip that 172 * is involved in normal cut and paste operations. 173 * 174 * @param clip The clipped data item to set. 175 * @see #getPrimaryClip() 176 * @see #clearPrimaryClip() 177 */ setPrimaryClip(@onNull ClipData clip)178 public void setPrimaryClip(@NonNull ClipData clip) { 179 try { 180 Objects.requireNonNull(clip); 181 clip.prepareToLeaveProcess(true); 182 mService.setPrimaryClip( 183 clip, 184 mContext.getOpPackageName(), 185 mContext.getAttributionTag(), 186 mContext.getUserId(), 187 mContext.getDeviceId()); 188 } catch (RemoteException e) { 189 throw e.rethrowFromSystemServer(); 190 } 191 } 192 193 /** 194 * Sets the current primary clip on the clipboard, attributed to the specified {@code 195 * sourcePackage}. The primary clip is the clip that is involved in normal cut and paste 196 * operations. 197 * 198 * @param clip The clipped data item to set. 199 * @param sourcePackage The package name of the app that is the source of the clip data. 200 * @throws IllegalArgumentException if the clip is null or contains no items. 201 * 202 * @hide 203 */ 204 @SystemApi 205 @RequiresPermission(Manifest.permission.SET_CLIP_SOURCE) setPrimaryClipAsPackage(@onNull ClipData clip, @NonNull String sourcePackage)206 public void setPrimaryClipAsPackage(@NonNull ClipData clip, @NonNull String sourcePackage) { 207 try { 208 Objects.requireNonNull(clip); 209 Objects.requireNonNull(sourcePackage); 210 clip.prepareToLeaveProcess(true); 211 mService.setPrimaryClipAsPackage( 212 clip, 213 mContext.getOpPackageName(), 214 mContext.getAttributionTag(), 215 mContext.getUserId(), 216 mContext.getDeviceId(), 217 sourcePackage); 218 } catch (RemoteException e) { 219 throw e.rethrowFromSystemServer(); 220 } 221 } 222 223 /** 224 * Clears any current primary clip on the clipboard. 225 * 226 * @see #setPrimaryClip(ClipData) 227 */ clearPrimaryClip()228 public void clearPrimaryClip() { 229 try { 230 mService.clearPrimaryClip( 231 mContext.getOpPackageName(), 232 mContext.getAttributionTag(), 233 mContext.getUserId(), 234 mContext.getDeviceId()); 235 } catch (RemoteException e) { 236 throw e.rethrowFromSystemServer(); 237 } 238 } 239 240 /** 241 * Returns the current primary clip on the clipboard. 242 * 243 * <em>If the application is not the default IME or does not have input focus this return 244 * {@code null}.</em> 245 * 246 * @see #setPrimaryClip(ClipData) 247 */ getPrimaryClip()248 public @Nullable ClipData getPrimaryClip() { 249 try { 250 return mService.getPrimaryClip( 251 mContext.getOpPackageName(), 252 mContext.getAttributionTag(), 253 mContext.getUserId(), 254 mContext.getDeviceId()); 255 } catch (RemoteException e) { 256 throw e.rethrowFromSystemServer(); 257 } 258 } 259 260 /** 261 * Returns a description of the current primary clip on the clipboard but not a copy of its 262 * data. 263 * 264 * <p><em>If the application is not the default IME or does not have input focus this return 265 * {@code null}.</em> 266 * 267 * @see #setPrimaryClip(ClipData) 268 */ getPrimaryClipDescription()269 public @Nullable ClipDescription getPrimaryClipDescription() { 270 try { 271 return mService.getPrimaryClipDescription( 272 mContext.getOpPackageName(), 273 mContext.getAttributionTag(), 274 mContext.getUserId(), 275 mContext.getDeviceId()); 276 } catch (RemoteException e) { 277 throw e.rethrowFromSystemServer(); 278 } 279 } 280 281 /** 282 * Returns true if there is currently a primary clip on the clipboard. 283 * 284 * <em>If the application is not the default IME or the does not have input focus this will 285 * return {@code false}.</em> 286 */ hasPrimaryClip()287 public boolean hasPrimaryClip() { 288 try { 289 return mService.hasPrimaryClip( 290 mContext.getOpPackageName(), 291 mContext.getAttributionTag(), 292 mContext.getUserId(), 293 mContext.getDeviceId()); 294 } catch (RemoteException e) { 295 throw e.rethrowFromSystemServer(); 296 } 297 } 298 addPrimaryClipChangedListener(OnPrimaryClipChangedListener what)299 public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) { 300 synchronized (mPrimaryClipChangedListeners) { 301 if (mPrimaryClipChangedListeners.isEmpty()) { 302 try { 303 mService.addPrimaryClipChangedListener( 304 mPrimaryClipChangedServiceListener, 305 mContext.getOpPackageName(), 306 mContext.getAttributionTag(), 307 mContext.getUserId(), 308 mContext.getDeviceId()); 309 } catch (RemoteException e) { 310 throw e.rethrowFromSystemServer(); 311 } 312 } 313 mPrimaryClipChangedListeners.add(what); 314 } 315 } 316 removePrimaryClipChangedListener(OnPrimaryClipChangedListener what)317 public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) { 318 synchronized (mPrimaryClipChangedListeners) { 319 mPrimaryClipChangedListeners.remove(what); 320 if (mPrimaryClipChangedListeners.isEmpty()) { 321 try { 322 mService.removePrimaryClipChangedListener( 323 mPrimaryClipChangedServiceListener, 324 mContext.getOpPackageName(), 325 mContext.getAttributionTag(), 326 mContext.getUserId(), 327 mContext.getDeviceId()); 328 } catch (RemoteException e) { 329 throw e.rethrowFromSystemServer(); 330 } 331 } 332 } 333 } 334 335 /** 336 * @deprecated Use {@link #getPrimaryClip()} instead. This retrieves 337 * the primary clip and tries to coerce it to a string. 338 */ 339 @Deprecated getText()340 public CharSequence getText() { 341 ClipData clip = getPrimaryClip(); 342 if (clip != null && clip.getItemCount() > 0) { 343 return clip.getItemAt(0).coerceToText(mContext); 344 } 345 return null; 346 } 347 348 /** 349 * @deprecated Use {@link #setPrimaryClip(ClipData)} instead. This 350 * creates a ClippedItem holding the given text and sets it as the 351 * primary clip. It has no label or icon. 352 */ 353 @Deprecated setText(CharSequence text)354 public void setText(CharSequence text) { 355 setPrimaryClip(ClipData.newPlainText(null, text)); 356 } 357 358 /** 359 * @deprecated Use {@link #hasPrimaryClip()} instead. 360 */ 361 @Deprecated hasText()362 public boolean hasText() { 363 try { 364 return mService.hasClipboardText( 365 mContext.getOpPackageName(), 366 mContext.getAttributionTag(), 367 mContext.getUserId(), 368 mContext.getDeviceId()); 369 } catch (RemoteException e) { 370 throw e.rethrowFromSystemServer(); 371 } 372 } 373 374 /** 375 * Returns the package name of the source of the current primary clip, or null if there is no 376 * primary clip or if a source is not available. 377 * 378 * @hide 379 */ 380 @TestApi 381 @Nullable 382 @RequiresPermission(Manifest.permission.SET_CLIP_SOURCE) getPrimaryClipSource()383 public String getPrimaryClipSource() { 384 try { 385 return mService.getPrimaryClipSource( 386 mContext.getOpPackageName(), 387 mContext.getAttributionTag(), 388 mContext.getUserId(), 389 mContext.getDeviceId()); 390 } catch (RemoteException e) { 391 throw e.rethrowFromSystemServer(); 392 } 393 } 394 395 @UnsupportedAppUsage reportPrimaryClipChanged()396 void reportPrimaryClipChanged() { 397 Object[] listeners; 398 399 synchronized (mPrimaryClipChangedListeners) { 400 final int N = mPrimaryClipChangedListeners.size(); 401 if (N <= 0) { 402 return; 403 } 404 listeners = mPrimaryClipChangedListeners.toArray(); 405 } 406 407 for (int i=0; i<listeners.length; i++) { 408 ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged(); 409 } 410 } 411 } 412