michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.util; michael@0: michael@0: import android.content.ClipData; michael@0: import android.content.Context; michael@0: import android.os.Build; michael@0: import android.util.Log; michael@0: michael@0: import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; michael@0: michael@0: import java.util.concurrent.SynchronousQueue; michael@0: michael@0: public final class Clipboard { michael@0: private static Context mContext; michael@0: private final static String LOGTAG = "GeckoClipboard"; michael@0: private final static SynchronousQueue sClipboardQueue = new SynchronousQueue(); michael@0: michael@0: private Clipboard() { michael@0: } michael@0: michael@0: public static void init(final Context c) { michael@0: if (mContext != null) { michael@0: Log.w(LOGTAG, "Clipboard.init() called twice!"); michael@0: return; michael@0: } michael@0: mContext = c.getApplicationContext(); michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "GetClipboardTextWrapper") michael@0: public static String getText() { michael@0: // If we're on the UI thread or the background thread, we have a looper on the thread michael@0: // and can just call this directly. For any other threads, post the call to the michael@0: // background thread. michael@0: michael@0: if (ThreadUtils.isOnUiThread() || ThreadUtils.isOnBackgroundThread()) { michael@0: return getClipboardTextImpl(); michael@0: } michael@0: michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: String text = getClipboardTextImpl(); michael@0: try { michael@0: sClipboardQueue.put(text != null ? text : ""); michael@0: } catch (InterruptedException ie) {} michael@0: } michael@0: }); michael@0: try { michael@0: return sClipboardQueue.take(); michael@0: } catch (InterruptedException ie) { michael@0: return ""; michael@0: } michael@0: } michael@0: michael@0: @WrapElementForJNI(stubName = "SetClipboardText") michael@0: public static void setText(final CharSequence text) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: @SuppressWarnings("deprecation") michael@0: public void run() { michael@0: if (Build.VERSION.SDK_INT >= 11) { michael@0: android.content.ClipboardManager cm = getClipboardManager(mContext); michael@0: ClipData clip = ClipData.newPlainText("Text", text); michael@0: try { michael@0: cm.setPrimaryClip(clip); michael@0: } catch (NullPointerException e) { michael@0: // Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw michael@0: // a NullPointerException if Samsung's /data/clipboard directory is full. michael@0: // Fortunately, the text is still successfully copied to the clipboard. michael@0: } michael@0: } else { michael@0: android.text.ClipboardManager cm = getDeprecatedClipboardManager(mContext); michael@0: cm.setText(text); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the clipboard is nonempty, false otherwise. michael@0: * michael@0: * @return true if the clipboard is nonempty, false otherwise. michael@0: */ michael@0: @WrapElementForJNI michael@0: public static boolean hasText() { michael@0: if (Build.VERSION.SDK_INT >= 11) { michael@0: android.content.ClipboardManager cm = getClipboardManager(mContext); michael@0: return cm.hasPrimaryClip(); michael@0: } michael@0: michael@0: android.text.ClipboardManager cm = getDeprecatedClipboardManager(mContext); michael@0: return cm.hasText(); michael@0: } michael@0: michael@0: /** michael@0: * Deletes all text from the clipboard. michael@0: */ michael@0: @WrapElementForJNI michael@0: public static void clearText() { michael@0: setText(null); michael@0: } michael@0: michael@0: private static android.content.ClipboardManager getClipboardManager(Context context) { michael@0: // In API Level 11 and above, CLIPBOARD_SERVICE returns android.content.ClipboardManager, michael@0: // which is a subclass of android.text.ClipboardManager. michael@0: return (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); michael@0: } michael@0: michael@0: private static android.text.ClipboardManager getDeprecatedClipboardManager(Context context) { michael@0: return (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); michael@0: } michael@0: michael@0: /* On some devices, access to the clipboard service needs to happen michael@0: * on a thread with a looper, so this function requires a looper is michael@0: * present on the thread. */ michael@0: @SuppressWarnings("deprecation") michael@0: private static String getClipboardTextImpl() { michael@0: if (Build.VERSION.SDK_INT >= 11) { michael@0: android.content.ClipboardManager cm = getClipboardManager(mContext); michael@0: if (cm.hasPrimaryClip()) { michael@0: ClipData clip = cm.getPrimaryClip(); michael@0: if (clip != null) { michael@0: ClipData.Item item = clip.getItemAt(0); michael@0: return item.coerceToText(mContext).toString(); michael@0: } michael@0: } michael@0: } else { michael@0: android.text.ClipboardManager cm = getDeprecatedClipboardManager(mContext); michael@0: if (cm.hasText()) { michael@0: return cm.getText().toString(); michael@0: } michael@0: } michael@0: return null; michael@0: } michael@0: }