Merge "[Telephony] Use #registerPhoneStateListener() to listen EVENT instead of #listen()" into sc-dev
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index dc432d0..e9ec299 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -105,23 +105,23 @@
     <string name="labelCF" msgid="3578719437928476078">"कॉल फॉरवर्डिंग"</string>
     <string name="labelCFU" msgid="8870170873036279706">"नेहमी फॉरवर्ड करा"</string>
     <string name="messageCFU" msgid="1361806450979589744">"नेहमी हा नंबर वापरा"</string>
-    <string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"सर्व कॉल अग्रेषित करत आहे"</string>
+    <string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"सर्व कॉल फॉरवर्ड करत आहे"</string>
     <string name="sum_cfu_enabled" msgid="5806923046528144526">"सर्व कॉल <xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
     <string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"नंबर अनुपलब्‍ध आहे"</string>
     <string name="sum_cfu_disabled" msgid="5010617134210809853">"बंद"</string>
     <string name="labelCFB" msgid="615265213360512768">"व्यस्त असताना"</string>
     <string name="messageCFB" msgid="1958017270393563388">"नंबर व्‍यस्‍त असताना"</string>
-    <string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर अग्रेषित करत आहे"</string>
+    <string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
     <string name="sum_cfb_disabled" msgid="3589913334164866035">"बंद"</string>
     <string name="disable_cfb_forbidden" msgid="4831494744351633961">"तुमचा फोन व्‍यस्‍त असताना तुमचा ऑपरेटर कॉल अग्रेषण करणे अक्षम करण्‍यास समर्थन करीत नाही."</string>
     <string name="labelCFNRy" msgid="3403533792248457946">"उत्तर न दिल्यास"</string>
     <string name="messageCFNRy" msgid="7644434155765359009">"नंबर अनुत्तरित असताना"</string>
-    <string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर अग्रेषित करत आहे"</string>
+    <string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
     <string name="sum_cfnry_disabled" msgid="1990563512406017880">"बंद"</string>
     <string name="disable_cfnry_forbidden" msgid="3174731413216550689">"तुमचा फोन उत्तर देत नसताना तुमचा ऑपरेटर कॉल अग्रेषण करणे अक्षम करण्‍यास समर्थन करीत नाही."</string>
     <string name="labelCFNRc" msgid="4163399350778066013">"आउट ऑफ रीच असताना"</string>
     <string name="messageCFNRc" msgid="6980340731313007250">"नंबर पोहचण्‍यायोग्‍य नसताना"</string>
-    <string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर अग्रेषित करत आहे"</string>
+    <string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
     <string name="sum_cfnrc_disabled" msgid="739289696796917683">"बंद"</string>
     <string name="disable_cfnrc_forbidden" msgid="775348748084726890">"तुमचा फोन पोहचण्‍यायोग्‍य नसताना तुमचा वाहक कॉल अग्रेषण करणे अक्षम करण्‍यास समर्थन करीत नाही."</string>
     <string name="registration_cf_forbidden" msgid="4386482610771190420">"तुमचा वाहक कॉल फॉरवर्डिंग करण्यास सपोर्ट करत नाही."</string>
@@ -157,7 +157,7 @@
   </string-array>
     <string name="vm_changed" msgid="4739599044379692505">"व्हॉइसमेल नंबर बदलला."</string>
     <string name="vm_change_failed" msgid="7877733929455763566">"व्हॉइसमेल नंबर बदलू शकले नाही.\nही समस्‍या  कायम राहिल्‍यास आपल्‍या वाहकाशी संपर्क साधा."</string>
-    <string name="fw_change_failed" msgid="9179241823460192148">"अग्रेषित करण्‍याचा नंबर बदलू शकलो नाही.\n ही समस्‍या कायम राहिल्‍यास आपल्‍या वाहकाशी संपर्क साधा."</string>
+    <string name="fw_change_failed" msgid="9179241823460192148">"फॉरवर्ड करण्‍याचा नंबर बदलू शकलो नाही.\n ही समस्‍या कायम राहिल्‍यास आपल्‍या वाहकाशी संपर्क साधा."</string>
     <string name="fw_get_in_vm_failed" msgid="2432678237218183844">"वर्तमान अग्रेषण नंबर सेटिंग्‍ज पुनर्प्राप्त करू शकलो नाही आणि सेव्ह करू शकलो नाही.\nतरीही नवीन प्रदात्‍यावर स्‍विच करायचे?"</string>
     <string name="no_change" msgid="3737264882821031892">"कोणतेही बदल केले नाहीत."</string>
     <string name="sum_voicemail_choose_provider" msgid="6750824719081403773">"व्हॉइसमेल सेवा निवडा"</string>
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 12c8cda..2c87f7c 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -263,10 +263,6 @@
     public void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
             List<Uri> contactNumbers, IRcsUceControllerCallback c) {
         enforceReadPrivilegedPermission("requestCapabilities");
-        if (!isUceSettingEnabled(subId, callingPackage, callingFeatureId)) {
-            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
-                    "The user has not enabled UCE for this subscription.");
-        }
         final long token = Binder.clearCallingIdentity();
         try {
             UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -284,13 +280,9 @@
     }
 
     @Override
