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