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