Call waiting support USSD function

-Add USSD function
-Call waiting uses TelephonyManager's get/set methods
Bug: 180540626
Test: build pass. Manual test: set/get call waiting.

Change-Id: Ia03fd4ddef1defb1f0f9091c58ab0f1e43936457
Merged-In: Ia03fd4ddef1defb1f0f9091c58ab0f1e43936457
diff --git a/res/xml/carrier_ss_string.xml b/res/xml/carrier_ss_string.xml
index 29de13a..b7771cd 100644
--- a/res/xml/carrier_ss_string.xml
+++ b/res/xml/carrier_ss_string.xml
@@ -146,4 +146,32 @@
             <entry key="status_code" definition="ok">7</entry>
         </command_result>
     </feature>
+    <feature name="callwaiting">
+        <command name="query"><!--For example: *#102#-->
+            <service_code>102</service_code>
+            <action_code>*#</action_code>
+            <response_format number ="1"><!--For example: 120*4#-->
+                <entry position="4" key="status_code"/>
+            </response_format>
+        </command>
+        <command name="activate"><!--For example: *102#-->
+            <service_code>102</service_code>
+            <action_code>*</action_code>
+            <response_format number="1"><!--For example: 102*5#-->
+                <entry position="4" key="status_code"/>
+            </response_format>
+        </command>
+        <command name="deactivate"><!--For example: #102#-->
+            <service_code>102</service_code>
+            <action_code>#</action_code>
+            <response_format number="1"><!--For example: 148*4#-->
+                <entry position="4" key="status_code"/>
+            </response_format>
+        </command>
+        <command_result number="3">
+            <entry key="status_code" definition="activate">5</entry>
+            <entry key="status_code" definition="deactivate">4</entry>
+            <entry key="status_code" definition="unregister">0</entry>
+        </command_result>
+    </feature>
 </resources>
diff --git a/res/xml/carrier_ss_string_850.xml b/res/xml/carrier_ss_string_850.xml
index 01eeee5..ed31fae 100644
--- a/res/xml/carrier_ss_string_850.xml
+++ b/res/xml/carrier_ss_string_850.xml
@@ -94,4 +94,32 @@
             <entry key="status_code" definition="ok">7</entry>
         </command_result>
     </feature>
+    <feature name="callwaiting">
+        <command name="query"><!--For example: *#102#-->
+            <service_code>102</service_code>
+            <action_code>*#</action_code>
+            <response_format number ="1"><!--For example: 120*4#-->
+                <entry position="4" key="status_code"/>
+            </response_format>
+        </command>
+        <command name="activate"><!--For example: *102#-->
+            <service_code>102</service_code>
+            <action_code>*</action_code>
+            <response_format number="1"><!--For example: 102*5#-->
+                <entry position="4" key="status_code"/>
+            </response_format>
+        </command>
+        <command name="deactivate"><!--For example: #102#-->
+            <service_code>102</service_code>
+            <action_code>#</action_code>
+            <response_format number="1"><!--For example: 148*4#-->
+                <entry position="4" key="status_code"/>
+            </response_format>
+        </command>
+        <command_result number="3">
+            <entry key="status_code" definition="activate">5</entry>
+            <entry key="status_code" definition="deactivate">4</entry>
+            <entry key="status_code" definition="unregister">0</entry>
+        </command_result>
+    </feature>
 </resources>
diff --git a/src/com/android/phone/CallWaitingSwitchPreference.java b/src/com/android/phone/CallWaitingSwitchPreference.java
index 41442fe..01dd3b2 100644
--- a/src/com/android/phone/CallWaitingSwitchPreference.java
+++ b/src/com/android/phone/CallWaitingSwitchPreference.java
@@ -1,18 +1,21 @@
 package com.android.phone;
 
+import static com.android.phone.TimeConsumingPreferenceActivity.EXCEPTION_ERROR;
 import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
 
 import android.content.Context;
-import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.preference.SwitchPreference;
+import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Log;
 
-import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.Phone;
 
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
 public class CallWaitingSwitchPreference extends SwitchPreference {
     private static final String LOG_TAG = "CallWaitingSwitchPreference";
     private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
@@ -20,6 +23,11 @@
     private final MyHandler mHandler = new MyHandler();
     private Phone mPhone;
     private TimeConsumingPreferenceListener mTcpListener;
+    private Executor mExecutor;
+    private TelephonyManager mTelephonyManager;
+    private boolean mIsDuringUpdateProcess = false;
+    private int mUpdateStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
+    private int mQueryStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
 
     public CallWaitingSwitchPreference(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -37,101 +45,84 @@
             TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone) {
         mPhone = phone;
         mTcpListener = listener;
+        mExecutor = Executors.newSingleThreadExecutor();
+        mTelephonyManager = getContext().getSystemService(
+                TelephonyManager.class).createForSubscriptionId(phone.getSubId());
 
         if (!skipReading) {
-            mPhone.getCallWaiting(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CALL_WAITING,
-                    MyHandler.MESSAGE_GET_CALL_WAITING, MyHandler.MESSAGE_GET_CALL_WAITING));
+            Log.d(LOG_TAG, "init getCallWaitingStatus");
+            mTelephonyManager.getCallWaitingStatus(mExecutor, this::queryStatusCallBack);
             if (mTcpListener != null) {
                 mTcpListener.onStarted(this, true);
             }
         }
     }
 
