Refactor EmergencyCallHelper -> RadioOnCallHelper

To make phone calls when the cellular radio is turned off automatically,
we will use a mechanism similar to the EmergencyCallHelper and share
the handling of the onComplete() callback.

Test: Telephony Unit Tests
Merged-In: Id782c759d3abd29d432782575f88d2ed83f083b2
Change-Id: Id782c759d3abd29d432782575f88d2ed83f083b2
diff --git a/res/values/config.xml b/res/values/config.xml
index 2078049..d8725e7 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -225,4 +225,7 @@
 
     <!-- The package name for the platform carrier config app, bundled with system image. -->
     <string name="platform_carrier_config_package" translatable="false">com.android.carrierconfig</string>
+
+    <!-- Whether the cellular radio is allowed to be power down when the Bluetooth can provide the data/call capabilities -->
+    <bool name="config_allowRadioPowerDownOnBluetooth">false</bool>
 </resources>
diff --git a/src/com/android/services/telephony/EmergencyCallHelper.java b/src/com/android/services/telephony/RadioOnHelper.java
similarity index 72%
rename from src/com/android/services/telephony/EmergencyCallHelper.java
rename to src/com/android/services/telephony/RadioOnHelper.java
index 295f4f7..ce7f72a 100644
--- a/src/com/android/services/telephony/EmergencyCallHelper.java
+++ b/src/com/android/services/telephony/RadioOnHelper.java
@@ -30,21 +30,22 @@
 import java.util.List;
 
 /**
- * Helper class 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.
+ * Helper class that implements special behavior related to emergency calls or make phone calls when
+ * radio is power off due to the device being on Bluetooth. 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) or a normal number while the radio is off (because of the device is on
+ * Bluetooth), by forcibly turning the radio back on, waiting for it to come up, and then retrying
+ * the call.
  */
-public class EmergencyCallHelper implements EmergencyCallStateListener.Callback {
+public class RadioOnHelper implements RadioOnStateListener.Callback {
 
     private final Context mContext;
-    private EmergencyCallStateListener.Callback mCallback;
-    private List<EmergencyCallStateListener> mListeners;
-    private List<EmergencyCallStateListener> mInProgressListeners;
-    private boolean mIsEmergencyCallingEnabled;
+    private RadioOnStateListener.Callback mCallback;
+    private List<RadioOnStateListener> mListeners;
+    private List<RadioOnStateListener> mInProgressListeners;
+    private boolean mIsRadioOnCallingEnabled;
 
-
-    public EmergencyCallHelper(Context context) {
+    public RadioOnHelper(Context context) {
         mContext = context;
         mInProgressListeners = new ArrayList<>(2);
     }
@@ -55,12 +56,12 @@
         }
         mListeners = new ArrayList<>(2);
         for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
-            mListeners.add(new EmergencyCallStateListener());
+            mListeners.add(new RadioOnStateListener());
         }
     }
     /**
      * Starts the "turn on radio" sequence. This is the (single) external API of the
-     * EmergencyCallHelper class.
+     * RadioOnHelper class.
      *
      * This method kicks off the following sequence:
      * - Power on the radio for each Phone
@@ -69,18 +70,19 @@
      * - Finally, clean up any leftover state.
      *
      * 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
+     * RadioOnHelper's handler (thus ensuring that the rest of the sequence is entirely
      * serialized, and runs on the main looper.)
      */
