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