+    private void queryStatusCallBack(int result) {
+        Log.d(LOG_TAG, "queryStatusCallBack: CW state " + result);
+        mQueryStatus = result;
+        mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MESSAGE_UPDATE_CALL_WAITING));
+    }
+
+    private void updateStatusCallBack(int result) {
+        Log.d(LOG_TAG, "updateStatusCallBack: CW state " + result + ", and re get");
+        mUpdateStatus = result;
+        mTelephonyManager.getCallWaitingStatus(mExecutor, this::queryStatusCallBack);
+    }
+
     @Override
     protected void onClick() {
         super.onClick();
-
-        mPhone.setCallWaiting(isChecked(),
-                mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+        mTelephonyManager.setCallWaitingEnabled(isChecked(), mExecutor, this::updateStatusCallBack);
         if (mTcpListener != null) {
+            mIsDuringUpdateProcess = true;
             mTcpListener.onStarted(this, false);
         }
     }
 
     private class MyHandler extends Handler {
-        static final int MESSAGE_GET_CALL_WAITING = 0;
-        static final int MESSAGE_SET_CALL_WAITING = 1;
+        static final int MESSAGE_UPDATE_CALL_WAITING = 0;
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MESSAGE_GET_CALL_WAITING:
-                    handleGetCallWaitingResponse(msg);
-                    break;
-                case MESSAGE_SET_CALL_WAITING:
-                    handleSetCallWaitingResponse(msg);
+                case MESSAGE_UPDATE_CALL_WAITING:
+                    updateUi();
                     break;
             }
         }
 
