/** * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr) * This file is part of CSipSimple. * * CSipSimple is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * If you own a pjsip commercial license you can also redistribute it * and/or modify it under the terms of the GNU Lesser General Public License * as an android library. * * CSipSimple is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CSipSimple. If not, see <http://www.gnu.org/licenses/>. */ /** * This file contains relicensed code from Apache copyright of * Copyright (C) 2006 The Android Open Source Project */ package com.csipsimple.utils; 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.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Vibrator; import com.csipsimple.models.CallerInfo; /** * Ringer manager for the Phone app. */ public class Ringer { private static final String THIS_FILE = "Ringer"; private static final int VIBRATE_LENGTH = 1000; // ms private static final int PAUSE_LENGTH = 1000; // ms // Uri for the ringtone. Uri customRingtoneUri; Vibrator vibrator; VibratorThread vibratorThread; HandlerThread ringerThread; Context context; private RingWorkerHandler ringerWorker; public Ringer(Context aContext) { context = aContext; vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); ringerThread = new HandlerThread("RingerThread"); ringerThread.start(); ringerWorker = new RingWorkerHandler(ringerThread.getLooper()); } /** * Starts the ringtone and/or vibrator. * */ public void ring(String remoteContact, String defaultRingtone) { Log.d(THIS_FILE, "==> ring() called..."); synchronized (this) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); //Save ringtone at the begining in case we raise vol Ringtone ringtone = getRingtone(remoteContact, defaultRingtone); ringerWorker.setRingtone(ringtone); //No ring no vibrate int ringerMode = audioManager.getRingerMode(); if (ringerMode == AudioManager.RINGER_MODE_SILENT) { Log.d(THIS_FILE, "skipping ring and vibrate because profile is Silent"); return; } // Vibrate int vibrateSetting = audioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); Log.d(THIS_FILE, "v=" + vibrateSetting + " rm=" + ringerMode); if (vibratorThread == null && (vibrateSetting == AudioManager.VIBRATE_SETTING_ON || ringerMode == AudioManager.RINGER_MODE_VIBRATE)) { vibratorThread = new VibratorThread(); Log.d(THIS_FILE, "Starting vibrator..."); vibratorThread.start(); } // Vibrate only if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0 ) { Log.d(THIS_FILE, "skipping ring because profile is Vibrate OR because volume is zero"); return; } // Ringer normal, audio set for ring, do it if(ringtone == null) { Log.d(THIS_FILE, "No ringtone available - do not ring"); return; } ringerWorker.startRinging(audioManager); } } /** * @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.) */ public boolean isRinging() { return (!ringerWorker.isStopped() || vibratorThread != null); } /** * Stops the ringtone and/or vibrator if any of these are actually * ringing/vibrating. */ public void stopRing() { synchronized (this) { Log.d(THIS_FILE, "==> stopRing() called..."); stopVibrator(); stopRinger(); } } private void stopRinger() { ringerWorker.askStop(); } private void stopVibrator() { if (vibratorThread != null) { vibratorThread.interrupt(); try { vibratorThread.join(250); // Should be plenty long (typ.) } catch (InterruptedException e) { } // Best efforts (typ.) vibratorThread = null; } } public void updateRingerMode() { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); synchronized (this) { int ringerMode = audioManager.getRingerMode(); // Silent : stop everything if (ringerMode == AudioManager.RINGER_MODE_SILENT) { stopRing(); return; } // Vibrate int vibrateSetting = audioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); // If not already started restart it if (vibratorThread == null && (vibrateSetting == AudioManager.VIBRATE_SETTING_ON || ringerMode == AudioManager.RINGER_MODE_VIBRATE)) { vibratorThread = new VibratorThread(); vibratorThread.start(); } // Vibrate only if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) { stopRinger(); return; } //Ringer ringerWorker.startRinging(audioManager); } } private class VibratorThread extends Thread { public void run() { try { while (true) { vibrator.vibrate(VIBRATE_LENGTH); Thread.sleep(VIBRATE_LENGTH + PAUSE_LENGTH); } } catch (InterruptedException ex) { Log.d(THIS_FILE, "Vibrator thread interrupt"); } finally { vibrator.cancel(); } Log.d(THIS_FILE, "Vibrator thread exiting"); } } /** * Thread worker class that handles the task playing ringtone */ private class RingWorkerHandler extends Handler { public static final int PROGRESS_RING = 0; private Boolean askedStopped = false; private Ringtone ringtone = null; public RingWorkerHandler(Looper looper) { super(looper); } /** * @param audioManager * */ public void startRinging(AudioManager audioManager) { if(ringtone != null) { Log.d(THIS_FILE, "Starting ring with " + ringtone.getTitle(context)); Message msg = ringerWorker.obtainMessage(RingWorkerHandler.PROGRESS_RING); msg.arg1 = RingWorkerHandler.PROGRESS_RING; Log.d(THIS_FILE, "Starting ringer..."); audioManager.setMode(AudioManager.MODE_RINGTONE); ringerWorker.sendMessage(msg); } } /** * @param ringtone */ public synchronized void setRingtone(Ringtone ringtone) { if(this.ringtone != null) { this.ringtone.stop(); } this.ringtone = ringtone; askedStopped = false; } public synchronized void askStop() { askedStopped = true; } public synchronized boolean isStopped() { return askedStopped || (ringtone == null); } public void handleMessage(Message msg) { if(ringtone == null) { return; } if (msg.arg1 == PROGRESS_RING) { synchronized (askedStopped) { if(askedStopped) { ringtone.stop(); ringtone = null; return; } } if(!ringtone.isPlaying()) { ringtone.play(); } Message msgBis = ringerWorker.obtainMessage(RingWorkerHandler.PROGRESS_RING); msg.arg1 = RingWorkerHandler.PROGRESS_RING; ringerWorker.sendMessageDelayed(msgBis, 100); } } } private Ringtone getRingtone(String remoteContact, String defaultRingtone) { Uri ringtoneUri = Uri.parse(defaultRingtone); // TODO - Should this be in a separate thread? We would still have to wait for // it to complete, so at present, no. CallerInfo callerInfo = CallerInfo.getCallerInfoFromSipUri(context, remoteContact); if(callerInfo != null && callerInfo.contactExists && callerInfo.contactRingtoneUri != null) { Log.d(THIS_FILE, "Found ringtone for " + callerInfo.name); ringtoneUri = callerInfo.contactRingtoneUri; } return RingtoneManager.getRingtone(context, ringtoneUri); } }