-    public void enableEmergencyCalling(EmergencyCallStateListener.Callback callback) {
+    public void enableRadioOnCalling(RadioOnStateListener.Callback callback) {
         setupListeners();
         mCallback = callback;
         mInProgressListeners.clear();
-        mIsEmergencyCallingEnabled = false;
+        mIsRadioOnCallingEnabled = false;
         for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
             Phone phone = PhoneFactory.getPhone(i);
-            if (phone == null)
+            if (phone == null) {
                 continue;
+            }
 
             mInProgressListeners.add(mListeners.get(i));
             mListeners.get(i).waitForRadioOn(phone, this);
@@ -120,11 +122,16 @@
      * Synchronization is not necessary.
      */
     @Override
-    public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
-        mIsEmergencyCallingEnabled |= isRadioReady;
+    public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
+        mIsRadioOnCallingEnabled |= isRadioReady;
         mInProgressListeners.remove(listener);
         if (mCallback != null && mInProgressListeners.isEmpty()) {
-            mCallback.onComplete(null, mIsEmergencyCallingEnabled);
+            mCallback.onComplete(null, mIsRadioOnCallingEnabled);
         }
     }
+
+    @Override
+    public boolean isOkToCall(Phone phone, int serviceState) {
+        return (mCallback == null) ? false : mCallback.isOkToCall(phone, serviceState);
+    }
 }
diff --git a/src/com/android/services/telephony/EmergencyCallStateListener.java b/src/com/android/services/telephony/RadioOnStateListener.java
similarity index 92%
rename from src/com/android/services/telephony/EmergencyCallStateListener.java
rename to src/com/android/services/telephony/RadioOnStateListener.java
index 036872d..7bfa9c6 100644
--- a/src/com/android/services/telephony/EmergencyCallStateListener.java
+++ b/src/com/android/services/telephony/RadioOnStateListener.java
@@ -31,15 +31,16 @@
 
 /**
  * Helper class that listens to a Phone's radio state and sends a callback when the radio state of
- * that Phone is either "in service" or "emergency calls only."
+ * that Phone is either "in service" or ("emergency calls only." if is emergency).
  */