-    public void requestNetworkAvailability(int subId, String callingPackage,
+    public void requestAvailability(int subId, String callingPackage,
             String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
-        enforceReadPrivilegedPermission("requestNetworkAvailability");
-        if (!isUceSettingEnabled(subId, callingPackage, callingFeatureId)) {
-            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
-                    "The user has not enabled UCE for this subscription.");
-        }
+        enforceReadPrivilegedPermission("requestAvailability");
         final long token = Binder.clearCallingIdentity();
         try {
             UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
diff --git a/src/com/android/phone/LocalConnectionImpl.java b/src/com/android/phone/LocalConnectionImpl.java
new file mode 100644
index 0000000..c2630ef
--- /dev/null
+++ b/src/com/android/phone/LocalConnectionImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.content.Context;
+import android.telephony.TelephonyLocalConnection;
+
+import com.android.phone.callcomposer.CallComposerPictureManager;
+
+import java.util.UUID;
+
+public class LocalConnectionImpl implements TelephonyLocalConnection.ConnectionImpl {
+    private Context mContext;
+
+    public LocalConnectionImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public String getCallComposerServerUrlForHandle(int subscriptionId, UUID uuid) {
+        return CallComposerPictureManager.getInstance(mContext, subscriptionId)
+                .getServerUrlForImageId(uuid);
+    }
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 0aaccea..23f6d7e 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -49,6 +49,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyLocalConnection;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
@@ -330,6 +331,9 @@
 
         ContentResolver resolver = getContentResolver();
 
+        // Initialize the shim from frameworks/opt/telephony into packages/services/Telephony.
+        TelephonyLocalConnection.setInstance(new LocalConnectionImpl(this));
+
         // Cache the "voice capable" flag.
         // This flag currently comes from a resource (which is
         // overrideable on a per-product basis):
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 59aed4b..b5ee6ca 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -131,6 +131,7 @@
 
 import com.android.ims.ImsManager;
 import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.ims.rcs.uce.eab.EabUtil;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.CallStateException;
@@ -185,6 +186,9 @@
 import com.android.internal.telephony.util.VoicemailNotificationSettingsUtil;
 import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.HexDump;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.callcomposer.CallComposerPictureTransfer;
+import com.android.phone.callcomposer.ImageData;
 import com.android.phone.settings.PickSmsSubscriptionActivity;
 import com.android.phone.vvm.PhoneAccountHandleConverter;
 import com.android.phone.vvm.RemoteVvmTaskManager;
@@ -209,7 +213,6 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -7007,13 +7010,28 @@
                 return;
             }
 
-            // TODO: pass along the bytes read to the carrier somehow
+            if (!readUntilEnd) {
+                loge("Did not finish reading entire image; aborting");
+                return;
+            }
 
-            ParcelUuid result = new ParcelUuid(UUID.randomUUID());
-            // TODO: cache this uuid that's been associated with the picture
-            Bundle outputResult = new Bundle();
-            outputResult.putParcelable(TelephonyManager.KEY_CALL_COMPOSER_PICTURE_HANDLE, result);
-            callback.send(-1, outputResult);
+            ImageData imageData = new ImageData(output.toByteArray(), contentType, null);
+            CallComposerPictureManager.getInstance(mApp, subscriptionId).handleUploadToServer(
+                    new CallComposerPictureTransfer.Factory() {},
+                    imageData,
+                    (result) -> {
+                        if (result.first != null) {
+                            ParcelUuid parcelUuid = new ParcelUuid(result.first);
+                            Bundle outputResult = new Bundle();
+                            outputResult.putParcelable(
+                                    TelephonyManager.KEY_CALL_COMPOSER_PICTURE_HANDLE, parcelUuid);
+                            callback.send(TelephonyManager.CallComposerException.SUCCESS,
+                                    outputResult);
+                        } else {
+                            callback.send(result.second, null);
+                        }
+                    }
+            );
         });
     }
 
