Integrate call composer into the call flow
* Link uploadCallComposerPicture with the actual http upload code
* Perform download of an incoming picture once call filtering completes
* Store uploaded image into the call log once a call has been made
* Add shell commands for CTS testing of the above
Bug: 177613111
Test: atest CallComposerTest
Merged-In: I403e0b4b004b3a22ec0b1dae40f1cc88fee494fc
Change-Id: If20fd2c535b1fc0c79ded7e91bfa10e262e4630e
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..2c83ff9 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -185,6 +185,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 +212,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 +7009,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);
+ }
+ }
+ );
});
}
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 5088424..5edd51c 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;
@@ -41,6 +43,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 +51,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 +67,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 +75,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.
@@ -192,6 +200,8 @@
return handleSingleRegistrationConfigCommand();
case RESTART_MODEM:
return handleRestartModemCommand();
+ case CALL_COMPOSER_SUBCOMMAND:
+ return handleCallComposerCommand();
default: {
return handleDefaultCommands(cmd);
}
@@ -413,10 +423,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 +445,7 @@
return 0;
}
switch (arg) {
- case DATA_ENABLE: {
+ case ENABLE: {
try {
mInterface.enableDataConnectivity();
} catch (RemoteException ex) {
@@ -445,7 +455,7 @@
}
break;
}
- case DATA_DISABLE: {
+ case DISABLE: {
try {
mInterface.disableDataConnectivity();
} catch (RemoteException ex) {
@@ -1664,4 +1674,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/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..971f41b 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,33 @@
@Override
public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
if (isImsConnection()) {
- if (!isBlocked && isInContacts) {
+ ImsPhone imsPhone = (ImsPhone) getPhone().getImsPhone();
+ if (imsPhone != null
+ && imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
+ && !isBlocked && isInContacts) {
ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
if (profile != null
&& !TextUtils.isEmpty(
- profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL))) {
- // TODO: start off the picture download
+ profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL))) {
+ CallComposerPictureManager manager = CallComposerPictureManager
+ .getInstance(getPhone().getContext(), getPhone().getSubId());
+ manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {},
+ profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL),
+ (result) -> {
+ if (result.first != null) {
+ Bundle newExtras = new Bundle();
+ newExtras.putParcelable(TelecomManager.EXTRA_INCOMING_PICTURE,
+ 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..459c7c4 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;
@@ -1709,6 +1711,12 @@
return;
}
+ if (extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) {
+ ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE);
+ CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId())
+ .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> { });
+ }
+
com.android.internal.telephony.Connection originalConnection = null;
try {
if (phone != null) {