-public class EmergencyCallStateListener {
+public class RadioOnStateListener {
 
     /**
-     * Receives the result of the EmergencyCallStateListener's attempt to turn on the radio.
+     * Receives the result of the RadioOnStateListener's attempt to turn on the radio.
      */
     interface Callback {
-        void onComplete(EmergencyCallStateListener listener, boolean isRadioReady);
+        void onComplete(RadioOnStateListener listener, boolean isRadioReady);
+        boolean isOkToCall(Phone phone, int serviceState);
     }
 
     // Number of times to retry the call, and time between retry attempts.
@@ -62,8 +63,8 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
                         Phone phone = (Phone) args.arg1;
-                        EmergencyCallStateListener.Callback callback =
-                                (EmergencyCallStateListener.Callback) args.arg2;
+                        RadioOnStateListener.Callback callback =
+                                (RadioOnStateListener.Callback) args.arg2;
                         startSequenceInternal(phone, callback);
                     } finally {
                         args.recycle();
@@ -89,7 +90,7 @@
 
     /**
      * Starts the "wait for radio" sequence. This is the (single) external API of the
-     * EmergencyCallStateListener class.
+     * RadioOnStateListener class.
      *
      * This method kicks off the following sequence:
      * - Listen for the service state change event telling us the radio has come up.
@@ -98,7 +99,7 @@
      * - Finally, clean up any leftover state.
      *
      * This method is safe to call from any thread, since it simply posts a message to the
-     * EmergencyCallStateListener's handler (thus ensuring that the rest of the sequence is entirely
+     * RadioOnStateListener's handler (thus ensuring that the rest of the sequence is entirely
      * serialized, and runs only on the handler thread.)
      */
     public void waitForRadioOn(Phone phone, Callback callback) {
@@ -123,7 +124,7 @@
     private void startSequenceInternal(Phone phone, Callback callback) {
         Log.d(this, "startSequenceInternal: Phone " + phone.getPhoneId());
 
-        // First of all, clean up any state left over from a prior emergency call sequence. This
+        // First of all, clean up any state left over from a prior RadioOn call sequence. This
         // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
         // we're already in the middle of the sequence.
         cleanup();
@@ -140,7 +141,7 @@
 
     /**
      * 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.
+     * finally come up. In that case, it's now safe to actually place the RadioOn call.
      */
     private void onServiceStateChanged(ServiceState state) {
         Log.d(this, "onServiceStateChanged(), new state = %s, Phone = %s", state,
@@ -171,8 +172,7 @@
      * UNAVAILABLE state, even if it is reporting the OUT_OF_SERVICE state.
      */
     private boolean isOkToCall(int serviceState) {
-        return (mPhone.getState() == PhoneConstants.State.OFFHOOK) ||
-                mPhone.getServiceStateTracker().isRadioOn();
+        return (mCallback == null) ? false : mCallback.isOkToCall(mPhone, serviceState);
     }
 
     /**
@@ -223,7 +223,7 @@
      * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
      *
      * Basically this method guarantees that there will be no more activity from the
-     * EmergencyCallStateListener until someone kicks off the whole sequence again with another call
+     * RadioOnStateListener until someone kicks off the whole sequence again with another call
      * to {@link #waitForRadioOn}
      *
      * TODO: Do the work for the comment below:
@@ -298,7 +298,7 @@
         if (this == o) return true;
         if (o == null || !getClass().equals(o.getClass())) return false;
 
-        EmergencyCallStateListener that = (EmergencyCallStateListener) o;
+        RadioOnStateListener that = (RadioOnStateListener) o;
 
         if (mNumRetriesSoFar != that.mNumRetriesSoFar) {
             return false;
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index ac3e50a..13fef03 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -118,7 +118,6 @@
                     mTelephonyConnectionServiceProxy);
 
     private ComponentName mExpectedComponentName = null;
-    private EmergencyCallHelper mEmergencyCallHelper;
     private EmergencyTonePlayer mEmergencyTonePlayer;
 
     // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
@@ -358,70 +357,40 @@
         final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
 
-        if (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn)) {
-            final Uri emergencyHandle = handle;
+        if ((isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
+                || isRadioPowerDownOnBluetooth()) {
+            final Uri resultHandle = handle;
             // By default, Connection based on the default Phone, since we need to return to Telecom
             // now.
-            final int defaultPhoneType = mPhoneFactoryProxy.getDefaultPhone().getPhoneType();
-            final Connection emergencyConnection = getTelephonyConnection(request, numberToDial,
-                    isEmergencyNumber, emergencyHandle, mPhoneFactoryProxy.getDefaultPhone());
-            if (mEmergencyCallHelper == null) {
-                mEmergencyCallHelper = new EmergencyCallHelper(this);
-            }
-            mEmergencyCallHelper.enableEmergencyCalling(new EmergencyCallStateListener.Callback() {
+            final Connection resultConnection = getTelephonyConnection(request, numberToDial,
+                    isEmergencyNumber, resultHandle, PhoneFactory.getDefaultPhone());
+            RadioOnHelper radioOnHelper = new RadioOnHelper(this);
+            radioOnHelper.enableRadioOnCalling(new RadioOnStateListener.Callback() {
                 @Override
-                public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
-                    // Make sure the Call has not already been canceled by the user.
-                    if (emergencyConnection.getState() == Connection.STATE_DISCONNECTED) {
-                        Log.i(this, "Emergency call disconnected before the outgoing call was " +
-                                "placed. Skipping emergency call placement.");
-                        return;
-                    }
-                    if (isRadioReady) {
-                        // Get the right phone object since the radio has been turned on
-                        // successfully.
-                        final Phone phone = getPhoneForAccount(request.getAccountHandle(),
-                                isEmergencyNumber);
-                        // If the PhoneType of the Phone being used is different than the Default
-                        // Phone, then we need create a new Connection using that PhoneType and
-                        // replace it in Telecom.
-                        if (phone.getPhoneType() != defaultPhoneType) {
-                            Connection repConnection = getTelephonyConnection(request, numberToDial,
-                                    isEmergencyNumber, emergencyHandle, phone);
-                            // If there was a failure, the resulting connection will not be a
-                            // TelephonyConnection, so don't place the call, just return!
-                            if (repConnection instanceof TelephonyConnection) {
-                                placeOutgoingConnection((TelephonyConnection) repConnection, phone,
-                                        request);
-                            }
-                            // Notify Telecom of the new Connection type.
-                            // TODO: Switch out the underlying connection instead of creating a new
-                            // one and causing UI Jank.
-                            addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone),
-                                    repConnection);
-                            // Remove the old connection from Telecom after.
-                            emergencyConnection.setDisconnected(
-                                    DisconnectCauseUtil.toTelecomDisconnectCause(
-                                            android.telephony.DisconnectCause.OUTGOING_CANCELED,
-                                            "Reconnecting outgoing Emergency Call."));
-                            emergencyConnection.destroy();
-                        } else {
-                            placeOutgoingConnection((TelephonyConnection) emergencyConnection,
-                                    phone, request);
-                        }
+                public void onComplete(RadioOnStateListener listener,
+                        boolean isRadioReady) {
+                    handleOnComplete(isRadioReady,
+                            isEmergencyNumber,
+                            resultConnection,
+                            request,
+                            numberToDial,
+                            resultHandle);
+                }
+
+                @Override
+                public boolean isOkToCall(Phone phone, int serviceState) {
+                    if (isEmergencyNumber) {
+                        return (phone.getState() == PhoneConstants.State.OFFHOOK)
+                                || phone.getServiceStateTracker().isRadioOn();
                     } else {
-                        Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
-                        emergencyConnection.setDisconnected(
-                                DisconnectCauseUtil.toTelecomDisconnectCause(
-                                        android.telephony.DisconnectCause.POWER_OFF,
-                                        "Failed to turn on radio."));
-                        emergencyConnection.destroy();
+                        return (phone.getState() == PhoneConstants.State.OFFHOOK)
+                                || serviceState == ServiceState.STATE_IN_SERVICE;
                     }
                 }
             });
             // Return the still unconnected GsmConnection and wait for the Radios to boot before
             // connecting it to the underlying Phone.
-            return emergencyConnection;
+            return resultConnection;
         } else {
             if (!canAddCall() && !isEmergencyNumber) {
                 Log.d(this, "onCreateOutgoingConnection, cannot add call .");
@@ -448,6 +417,76 @@
     }
 
     /**
+     * Whether the cellular radio is power off because the device is on Bluetooth.
+     */
+    private boolean isRadioPowerDownOnBluetooth() {
+        final Context context = getApplicationContext();
+        final boolean allowed = context.getResources().getBoolean(
+                R.bool.config_allowRadioPowerDownOnBluetooth);
+        final int cellOn = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.CELL_ON,
+                PhoneConstants.CELL_OFF_FLAG);
+        return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn());
+    }
+
+    /**
+     * Handle the onComplete callback of RadioOnStateListener.
+     */
+    private void handleOnComplete(boolean isRadioReady,
+            boolean isEmergencyNumber,
+            Connection originalConnection,
+            ConnectionRequest request,
+            String numberToDial,
+            Uri handle) {
+        // Make sure the Call has not already been canceled by the user.
+        if (originalConnection.getState() == Connection.STATE_DISCONNECTED) {
+            Log.i(this, "Emergency call disconnected before the outgoing call was "
+                    + "placed. Skipping emergency call placement.");
+            return;
+        }
+        if (isRadioReady) {
+            // Get the right phone object since the radio has been turned on
+            // successfully.
+            final Phone phone = getPhoneForAccount(request.getAccountHandle(),
+                    isEmergencyNumber);
+            // If the PhoneType of the Phone being used is different than the Default
+            // Phone, then we need create a new Connection using that PhoneType and
+            // replace it in Telecom.
+            if (phone.getPhoneType() != PhoneFactory.getDefaultPhone().getPhoneType()) {
+                Connection repConnection = getTelephonyConnection(request, numberToDial,
+                        isEmergencyNumber, handle, phone);
+                // If there was a failure, the resulting connection will not be a
+                // TelephonyConnection, so don't place the call, just return!
+                if (repConnection instanceof TelephonyConnection) {
+                    placeOutgoingConnection((TelephonyConnection) repConnection, phone,
+                            request);
+                }
+                // Notify Telecom of the new Connection type.
+                // TODO: Switch out the underlying connection instead of creating a new
+                // one and causing UI Jank.
+                addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone),
+                        repConnection);
+                // Remove the old connection from Telecom after.
+                originalConnection.setDisconnected(
+                        DisconnectCauseUtil.toTelecomDisconnectCause(
+                                android.telephony.DisconnectCause.OUTGOING_CANCELED,
+                                "Reconnecting outgoing Emergency Call."));
+                originalConnection.destroy();
+            } else {
+                placeOutgoingConnection((TelephonyConnection) originalConnection,
+                        phone, request);
+            }
+        } else {
+            Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
+            originalConnection.setDisconnected(
+                    DisconnectCauseUtil.toTelecomDisconnectCause(
+                            android.telephony.DisconnectCause.POWER_OFF,
+                            "Failed to turn on radio."));
+            originalConnection.destroy();
+        }
+    }
+
+    /**
      * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
      *      otherwise.
      */
@@ -551,6 +590,10 @@
                                         "ServiceState.STATE_OUT_OF_SERVICE"));
                     }
                 case ServiceState.STATE_POWER_OFF:
