blob: a8824907a79132dcb57f488ab7404f205a73bfd7 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.phone;
import android.content.Context;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.SystemVibrator;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.telephony.Phone;
/**
* Ringer manager for the Phone app.
*/
public class Ringer {
private static final String LOG_TAG = "Ringer";
private static final boolean DBG =
(PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
private static final int PLAY_RING_ONCE = 1;
private static final int STOP_RING = 3;
private static final int VIBRATE_LENGTH = 1000; // ms
private static final int PAUSE_LENGTH = 1000; // ms
/** The singleton instance. */
private static Ringer sInstance;
// Uri for the ringtone.
Uri mCustomRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
Ringtone mRingtone;
Vibrator mVibrator;
IPowerManager mPowerManager;
volatile boolean mContinueVibrating;
VibratorThread mVibratorThread;
Context mContext;
private Worker mRingThread;
private Handler mRingHandler;
private long mFirstRingEventTime = -1;
private long mFirstRingStartTime = -1;
/**
* Initialize the singleton Ringer instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
/* package */ static Ringer init(Context context) {
synchronized (Ringer.class) {
if (sInstance == null) {
sInstance = new Ringer(context);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
return sInstance;
}
}
/** Private constructor; @see init() */
private Ringer(Context context) {
mContext = context;
mPowerManager = IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
// We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
// vibrator object will be isolated from others.
mVibrator = new SystemVibrator(context);
}
/**
* After a radio technology change, e.g. from CDMA to GSM or vice versa,
* the Context of the Ringer has to be updated. This is done by that function.
*
* @parameter Phone, the new active phone for the appropriate radio
* technology
*/
void updateRingerContextAfterRadioTechnologyChange(Phone phone) {
if(DBG) Log.d(LOG_TAG, "updateRingerContextAfterRadioTechnologyChange...");
mContext = phone.getContext();
}
/**
* @return true if we're playing a ringtone and/or vibrating
* to indicate that there's an incoming call.
* ("Ringing" here is used in the general sense. If you literally
* need to know if we're playing a ringtone or vibrating, use
* isRingtonePlaying() or isVibrating() instead.)
*
* @see isVibrating
* @see isRingtonePlaying
*/
boolean isRinging() {
synchronized (this) {
return (isRingtonePlaying() || isVibrating());
}
}
/**
* @return true if the ringtone is playing
* @see isVibrating
* @see isRinging
*/
private boolean isRingtonePlaying() {
synchronized (this) {
return (mRingtone != null && mRingtone.isPlaying()) ||
(mRingHandler != null && mRingHandler.hasMessages(PLAY_RING_ONCE));
}
}
/**
* @return true if we're vibrating in response to an incoming call
* @see isVibrating
* @see isRinging
*/
private boolean isVibrating() {
synchronized (this) {
return (mVibratorThread != null);
}
}
/**
* Starts the ringtone and/or vibrator
*/
void ring() {
if (DBG) log("ring()...");
synchronized (this) {
try {
if (PhoneGlobals.getInstance().showBluetoothIndication()) {
mPowerManager.setAttentionLight(true, 0x000000ff);
} else {
mPowerManager.setAttentionLight(true, 0x00ffffff);
}
} catch (RemoteException ex) {
// the other end of this binder call is in the system process.
}
if (shouldVibrate() && mVibratorThread == null) {
mContinueVibrating = true;
mVibratorThread = new VibratorThread();
if (DBG) log("- starting vibrator...");
mVibratorThread.start();
}
AudioManager audioManager =
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
if (DBG) log("skipping ring because volume is zero");
return;
}
makeLooper();
if (mFirstRingEventTime < 0) {
mFirstRingEventTime = SystemClock.elapsedRealtime();
mRingHandler.sendEmptyMessage(PLAY_RING_ONCE);
} else {
// For repeat rings, figure out by how much to delay
// the ring so that it happens the correct amount of
// time after the previous ring
if (mFirstRingStartTime > 0) {
// Delay subsequent rings by the delta between event
// and play time of the first ring
if (DBG) {
log("delaying ring by " + (mFirstRingStartTime - mFirstRingEventTime));
}
mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE,
mFirstRingStartTime - mFirstRingEventTime);
} else {
// We've gotten two ring events so far, but the ring
// still hasn't started. Reset the event time to the
// time of this event to maintain correct spacing.
mFirstRingEventTime = SystemClock.elapsedRealtime();
}
}
}
}
boolean shouldVibrate() {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int ringerMode = audioManager.getRingerMode();
if (CallFeaturesSetting.getVibrateWhenRinging(mContext)) {
return ringerMode != AudioManager.RINGER_MODE_SILENT;
} else {
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
}
}
/**
* Stops the ringtone and/or vibrator if any of these are actually
* ringing/vibrating.
*/
void stopRing() {
synchronized (this) {
if (DBG) log("stopRing()...");
try {
mPowerManager.setAttentionLight(false, 0x00000000);
} catch (RemoteException ex) {
// the other end of this binder call is in the system process.
}
if (mRingHandler != null) {
mRingHandler.removeCallbacksAndMessages(null);
Message msg = mRingHandler.obtainMessage(STOP_RING);
msg.obj = mRingtone;
mRingHandler.sendMessage(msg);
PhoneUtils.setAudioMode();
mRingThread = null;
mRingHandler = null;
mRingtone = null;
mFirstRingEventTime = -1;
mFirstRingStartTime = -1;
} else {
if (DBG) log("- stopRing: null mRingHandler!");
}
if (mVibratorThread != null) {
if (DBG) log("- stopRing: cleaning up vibrator thread...");
mContinueVibrating = false;
mVibratorThread = null;
}
// Also immediately cancel any vibration in progress.
mVibrator.cancel();
}
}
private class VibratorThread extends Thread {
public void run() {
while (mContinueVibrating) {
mVibrator.vibrate(VIBRATE_LENGTH);
SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH);
}
}
}
private class Worker implements Runnable {
private final Object mLock = new Object();
private Looper mLooper;
Worker(String name) {
Thread t = new Thread(null, this, name);
t.start();
synchronized (mLock) {
while (mLooper == null) {
try {
mLock.wait();
} catch (InterruptedException ex) {
}
}
}
}
public Looper getLooper() {
return mLooper;
}
public void run() {
synchronized (mLock) {
Looper.prepare();
mLooper = Looper.myLooper();
mLock.notifyAll();
}
Looper.loop();
}
public void quit() {
mLooper.quit();
}
}
/**
* Sets the ringtone uri in preparation for ringtone creation
* in makeLooper(). This uri is defaulted to the phone-wide
* default ringtone.
*/
void setCustomRingtoneUri (Uri uri) {
if (uri != null) {
mCustomRingtoneUri = uri;
}
}
private void makeLooper() {
if (mRingThread == null) {
mRingThread = new Worker("ringer");
mRingHandler = new Handler(mRingThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Ringtone r = null;
switch (msg.what) {
case PLAY_RING_ONCE:
if (DBG) log("mRingHandler: PLAY_RING_ONCE...");
if (mRingtone == null && !hasMessages(STOP_RING)) {
// create the ringtone with the uri
if (DBG) log("creating ringtone: " + mCustomRingtoneUri);
r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri);
synchronized (Ringer.this) {
if (!hasMessages(STOP_RING)) {
mRingtone = r;
}
}
}
r = mRingtone;
if (r != null && !hasMessages(STOP_RING) && !r.isPlaying()) {
PhoneUtils.setAudioMode();
r.play();
synchronized (Ringer.this) {
if (mFirstRingStartTime < 0) {
mFirstRingStartTime = SystemClock.elapsedRealtime();
}
}
}
break;
case STOP_RING:
if (DBG) log("mRingHandler: STOP_RING...");
r = (Ringtone) msg.obj;
if (r != null) {
r.stop();
} else {
if (DBG) log("- STOP_RING with null ringtone! msg = " + msg);
}
getLooper().quit();
break;
}
}
};
}
}
private static void log(String msg) {
Log.d(LOG_TAG, msg);
}
}