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