Merge "Support Gba Api"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 83a5673..1613ca8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2172,4 +2172,12 @@
     <string name="carrier_provisioning">Carrier Provisioning Info</string>
     <!-- Trigger Carrier Provisioning [CHAR LIMIT=NONE] -->
     <string name="trigger_carrier_provisioning">Trigger Carrier Provisioning</string>
+
+    <!-- details of the message popped up when there is
+    bad call quality caused by bluetooth connection-->
+    <string name="call_quality_notification_bluetooth_details">
+        Suggestion: Improve Bluetooth connectivity</string>
+    <!-- name of the notification that pops up during
+    a phone call when there is bad call quality -->
+    <string name="call_quality_notification_name">Call Quality Notification</string>
 </resources>
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index fe4a0ba..4fb61f0 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.READ_PHONE_STATE;
 
 import android.annotation.Nullable;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -106,6 +107,12 @@
 
     private static final String ACTION_MOBILE_NETWORK_LIST = "android.settings.MOBILE_NETWORK_LIST";
 
+    /**
+     * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background
+     * service to do VVM processing.
+     */
+    private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000L;
+
     /** The singleton NotificationMgr instance. */
     private static NotificationMgr sInstance;
 
@@ -470,7 +477,10 @@
                             pendingIntent);
                 }
             }
-            mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE);
+
+            BroadcastOptions bopts = BroadcastOptions.makeBasic();
+            bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS);
+            mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, bopts.toBundle());
             return true;
         }
 
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index ca29534..2f37870 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -310,6 +310,8 @@
     private static final int EVENT_GET_SYSTEM_SELECTION_CHANNELS_DONE = 98;
     private static final int CMD_SET_DATA_THROTTLING = 99;
     private static final int EVENT_SET_DATA_THROTTLING_DONE = 100;
+    private static final int CMD_SET_SIM_POWER = 101;
+    private static final int EVENT_SET_SIM_POWER_DONE = 102;
 
     // Parameters of select command.
     private static final int SELECT_COMMAND = 0xA4;
@@ -1753,6 +1755,56 @@
                     Log.w(LOG_TAG, "DataThrottlingResult = " + request.result);
                     notifyRequester(request);
                     break;
+
+                case CMD_SET_SIM_POWER: {
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted = obtainMessage(EVENT_SET_SIM_POWER_DONE, request);
+                    request = (MainThreadRequest) msg.obj;
+                    int stateToSet =
+                            ((Pair<Integer, IIntegerConsumer>)
+                                    request.argument).first;
+                    request.phone.setSimPowerState(stateToSet, onCompleted, request.workSource);
+                    break;
+                }
+                case EVENT_SET_SIM_POWER_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    IIntegerConsumer callback =
+                            ((Pair<Integer, IIntegerConsumer>) request.argument).second;
+                    if (ar.exception != null) {
+                        loge("setSimPower exception: " + ar.exception);
+                        int errorCode = TelephonyManager.CallForwardingInfoCallback
+                                .RESULT_ERROR_UNKNOWN;
+                        if (ar.exception instanceof CommandException) {
+                            CommandException.Error error =
+                                    ((CommandException) (ar.exception)).getCommandError();
+                            if (error == CommandException.Error.SIM_ERR) {
+                                errorCode = TelephonyManager.SET_SIM_POWER_STATE_SIM_ERROR;
+                            } else if (error == CommandException.Error.INVALID_ARGUMENTS) {
+                                errorCode = TelephonyManager.SET_SIM_POWER_STATE_ALREADY_IN_STATE;
+                            } else if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
+                                errorCode = TelephonyManager.SET_SIM_POWER_STATE_NOT_SUPPORTED;
+                            } else {
+                                errorCode = TelephonyManager.SET_SIM_POWER_STATE_MODEM_ERROR;
+                            }
+                        }
+                        try {
+                            callback.accept(errorCode);
+                        } catch (RemoteException e) {
+                            // Ignore if the remote process is no longer available to call back.
+                            Log.w(LOG_TAG, "setSimPower: callback not available.");
+                        }
+                    } else {
+                        try {
+                            callback.accept(TelephonyManager.SET_SIM_POWER_STATE_SUCCESS);
+                        } catch (RemoteException e) {
+                            // Ignore if the remote process is no longer available to call back.
+                            Log.w(LOG_TAG, "setSimPower: callback not available.");
+                        }
+                    }
+                    break;
+                }
+
                 default:
                     Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
                     break;
