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;
25import android.os.StrictMode;
26import android.util.Log;
27
28import java.util.ArrayList;
29
30/**
31 * Interface to the clipboard service, for placing and retrieving text in
32 * the global clipboard.
33 *
34 * <p>
35 * You do not instantiate this class directly; instead, retrieve it through
36 * {@link android.content.Context#getSystemService}.
37 *
38 * <p>
39 * The ClipboardManager API itself is very simple: it consists of methods
40 * to atomically get and set the current primary clipboard data.  That data
41 * is expressed as a {@link ClipData} object, which defines the protocol
42 * for data exchange between applications.
43 *
44 * <div class="special reference">
45 * <h3>Developer Guides</h3>
46 * <p>For more information about using the clipboard framework, read the
47 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
48 * developer guide.</p>
49 * </div>
50 *
51 * @see android.content.Context#getSystemService
52 */
53public class ClipboardManager extends android.text.ClipboardManager {
54    private final static Object sStaticLock = new Object();
55    private static IClipboard sService;
56
57    private final Context mContext;
58
59    private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
60             = new ArrayList<OnPrimaryClipChangedListener>();
61
62    private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
63            = new IOnPrimaryClipChangedListener.Stub() {
64        public void dispatchPrimaryClipChanged() {
65            mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED);
66        }
67    };
68
69    static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1;
70
71    private final Handler mHandler = new Handler() {
72        @Override
73        public void handleMessage(Message msg) {
74            switch (msg.what) {
75                case MSG_REPORT_PRIMARY_CLIP_CHANGED:
76                    reportPrimaryClipChanged();
77            }
78        }
79    };
80
81    /**
82     * Defines a listener callback that is invoked when the primary clip on the clipboard changes.
83     * Objects that want to register a listener call
84     * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener)
85     * addPrimaryClipChangedListener()} with an
86     * object that implements OnPrimaryClipChangedListener.
87     *
88     */
89    public interface OnPrimaryClipChangedListener {
90
91        /**
92         * Callback that is invoked by {@link android.content.ClipboardManager} when the primary
93         * clip changes.
94         */
95        void onPrimaryClipChanged();
96    }
97
98    static private IClipboard getService() {
99        synchronized (sStaticLock) {
100            if (sService != null) {
101                return sService;
102            }
103            IBinder b = ServiceManager.getService("clipboard");
104            sService = IClipboard.Stub.asInterface(b);
105            return sService;
106        }
107    }
108
109    /** {@hide} */
110    public ClipboardManager(Context context, Handler handler) {
111        mContext = context;
112    }
113
114    /**
115     * Sets the current primary clip on the clipboard.  This is the clip that
116     * is involved in normal cut and paste operations.
117     *
118     * @param clip The clipped data item to set.
119     */
120    public void setPrimaryClip(ClipData clip) {
121        try {
122            if (clip != null) {
123                clip.prepareToLeaveProcess();
124            }
125            getService().setPrimaryClip(clip, mContext.getOpPackageName());
126        } catch (RemoteException e) {
127        }
128    }
129
130    /**
131     * Returns the current primary clip on the clipboard.
132     */
133    public ClipData getPrimaryClip() {
134        try {
135            return getService().getPrimaryClip(mContext.getOpPackageName());
136        } catch (RemoteException e) {
137            return null;
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    public ClipDescription getPrimaryClipDescription() {
146        try {
147            return getService().getPrimaryClipDescription(mContext.getOpPackageName());
148        } catch (RemoteException e) {
149            return null;
150        }
151    }
152
153    /**
154     * Returns true if there is currently a primary clip on the clipboard.
155     */
156    public boolean hasPrimaryClip() {
157        try {
158            return getService().hasPrimaryClip(mContext.getOpPackageName());
159        } catch (RemoteException e) {
160            return false;
161        }
162    }
163
164    public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
165        synchronized (mPrimaryClipChangedListeners) {
166            if (mPrimaryClipChangedListeners.size() == 0) {
167                try {
168                    getService().addPrimaryClipChangedListener(
169                            mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
170                } catch (RemoteException e) {
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                }
186            }
187        }
188    }
189
190    /**
191     * @deprecated Use {@link #getPrimaryClip()} instead.  This retrieves
192     * the primary clip and tries to coerce it to a string.
193     */
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    public void setText(CharSequence text) {
208        setPrimaryClip(ClipData.newPlainText(null, text));
209    }
210
211    /**
212     * @deprecated Use {@link #hasPrimaryClip()} instead.
213     */
214    public boolean hasText() {
215        try {
216            return getService().hasClipboardText(mContext.getOpPackageName());
217        } catch (RemoteException e) {
218            return false;
219        }
220    }
221
222    void reportPrimaryClipChanged() {
223        Object[] listeners;
224
225        synchronized (mPrimaryClipChangedListeners) {
226            final int N = mPrimaryClipChangedListeners.size();
227            if (N <= 0) {
228                return;
229            }
230            listeners = mPrimaryClipChangedListeners.toArray();
231        }
232
233        for (int i=0; i<listeners.length; i++) {
234            ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged();
235        }
236    }
237}
238