Home | History | Annotate | Download | only in phone
      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 
     17 package com.android.phone;
     18 
     19 import android.content.Context;
     20 import android.media.AudioManager;
     21 import android.media.Ringtone;
     22 import android.media.RingtoneManager;
     23 import android.net.Uri;
     24 import android.os.Handler;
     25 import android.os.IPowerManager;
     26 import android.os.Looper;
     27 import android.os.Message;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.os.SystemClock;
     31 import android.os.SystemProperties;
     32 import android.os.Vibrator;
     33 import android.util.Log;
     34 
     35 import com.android.internal.telephony.Phone;
     36 /**
     37  * Ringer manager for the Phone app.
     38  */
     39 public 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