• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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