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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemService;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.os.Handler;
24 import android.os.RemoteException;
25 import android.os.ServiceManager;
26 import android.os.ServiceManager.ServiceNotFoundException;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.ArrayList;
31 
32 /**
33  * Interface to the clipboard service, for placing and retrieving text in
34  * the global clipboard.
35  *
36  * <p>
37  * The ClipboardManager API itself is very simple: it consists of methods
38  * to atomically get and set the current primary clipboard data.  That data
39  * is expressed as a {@link ClipData} object, which defines the protocol
40  * for data exchange between applications.
41  *
42  * <div class="special reference">
43  * <h3>Developer Guides</h3>
44  * <p>For more information about using the clipboard framework, read the
45  * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
46  * developer guide.</p>
47  * </div>
48  */
49 @SystemService(Context.CLIPBOARD_SERVICE)
50 public class ClipboardManager extends android.text.ClipboardManager {
51     private final Context mContext;
52     private final Handler mHandler;
53     private final IClipboard mService;
54 
55     private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
56              = new ArrayList<OnPrimaryClipChangedListener>();
57 
58     private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
59             = new IOnPrimaryClipChangedListener.Stub() {
60         @Override
61         public void dispatchPrimaryClipChanged() {
62             mHandler.post(() -> {
63                 reportPrimaryClipChanged();
64             });
65         }
66     };
67 
68     /**
69      * Defines a listener callback that is invoked when the primary clip on the clipboard changes.
70      * Objects that want to register a listener call
71      * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener)
72      * addPrimaryClipChangedListener()} with an
73      * object that implements OnPrimaryClipChangedListener.
74      *
75      */
76     public interface OnPrimaryClipChangedListener {
77 
78         /**
79          * Callback that is invoked by {@link android.content.ClipboardManager} when the primary
80          * clip changes.
81          */
onPrimaryClipChanged()82         void onPrimaryClipChanged();
83     }
84 
85     /** {@hide} */
86     @UnsupportedAppUsage
ClipboardManager(Context context, Handler handler)87     public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
88         mContext = context;
89         mHandler = handler;
90         mService = IClipboard.Stub.asInterface(
91                 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
92     }
93 
94     /**
95      * Sets the current primary clip on the clipboard.  This is the clip that
96      * is involved in normal cut and paste operations.
97      *
98      * @param clip The clipped data item to set.
99      * @see #getPrimaryClip()
100      * @see #clearPrimaryClip()
101      */
setPrimaryClip(@onNull ClipData clip)102     public void setPrimaryClip(@NonNull ClipData clip) {
103         try {
104             Preconditions.checkNotNull(clip);
105             clip.prepareToLeaveProcess(true);
106             mService.setPrimaryClip(clip, mContext.getOpPackageName(), mContext.getUserId());
107         } catch (RemoteException e) {
108             throw e.rethrowFromSystemServer();
109         }
110     }
111 
112     /**
113      * Clears any current primary clip on the clipboard.
114      *
115      * @see #setPrimaryClip(ClipData)
116      */
clearPrimaryClip()117     public void clearPrimaryClip() {
118         try {
119             mService.clearPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
120         } catch (RemoteException e) {
121             throw e.rethrowFromSystemServer();
122         }
123     }
124 
125     /**
126      * Returns the current primary clip on the clipboard.
127      *
128      * <em>If the application is not the default IME or does not have input focus this return
129      * {@code null}.</em>
130      *
131      * @see #setPrimaryClip(ClipData)
132      */
getPrimaryClip()133     public @Nullable ClipData getPrimaryClip() {
134         try {
135             return mService.getPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
136         } catch (RemoteException e) {
137             throw e.rethrowFromSystemServer();
138         }
139     }
140 
141     /**
142      * Returns a description of the current primary clip on the clipboard
143      * but not a copy of its data.
144      *
145      * <em>If the application is not the default IME or does not have input focus this return
146      * {@code null}.</em>
147      *
148      * @see #setPrimaryClip(ClipData)
149      */
getPrimaryClipDescription()150     public @Nullable ClipDescription getPrimaryClipDescription() {
151         try {
152             return mService.getPrimaryClipDescription(mContext.getOpPackageName(),
153                     mContext.getUserId());
154         } catch (RemoteException e) {
155             throw e.rethrowFromSystemServer();
156         }
157     }
158 
159     /**
160      * Returns true if there is currently a primary clip on the clipboard.
161      *
162      * <em>If the application is not the default IME or the does not have input focus this will
163      * return {@code false}.</em>
164      */
hasPrimaryClip()165     public boolean hasPrimaryClip() {
166         try {
167             return mService.hasPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
168         } catch (RemoteException e) {
169             throw e.rethrowFromSystemServer();
170         }
171     }
172 
addPrimaryClipChangedListener(OnPrimaryClipChangedListener what)173     public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
174         synchronized (mPrimaryClipChangedListeners) {
175             if (mPrimaryClipChangedListeners.isEmpty()) {
176                 try {
177                     mService.addPrimaryClipChangedListener(
178                             mPrimaryClipChangedServiceListener, mContext.getOpPackageName(),
179                             mContext.getUserId());
180                 } catch (RemoteException e) {
181                     throw e.rethrowFromSystemServer();
182                 }
183             }
184             mPrimaryClipChangedListeners.add(what);
185         }
186     }
187 
removePrimaryClipChangedListener(OnPrimaryClipChangedListener what)188     public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
189         synchronized (mPrimaryClipChangedListeners) {
190             mPrimaryClipChangedListeners.remove(what);
191             if (mPrimaryClipChangedListeners.isEmpty()) {
192                 try {
193                     mService.removePrimaryClipChangedListener(
194                             mPrimaryClipChangedServiceListener, mContext.getOpPackageName(),
195                             mContext.getUserId());
196                 } catch (RemoteException e) {
197                     throw e.rethrowFromSystemServer();
198                 }
199             }
200         }
201     }
202 
203     /**
204      * @deprecated Use {@link #getPrimaryClip()} instead.  This retrieves
205      * the primary clip and tries to coerce it to a string.
206      */
207     @Deprecated
getText()208     public CharSequence getText() {
209         ClipData clip = getPrimaryClip();
210         if (clip != null && clip.getItemCount() > 0) {
211             return clip.getItemAt(0).coerceToText(mContext);
212         }
213         return null;
214     }
215 
216     /**
217      * @deprecated Use {@link #setPrimaryClip(ClipData)} instead.  This
218      * creates a ClippedItem holding the given text and sets it as the
219      * primary clip.  It has no label or icon.
220      */
221     @Deprecated
setText(CharSequence text)222     public void setText(CharSequence text) {
223         setPrimaryClip(ClipData.newPlainText(null, text));
224     }
225 
226     /**
227      * @deprecated Use {@link #hasPrimaryClip()} instead.
228      */
229     @Deprecated
hasText()230     public boolean hasText() {
231         try {
232             return mService.hasClipboardText(mContext.getOpPackageName(), mContext.getUserId());
233         } catch (RemoteException e) {
234             throw e.rethrowFromSystemServer();
235         }
236     }
237 
238     @UnsupportedAppUsage
reportPrimaryClipChanged()239     void reportPrimaryClipChanged() {
240         Object[] listeners;
241 
242         synchronized (mPrimaryClipChangedListeners) {
243             final int N = mPrimaryClipChangedListeners.size();
244             if (N <= 0) {
245                 return;
246             }
247             listeners = mPrimaryClipChangedListeners.toArray();
248         }
249 
250         for (int i=0; i<listeners.length; i++) {
251             ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged();
252         }
253     }
254 }
255