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