-        private void handleGetCallWaitingResponse(Message msg) {
-            AsyncResult ar = (AsyncResult) msg.obj;
-
+        private void updateUi() {
             if (mTcpListener != null) {
-                if (msg.arg2 == MESSAGE_SET_CALL_WAITING) {
+                if (mIsDuringUpdateProcess) {
                     mTcpListener.onFinished(CallWaitingSwitchPreference.this, false);
                 } else {
                     mTcpListener.onFinished(CallWaitingSwitchPreference.this, true);
                 }
             }
 
-            if (ar.exception instanceof CommandException) {
-                if (DBG) {
-                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: CommandException=" +
-                            ar.exception);
-                }
+            if (mIsDuringUpdateProcess && (
+                    mUpdateStatus == TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED
+                            || mUpdateStatus
+                            == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR)) {
+                Log.d(LOG_TAG, "handleSetCallWaitingResponse: Exception");
                 if (mTcpListener != null) {
-                    mTcpListener.onException(CallWaitingSwitchPreference.this,
-                            (CommandException)ar.exception);
+                    mTcpListener.onError(CallWaitingSwitchPreference.this, EXCEPTION_ERROR);
                 }
-            } else if (ar.userObj instanceof Throwable || ar.exception != null) {
-                // Still an error case but just not a CommandException.
-                if (DBG) {
-                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception" + ar.exception);
-                }
+            } else if (mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED
+                    || mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR) {
+                Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception");
                 if (mTcpListener != null) {
                     mTcpListener.onError(CallWaitingSwitchPreference.this, RESPONSE_ERROR);
                 }
             } else {
-                if (DBG) {
-                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: CW state successfully queried.");
-                }
-                int[] cwArray = (int[])ar.result;
-                // If cwArray[0] is = 1, then cwArray[1] must follow,
-                // with the TS 27.007 service class bit vector of services
-                // for which call waiting is enabled.
-                try {
-                    setChecked(((cwArray[0] == 1) && ((cwArray[1] & 0x01) == 0x01)));
-                } catch (ArrayIndexOutOfBoundsException e) {
-                    Log.e(LOG_TAG, "handleGetCallWaitingResponse: improper result: err ="
-                            + e.getMessage());
+                if (mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_ENABLED) {
+                    setChecked(true);
+                } else {
+                    setChecked(false);
                 }
             }
-        }
-
-        private void handleSetCallWaitingResponse(Message msg) {
-            AsyncResult ar = (AsyncResult) msg.obj;
-
-            if (ar.exception != null) {
-                if (DBG) {
-                    Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception=" + ar.exception);
-                }
-                //setEnabled(false);
-            }
-            if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
-
-            mPhone.getCallWaiting(obtainMessage(MESSAGE_GET_CALL_WAITING,
-                    MESSAGE_SET_CALL_WAITING, MESSAGE_SET_CALL_WAITING, ar.exception));
+            mIsDuringUpdateProcess = false;
         }
     }
 }
diff --git a/src/com/android/phone/CallWaitingUssdResultReceiver.java b/src/com/android/phone/CallWaitingUssdResultReceiver.java
new file mode 100644
index 0000000..b9049e9
--- /dev/null
+++ b/src/com/android/phone/CallWaitingUssdResultReceiver.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 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 static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.telephony.TelephonyManager;
+import android.telephony.UssdResponse;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IIntegerConsumer;
+
+import java.util.HashMap;
+
+/**
+ * Handling the call waiting USSD result.
+ */
+public class CallWaitingUssdResultReceiver extends ResultReceiver {
+    private static final String LOG_TAG = "CwUssdResultReceiver";
+
+    private IIntegerConsumer mCallback;
+    private CarrierXmlParser mCarrierXmlParser;
+    private CarrierXmlParser.SsEntry.SSAction mSsAction;
+
+    CallWaitingUssdResultReceiver(Handler handler, IIntegerConsumer callback,
+            CarrierXmlParser carrierXmlParser, CarrierXmlParser.SsEntry.SSAction action) {
+        super(handler);
+        mCallback = callback;
+        mCarrierXmlParser = carrierXmlParser;
+        mSsAction = action;
+    }
+
+    @Override
+    protected void onReceiveResult(int resultCode, Bundle ussdResponse) {
+        log("USSD:" + resultCode);
+        checkNotNull(ussdResponse, "ussdResponse cannot be null.");
+        UssdResponse response = ussdResponse.getParcelable(
+                TelephonyManager.USSD_RESPONSE);
+
+        if (resultCode == TelephonyManager.USSD_RETURN_SUCCESS) {
+            int callWaitingStatus = getStatusFromResponse(response);
+            try {
+                mCallback.accept(callWaitingStatus);
+            } catch (RemoteException e) {
+                log("Fail to notify getCallWaitingStatus due to " + e);
+            }
+        } else {
+            try {
+                mCallback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+            } catch (RemoteException e) {
+                log("Fail to notify getCallWaitingStatus due to " + e);
+            }
+        }
+    }
+
+    private int getStatusFromResponse(UssdResponse response) {
+        int callWaitingStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
+
+        CarrierXmlParser.SsFeature callWaitingFeature = mCarrierXmlParser.getFeature(
+                CarrierXmlParser.FEATURE_CALL_WAITING);
+        if (callWaitingFeature == null) {
+            return callWaitingStatus;
+        }
+
+        HashMap<String, String> analysisResult = callWaitingFeature
+                .getResponseSet(mSsAction, response.getReturnMessage().toString());
+        if (analysisResult.get(CarrierXmlParser.TAG_RESPONSE_STATUS_ERROR) != null) {
+            return callWaitingStatus;
+        }
+
+        if (analysisResult != null && analysisResult.size() != 0) {
+            String tmpStatusStr = analysisResult.get(
+                    CarrierXmlParser.TAG_RESPONSE_STATUS);
+
+            if (!TextUtils.isEmpty(tmpStatusStr)) {
+                if (tmpStatusStr.equals(
+                        CarrierXmlParser.TAG_COMMAND_RESULT_DEFINITION_ACTIVATE)) {
+                    callWaitingStatus =
+                            TelephonyManager.CALL_WAITING_STATUS_ENABLED;
+                } else if (tmpStatusStr.equals(
+                        CarrierXmlParser.TAG_COMMAND_RESULT_DEFINITION_DEACTIVATE)) {
+                    callWaitingStatus =
+                            TelephonyManager.CALL_WAITING_STATUS_DISABLED;
+                }
+            }
+        }
+        return callWaitingStatus;
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CarrierXmlParser.java b/src/com/android/phone/CarrierXmlParser.java
index 18602c9..6e01b43 100644
--- a/src/com/android/phone/CarrierXmlParser.java
+++ b/src/com/android/phone/CarrierXmlParser.java
@@ -67,6 +67,7 @@
 
     // To define feature's item name in xml
     public static final String FEATURE_CALL_FORWARDING = "callforwarding";
+    public static final String FEATURE_CALL_WAITING = "callwaiting";
     public static final String FEATURE_CALLER_ID = "callerid";
 
     // COMMAND_NAME is xml's command name.
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 9a1e275..01db02d 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -209,6 +209,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
@@ -1152,7 +1153,6 @@
                     }
                     break;
                 }
-
                 case EVENT_PERFORM_NETWORK_SCAN_DONE:
                     ar = (AsyncResult) msg.obj;
                     request = (MainThreadRequest) ar.userObj;
@@ -5879,10 +5879,9 @@
      */
     @Override
     public void getCallWaitingStatus(int subId, IIntegerConsumer callback) {
-        enforceReadPrivilegedPermission("getCallForwarding");
+        enforceReadPrivilegedPermission("getCallWaitingStatus");
         long identity = Binder.clearCallingIdentity();
         try {
-
             Phone phone = getPhone(subId);
             if (phone == null) {
                 try {
@@ -5892,11 +5891,35 @@
                 }
                 return;
             }
-
-            Consumer<Integer> argument = FunctionalUtils.ignoreRemoteException(callback::accept);
+            CarrierConfigManager configManager = new CarrierConfigManager(phone.getContext());
+            PersistableBundle c = configManager.getConfigForSubId(subId);
+            boolean requireUssd = c.getBoolean(
+                    CarrierConfigManager.KEY_USE_CALL_WAITING_USSD_BOOL, false);
 
             if (DBG) log("getCallWaitingStatus: subId " + subId);
-            sendRequestAsync(CMD_GET_CALL_WAITING, argument, phone, null);
+            if (requireUssd) {
+                CarrierXmlParser carrierXmlParser = new CarrierXmlParser(phone.getContext(),
+                        getSubscriptionCarrierId(subId));
+                String newUssdCommand = "";
+                try {
+                    newUssdCommand = carrierXmlParser.getFeature(
+                            CarrierXmlParser.FEATURE_CALL_WAITING)
+                            .makeCommand(CarrierXmlParser.SsEntry.SSAction.QUERY, null);
+                } catch (NullPointerException e) {
+                    loge("Failed to generate USSD number" + e);
+                }
+                ResultReceiver wrappedCallback = new CallWaitingUssdResultReceiver(
+                        mMainThreadHandler, callback, carrierXmlParser,
+                        CarrierXmlParser.SsEntry.SSAction.QUERY);
+                final String ussdCommand = newUssdCommand;
+                Executors.newSingleThreadExecutor().execute(() -> {
+                    handleUssdRequest(subId, ussdCommand, wrappedCallback);
+                });
+            } else {
+                Consumer<Integer> argument = FunctionalUtils.ignoreRemoteException(
+                        callback::accept);
+                sendRequestAsync(CMD_GET_CALL_WAITING, argument, phone, null);
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -5922,10 +5945,38 @@
                 return;
             }
 
-            Pair<Boolean, Consumer<Integer>> arguments = Pair.create(enable,
-                    FunctionalUtils.ignoreRemoteException(callback::accept));
+            CarrierConfigManager configManager = new CarrierConfigManager(phone.getContext());
+            PersistableBundle c = configManager.getConfigForSubId(subId);
+            boolean requireUssd = c.getBoolean(
+                    CarrierConfigManager.KEY_USE_CALL_WAITING_USSD_BOOL, false);
 
-            sendRequestAsync(CMD_SET_CALL_WAITING, arguments, phone, null);
+            if (DBG) log("getCallWaitingStatus: subId " + subId);
+            if (requireUssd) {
+                CarrierXmlParser carrierXmlParser = new CarrierXmlParser(phone.getContext(),
+                        getSubscriptionCarrierId(subId));
+                CarrierXmlParser.SsEntry.SSAction ssAction =
+                        enable ? CarrierXmlParser.SsEntry.SSAction.UPDATE_ACTIVATE
+                                : CarrierXmlParser.SsEntry.SSAction.UPDATE_DEACTIVATE;
+                String newUssdCommand = "";
+                try {
+                    newUssdCommand = carrierXmlParser.getFeature(
+                            CarrierXmlParser.FEATURE_CALL_WAITING)
+                            .makeCommand(ssAction, null);
+                } catch (NullPointerException e) {
+                    loge("Failed to generate USSD number" + e);
+                }
+                ResultReceiver wrappedCallback = new CallWaitingUssdResultReceiver(
+                        mMainThreadHandler, callback, carrierXmlParser, ssAction);
+                final String ussdCommand = newUssdCommand;
+                Executors.newSingleThreadExecutor().execute(() -> {
+                    handleUssdRequest(subId, ussdCommand, wrappedCallback);
+                });
+            } else {
+                Pair<Boolean, Consumer<Integer>> arguments = Pair.create(enable,
+                        FunctionalUtils.ignoreRemoteException(callback::accept));
+
+                sendRequestAsync(CMD_SET_CALL_WAITING, arguments, phone, null);
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }