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