Home | History | Annotate | Download | only in ringtone
      1 /*
      2  * Copyright (C) 2016 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 
     17 package com.android.incallui.ringtone;
     18 
     19 import android.media.AudioManager;
     20 import android.media.ToneGenerator;
     21 import android.support.annotation.NonNull;
     22 import android.support.annotation.Nullable;
     23 import com.android.incallui.Log;
     24 import com.android.incallui.async.PausableExecutor;
     25 import java.util.Objects;
     26 import java.util.concurrent.CountDownLatch;
     27 import java.util.concurrent.TimeUnit;
     28 
     29 /**
     30  * Class responsible for playing in-call related tones in a background thread. This class only
     31  * allows one tone to be played at a time.
     32  */
     33 public class InCallTonePlayer {
     34 
     35   public static final int TONE_CALL_WAITING = 4;
     36 
     37   public static final int VOLUME_RELATIVE_HIGH_PRIORITY = 80;
     38 
     39   @NonNull private final ToneGeneratorFactory toneGeneratorFactory;
     40   @NonNull private final PausableExecutor executor;
     41   private @Nullable CountDownLatch numPlayingTones;
     42 
     43   /**
     44    * Creates a new InCallTonePlayer.
     45    *
     46    * @param toneGeneratorFactory the {@link ToneGeneratorFactory} used to create {@link
     47    *     ToneGenerator}s.
     48    * @param executor the {@link PausableExecutor} used to play tones in a background thread.
     49    * @throws NullPointerException if audioModeProvider, toneGeneratorFactory, or executor are {@code
     50    *     null}.
     51    */
     52   public InCallTonePlayer(
     53       @NonNull ToneGeneratorFactory toneGeneratorFactory, @NonNull PausableExecutor executor) {
     54     this.toneGeneratorFactory = Objects.requireNonNull(toneGeneratorFactory);
     55     this.executor = Objects.requireNonNull(executor);
     56   }
     57 
     58   /** @return {@code true} if a tone is currently playing, {@code false} otherwise. */
     59   public boolean isPlayingTone() {
     60     return numPlayingTones != null && numPlayingTones.getCount() > 0;
     61   }
     62 
     63   /**
     64    * Plays the given tone in a background thread.
     65    *
     66    * @param tone the tone to play.
     67    * @throws IllegalStateException if a tone is already playing.
     68    * @throws IllegalArgumentException if the tone is invalid.
     69    */
     70   public void play(int tone) {
     71     if (isPlayingTone()) {
     72       throw new IllegalStateException("Tone already playing");
     73     }
     74     final ToneGeneratorInfo info = getToneGeneratorInfo(tone);
     75     numPlayingTones = new CountDownLatch(1);
     76     executor.execute(
     77         new Runnable() {
     78           @Override
     79           public void run() {
     80             playOnBackgroundThread(info);
     81           }
     82         });
     83   }
     84 
     85   private ToneGeneratorInfo getToneGeneratorInfo(int tone) {
     86     switch (tone) {
     87       case TONE_CALL_WAITING:
     88         /*
     89          * DialerCall waiting tones play until they're stopped either by the user accepting or
     90          * declining the call so the tone length is set at what's effectively forever. The
     91          * tone is played at a high priority volume and through STREAM_VOICE_CALL since it's
     92          * call related and using that stream will route it through bluetooth devices
     93          * appropriately.
     94          */
     95         return new ToneGeneratorInfo(
     96             ToneGenerator.TONE_SUP_CALL_WAITING,
     97             VOLUME_RELATIVE_HIGH_PRIORITY,
     98             Integer.MAX_VALUE,
     99             AudioManager.STREAM_VOICE_CALL);
    100       default:
    101         throw new IllegalArgumentException("Bad tone: " + tone);
    102     }
    103   }
    104 
    105   private void playOnBackgroundThread(ToneGeneratorInfo info) {
    106     ToneGenerator toneGenerator = null;
    107     try {
    108       Log.v(this, "Starting tone " + info);
    109       toneGenerator = toneGeneratorFactory.newInCallToneGenerator(info.stream, info.volume);
    110       toneGenerator.startTone(info.tone);
    111       /*
    112        * During tests, this will block until the tests call mExecutor.ackMilestone. This call
    113        * allows for synchronization to the point where the tone has started playing.
    114        */
    115       executor.milestone();
    116       if (numPlayingTones != null) {
    117         numPlayingTones.await(info.toneLengthMillis, TimeUnit.MILLISECONDS);
    118         // Allows for synchronization to the point where the tone has completed playing.
    119         executor.milestone();
    120       }
    121     } catch (InterruptedException e) {
    122       Log.w(this, "Interrupted while playing in-call tone.");
    123     } finally {
    124       if (toneGenerator != null) {
    125         toneGenerator.release();
    126       }
    127       if (numPlayingTones != null) {
    128         numPlayingTones.countDown();
    129       }
    130       // Allows for synchronization to the point where this background thread has cleaned up.
    131       executor.milestone();
    132     }
    133   }
    134 
    135   /** Stops playback of the current tone. */
    136   public void stop() {
    137     if (numPlayingTones != null) {
    138       numPlayingTones.countDown();
    139     }
    140   }
    141 
    142   private static class ToneGeneratorInfo {
    143 
    144     public final int tone;
    145     public final int volume;
    146     public final int toneLengthMillis;
    147     public final int stream;
    148 
    149     public ToneGeneratorInfo(int toneGeneratorType, int volume, int toneLengthMillis, int stream) {
    150       this.tone = toneGeneratorType;
    151       this.volume = volume;
    152       this.toneLengthMillis = toneLengthMillis;
    153       this.stream = stream;
    154     }
    155 
    156     @Override
    157     public String toString() {
    158       return "ToneGeneratorInfo{"
    159           + "toneLengthMillis="
    160           + toneLengthMillis
    161           + ", tone="
    162           + tone
    163           + ", volume="
    164           + volume
    165           + '}';
    166     }
    167   }
    168 }
    169