Ringer.java revision 0fd72beba9fbbdbdbb516ce3ea0eb707818ab748
1/*
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.Looper;
26import android.os.Message;
27import android.os.SystemClock;
28import android.os.SystemProperties;
29import android.os.Vibrator;
30import android.util.Log;
31
32import com.android.internal.telephony.Phone;
33
34/**
35 * Ringer manager for the Phone app.
36 */
37public class Ringer {
38    private static final String TAG = PhoneApp.LOG_TAG;
39
40    // Enable debug logging for userdebug builds.
41    private static final boolean DBG =
42            (SystemProperties.getInt("ro.debuggable", 0) == 1);
43
44    private static final int PLAY_RING_ONCE = 1;
45    private static final int STOP_RING = 3;
46
47    private static final int VIBRATE_LENGTH = 1000; // ms
48    private static final int PAUSE_LENGTH = 1000; // ms
49
50    // Uri for the ringtone.
51    Uri mCustomRingtoneUri;
52
53    Ringtone mRingtone;
54    Vibrator mVibrator = new Vibrator();
55    volatile boolean mContinueVibrating;
56    VibratorThread mVibratorThread;
57    Context mContext;
58    private Worker mRingThread;
59    private Handler mRingHandler;
60    private boolean mRingPending;
61    private long mFirstRingEventTime = -1;
62    private long mFirstRingStartTime = -1;
63
64    Ringer(Phone phone) {
65        mContext = phone.getContext();
66    }
67
68    /**
69     * @return true if we're playing a ringtone and/or vibrating
70     *     to indicate that there's an incoming call.
71     *     ("Ringing" here is used in the general sense.  If you literally
72     *     need to know if we're playing a ringtone or vibrating, use
73     *     isRingtonePlaying() or isVibrating() instead.)
74     *
75     * @see isVibrating
76     * @see isRingtonePlaying
77     */
78    boolean isRinging() {
79        synchronized (this) {
80            return (isRingtonePlaying() || isVibrating());
81        }
82    }
83
84    /**
85     * @return true if the ringtone is playing
86     * @see isVibrating
87     * @see isRinging
88     */
89    private boolean isRingtonePlaying() {
90        synchronized (this) {
91            return (mRingtone != null && mRingtone.isPlaying()) ||
92                    (mRingHandler != null && mRingHandler.hasMessages(PLAY_RING_ONCE));
93        }
94    }
95
96    /**
97     * @return true if we're vibrating in response to an incoming call
98     * @see isVibrating
99     * @see isRinging
100     */
101    private boolean isVibrating() {
102        synchronized (this) {
103            return (mVibratorThread != null);
104        }
105    }
106
107    /**
108     * Starts the ringtone and/or vibrator
109     */
110    void ring() {
111        if (DBG) log("ring()...");
112
113        synchronized (this) {
114            if (shouldVibrate() && mVibratorThread == null) {
115                mContinueVibrating = true;
116                mVibratorThread = new VibratorThread();
117                if (DBG) log("- starting vibrator...");
118                mVibratorThread.start();
119            }
120            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
121
122            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
123                if (DBG) log("skipping ring because volume is zero");
124                return;
125            }
126
127            if (!isRingtonePlaying() && !mRingPending) {
128                makeLooper();
129                mRingHandler.removeCallbacksAndMessages(null);
130                mRingPending = true;
131                if (mFirstRingEventTime < 0) {
132                    mFirstRingEventTime = SystemClock.elapsedRealtime();
133                    mRingHandler.sendEmptyMessage(PLAY_RING_ONCE);
134                } else {
135                    // For repeat rings, figure out by how much to delay
136                    // the ring so that it happens the correct amount of
137                    // time after the previous ring
138                    if (mFirstRingStartTime > 0) {
139                        // Delay subsequent rings by the delta between event
140                        // and play time of the first ring
141                        if (DBG) {
142                            log("delaying ring by " + (mFirstRingStartTime - mFirstRingEventTime));
143                        }
144                        mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE,
145                                mFirstRingStartTime - mFirstRingEventTime);
146                    } else {
147                        // We've gotten two ring events so far, but the ring
148                        // still hasn't started. Reset the event time to the
149                        // time of this event to maintain correct spacing.
150                        mFirstRingEventTime = SystemClock.elapsedRealtime();
151                    }
152                }
153            } else {
154                if (DBG) log("skipping ring because one is playing or pending: " + mRingtone + "/" + mRingHandler);
155            }
156        }
157    }
158
159    boolean shouldVibrate() {
160        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
161        return audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER);
162    }
163
164    /**
165     * Stops the ringtone and/or vibrator if any of these are actually
166     * ringing/vibrating.
167     */
168    void stopRing() {
169        synchronized (this) {
170            if (DBG) log("stopRing()...");
171
172            if (mRingHandler != null) {
173                mRingHandler.removeCallbacksAndMessages(null);
174                Message msg = mRingHandler.obtainMessage(STOP_RING);
175                msg.obj = mRingtone;
176                mRingHandler.sendMessage(msg);
177                PhoneUtils.setAudioMode(mContext, AudioManager.MODE_NORMAL);
178                mRingThread = null;
179                mRingHandler = null;
180                mRingtone = null;
181                mFirstRingEventTime = -1;
182                mFirstRingStartTime = -1;
183                mRingPending = false;
184            } else {
185                if (DBG) log("- stopRing: null mRingHandler!");
186            }
187
188            if (mVibratorThread != null) {
189                if (DBG) log("- stopRing: cleaning up vibrator thread...");
190                mContinueVibrating = false;
191                mVibratorThread = null;
192            }
193            // Also immediately cancel any vibration in progress.
194            mVibrator.cancel();
195        }
196    }
197
198    private class VibratorThread extends Thread {
199        public void run() {
200            while (mContinueVibrating) {
201                mVibrator.vibrate(VIBRATE_LENGTH);
202                SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH);
203            }
204        }
205    }
206    private class Worker implements Runnable {
207        private final Object mLock = new Object();
208        private Looper mLooper;
209
210        Worker(String name) {
211            Thread t = new Thread(null, this, name);
212            t.start();
213            synchronized (mLock) {
214                while (mLooper == null) {
215                    try {
216                        mLock.wait();
217                    } catch (InterruptedException ex) {
218                    }
219                }
220            }
221        }
222
223        public Looper getLooper() {
224            return mLooper;
225        }
226
227        public void run() {
228            synchronized (mLock) {
229                Looper.prepare();
230                mLooper = Looper.myLooper();
231                mLock.notifyAll();
232            }
233            Looper.loop();
234        }
235
236        public void quit() {
237            mLooper.quit();
238        }
239    }
240
241    /**
242     * set the ringtone uri in preparation for ringtone creation
243     * in makeLooper().  This uri is defaulted to the phone-wide
244     * default ringtone.
245     */
246    void setCustomRingtoneUri (Uri uri) {
247        if (uri != null) {
248            mCustomRingtoneUri = uri;
249        }
250    }
251
252    private void makeLooper() {
253        if (mRingThread == null) {
254            mRingThread = new Worker("ringer");
255            mRingHandler = new Handler(mRingThread.getLooper()) {
256                @Override
257                public void handleMessage(Message msg) {
258                    Ringtone r = null;
259                    switch (msg.what) {
260                        case PLAY_RING_ONCE:
261                            if (DBG) log("mRingHandler: PLAY_RING_ONCE...");
262                            if (mRingtone == null && ! hasMessages(STOP_RING)) {
263                                // create the ringtone with the uri
264                                if (DBG) log("creating ringtone with uri " + mCustomRingtoneUri);
265                                r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri);
266                                synchronized (Ringer.this) {
267                                    if (! hasMessages(STOP_RING)) {
268                                        mRingtone = r;
269                                    }
270                                }
271                            }
272                            r = mRingtone;
273                            if (r != null && ! hasMessages(STOP_RING)) {
274                                PhoneUtils.setAudioMode(mContext, AudioManager.MODE_RINGTONE);
275                                r.play();
276                                synchronized (Ringer.this) {
277                                    mRingPending = false;
278                                    if (mFirstRingStartTime < 0) {
279                                        mFirstRingStartTime = SystemClock.elapsedRealtime();
280                                    }
281                                }
282                            }
283                            break;
284                        case STOP_RING:
285                            if (DBG) log("mRingHandler: STOP_RING...");
286                            r = (Ringtone) msg.obj;
287                            if (r != null) {
288                                r.stop();
289                            } else {
290                                if (DBG) log("- STOP_RING with null ringtone!  msg = " + msg);
291                            }
292                            getLooper().quit();
293                            break;
294                    }
295                }
296            };
297        }
298    }
299
300    private static void log(String msg) {
301        Log.d(TAG, "[Ringer] " + msg);
302    }
303}
304