blob: a0096ea4a678dccbb5c7ae2616f1982f40d6daa8 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
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
17package com.android.phone;
18
19import android.content.Context;
20import android.media.AudioManager;
21import android.media.Ringtone;
22import android.media.RingtoneManager;
23import android.net.Uri;
24import android.os.Handler;
25import android.os.IPowerManager;
26import android.os.Looper;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.SystemClock;
31import android.os.SystemProperties;
32import android.os.SystemVibrator;
33import android.os.Vibrator;
34import android.provider.Settings;
35import android.util.Log;
36
37import com.android.internal.telephony.Phone;
38/**
39 * Ringer manager for the Phone app.
40 */
41public 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
Santos Cordon27a3c1f2013-08-06 07:49:27 -070058 private final BluetoothManager mBluetoothManager;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070059 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 */
Santos Cordon27a3c1f2013-08-06 07:49:27 -070074 /* package */ static Ringer init(Context context, BluetoothManager bluetoothManager) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -070075 synchronized (Ringer.class) {
76 if (sInstance == null) {
Santos Cordon27a3c1f2013-08-06 07:49:27 -070077 sInstance = new Ringer(context, bluetoothManager);
Santos Cordon7d4ddf62013-07-10 11:58:08 -070078 } else {
79 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
80 }
81 return sInstance;
82 }
83 }
84
85 /** Private constructor; @see init() */
Santos Cordon27a3c1f2013-08-06 07:49:27 -070086 private Ringer(Context context, BluetoothManager bluetoothManager) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -070087 mContext = context;
Santos Cordon27a3c1f2013-08-06 07:49:27 -070088 mBluetoothManager = bluetoothManager;
89 mPowerManager = IPowerManager.Stub.asInterface(
90 ServiceManager.getService(Context.POWER_SERVICE));
Santos Cordon7d4ddf62013-07-10 11:58:08 -070091 // 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 {
Santos Cordon27a3c1f2013-08-06 07:49:27 -0700155 if (mBluetoothManager.showBluetoothIndication()) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700156 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}