@@ -9890,6 +9908,36 @@
         }
     }
 
+    /**
+     * Get the EAB contact from the EAB database.
+     */
+    @Override
+    public String getContactFromEab(String contact) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getContactFromEab");
+        enforceModifyPermission();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return EabUtil.getContactFromEab(getDefaultPhone().getContext(), contact);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Remove the EAB contacts from the EAB database.
+     */
+    @Override
+    public int removeContactFromEab(int subId, String contacts) {
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "removeCapabilitiesFromEab");
+        enforceModifyPermission();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return EabUtil.removeContactFromEab(subId, contacts, getDefaultPhone().getContext());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Override
     public void setSignalStrengthUpdateRequest(int subId, SignalStrengthUpdateRequest request,
             String callingPackage) {
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 5088424..3130913 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -21,7 +21,9 @@
 import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_BATTERY_STATE;
 import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE;
 
+import android.Manifest;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -32,6 +34,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.feature.ImsFeature;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.ITelephony;
@@ -41,6 +44,7 @@
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.phone.callcomposer.CallComposerPictureManager;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -48,6 +52,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Takes actions based on the adb commands given by "adb shell cmd phone ...". Be careful, no
@@ -62,6 +68,7 @@
     private static final boolean VDBG = true;
     private static final int DEFAULT_PHONE_ID = 0;
 
+    private static final String CALL_COMPOSER_SUBCOMMAND = "callcomposer";
     private static final String IMS_SUBCOMMAND = "ims";
     private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify";
     private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode";
@@ -69,14 +76,16 @@
     private static final String RESTART_MODEM = "restart-modem";
     private static final String CARRIER_CONFIG_SUBCOMMAND = "cc";
     private static final String DATA_TEST_MODE = "data";
-    private static final String DATA_ENABLE = "enable";
-    private static final String DATA_DISABLE = "disable";
+    private static final String ENABLE = "enable";
+    private static final String DISABLE = "disable";
+    private static final String QUERY = "query";
+
+    private static final String CALL_COMPOSER_TEST_MODE = "test_mode";
+    private static final String CALL_COMPOSER_SIMULATE_CALL = "simulate-outgoing-call";
 
     private static final String IMS_SET_IMS_SERVICE = "set-ims-service";
     private static final String IMS_GET_IMS_SERVICE = "get-ims-service";
     private static final String IMS_CLEAR_SERVICE_OVERRIDE = "clear-ims-service-override";
-    private static final String IMS_ENABLE = "enable";
-    private static final String IMS_DISABLE = "disable";
     // Used to disable or enable processing of conference event package data from the network.
     // This is handy for testing scenarios where CEP data does not exist on a network which does
     // support CEP data.
@@ -104,6 +113,10 @@
     private static final String D2D_SUBCOMMAND = "d2d";
     private static final String D2D_SEND = "send";
 
+    private static final String RCS_UCE_COMMAND = "uce";
+    private static final String UCE_GET_EAB_CONTACT = "get-eab-contact";
+    private static final String UCE_REMOVE_EAB_CONTACT = "remove-eab-contact";
+
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -173,6 +186,8 @@
             case IMS_SUBCOMMAND: {
                 return handleImsCommand();
             }
+            case RCS_UCE_COMMAND:
+                return handleRcsUceCommand();
             case NUMBER_VERIFICATION_SUBCOMMAND:
                 return handleNumberVerificationCommand();
             case EMERGENCY_NUMBER_TEST_MODE:
@@ -192,6 +207,8 @@
                 return handleSingleRegistrationConfigCommand();
             case RESTART_MODEM:
                 return handleRestartModemCommand();
+            case CALL_COMPOSER_SUBCOMMAND:
+                return handleCallComposerCommand();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -206,6 +223,8 @@
         pw.println("    Print this help text.");
         pw.println("  ims");
         pw.println("    IMS Commands.");
+        pw.println("  uce");
+        pw.println("    RCS User Capability Exchange Commands.");
         pw.println("  emergency-number-test-mode");
         pw.println("    Emergency Number Test Mode Commands.");
         pw.println("  end-block-suppression");
@@ -221,6 +240,7 @@
         pw.println("  restart-modem");
         pw.println("    Restart modem command.");
         onHelpIms();
+        onHelpUce();
         onHelpEmergencyNumber();
         onHelpEndBlockSupperssion();
         onHelpDataTestMode();
@@ -282,6 +302,23 @@
         pw.println("    enables or disables handling or network conference event package data.");
     }
 
