Home | History | Annotate | Download | only in telecom
      1 /*
      2  * Copyright 2014, 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.server.telecom;
     18 
     19 import android.media.Ringtone;
     20 import android.net.Uri;
     21 import android.os.Handler;
     22 import android.os.HandlerThread;
     23 import android.os.Message;
     24 import android.telecom.Log;
     25 
     26 import com.android.internal.annotations.VisibleForTesting;
     27 import com.android.internal.os.SomeArgs;
     28 import com.android.internal.util.Preconditions;
     29 
     30 /**
     31  * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
     32  * used from the main thread.
     33  */
     34 @VisibleForTesting
     35 public class AsyncRingtonePlayer {
     36     // Message codes used with the ringtone thread.
     37     private static final int EVENT_PLAY = 1;
     38     private static final int EVENT_STOP = 2;
     39     private static final int EVENT_REPEAT = 3;
     40 
     41     // The interval in which to restart the ringer.
     42     private static final int RESTART_RINGER_MILLIS = 3000;
     43 
     44     /** Handler running on the ringtone thread. */
     45     private Handler mHandler;
     46 
     47     /** The current ringtone. Only used by the ringtone thread. */
     48     private Ringtone mRingtone;
     49 
     50     /** Plays the ringtone. */
     51     public void play(RingtoneFactory factory, Call incomingCall) {
     52         Log.d(this, "Posting play.");
     53         SomeArgs args = SomeArgs.obtain();
     54         args.arg1 = factory;
     55         args.arg2 = incomingCall;
     56         postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
     57     }
     58 
     59     /** Stops playing the ringtone. */
     60     public void stop() {
     61         Log.d(this, "Posting stop.");
     62         postMessage(EVENT_STOP, false /* shouldCreateHandler */, null);
     63     }
     64 
     65     /**
     66      * Posts a message to the ringtone-thread handler. Creates the handler if specified by the
     67      * parameter shouldCreateHandler.
     68      *
     69      * @param messageCode The message to post.
     70      * @param shouldCreateHandler True when a handler should be created to handle this message.
     71      */
     72     private void postMessage(int messageCode, boolean shouldCreateHandler, SomeArgs args) {
     73         synchronized(this) {
     74             if (mHandler == null && shouldCreateHandler) {
     75                 mHandler = getNewHandler();
     76             }
     77 
     78             if (mHandler == null) {
     79                 Log.d(this, "Message %d skipped because there is no handler.", messageCode);
     80             } else {
     81                 mHandler.obtainMessage(messageCode, args).sendToTarget();
     82             }
     83         }
     84     }
     85 
     86     /**
     87      * Creates a new ringtone Handler running in its own thread.
     88      */
     89     private Handler getNewHandler() {
     90         Preconditions.checkState(mHandler == null);
     91 
     92         HandlerThread thread = new HandlerThread("ringtone-player");
     93         thread.start();
     94 
     95         return new Handler(thread.getLooper(), null /*callback*/, true /*async*/) {
     96             @Override
     97             public void handleMessage(Message msg) {
     98                 switch(msg.what) {
     99                     case EVENT_PLAY:
    100                         handlePlay((SomeArgs) msg.obj);
    101                         break;
    102                     case EVENT_REPEAT:
    103                         handleRepeat();
    104                         break;
    105                     case EVENT_STOP:
    106                         handleStop();
    107                         break;
    108                 }
    109             }
    110         };
    111     }
    112 
    113     /**
    114      * Starts the actual playback of the ringtone. Executes on ringtone-thread.
    115      */
    116     private void handlePlay(SomeArgs args) {
    117         RingtoneFactory factory = (RingtoneFactory) args.arg1;
    118         Call incomingCall = (Call) args.arg2;
    119         args.recycle();
    120         // don't bother with any of this if there is an EVENT_STOP waiting.
    121         if (mHandler.hasMessages(EVENT_STOP)) {
    122             return;
    123         }
    124 
    125         // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play
    126         // anything.
    127         if(Uri.EMPTY.equals(incomingCall.getRingtone())) {
    128             mRingtone = null;
    129             return;
    130         }
    131 
    132         ThreadUtil.checkNotOnMainThread();
    133         Log.i(this, "Play ringtone.");
    134 
    135         if (mRingtone == null) {
    136             mRingtone = factory.getRingtone(incomingCall);
    137             if (mRingtone == null) {
    138                 Uri ringtoneUri = incomingCall.getRingtone();
    139                 String ringtoneUriString = (ringtoneUri == null) ? "null" :
    140                         ringtoneUri.toSafeString();
    141                 Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
    142                         "factory. Skipping ringing. Uri was: " + ringtoneUriString);
    143                 return;
    144             }
    145         }
    146 
    147         handleRepeat();
    148     }
    149 
    150     private void handleRepeat() {
    151         if (mRingtone == null) {
    152             return;
    153         }
    154 
    155         if (mRingtone.isPlaying()) {
    156             Log.d(this, "Ringtone already playing.");
    157         } else {
    158             mRingtone.play();
    159             Log.i(this, "Repeat ringtone.");
    160         }
    161 
    162         // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.
    163         synchronized(this) {
    164             if (!mHandler.hasMessages(EVENT_REPEAT)) {
    165                 mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);
    166             }
    167         }
    168     }
    169 
    170     /**
    171      * Stops the playback of the ringtone. Executes on the ringtone-thread.
    172      */
    173     private void handleStop() {
    174         ThreadUtil.checkNotOnMainThread();
    175         Log.i(this, "Stop ringtone.");
    176 
    177         if (mRingtone != null) {
    178             Log.d(this, "Ringtone.stop() invoked.");
    179             mRingtone.stop();
    180             mRingtone = null;
    181         }
    182 
    183         synchronized(this) {
    184             // At the time that STOP is handled, there should be no need for repeat messages in the
    185             // queue.
    186             mHandler.removeMessages(EVENT_REPEAT);
    187 
    188             if (mHandler.hasMessages(EVENT_PLAY)) {
    189                 Log.v(this, "Keeping alive ringtone thread for subsequent play request.");
    190             } else {
    191                 mHandler.removeMessages(EVENT_STOP);
    192                 mHandler.getLooper().quitSafely();
    193                 mHandler = null;
    194                 Log.v(this, "Handler cleared.");
    195             }
    196         }
    197     }
    198 }
    199