1 package org.robolectric.shadows; 2 3 import android.media.ToneGenerator; 4 import androidx.annotation.VisibleForTesting; 5 import com.google.auto.value.AutoValue; 6 import com.google.common.collect.ImmutableList; 7 import java.time.Duration; 8 import java.util.ArrayDeque; 9 import java.util.Deque; 10 import org.robolectric.annotation.Implementation; 11 import org.robolectric.annotation.Implements; 12 import org.robolectric.annotation.Resetter; 13 14 /** 15 * Shadow of ToneGenerator. 16 * 17 * <p>Records all tones that were passed to the class. 18 * 19 * <p>This class uses _static_ state to store the tones that were passed to it. This is because 20 * users of the original class are expected to instantiate new instances of ToneGenerator on demand 21 * and clean up the instance after use. This makes it messy to grab the correct instance of 22 * ToneGenerator to properly shadow. 23 * 24 * <p>Additionally, there is a maximum number of tones that this class can support. Tones are stored 25 * in a first-in-first-out basis. 26 */ 27 @Implements(value = ToneGenerator.class) 28 public class ShadowToneGenerator { 29 // A maximum value is required to avoid OOM errors 30 // The number chosen here is arbitrary but should be reasonable for any use case of this class 31 @VisibleForTesting static final int MAXIMUM_STORED_TONES = 2000; 32 33 // This is static because using ToneGenerator has the object created or destroyed on demand. 34 // This makes it very difficult for a test to get access the appropriate instance of 35 // toneGenerator. 36 // The list offers a record of all tones that have been started. 37 // The list has a maximum size of MAXIMUM_STORED_TONES to avoid OOM errors 38 private static final Deque<Tone> playedTones = new ArrayDeque<>(); 39 40 /** 41 * This method will intercept calls to startTone and record the played tone into a static list. 42 * 43 * <p>Note in the original {@link ToneGenerator}, this function will start a tone. Subsequent 44 * calls to this function will cancel the currently playing tone and play a new tone instead. 45 * Since no tone is actually played and no process is started, this tone cannot be interrupted. 46 */ 47 @Implementation startTone(int toneType, int durationMs)48 protected boolean startTone(int toneType, int durationMs) { 49 playedTones.add(Tone.create(toneType, Duration.ofMillis(durationMs))); 50 if (playedTones.size() > MAXIMUM_STORED_TONES) { 51 playedTones.removeFirst(); 52 } 53 54 return true; 55 } 56 57 /** 58 * This function returns the list of tones that the application requested to be played. Note that 59 * this will return all tones requested by all ToneGenerators. 60 * 61 * @return A defensive copy of the list of tones played by all tone generators. 62 */ getPlayedTones()63 public static ImmutableList<Tone> getPlayedTones() { 64 return ImmutableList.copyOf(playedTones); 65 } 66 67 @Resetter reset()68 public static void reset() { 69 playedTones.clear(); 70 } 71 72 /** Stores data about a tone played by the ToneGenerator */ 73 @AutoValue 74 public abstract static class Tone { 75 76 /** 77 * The type of the tone. 78 * 79 * @see ToneGenerator for a list of possible tones 80 */ type()81 public abstract int type(); 82 duration()83 public abstract Duration duration(); 84 create(int type, Duration duration)85 static Tone create(int type, Duration duration) { 86 return new AutoValue_ShadowToneGenerator_Tone(type, duration); 87 } 88 } 89 } 90