@@ -7846,7 +7898,37 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             if (phone != null) {
-                phone.setSimPowerState(state, workSource);
+                phone.setSimPowerState(state, null, workSource);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Set SIM card power state.
+     *
+     * @param slotIndex SIM slot id.
+     * @param state  State of SIM (power down, power up, pass through)
+     * @param callback  callback to trigger after success or failure
+     * - {@link android.telephony.TelephonyManager#CARD_POWER_DOWN}
+     * - {@link android.telephony.TelephonyManager#CARD_POWER_UP}
+     * - {@link android.telephony.TelephonyManager#CARD_POWER_UP_PASS_THROUGH}
+     *
+     **/
+    @Override
+    public void setSimPowerStateForSlotWithCallback(int slotIndex, int state,
+            IIntegerConsumer callback) {
+        enforceModifyPermission();
+        Phone phone = PhoneFactory.getPhone(slotIndex);
+
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (phone != null) {
+                Pair<Integer, IIntegerConsumer> arguments = Pair.create(state, callback);
+                sendRequestAsync(CMD_SET_SIM_POWER, arguments, phone, workSource);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/src/com/android/services/telephony/CallQualityManager.java b/src/com/android/services/telephony/CallQualityManager.java
new file mode 100644
index 0000000..01b5bae
--- /dev/null
+++ b/src/com/android/services/telephony/CallQualityManager.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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.services.telephony;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.telecom.BluetoothCallQualityReport;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
+
+/**
+ * class to handle call quality events that are received by telecom and telephony
+ */
+public class CallQualityManager {
+    private static final String TAG = CallQualityManager.class.getCanonicalName();
+    private static final String CALL_QUALITY_REPORT_CHANNEL = "call_quality_report_channel";
+
+    /** notification ids */
+    public static final int BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID = 700;
+
+    public static final String CALL_QUALITY_CHANNEL_ID = "CallQualityNotification";
+
+    private final Context mContext;
+    private final NotificationChannel mNotificationChannel;
+    private final NotificationManager mNotificationManager;
+
+    public CallQualityManager(Context context) {
+        mContext = context;
+        mNotificationChannel = new NotificationChannel(CALL_QUALITY_CHANNEL_ID,
+                mContext.getString(R.string.call_quality_notification_name),
+                NotificationManager.IMPORTANCE_HIGH);
+        mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mNotificationManager.createNotificationChannel(mNotificationChannel);
+    }
+
+    /**
+     * method that is called whenever a
+     * {@code BluetoothCallQualityReport.EVENT_SEND_BLUETOOTH_CALL_QUALITY_REPORT} is received
+     * @param extras Bundle that includes serialized {@code BluetoothCallQualityReport} parcelable
+     */
+    @VisibleForTesting
+    public void onBluetoothCallQualityReported(Bundle extras) {
+        if (extras == null) {
+            Log.d(TAG, "onBluetoothCallQualityReported: no extras provided");
+        }
+
+        BluetoothCallQualityReport callQualityReport = extras.getParcelable(
+                BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT);
+
+        if (callQualityReport.isChoppyVoice()) {
+            onChoppyVoice();
+        }
+        // TODO: once other signals are also sent, we will add more actions here
+    }
+
+    /**
+     * method to post a notification to user suggesting ways to improve call quality in case of
+     * bluetooth choppy voice
+     */
+    @VisibleForTesting
+    public void onChoppyVoice() {
+        String title = "Call Quality Improvement";
+        //TODO: update call_quality_bluetooth_enhancement_suggestion with below before submitting:
+//        "Voice is not being transmitted properly via your bluetooth device."
+//                + "To improve, try:\n"
+//                + "1. moving your phone closer to your bluetooth device\n"
+//                + "2. using a different bluetooth device, or your phone's speaker\n";
+        popUpNotification(title,
+                mContext.getText(R.string.call_quality_notification_bluetooth_details));
+    }
+
+    private void popUpNotification(String title, CharSequence details) {
+        int iconId = android.R.drawable.stat_notify_error;
+
+        Notification notification = new Notification.Builder(mContext)
+                .setSmallIcon(iconId)
+                .setWhen(System.currentTimeMillis())
+                .setAutoCancel(true)
+                .setContentTitle(title)
+                .setContentText(details)
+                .setStyle(new Notification.BigTextStyle().bigText(details))
+                .setOngoing(true)
+                .setChannelId(CALL_QUALITY_CHANNEL_ID)
+                .setOnlyAlertOnce(true)
+                .build();
+
+        mNotificationManager.notify(TAG, BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID, notification);
+    }
+}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index d3c1d40..8c56b11 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -1412,6 +1412,17 @@
         return false;
     }
 
+    PhoneAccountHandle getPhoneAccountHandleForSubId(int subId) {
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getSubId() == subId) {
+                    return entry.getPhoneAccountHandle();
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Un-registers any {@link PhoneAccount}s which are no longer present in the list
      * {@code AccountEntry}(s).
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 7af1678..ab5c353 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.telecom.BluetoothCallQualityReport;
 import android.telecom.CallAudioState;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -810,6 +811,8 @@
     private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
             new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
 
+    private CallQualityManager mCallQualityManager;
+
     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
             String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
         setCallDirection(callDirection);
@@ -819,6 +822,20 @@
         }
     }
 
+    @Override
+    public void onCallEvent(String event, Bundle extras) {
+        switch (event) {
+            case BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT:
+                if (mCallQualityManager == null) {
+                    mCallQualityManager = new CallQualityManager(getPhone().getContext());
+                }
+                mCallQualityManager.onBluetoothCallQualityReported(extras);
+                break;
+            default:
+                break;
+        }
+
+    }
     /**
      * Creates a clone of the current {@link TelephonyConnection}.
      *
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 6f6360d..11d7044 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -1162,9 +1162,9 @@
                             phone.getPhoneId()));
         }
 
-
+        PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle());
         final TelephonyConnection connection =
-                createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
+                createConnectionFor(phone, null, true /* isOutgoing */, accountHandle,
                         request.getTelecomCallId(), request.isAdhocConferenceCall());
         if (connection == null) {
             return Connection.createFailedConnection(
@@ -2491,4 +2491,20 @@
         addConference(conference);
         conference.addTelephonyConferenceListener(mTelephonyConferenceListener);
     }
+
+    private PhoneAccountHandle adjustAccountHandle(Phone phone,
+            PhoneAccountHandle origAccountHandle) {
+        int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle);
+        int subId = phone.getSubId();
+        if (origSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && origSubId != subId) {
+            PhoneAccountHandle handle = TelecomAccountRegistry.getInstance(this)
+                .getPhoneAccountHandleForSubId(subId);
+            if (handle != null) {
+                return handle;
+            }
+        }
+        return origAccountHandle;
+    }
 }