+                    // Don't disconnect if radio is power off because the device is on Bluetooth.
+                    if (isRadioPowerDownOnBluetooth()) {
+                        break;
+                    }
                     return Connection.createFailedConnection(
                             DisconnectCauseUtil.toTelecomDisconnectCause(
                                     android.telephony.DisconnectCause.POWER_OFF,
diff --git a/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java b/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
similarity index 91%
rename from tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java
rename to tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
index a9221e9..abac699 100644
--- a/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java
+++ b/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.services.telephony;
@@ -44,24 +44,24 @@
 import static org.mockito.Mockito.when;
 
 /**
- * Tests the EmergencyCallStateListener, which listens to one Phone and waits until its service
+ * Tests the RadioOnStateListener, which listens to one Phone and waits until its service
  * state changes to accepting emergency calls or in service. If it can not find a tower to camp onto
  * for emergency calls, then it will fail after a timeout period.
  */
 @RunWith(AndroidJUnit4.class)
-public class EmergencyCallStateListenerTest extends TelephonyTestBase {
+public class RadioOnStateListenerTest extends TelephonyTestBase {
 
     private static final long TIMEOUT_MS = 100;
 
     @Mock Phone mMockPhone;
     @Mock ServiceStateTracker mMockServiceStateTracker;
-    @Mock EmergencyCallStateListener.Callback mCallback;
-    EmergencyCallStateListener mListener;
+    @Mock RadioOnStateListener.Callback mCallback;
+    RadioOnStateListener mListener;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mListener = new EmergencyCallStateListener();
+        mListener = new RadioOnStateListener();
     }
 
     @After
@@ -82,7 +82,7 @@
 
         verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class));
         verify(mMockPhone).registerForServiceStateChanged(any(Handler.class),
-                eq(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
+                eq(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
     }
 
     /**
@@ -105,7 +105,7 @@
         mListener.waitForRadioOn(mMockPhone, mCallback);
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
 
-        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
                 new AsyncResult(null, state, null)).sendToTarget();
 
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
@@ -134,7 +134,7 @@
         mListener.waitForRadioOn(mMockPhone, mCallback);
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
 
-        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
                 new AsyncResult(null, state, null)).sendToTarget();
 
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
@@ -166,7 +166,7 @@
 
         // Still expect an answer because we will be sending the onComplete message as soon as the
         // radio is confirmed to be on, whether or not it is out of service or not.
-        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
                 new AsyncResult(null, state, null)).sendToTarget();
 
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);