+    private void onHelpUce() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("User Capability Exchange Commands:");
+        pw.println("  uce get-eab-contact [PHONE_NUMBER]");
+        pw.println("    Get the EAB contacts from the EAB database.");
+        pw.println("    Options are:");
+        pw.println("      PHONE_NUMBER: The phone numbers to be removed from the EAB databases");
+        pw.println("    Expected output format :");
+        pw.println("      [PHONE_NUMBER],[RAW_CONTACT_ID],[CONTACT_ID],[DATA_ID]");
+        pw.println("  uce remove-eab-contact [-s SLOT_ID] [PHONE_NUMBER]");
+        pw.println("    Remove the EAB contacts from the EAB database.");
+        pw.println("    Options are:");
+        pw.println("      -s: The SIM slot ID to read carrier config value for. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
+        pw.println("      PHONE_NUMBER: The phone numbers to be removed from the EAB databases");
+    }
+
     private void onHelpNumberVerification() {
         PrintWriter pw = getOutPrintWriter();
         pw.println("Number verification commands");
@@ -413,10 +450,10 @@
             case IMS_CLEAR_SERVICE_OVERRIDE: {
                 return handleImsClearCarrierServiceCommand();
             }
-            case IMS_ENABLE: {
+            case ENABLE: {
                 return handleEnableIms();
             }
-            case IMS_DISABLE: {
+            case DISABLE: {
                 return handleDisableIms();
             }
             case IMS_CEP: {
@@ -435,7 +472,7 @@
             return 0;
         }
         switch (arg) {
-            case DATA_ENABLE: {
+            case ENABLE: {
                 try {
                     mInterface.enableDataConnectivity();
                 } catch (RemoteException ex) {
@@ -445,7 +482,7 @@
                 }
                 break;
             }
-            case DATA_DISABLE: {
+            case DISABLE: {
                 try {
                     mInterface.disableDataConnectivity();
                 } catch (RemoteException ex) {
@@ -1582,6 +1619,69 @@
         return -1;
     }
 
+    private int handleRcsUceCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            Log.w(LOG_TAG, "cannot get uce parameter");
+            return -1;
+        }
+
+        switch (arg) {
+            case UCE_REMOVE_EAB_CONTACT:
+                return handleRemovingEabContactCommand();
+            case UCE_GET_EAB_CONTACT:
+                return handleGettingEabContactCommand();
+        }
+        return -1;
+    }
+
+    private int handleRemovingEabContactCommand() {
+        int subId = getSubId("uce remove-eab-contact");
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return -1;
+        }
+
+        String phoneNumber = getNextArgRequired();
+        if (TextUtils.isEmpty(phoneNumber)) {
+            return -1;
+        }
+        int result = 0;
+        try {
+            result = mInterface.removeContactFromEab(subId, phoneNumber);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "uce remove-eab-contact -s " + subId + ", error " + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+
+        if (VDBG) {
+            Log.v(LOG_TAG, "uce remove-eab-contact -s " + subId + ", result: " + result);
+        }
+        return result;
+    }
+
+    private int handleGettingEabContactCommand() {
+        String phoneNumber = getNextArgRequired();
+        if (TextUtils.isEmpty(phoneNumber)) {
+            return -1;
+        }
+        String result = "";
+        try {
+            result = mInterface.getContactFromEab(phoneNumber);
+
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "uce get-eab-contact, error " + e.getMessage());
+            getErrPrintWriter().println("Exception: " + e.getMessage());
+            return -1;
+        }
+
+        if (VDBG) {
+            Log.v(LOG_TAG, "uce get-eab-contact, result: " + result);
+        }
+        getOutPrintWriter().println(result);
+        return 0;
+    }
+
     private int handleSrcSetDeviceEnabledCommand() {
         String enabledStr = getNextArg();
         if (enabledStr == null) {
@@ -1664,4 +1764,62 @@
         getOutPrintWriter().println(result);
         return 0;
     }
+
+    private void onHelpCallComposer() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Call composer commands");
+        pw.println("  callcomposer test-mode enable|disable|query");
+        pw.println("    Enables or disables test mode for call composer. In test mode, picture");
+        pw.println("    upload/download from carrier servers is disabled, and operations are");
+        pw.println("    performed using emulated local files instead.");
+        pw.println("  callcomposer simulate-outgoing-call [subId] [UUID]");
+        pw.println("    Simulates an outgoing call being placed with the picture ID as");
+        pw.println("    the provided UUID. This triggers storage to the call log.");
+    }
+
+    private int handleCallComposerCommand() {
+        String arg = getNextArg();
+        if (arg == null) {
+            onHelpCallComposer();
+            return 0;
+        }
+
+        mContext.enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE,
+                "MODIFY_PHONE_STATE required for call composer shell cmds");
+        switch (arg) {
+            case CALL_COMPOSER_TEST_MODE: {
+                String enabledStr = getNextArg();
+                if (ENABLE.equals(enabledStr)) {
+                    CallComposerPictureManager.sTestMode = true;
+                } else if (DISABLE.equals(enabledStr)) {
+                    CallComposerPictureManager.sTestMode = false;
+                } else if (QUERY.equals(enabledStr)) {
+                    getOutPrintWriter().println(CallComposerPictureManager.sTestMode);
+                } else {
+                    onHelpCallComposer();
+                    return 1;
+                }
+                break;
+            }
+            case CALL_COMPOSER_SIMULATE_CALL: {
+                int subscriptionId = Integer.valueOf(getNextArg());
+                String uuidString = getNextArg();
+                UUID uuid = UUID.fromString(uuidString);
+                CompletableFuture<Uri> storageUriFuture = new CompletableFuture<>();
+                Binder.withCleanCallingIdentity(() -> {
+                    CallComposerPictureManager.getInstance(mContext, subscriptionId)
+                            .storeUploadedPictureToCallLog(uuid, storageUriFuture::complete);
+                });
+                try {
+                    Uri uri = storageUriFuture.get();
+                    getOutPrintWriter().println(String.valueOf(uri));
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                break;
+            }
+        }
+
+        return 0;
+    }
 }
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureManager.java b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
index 81f088a..b27a27c 100644
--- a/src/com/android/phone/callcomposer/CallComposerPictureManager.java
+++ b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
@@ -79,8 +79,8 @@
 
     // disabled provisionally until the auth stack is fully operational
     @VisibleForTesting
-    public static boolean sHttpOperationsEnabled = false;
-    private static final String FAKE_SERVER_URL = "https://example.com/FAKE.png";
+    public static boolean sTestMode = false;
+    public static final String FAKE_SERVER_URL = "https://example.com/FAKE.png";
 
     public interface CallLogProxy {
         default void storeCallComposerPictureAsUser(Context context,
@@ -111,7 +111,7 @@
 
     public void handleUploadToServer(CallComposerPictureTransfer.Factory transferFactory,
             ImageData imageData, Consumer<Pair<UUID, Integer>> callback) {
-        if (!sHttpOperationsEnabled) {
+        if (sTestMode) {
             UUID id = UUID.randomUUID();
             mCachedImages.put(id, imageData);
             mCachedServerUrls.put(id, FAKE_SERVER_URL);
@@ -171,7 +171,7 @@
 
     public void handleDownloadFromServer(CallComposerPictureTransfer.Factory transferFactory,
             String remoteUrl, Consumer<Pair<Uri, Integer>> callback) {
-        if (!sHttpOperationsEnabled) {
+        if (sTestMode) {
             ImageData imageData = new ImageData(getPlaceholderPictureAsBytes(), "image/png", null);
             UUID id = UUID.randomUUID();
             mCachedImages.put(id, imageData);
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 1fd6739..fd8d936 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -642,6 +642,8 @@
         mCellInfoRefreshRateSpinner.setOnItemSelectedListener(mCellInfoRefreshRateHandler);
         //set selection after registering listener to force update
         mCellInfoRefreshRateSpinner.setSelection(mCellInfoRefreshRateIndex);
+        // Request cell information update from RIL.
+        mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[mCellInfoRefreshRateIndex]);
 
         //set selection before registering to prevent update
         mPreferredNetworkType.setSelection(mPreferredNetworkTypeResult, true);
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index ee4baae..235cbce 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.phone.NumberVerificationManager;
 import com.android.phone.PhoneUtils;
+import com.android.phone.callcomposer.CallComposerPictureManager;
 import com.android.telephony.Rlog;
 
 import java.util.List;
@@ -297,6 +298,11 @@
             if (imsCall != null) {
                 ImsCallProfile imsCallProfile = imsCall.getCallProfile();
                 if (imsCallProfile != null) {
+                    if (CallComposerPictureManager.sTestMode) {
+                        imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_PICTURE_URL,
+                                CallComposerPictureManager.FAKE_SERVER_URL);
+                    }
+
                     extras.putInt(TelecomManager.EXTRA_PRIORITY,
                             imsCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_PRIORITY));
                     extras.putString(TelecomManager.EXTRA_CALL_SUBJECT,
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 5f51efe..c41bf1a 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -76,6 +76,8 @@
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.callcomposer.CallComposerPictureTransfer;
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
@@ -1131,13 +1133,35 @@
     @Override
     public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
         if (isImsConnection()) {
-            if (!isBlocked && isInContacts) {
+            ImsPhone imsPhone = (getPhone() instanceof ImsPhone) ? (ImsPhone) getPhone() : null;
+            if (imsPhone != null
+                    && imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
+                    && !isBlocked && isInContacts) {
                 ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
                 ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
+                String serverUrl = CallComposerPictureManager.sTestMode
+                        ? CallComposerPictureManager.FAKE_SERVER_URL
+                        : profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL);
                 if (profile != null
-                        && !TextUtils.isEmpty(
-                                profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL))) {
-                    // TODO: start off the picture download
+                        && !TextUtils.isEmpty(serverUrl)) {
+                    CallComposerPictureManager manager = CallComposerPictureManager
+                            .getInstance(getPhone().getContext(), getPhone().getSubId());
+                    manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {},
+                            serverUrl,
+                            (result) -> {
+                                if (result.first != null) {
+                                    Bundle newExtras = new Bundle();
+                                    newExtras.putParcelable(TelecomManager.EXTRA_PICTURE_URI,
+                                            result.first);
+                                    putTelephonyExtras(newExtras);
+                                } else {
+                                    Log.i(this, "Call composer picture download:"
+                                            + " error=" + result.second);
+                                    Bundle newExtras = new Bundle();
+                                    newExtras.putBoolean(TelecomManager.EXTRA_HAS_PICTURE, false);
+                                    putTelephonyExtras(newExtras);
+                                }
+                            });
                 }
             }
         }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 5b2ed38..219c782 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -31,6 +31,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.ParcelUuid;
 import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
@@ -70,6 +71,7 @@
 import com.android.phone.MMIDialogActivity;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
+import com.android.phone.callcomposer.CallComposerPictureManager;
 import com.android.phone.settings.SuppServicesUiUtil;
 
 import java.lang.ref.WeakReference;
@@ -1080,7 +1082,8 @@
         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
             int dataNetType = phone.getServiceState().getDataNetworkType();
             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
-                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA ||
+                    dataNetType == TelephonyManager.NETWORK_TYPE_NR) {
                 state = phone.getServiceState().getDataRegistrationState();
             }
         }
@@ -1709,6 +1712,22 @@
             return;
         }
 
+        if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) {
+            ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE);
+            CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId())
+                    .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> {
+                        if (uri != null) {
+                            try {
+                                Bundle b = new Bundle();
+                                b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri);
+                                connection.putTelephonyExtras(b);
+                            } catch (Exception e) {
+                                Log.e(this, e, "Couldn't set picture extra on outgoing call");
+                            }
+                        }
+                    });
+        }
+
         com.android.internal.telephony.Connection originalConnection = null;
         try {
             if (phone != null) {
diff --git a/tests/src/com/android/phone/callcomposer/PictureManagerTest.java b/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
index 80b1dd6..b52b297 100644
--- a/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
+++ b/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
@@ -63,12 +63,14 @@
     @Mock Context context;
     @Mock TelephonyManager telephonyManager;
 
-    private boolean originalHttpOpValue = false;
+    private boolean originalTestMode = false;
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        originalHttpOpValue = CallComposerPictureManager.sHttpOperationsEnabled;
-        CallComposerPictureManager.sHttpOperationsEnabled = true;
+        originalTestMode = CallComposerPictureManager.sTestMode;
+        // Even though this is a test, we want test mode off so we can actually exercise the logic
+        // in the class.
+        CallComposerPictureManager.sTestMode = false;
         when(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(telephonyManager);
         when(context.getSystemServiceName(TelephonyManager.class))
                 .thenReturn(Context.TELEPHONY_SERVICE);
@@ -81,7 +83,7 @@
 
     @After
     public void tearDown() throws Exception {
-        CallComposerPictureManager.sHttpOperationsEnabled = originalHttpOpValue;
+        CallComposerPictureManager.sTestMode = originalTestMode;
         CallComposerPictureManager.clearInstances();
     }