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