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