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