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.content.Context; 20import android.media.AudioManager; 21import android.media.Ringtone; 22import android.media.RingtoneManager; 23import android.net.Uri; 24import android.os.Handler; 25import android.os.HandlerThread; 26import android.os.Message; 27import android.provider.Settings; 28 29import com.android.internal.util.Preconditions; 30 31/** 32 * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be 33 * used from the main thread. 34 */ 35class 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 /** 51 * The context. 52 */ 53 private final Context mContext; 54 55 AsyncRingtonePlayer(Context context) { 56 mContext = context; 57 } 58 59 /** Plays the ringtone. */ 60 void play(Uri ringtone) { 61 Log.d(this, "Posting play."); 62 postMessage(EVENT_PLAY, true /* shouldCreateHandler */, ringtone); 63 } 64 65 /** Stops playing the ringtone. */ 66 void stop() { 67 Log.d(this, "Posting stop."); 68 postMessage(EVENT_STOP, false /* shouldCreateHandler */, null); 69 } 70 71 /** 72 * Posts a message to the ringtone-thread handler. Creates the handler if specified by the 73 * parameter shouldCreateHandler. 74 * 75 * @param messageCode The message to post. 76 * @param shouldCreateHandler True when a handler should be created to handle this message. 77 */ 78 private void postMessage(int messageCode, boolean shouldCreateHandler, Uri ringtone) { 79 synchronized(this) { 80 if (mHandler == null && shouldCreateHandler) { 81 mHandler = getNewHandler(); 82 } 83 84 if (mHandler == null) { 85 Log.d(this, "Message %d skipped because there is no handler.", messageCode); 86 } else { 87 mHandler.obtainMessage(messageCode, ringtone).sendToTarget(); 88 } 89 } 90 } 91 92 /** 93 * Creates a new ringtone Handler running in its own thread. 94 */ 95 private Handler getNewHandler() { 96 Preconditions.checkState(mHandler == null); 97 98 HandlerThread thread = new HandlerThread("ringtone-player"); 99 thread.start(); 100 101 return new Handler(thread.getLooper()) { 102 @Override 103 public void handleMessage(Message msg) { 104 switch(msg.what) { 105 case EVENT_PLAY: 106 handlePlay((Uri) msg.obj); 107 break; 108 case EVENT_REPEAT: 109 handleRepeat(); 110 break; 111 case EVENT_STOP: 112 handleStop(); 113 break; 114 } 115 } 116 }; 117 } 118 119 /** 120 * Starts the actual playback of the ringtone. Executes on ringtone-thread. 121 */ 122 private void handlePlay(Uri ringtoneUri) { 123 // don't bother with any of this if there is an EVENT_STOP waiting. 124 if (mHandler.hasMessages(EVENT_STOP)) { 125 return; 126 } 127 128 ThreadUtil.checkNotOnMainThread(); 129 Log.i(this, "Play ringtone."); 130 131 if (mRingtone == null) { 132 mRingtone = getRingtone(ringtoneUri); 133 134 // Cancel everything if there is no ringtone. 135 if (mRingtone == null) { 136 handleStop(); 137 return; 138 } 139 } 140 141 handleRepeat(); 142 } 143 144 private void handleRepeat() { 145 if (mRingtone == null) { 146 return; 147 } 148 149 if (mRingtone.isPlaying()) { 150 Log.d(this, "Ringtone already playing."); 151 } else { 152 mRingtone.play(); 153 Log.i(this, "Repeat ringtone."); 154 } 155 156 // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}. 157 synchronized(this) { 158 if (!mHandler.hasMessages(EVENT_REPEAT)) { 159 mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS); 160 } 161 } 162 } 163 164 /** 165 * Stops the playback of the ringtone. Executes on the ringtone-thread. 166 */ 167 private void handleStop() { 168 ThreadUtil.checkNotOnMainThread(); 169 Log.i(this, "Stop ringtone."); 170 171 if (mRingtone != null) { 172 Log.d(this, "Ringtone.stop() invoked."); 173 mRingtone.stop(); 174 mRingtone = null; 175 } 176 177 synchronized(this) { 178 // At the time that STOP is handled, there should be no need for repeat messages in the 179 // queue. 180 mHandler.removeMessages(EVENT_REPEAT); 181 182 if (mHandler.hasMessages(EVENT_PLAY)) { 183 Log.v(this, "Keeping alive ringtone thread for subsequent play request."); 184 } else { 185 mHandler.removeMessages(EVENT_STOP); 186 mHandler.getLooper().quitSafely(); 187 mHandler = null; 188 Log.v(this, "Handler cleared."); 189 } 190 } 191 } 192 193 private Ringtone getRingtone(Uri ringtoneUri) { 194 if (ringtoneUri == null) { 195 ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI; 196 } 197 198 Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri); 199 if (ringtone != null) { 200 ringtone.setStreamType(AudioManager.STREAM_RING); 201 } 202 return ringtone; 203 } 204} 205