Ringer.java revision c51f5ac3de9db72a11dfbb1cefd8cc72f7a0fc7c
1/* 2 * Copyright (C) 2006 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.phone; 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.IHardwareService; 26import android.os.Looper; 27import android.os.Message; 28import android.os.RemoteException; 29import android.os.ServiceManager; 30import android.os.SystemClock; 31import android.os.SystemProperties; 32import android.os.Vibrator; 33import android.util.Log; 34 35import com.android.internal.telephony.Phone; 36 37/** 38 * Ringer manager for the Phone app. 39 */ 40public class Ringer { 41 private static final String LOG_TAG = "Ringer"; 42 private static final boolean DBG = 43 (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 44 45 private static final int PLAY_RING_ONCE = 1; 46 private static final int STOP_RING = 3; 47 48 private static final int VIBRATE_LENGTH = 1000; // ms 49 private static final int PAUSE_LENGTH = 1000; // ms 50 51 // Uri for the ringtone. 52 Uri mCustomRingtoneUri; 53 54 Ringtone mRingtone; 55 Vibrator mVibrator = new Vibrator(); 56 IHardwareService mHardwareService; 57 volatile boolean mContinueVibrating; 58 VibratorThread mVibratorThread; 59 Context mContext; 60 private Worker mRingThread; 61 private Handler mRingHandler; 62 private boolean mRingPending; 63 private long mFirstRingEventTime = -1; 64 private long mFirstRingStartTime = -1; 65 66 Ringer(Phone phone) { 67 mContext = phone.getContext(); 68 mHardwareService = IHardwareService.Stub.asInterface(ServiceManager.getService("hardware")); 69 } 70 71 /** 72 * After a radio technology change, e.g. from CDMA to GSM or vice versa, 73 * the Context of the Ringer has to be updated. This is done by that function. 74 * 75 * @parameter Phone, the new active phone for the appropriate radio 76 * technology 77 */ 78 void updateRingerContextAfterRadioTechnologyChange(Phone phone) { 79 if(DBG) Log.d(LOG_TAG, "updateRingerContextAfterRadioTechnologyChange..."); 80 mContext = phone.getContext(); 81 } 82 83 /** 84 * @return true if we're playing a ringtone and/or vibrating 85 * to indicate that there's an incoming call. 86 * ("Ringing" here is used in the general sense. If you literally 87 * need to know if we're playing a ringtone or vibrating, use 88 * isRingtonePlaying() or isVibrating() instead.) 89 * 90 * @see isVibrating 91 * @see isRingtonePlaying 92 */ 93 boolean isRinging() { 94 synchronized (this) { 95 return (isRingtonePlaying() || isVibrating()); 96 } 97 } 98 99 /** 100 * @return true if the ringtone is playing 101 * @see isVibrating 102 * @see isRinging 103 */ 104 private boolean isRingtonePlaying() { 105 synchronized (this) { 106 return (mRingtone != null && mRingtone.isPlaying()) || 107 (mRingHandler != null && mRingHandler.hasMessages(PLAY_RING_ONCE)); 108 } 109 } 110 111 /** 112 * @return true if we're vibrating in response to an incoming call 113 * @see isVibrating 114 * @see isRinging 115 */ 116 private boolean isVibrating() { 117 synchronized (this) { 118 return (mVibratorThread != null); 119 } 120 } 121 122 /** 123 * Starts the ringtone and/or vibrator 124 */ 125 void ring() { 126 if (DBG) log("ring()..."); 127 128 synchronized (this) { 129 try { 130 mHardwareService.setAttentionLight(true); 131 } catch (RemoteException ex) { 132 // the other end of this binder call is in the system process. 133 } 134 135 if (shouldVibrate() && mVibratorThread == null) { 136 mContinueVibrating = true; 137 mVibratorThread = new VibratorThread(); 138 if (DBG) log("- starting vibrator..."); 139 mVibratorThread.start(); 140 } 141 AudioManager audioManager = 142 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 143 144 if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) { 145 if (DBG) log("skipping ring because volume is zero"); 146 return; 147 } 148 149 if (!isRingtonePlaying() && !mRingPending) { 150 makeLooper(); 151 mRingHandler.removeCallbacksAndMessages(null); 152 mRingPending = true; 153 if (mFirstRingEventTime < 0) { 154 mFirstRingEventTime = SystemClock.elapsedRealtime(); 155 mRingHandler.sendEmptyMessage(PLAY_RING_ONCE); 156 } else { 157 // For repeat rings, figure out by how much to delay 158 // the ring so that it happens the correct amount of 159 // time after the previous ring 160 if (mFirstRingStartTime > 0) { 161 // Delay subsequent rings by the delta between event 162 // and play time of the first ring 163 if (DBG) { 164 log("delaying ring by " + (mFirstRingStartTime - mFirstRingEventTime)); 165 } 166 mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE, 167 mFirstRingStartTime - mFirstRingEventTime); 168 } else { 169 // We've gotten two ring events so far, but the ring 170 // still hasn't started. Reset the event time to the 171 // time of this event to maintain correct spacing. 172 mFirstRingEventTime = SystemClock.elapsedRealtime(); 173 } 174 } 175 } else { 176 if (DBG) log("skipping ring, already playing or pending: " 177 + mRingtone + "/" + mRingHandler); 178 } 179 } 180 } 181 182 boolean shouldVibrate() { 183 AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 184 return audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER); 185 } 186 187 /** 188 * Stops the ringtone and/or vibrator if any of these are actually 189 * ringing/vibrating. 190 */ 191 void stopRing() { 192 synchronized (this) { 193 if (DBG) log("stopRing()..."); 194 195 try { 196 mHardwareService.setAttentionLight(false); 197 } catch (RemoteException ex) { 198 // the other end of this binder call is in the system process. 199 } 200 201 if (mRingHandler != null) { 202 mRingHandler.removeCallbacksAndMessages(null); 203 Message msg = mRingHandler.obtainMessage(STOP_RING); 204 msg.obj = mRingtone; 205 mRingHandler.sendMessage(msg); 206 PhoneUtils.setAudioMode(mContext, AudioManager.MODE_NORMAL); 207 mRingThread = null; 208 mRingHandler = null; 209 mRingtone = null; 210 mFirstRingEventTime = -1; 211 mFirstRingStartTime = -1; 212 mRingPending = false; 213 } else { 214 if (DBG) log("- stopRing: null mRingHandler!"); 215 } 216 217 if (mVibratorThread != null) { 218 if (DBG) log("- stopRing: cleaning up vibrator thread..."); 219 mContinueVibrating = false; 220 mVibratorThread = null; 221 } 222 // Also immediately cancel any vibration in progress. 223 mVibrator.cancel(); 224 } 225 } 226 227 private class VibratorThread extends Thread { 228 public void run() { 229 while (mContinueVibrating) { 230 mVibrator.vibrate(VIBRATE_LENGTH); 231 SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH); 232 } 233 } 234 } 235 private class Worker implements Runnable { 236 private final Object mLock = new Object(); 237 private Looper mLooper; 238 239 Worker(String name) { 240 Thread t = new Thread(null, this, name); 241 t.start(); 242 synchronized (mLock) { 243 while (mLooper == null) { 244 try { 245 mLock.wait(); 246 } catch (InterruptedException ex) { 247 } 248 } 249 } 250 } 251 252 public Looper getLooper() { 253 return mLooper; 254 } 255 256 public void run() { 257 synchronized (mLock) { 258 Looper.prepare(); 259 mLooper = Looper.myLooper(); 260 mLock.notifyAll(); 261 } 262 Looper.loop(); 263 } 264 265 public void quit() { 266 mLooper.quit(); 267 } 268 } 269 270 /** 271 * Sets the ringtone uri in preparation for ringtone creation 272 * in makeLooper(). This uri is defaulted to the phone-wide 273 * default ringtone. 274 */ 275 void setCustomRingtoneUri (Uri uri) { 276 if (uri != null) { 277 mCustomRingtoneUri = uri; 278 } 279 } 280 281 private void makeLooper() { 282 if (mRingThread == null) { 283 mRingThread = new Worker("ringer"); 284 mRingHandler = new Handler(mRingThread.getLooper()) { 285 @Override 286 public void handleMessage(Message msg) { 287 Ringtone r = null; 288 switch (msg.what) { 289 case PLAY_RING_ONCE: 290 if (DBG) log("mRingHandler: PLAY_RING_ONCE..."); 291 if (mRingtone == null && !hasMessages(STOP_RING)) { 292 // create the ringtone with the uri 293 if (DBG) log("creating ringtone: " + mCustomRingtoneUri); 294 r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri); 295 synchronized (Ringer.this) { 296 if (!hasMessages(STOP_RING)) { 297 mRingtone = r; 298 } 299 } 300 } 301 r = mRingtone; 302 if (r != null && !hasMessages(STOP_RING)) { 303 PhoneUtils.setAudioMode(mContext, AudioManager.MODE_RINGTONE); 304 r.play(); 305 synchronized (Ringer.this) { 306 mRingPending = false; 307 if (mFirstRingStartTime < 0) { 308 mFirstRingStartTime = SystemClock.elapsedRealtime(); 309 } 310 } 311 } 312 break; 313 case STOP_RING: 314 if (DBG) log("mRingHandler: STOP_RING..."); 315 r = (Ringtone) msg.obj; 316 if (r != null) { 317 r.stop(); 318 } else { 319 if (DBG) log("- STOP_RING with null ringtone! msg = " + msg); 320 } 321 getLooper().quit(); 322 break; 323 } 324 } 325 }; 326 } 327 } 328 329 private static void log(String msg) { 330 Log.d(LOG_TAG, msg); 331 } 332} 333