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