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