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