Blanket copy of PhoneApp to services/Telephony.

First phase of splitting out InCallUI from PhoneApp.

Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/Ringer.java b/src/com/android/phone/Ringer.java
new file mode 100644
index 0000000..a882490
--- /dev/null
+++ b/src/com/android/phone/Ringer.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.SystemVibrator;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+/**
+ * Ringer manager for the Phone app.
+ */
+public class Ringer {
+    private static final String LOG_TAG = "Ringer";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    private static final int PLAY_RING_ONCE = 1;
+    private static final int STOP_RING = 3;
+
+    private static final int VIBRATE_LENGTH = 1000; // ms
+    private static final int PAUSE_LENGTH = 1000; // ms
+
+    /** The singleton instance. */
+    private static Ringer sInstance;
+
+    // Uri for the ringtone.
+    Uri mCustomRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
+
+    Ringtone mRingtone;
+    Vibrator mVibrator;
+    IPowerManager mPowerManager;
+    volatile boolean mContinueVibrating;
+    VibratorThread mVibratorThread;
+    Context mContext;
+    private Worker mRingThread;
+    private Handler mRingHandler;
+    private long mFirstRingEventTime = -1;
+    private long mFirstRingStartTime = -1;
+
+    /**
+     * Initialize the singleton Ringer instance.
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     */
+    /* package */ static Ringer init(Context context) {
+        synchronized (Ringer.class) {
+            if (sInstance == null) {
+                sInstance = new Ringer(context);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /** Private constructor; @see init() */
+    private Ringer(Context context) {
+        mContext = context;
+        mPowerManager = IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
+        // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
+        // vibrator object will be isolated from others.
+        mVibrator = new SystemVibrator(context);
+    }
+
+    /**
+     * After a radio technology change, e.g. from CDMA to GSM or vice versa,
+     * the Context of the Ringer has to be updated. This is done by that function.
+     *
+     * @parameter Phone, the new active phone for the appropriate radio
+     * technology
+     */
+    void updateRingerContextAfterRadioTechnologyChange(Phone phone) {
+        if(DBG) Log.d(LOG_TAG, "updateRingerContextAfterRadioTechnologyChange...");
+        mContext = phone.getContext();
+    }
+
+    /**
+     * @return true if we're playing a ringtone and/or vibrating
+     *     to indicate that there's an incoming call.
+     *     ("Ringing" here is used in the general sense.  If you literally
+     *     need to know if we're playing a ringtone or vibrating, use
+     *     isRingtonePlaying() or isVibrating() instead.)
+     *
+     * @see isVibrating
+     * @see isRingtonePlaying
+     */
+    boolean isRinging() {
+        synchronized (this) {
+            return (isRingtonePlaying() || isVibrating());
+        }
+    }
+
+    /**
+     * @return true if the ringtone is playing
+     * @see isVibrating
+     * @see isRinging
+     */
+    private boolean isRingtonePlaying() {
+        synchronized (this) {
+            return (mRingtone != null && mRingtone.isPlaying()) ||
+                    (mRingHandler != null && mRingHandler.hasMessages(PLAY_RING_ONCE));
+        }
+    }
+
+    /**
+     * @return true if we're vibrating in response to an incoming call
+     * @see isVibrating
+     * @see isRinging
+     */
+    private boolean isVibrating() {
+        synchronized (this) {
+            return (mVibratorThread != null);
+        }
+    }
+
+    /**
+     * Starts the ringtone and/or vibrator
+     */
+    void ring() {
+        if (DBG) log("ring()...");
+
+        synchronized (this) {
+            try {
+                if (PhoneGlobals.getInstance().showBluetoothIndication()) {
+                    mPowerManager.setAttentionLight(true, 0x000000ff);
+                } else {
+                    mPowerManager.setAttentionLight(true, 0x00ffffff);
+                }
+            } catch (RemoteException ex) {
+                // the other end of this binder call is in the system process.
+            }
+
+            if (shouldVibrate() && mVibratorThread == null) {
+                mContinueVibrating = true;
+                mVibratorThread = new VibratorThread();
+                if (DBG) log("- starting vibrator...");
+                mVibratorThread.start();
+            }
+            AudioManager audioManager =
+                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
+                if (DBG) log("skipping ring because volume is zero");
+                return;
+            }
+
+            makeLooper();
+            if (mFirstRingEventTime < 0) {
+                mFirstRingEventTime = SystemClock.elapsedRealtime();
+                mRingHandler.sendEmptyMessage(PLAY_RING_ONCE);
+            } else {
+                // For repeat rings, figure out by how much to delay
+                // the ring so that it happens the correct amount of
+                // time after the previous ring
+                if (mFirstRingStartTime > 0) {
+                    // Delay subsequent rings by the delta between event
+                    // and play time of the first ring
+                    if (DBG) {
+                        log("delaying ring by " + (mFirstRingStartTime - mFirstRingEventTime));
+                    }
+                    mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE,
+                            mFirstRingStartTime - mFirstRingEventTime);
+                } else {
+                    // We've gotten two ring events so far, but the ring
+                    // still hasn't started. Reset the event time to the
+                    // time of this event to maintain correct spacing.
+                    mFirstRingEventTime = SystemClock.elapsedRealtime();
+                }
+            }
+        }
+    }
+
+    boolean shouldVibrate() {
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        int ringerMode = audioManager.getRingerMode();
+        if (CallFeaturesSetting.getVibrateWhenRinging(mContext)) {
+            return ringerMode != AudioManager.RINGER_MODE_SILENT;
+        } else {
+            return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+        }
+    }
+
+    /**
+     * Stops the ringtone and/or vibrator if any of these are actually
+     * ringing/vibrating.
+     */
+    void stopRing() {
+        synchronized (this) {
+            if (DBG) log("stopRing()...");
+
+            try {
+                mPowerManager.setAttentionLight(false, 0x00000000);
+            } catch (RemoteException ex) {
+                // the other end of this binder call is in the system process.
+            }
+
+            if (mRingHandler != null) {
+                mRingHandler.removeCallbacksAndMessages(null);
+                Message msg = mRingHandler.obtainMessage(STOP_RING);
+                msg.obj = mRingtone;
+                mRingHandler.sendMessage(msg);
+                PhoneUtils.setAudioMode();
+                mRingThread = null;
+                mRingHandler = null;
+                mRingtone = null;
+                mFirstRingEventTime = -1;
+                mFirstRingStartTime = -1;
+            } else {
+                if (DBG) log("- stopRing: null mRingHandler!");
+            }
+
+            if (mVibratorThread != null) {
+                if (DBG) log("- stopRing: cleaning up vibrator thread...");
+                mContinueVibrating = false;
+                mVibratorThread = null;
+            }
+            // Also immediately cancel any vibration in progress.
+            mVibrator.cancel();
+        }
+    }
+
+    private class VibratorThread extends Thread {
+        public void run() {
+            while (mContinueVibrating) {
+                mVibrator.vibrate(VIBRATE_LENGTH);
+                SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH);
+            }
+        }
+    }
+    private class Worker implements Runnable {
+        private final Object mLock = new Object();
+        private Looper mLooper;
+
+        Worker(String name) {
+            Thread t = new Thread(null, this, name);
+            t.start();
+            synchronized (mLock) {
+                while (mLooper == null) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException ex) {
+                    }
+                }
+            }
+        }
+
+        public Looper getLooper() {
+            return mLooper;
+        }
+
+        public void run() {
+            synchronized (mLock) {
+                Looper.prepare();
+                mLooper = Looper.myLooper();
+                mLock.notifyAll();
+            }
+            Looper.loop();
+        }
+
+        public void quit() {
+            mLooper.quit();
+        }
+    }
+
+    /**
+     * Sets the ringtone uri in preparation for ringtone creation
+     * in makeLooper().  This uri is defaulted to the phone-wide
+     * default ringtone.
+     */
+    void setCustomRingtoneUri (Uri uri) {
+        if (uri != null) {
+            mCustomRingtoneUri = uri;
+        }
+    }
+
+    private void makeLooper() {
+        if (mRingThread == null) {
+            mRingThread = new Worker("ringer");
+            mRingHandler = new Handler(mRingThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    Ringtone r = null;
+                    switch (msg.what) {
+                        case PLAY_RING_ONCE:
+                            if (DBG) log("mRingHandler: PLAY_RING_ONCE...");
+                            if (mRingtone == null && !hasMessages(STOP_RING)) {
+                                // create the ringtone with the uri
+                                if (DBG) log("creating ringtone: " + mCustomRingtoneUri);
+                                r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri);
+                                synchronized (Ringer.this) {
+                                    if (!hasMessages(STOP_RING)) {
+                                        mRingtone = r;
+                                    }
+                                }
+                            }
+                            r = mRingtone;
+                            if (r != null && !hasMessages(STOP_RING) && !r.isPlaying()) {
+                                PhoneUtils.setAudioMode();
+                                r.play();
+                                synchronized (Ringer.this) {
+                                    if (mFirstRingStartTime < 0) {
+                                        mFirstRingStartTime = SystemClock.elapsedRealtime();
+                                    }
+                                }
+                            }
+                            break;
+                        case STOP_RING:
+                            if (DBG) log("mRingHandler: STOP_RING...");
+                            r = (Ringtone) msg.obj;
+                            if (r != null) {
+                                r.stop();
+                            } else {
+                                if (DBG) log("- STOP_RING with null ringtone!  msg = " + msg);
+                            }
+                            getLooper().quit();
+                            break;
+                    }
+                }
+            };
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}