package org.robolectric.shadows;

import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.ClipboardManager.OnPrimaryClipChangedListener;
import com.google.android.collect.Lists;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import java.util.Collection;
import static org.robolectric.Robolectric.directlyOn;

@SuppressWarnings("UnusedDeclaration")
@Implements(ClipboardManager.class)
public class ShadowClipboardManager {
  @RealObject private ClipboardManager realClipboardManager;
  private final Collection<OnPrimaryClipChangedListener> listeners = Lists.newArrayList();
  private ClipData clip;

  @Implementation
  public void setPrimaryClip(ClipData clip) {
    if (clip != null) {
       clip.prepareToLeaveProcess();
    }
    this.clip = clip;

    // Synchronously copy the listeners, then handle the change event for each.
    OnPrimaryClipChangedListener[] listenersCopy;
    synchronized (listeners) {
      listenersCopy = new OnPrimaryClipChangedListener[listeners.size()];
      listenersCopy = listeners.toArray(listenersCopy);
    }
    for (OnPrimaryClipChangedListener listener : listenersCopy) {
      listener.onPrimaryClipChanged();
    }
  }

  @Implementation
  public ClipData getPrimaryClip() {
    return clip;
  }

  @Implementation
  public ClipDescription getPrimaryClipDescription() {
    return clip == null ? null : clip.getDescription();
  }

  @Implementation
  public boolean hasPrimaryClip() {
    return clip != null;
  }

  @Implementation
  public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
    synchronized (listeners) {
      listeners.add(listener);
    }
  }

  @Implementation
  public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
    synchronized (listeners) {
      listeners.remove(listener);
    }
  }

  @Implementation
  public void setText(CharSequence text) {
    setPrimaryClip(ClipData.newPlainText(null, text));
  }

  @Implementation
  public boolean hasText() {
    CharSequence text = directlyOn(realClipboardManager, ClipboardManager.class).getText();
    return text != null && text.length() > 0;
  }
}

@@ -215,49 +215,8 @@
 er;%0A
-import com.google.android.collect.Lists;%0A
 impo
@@ -257,24 +257,24 @@
 ementation;%0A
+
 import org.r
@@ -353,16 +353,17 @@
 Object;%0A
+%0A
 import j
@@ -379,24 +379,75 @@
 Collection;%0A
+import java.util.concurrent.CopyOnWriteArrayList;%0A%0A
 import stati
@@ -736,26 +736,62 @@
 s = 
-Lists.newArrayList
+new CopyOnWriteArrayList%3COnPrimaryClipChangedListener%3E
 ();%0A
@@ -974,305 +974,8 @@
 p;%0A%0A
-    // Synchronously copy the listeners, then handle the change event for each.%0A    OnPrimaryClipChangedListener%5B%5D listenersCopy;%0A    synchronized (listeners) %7B%0A      listenersCopy = new OnPrimaryClipChangedListener%5Blisteners.size()%5D;%0A      listenersCopy = listeners.toArray(listenersCopy);%0A    %7D%0A
     
@@ -1028,20 +1028,16 @@
 isteners
-Copy
 ) %7B%0A    
@@ -1484,41 +1484,8 @@
 ) %7B%0A
-    synchronized (listeners) %7B%0A  
     
@@ -1501,38 +1501,32 @@
 .add(listener);%0A
-    %7D%0A
   %7D%0A%0A  @Implemen
@@ -1624,41 +1624,8 @@
 ) %7B%0A
-    synchronized (listeners) %7B%0A  
     
@@ -1652,22 +1652,16 @@
 tener);%0A
-    %7D%0A
   %7D%0A%0A  @

Address review comments - prefer CopyOnWriteArrayList over our own synchronization