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