summaryrefslogtreecommitdiff
path: root/src/com/android/phone/EmergencyCallHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/phone/EmergencyCallHelper.java')
-rw-r--r--src/com/android/phone/EmergencyCallHelper.java545
1 files changed, 0 insertions, 545 deletions
diff --git a/src/com/android/phone/EmergencyCallHelper.java b/src/com/android/phone/EmergencyCallHelper.java
deleted file mode 100644
index 7f5b0d2c..00000000
--- a/src/com/android/phone/EmergencyCallHelper.java
+++ /dev/null
@@ -1,545 +0,0 @@
-/*
- * Copyright (C) 2011 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 com.android.internal.telephony.CallManager;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.phone.Constants.CallStatusCode;
-import com.android.phone.InCallUiState.ProgressIndicationType;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.telephony.ServiceState;
-import android.util.Log;
-
-
-/**
- * Helper class for the {@link CallController} that implements special
- * behavior related to emergency calls. Specifically, this class handles
- * the case of the user trying to dial an emergency number while the radio
- * is off (i.e. the device is in airplane mode), by forcibly turning the
- * radio back on, waiting for it to come up, and then retrying the
- * emergency call.
- *
- * This class is instantiated lazily (the first time the user attempts to
- * make an emergency call from airplane mode) by the the
- * {@link CallController} singleton.
- */
-public class EmergencyCallHelper extends Handler {
- private static final String TAG = "EmergencyCallHelper";
- private static final boolean DBG = false;
-
- // Number of times to retry the call, and time between retry attempts.
- public static final int MAX_NUM_RETRIES = 6;
- public static final long TIME_BETWEEN_RETRIES = 5000; // msec
-
- // Timeout used with our wake lock (just as a safety valve to make
- // sure we don't hold it forever).
- public static final long WAKE_LOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes in msec
-
- // Handler message codes; see handleMessage()
- private static final int START_SEQUENCE = 1;
- private static final int SERVICE_STATE_CHANGED = 2;
- private static final int DISCONNECT = 3;
- private static final int RETRY_TIMEOUT = 4;
-
- private CallController mCallController;
- private PhoneGlobals mApp;
- private CallManager mCM;
- private Phone mPhone;
- private String mNumber; // The emergency number we're trying to dial
- private int mNumRetriesSoFar;
-
- // Wake lock we hold while running the whole sequence
- private PowerManager.WakeLock mPartialWakeLock;
-
- public EmergencyCallHelper(CallController callController) {
- if (DBG) log("EmergencyCallHelper constructor...");
- mCallController = callController;
- mApp = PhoneGlobals.getInstance();
- mCM = mApp.mCM;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case START_SEQUENCE:
- startSequenceInternal(msg);
- break;
- case SERVICE_STATE_CHANGED:
- onServiceStateChanged(msg);
- break;
- case DISCONNECT:
- onDisconnect(msg);
- break;
- case RETRY_TIMEOUT:
- onRetryTimeout();
- break;
- default:
- Log.wtf(TAG, "handleMessage: unexpected message: " + msg);
- break;
- }
- }
-
- /**
- * Starts the "emergency call from airplane mode" sequence.
- *
- * This is the (single) external API of the EmergencyCallHelper class.
- * This method is called from the CallController placeCall() sequence
- * if the user dials a valid emergency number, but the radio is
- * powered-off (presumably due to airplane mode.)
- *
- * This method kicks off the following sequence:
- * - Power on the radio
- * - Listen for the service state change event telling us the radio has come up
- * - Then launch the emergency call
- * - Retry if the call fails with an OUT_OF_SERVICE error
- * - Retry if we've gone 5 seconds without any response from the radio
- * - Finally, clean up any leftover state (progress UI, wake locks, etc.)
- *
- * This method is safe to call from any thread, since it simply posts
- * a message to the EmergencyCallHelper's handler (thus ensuring that
- * the rest of the sequence is entirely serialized, and runs only on
- * the handler thread.)
- *
- * This method does *not* force the in-call UI to come up; our caller
- * is responsible for doing that (presumably by calling
- * PhoneApp.displayCallScreen().)
- */
- public void startEmergencyCallFromAirplaneModeSequence(String number) {
- if (DBG) log("startEmergencyCallFromAirplaneModeSequence('" + number + "')...");
- Message msg = obtainMessage(START_SEQUENCE, number);
- sendMessage(msg);
- }
-
- /**
- * Actual implementation of startEmergencyCallFromAirplaneModeSequence(),
- * guaranteed to run on the handler thread.
- * @see startEmergencyCallFromAirplaneModeSequence()
- */
- private void startSequenceInternal(Message msg) {
- if (DBG) log("startSequenceInternal(): msg = " + msg);
-
- // First of all, clean up any state (including mPartialWakeLock!)
- // left over from a prior emergency call sequence.
- // This ensures that we'll behave sanely if another
- // startEmergencyCallFromAirplaneModeSequence() comes in while
- // we're already in the middle of the sequence.
- cleanup();
-
- mNumber = (String) msg.obj;
- if (DBG) log("- startSequenceInternal: Got mNumber: '" + mNumber + "'");
-
- mNumRetriesSoFar = 0;
-
- // Reset mPhone to whatever the current default phone is right now.
- mPhone = mApp.mCM.getDefaultPhone();
-
- // Wake lock to make sure the processor doesn't go to sleep midway
- // through the emergency call sequence.
- PowerManager pm = (PowerManager) mApp.getSystemService(Context.POWER_SERVICE);
- mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- // Acquire with a timeout, just to be sure we won't hold the wake
- // lock forever even if a logic bug (in this class) causes us to
- // somehow never call cleanup().
- if (DBG) log("- startSequenceInternal: acquiring wake lock");
- mPartialWakeLock.acquire(WAKE_LOCK_TIMEOUT);
-
- // No need to check the current service state here, since the only
- // reason the CallController would call this method in the first
- // place is if the radio is powered-off.
- //
- // So just go ahead and turn the radio on.
-
- powerOnRadio(); // We'll get an onServiceStateChanged() callback
- // when the radio successfully comes up.
-
- // Next step: when the SERVICE_STATE_CHANGED event comes in,
- // we'll retry the call; see placeEmergencyCall();
- // But also, just in case, start a timer to make sure we'll retry
- // the call even if the SERVICE_STATE_CHANGED event never comes in
- // for some reason.
- startRetryTimer();
-
- // And finally, let the in-call UI know that we need to
- // display the "Turning on radio..." progress indication.
- mApp.inCallUiState.setProgressIndication(ProgressIndicationType.TURNING_ON_RADIO);
-
- // (Our caller is responsible for calling mApp.displayCallScreen().)
- }
-
- /**
- * Handles the SERVICE_STATE_CHANGED event.
- *
- * (Normally this event tells us that the radio has finally come
- * up. In that case, it's now safe to actually place the
- * emergency call.)
- */
- private void onServiceStateChanged(Message msg) {
- ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
- if (DBG) log("onServiceStateChanged()... new state = " + state);
-
- // Possible service states:
- // - STATE_IN_SERVICE // Normal operation
- // - STATE_OUT_OF_SERVICE // Still searching for an operator to register to,
- // // or no radio signal
- // - STATE_EMERGENCY_ONLY // Phone is locked; only emergency numbers are allowed
- // - STATE_POWER_OFF // Radio is explicitly powered off (airplane mode)
-
- // Once we reach either STATE_IN_SERVICE or STATE_EMERGENCY_ONLY,
- // it's finally OK to place the emergency call.
- boolean okToCall = (state.getState() == ServiceState.STATE_IN_SERVICE)
- || (state.getState() == ServiceState.STATE_EMERGENCY_ONLY);
-
- if (okToCall) {
- // Woo hoo! It's OK to actually place the call.
- if (DBG) log("onServiceStateChanged: ok to call!");
-
- // Deregister for the service state change events.
- unregisterForServiceStateChanged();
-
- // Take down the "Turning on radio..." indication.
- mApp.inCallUiState.clearProgressIndication();
-
- placeEmergencyCall();
-
- // The in-call UI is probably still up at this point,
- // but make sure of that:
- mApp.displayCallScreen();
- } else {
- // The service state changed, but we're still not ready to call yet.
- // (This probably was the transition from STATE_POWER_OFF to
- // STATE_OUT_OF_SERVICE, which happens immediately after powering-on
- // the radio.)
- //
- // So just keep waiting; we'll probably get to either
- // STATE_IN_SERVICE or STATE_EMERGENCY_ONLY very shortly.
- // (Or even if that doesn't happen, we'll at least do another retry
- // when the RETRY_TIMEOUT event fires.)
- if (DBG) log("onServiceStateChanged: not ready to call yet, keep waiting...");
- }
- }
-
- /**
- * Handles a DISCONNECT event from the telephony layer.
- *
- * Even after we successfully place an emergency call (after powering
- * on the radio), it's still possible for the call to fail with the
- * disconnect cause OUT_OF_SERVICE. If so, schedule a retry.
- */
- private void onDisconnect(Message msg) {
- Connection conn = (Connection) ((AsyncResult) msg.obj).result;
- Connection.DisconnectCause cause = conn.getDisconnectCause();
- if (DBG) log("onDisconnect: connection '" + conn
- + "', addr '" + conn.getAddress() + "', cause = " + cause);
-
- if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
- // Wait a bit more and try again (or just bail out totally if
- // we've had too many failures.)
- if (DBG) log("- onDisconnect: OUT_OF_SERVICE, need to retry...");
- scheduleRetryOrBailOut();
- } else {
- // Any other disconnect cause means we're done.
- // Either the emergency call succeeded *and* ended normally,
- // or else there was some error that we can't retry. In either
- // case, just clean up our internal state.)
-
- if (DBG) log("==> Disconnect event; clean up...");
- cleanup();
-
- // Nothing else to do here. If the InCallScreen was visible,
- // it would have received this disconnect event too (so it'll
- // show the "Call ended" state and finish itself without any
- // help from us.)
- }
- }
-
- /**
- * Handles the retry timer expiring.
- */
- private void onRetryTimeout() {
- PhoneConstants.State phoneState = mCM.getState();
- int serviceState = mPhone.getServiceState().getState();
- if (DBG) log("onRetryTimeout(): phone state " + phoneState
- + ", service state " + serviceState
- + ", mNumRetriesSoFar = " + mNumRetriesSoFar);
-
- // - If we're actually in a call, we've succeeded.
- //
- // - Otherwise, if the radio is now on, that means we successfully got
- // out of airplane mode but somehow didn't get the service state
- // change event. In that case, try to place the call.
- //
- // - If the radio is still powered off, try powering it on again.
-
- if (phoneState == PhoneConstants.State.OFFHOOK) {
- if (DBG) log("- onRetryTimeout: Call is active! Cleaning up...");
- cleanup();
- return;
- }
-
- if (serviceState != ServiceState.STATE_POWER_OFF) {
- // Woo hoo -- we successfully got out of airplane mode.
-
- // Deregister for the service state change events; we don't need
- // these any more now that the radio is powered-on.
- unregisterForServiceStateChanged();
-
- // Take down the "Turning on radio..." indication.
- mApp.inCallUiState.clearProgressIndication();
-
- placeEmergencyCall(); // If the call fails, placeEmergencyCall()
- // will schedule a retry.
- } else {
- // Uh oh; we've waited the full TIME_BETWEEN_RETRIES and the
- // radio is still not powered-on. Try again...
-
- if (DBG) log("- Trying (again) to turn on the radio...");
- powerOnRadio(); // Again, we'll (hopefully) get an onServiceStateChanged()
- // callback when the radio successfully comes up.
-
- // ...and also set a fresh retry timer (or just bail out
- // totally if we've had too many failures.)
- scheduleRetryOrBailOut();
- }
-
- // Finally, the in-call UI is probably still up at this point,
- // but make sure of that:
- mApp.displayCallScreen();
- }
-
- /**
- * Attempt to power on the radio (i.e. take the device out
- * of airplane mode.)
- *
- * Additionally, start listening for service state changes;
- * we'll eventually get an onServiceStateChanged() callback
- * when the radio successfully comes up.
- */
- private void powerOnRadio() {
- if (DBG) log("- powerOnRadio()...");
-
- // We're about to turn on the radio, so arrange to be notified
- // when the sequence is complete.
- registerForServiceStateChanged();
-
- // If airplane mode is on, we turn it off the same way that the
- // Settings activity turns it off.
- if (Settings.Global.getInt(mApp.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
- if (DBG) log("==> Turning off airplane mode...");
-
- // Change the system setting
- Settings.Global.putInt(mApp.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0);
-
- // Post the intent
- Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.putExtra("state", false);
- mApp.sendBroadcastAsUser(intent, UserHandle.ALL);
- } else {
- // Otherwise, for some strange reason the radio is off
- // (even though the Settings database doesn't think we're
- // in airplane mode.) In this case just turn the radio
- // back on.
- if (DBG) log("==> (Apparently) not in airplane mode; manually powering radio on...");
- mPhone.setRadioPower(true);
- }
- }
-
- /**
- * Actually initiate the outgoing emergency call.
- * (We do this once the radio has successfully been powered-up.)
- *
- * If the call succeeds, we're done.
- * If the call fails, schedule a retry of the whole sequence.
- */
- private void placeEmergencyCall() {
- if (DBG) log("placeEmergencyCall()...");
-
- // Place an outgoing call to mNumber.
- // Note we call PhoneUtils.placeCall() directly; we don't want any
- // of the behavior from CallController.placeCallInternal() here.
- // (Specifically, we don't want to start the "emergency call from
- // airplane mode" sequence from the beginning again!)
-
- registerForDisconnect(); // Get notified when this call disconnects
-
- if (DBG) log("- placing call to '" + mNumber + "'...");
- int callStatus = PhoneUtils.placeCall(mApp,
- mPhone,
- mNumber,
- null, // contactUri
- true, // isEmergencyCall
- null); // gatewayUri
- if (DBG) log("- PhoneUtils.placeCall() returned status = " + callStatus);
-
- boolean success;
- // Note PhoneUtils.placeCall() returns one of the CALL_STATUS_*
- // constants, not a CallStatusCode enum value.
- switch (callStatus) {
- case PhoneUtils.CALL_STATUS_DIALED:
- success = true;
- break;
-
- case PhoneUtils.CALL_STATUS_DIALED_MMI:
- case PhoneUtils.CALL_STATUS_FAILED:
- default:
- // Anything else is a failure, and we'll need to retry.
- Log.w(TAG, "placeEmergencyCall(): placeCall() failed: callStatus = " + callStatus);
- success = false;
- break;
- }
-
- if (success) {
- if (DBG) log("==> Success from PhoneUtils.placeCall()!");
- // Ok, the emergency call is (hopefully) under way.
-
- // We're not done yet, though, so don't call cleanup() here.
- // (It's still possible that this call will fail, and disconnect
- // with cause==OUT_OF_SERVICE. If so, that will trigger a retry
- // from the onDisconnect() method.)
- } else {
- if (DBG) log("==> Failure.");
- // Wait a bit more and try again (or just bail out totally if
- // we've had too many failures.)
- scheduleRetryOrBailOut();
- }
- }
-
- /**
- * Schedules a retry in response to some failure (either the radio
- * failing to power on, or a failure when trying to place the call.)
- * Or, if we've hit the retry limit, bail out of this whole sequence
- * and display a failure message to the user.
- */
- private void scheduleRetryOrBailOut() {
- mNumRetriesSoFar++;
- if (DBG) log("scheduleRetryOrBailOut()... mNumRetriesSoFar is now " + mNumRetriesSoFar);
-
- if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
- Log.w(TAG, "scheduleRetryOrBailOut: hit MAX_NUM_RETRIES; giving up...");
- cleanup();
- // ...and have the InCallScreen display a generic failure
- // message.
- mApp.inCallUiState.setPendingCallStatusCode(CallStatusCode.CALL_FAILED);
- } else {
- if (DBG) log("- Scheduling another retry...");
- startRetryTimer();
- mApp.inCallUiState.setProgressIndication(ProgressIndicationType.RETRYING);
- }
- }
-
- /**
- * Clean up when done with the whole sequence: either after
- * successfully placing *and* ending the emergency call, or after
- * bailing out because of too many failures.
- *
- * The exact cleanup steps are:
- * - Take down any progress UI (and also ask the in-call UI to refresh itself,
- * if it's still visible)
- * - Double-check that we're not still registered for any telephony events
- * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
- * - Make sure we're not still holding any wake locks
- *
- * Basically this method guarantees that there will be no more
- * activity from the EmergencyCallHelper until the CallController
- * kicks off the whole sequence again with another call to
- * startEmergencyCallFromAirplaneModeSequence().
- *
- * Note we don't call this method simply after a successful call to
- * placeCall(), since it's still possible the call will disconnect
- * very quickly with an OUT_OF_SERVICE error.
- */
- private void cleanup() {
- if (DBG) log("cleanup()...");
-
- // Take down the "Turning on radio..." indication.
- mApp.inCallUiState.clearProgressIndication();
-
- unregisterForServiceStateChanged();
- unregisterForDisconnect();
- cancelRetryTimer();
-
- // Release / clean up the wake lock
- if (mPartialWakeLock != null) {
- if (mPartialWakeLock.isHeld()) {
- if (DBG) log("- releasing wake lock");
- mPartialWakeLock.release();
- }
- mPartialWakeLock = null;
- }
-
- // And finally, ask the in-call UI to refresh itself (to clean up the
- // progress indication if necessary), if it's currently visible.
- mApp.updateInCallScreen();
- }
-
- private void startRetryTimer() {
- removeMessages(RETRY_TIMEOUT);
- sendEmptyMessageDelayed(RETRY_TIMEOUT, TIME_BETWEEN_RETRIES);
- }
-
- private void cancelRetryTimer() {
- removeMessages(RETRY_TIMEOUT);
- }
-
- private void registerForServiceStateChanged() {
- // Unregister first, just to make sure we never register ourselves
- // twice. (We need this because Phone.registerForServiceStateChanged()
- // does not prevent multiple registration of the same handler.)
- mPhone.unregisterForServiceStateChanged(this); // Safe even if not currently registered
- mPhone.registerForServiceStateChanged(this, SERVICE_STATE_CHANGED, null);
- }
-
- private void unregisterForServiceStateChanged() {
- // This method is safe to call even if we haven't set mPhone yet.
- if (mPhone != null) {
- mPhone.unregisterForServiceStateChanged(this); // Safe even if unnecessary
- }
- removeMessages(SERVICE_STATE_CHANGED); // Clean up any pending messages too
- }
-
- private void registerForDisconnect() {
- // Note: no need to unregister first, since
- // CallManager.registerForDisconnect() automatically prevents
- // multiple registration of the same handler.
- mCM.registerForDisconnect(this, DISCONNECT, null);
- }
-
- private void unregisterForDisconnect() {
- mCM.unregisterForDisconnect(this); // Safe even if not currently registered
- removeMessages(DISCONNECT); // Clean up any pending messages too
- }
-
-
- //
- // Debugging
- //
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}