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