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