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.content.Context;
20import android.os.Message;
21import android.os.RemoteException;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.ServiceManager;
25
26import java.util.ArrayList;
27
28/**
29 * Interface to the clipboard service, for placing and retrieving text in
30 * the global clipboard.
31 *
32 * <p>
33 * You do not instantiate this class directly; instead, retrieve it through
34 * {@link android.content.Context#getSystemService}.
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 * @see android.content.Context#getSystemService
50 */
51public class ClipboardManager extends android.text.ClipboardManager {
52    private final static Object sStaticLock = new Object();
53    private static IClipboard sService;
54
55    private final Context mContext;
56
57    private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
58             = new ArrayList<OnPrimaryClipChangedListener>();
59
60    private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
61            = new IOnPrimaryClipChangedListener.Stub() {
62        public void dispatchPrimaryClipChanged() {
63            mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED);
64        }
65    };
66
67    static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1;
68
69    private final Handler mHandler = new Handler() {
70        @Override
71        public void handleMessage(Message msg) {
72            switch (msg.what) {
73                case MSG_REPORT_PRIMARY_CLIP_CHANGED:
74                    reportPrimaryClipChanged();
75            }
76        }
77    };
78
79    /**
80     * Defines a listener callback that is invoked when the primary clip on the clipboard changes.
81     * Objects that want to register a listener call
82     * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener)
83     * addPrimaryClipChangedListener()} with an
84     * object that implements OnPrimaryClipChangedListener.
85     *
86     */
87    public interface OnPrimaryClipChangedListener {
88
89        /**
90         * Callback that is invoked by {@link android.content.ClipboardManager} when the primary
91         * clip changes.
92         */
93        void onPrimaryClipChanged();
94    }
95
96    static private IClipboard getService() {
97        synchronized (sStaticLock) {
98            if (sService != null) {
99                return sService;
100            }
101            IBinder b = ServiceManager.getService("clipboard");
102            sService = IClipboard.Stub.asInterface(b);
103            return sService;
104        }
105    }
106
107    /** {@hide} */
108    public ClipboardManager(Context context, Handler handler) {
109        mContext = context;
110    }
111
112    /**
113     * Sets the current primary clip on the clipboard.  This is the clip that
114     * is involved in normal cut and paste operations.
115     *
116     * @param clip The clipped data item to set.
117     */
118    public void setPrimaryClip(ClipData clip) {
119        try {
120            if (clip != null) {
121                clip.prepareToLeaveProcess(true);
122            }
123            getService().setPrimaryClip(clip, mContext.getOpPackageName());
124        } catch (RemoteException e) {
125            throw e.rethrowFromSystemServer();
126        }
127    }
128
129    /**
130     * Returns the current primary clip on the clipboard.
131     */
132    public ClipData getPrimaryClip() {
133        try {
134            return getService().getPrimaryClip(mContext.getOpPackageName());
135        } catch (RemoteException e) {
136            throw e.rethrowFromSystemServer();
137        }
138    }
139
140    /**
141     * Returns a description of the current primary clip on the clipboard
142     * but not a copy of its data.
143     */
144    public ClipDescription getPrimaryClipDescription() {
145        try {
146            return getService().getPrimaryClipDescription(mContext.getOpPackageName());
147        } catch (RemoteException e) {
148            throw e.rethrowFromSystemServer();
149        }
150    }
151
152    /**
153     * Returns true if there is currently a primary clip on the clipboard.
154     */
155    public boolean hasPrimaryClip() {
156        try {
157            return getService().hasPrimaryClip(mContext.getOpPackageName());
158        } catch (RemoteException e) {
159            throw e.rethrowFromSystemServer();
160        }
161    }
162
163    public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
164        synchronized (mPrimaryClipChangedListeners) {
165            if (mPrimaryClipChangedListeners.size() == 0) {
166                try {
167                    getService().addPrimaryClipChangedListener(
168                            mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
169                } catch (RemoteException e) {
170                    throw e.rethrowFromSystemServer();
171                }
172            }
173            mPrimaryClipChangedListeners.add(what);
174        }
175    }
176
177    public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
178        synchronized (mPrimaryClipChangedListeners) {
179            mPrimaryClipChangedListeners.remove(what);
180            if (mPrimaryClipChangedListeners.size() == 0) {
181                try {
182                    getService().removePrimaryClipChangedListener(
183                            mPrimaryClipChangedServiceListener);
184                } catch (RemoteException e) {
185                    throw e.rethrowFromSystemServer();
186                }
187            }
188        }
189    }
190
191    /**
192     * @deprecated Use {@link #getPrimaryClip()} instead.  This retrieves
193     * the primary clip and tries to coerce it to a string.
194     */
195    public CharSequence getText() {
196        ClipData clip = getPrimaryClip();
197        if (clip != null && clip.getItemCount() > 0) {
198            return clip.getItemAt(0).coerceToText(mContext);
199        }
200        return null;
201    }
202
203    /**
204     * @deprecated Use {@link #setPrimaryClip(ClipData)} instead.  This
205     * creates a ClippedItem holding the given text and sets it as the
206     * primary clip.  It has no label or icon.
207     */
208    public void setText(CharSequence text) {
209        setPrimaryClip(ClipData.newPlainText(null, text));
210    }
211
212    /**
213     * @deprecated Use {@link #hasPrimaryClip()} instead.
214     */
215    public boolean hasText() {
216        try {
217            return getService().hasClipboardText(mContext.getOpPackageName());
218        } catch (RemoteException e) {
219            throw e.rethrowFromSystemServer();
220        }
221    }
222
223    void reportPrimaryClipChanged() {
224        Object[] listeners;
225
226        synchronized (mPrimaryClipChangedListeners) {
227            final int N = mPrimaryClipChangedListeners.size();
228            if (N <= 0) {
229                return;
230            }
231            listeners = mPrimaryClipChangedListeners.toArray();
232        }
233
234        for (int i=0; i<listeners.length; i++) {
235            ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged();
236        }
237    }
238}
239