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