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