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