Make changes to upload code resulting from testing
Make tweaks to the HTTP upload process that were revealed to be
necessary during the live network testing process.
Bug: 182425585
Test: manual
Change-Id: Ib0058bc6de4f8a5e2bb3f4d5b2e84c05d3617fad
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureManager.java b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
index 3c9e27e..818994a 100644
--- a/src/com/android/phone/callcomposer/CallComposerPictureManager.java
+++ b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
@@ -20,6 +20,7 @@
import android.location.Location;
import android.net.Uri;
import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.provider.CallLog;
import android.telephony.CarrierConfigManager;
@@ -33,14 +34,12 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.phone.callcomposer.CallComposerPictureTransfer.PictureCallback;
import com.android.phone.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
-import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -53,6 +52,7 @@
public class CallComposerPictureManager {
private static final String TAG = CallComposerPictureManager.class.getSimpleName();
private static final SparseArray<CallComposerPictureManager> sInstances = new SparseArray<>();
+ private static final String THREE_GPP_BOOTSTRAPPING = "3GPP-bootstrapping";
public static CallComposerPictureManager getInstance(Context context, int subscriptionId) {
synchronized (sInstances) {
@@ -104,7 +104,7 @@
private final HashMap<UUID, String> mCachedServerUrls = new HashMap<>();
private final HashMap<UUID, ImageData> mCachedImages = new HashMap<>();
- private final Map<String, GbaCredentials> mCachedCredentials = new HashMap<>();
+ private GbaCredentials mCachedCredentials = null;
private final int mSubscriptionId;
private final TelephonyManager mTelephonyManager;
private final Context mContext;
@@ -127,7 +127,8 @@
return;
}
- String uploadUrl = mTelephonyManager.getCarrierConfig().getString(
+ PersistableBundle carrierConfig = mTelephonyManager.getCarrierConfig();
+ String uploadUrl = carrierConfig.getString(
CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
if (TextUtils.isEmpty(uploadUrl)) {
Log.e(TAG, "Call composer upload URL not configured in carrier config");
@@ -141,7 +142,7 @@
mSubscriptionId, uploadUrl, sExecutorService);
AtomicBoolean hasRetried = new AtomicBoolean(false);
- transfer.setCallback(new PictureCallback() {
+ transfer.setCallback(new CallComposerPictureTransfer.PictureCallback() {
@Override
public void onError(int error) {
callback.accept(Pair.create(null, error));
@@ -157,7 +158,7 @@
}
GbaCredentialsSupplier supplier =
(realm, executor) ->
- getGbaCredentials(credentialRefresh, realm, executor);
+ getGbaCredentials(credentialRefresh, carrierConfig, executor);
sExecutorService.schedule(() -> transfer.uploadPicture(imageData, supplier),
backoffMillis, TimeUnit.MILLISECONDS);
@@ -174,7 +175,7 @@
});
transfer.uploadPicture(imageData,
- (realm, executor) -> getGbaCredentials(false, realm, executor));
+ (realm, executor) -> getGbaCredentials(false, carrierConfig, executor));
}
public void handleDownloadFromServer(CallComposerPictureTransfer.Factory transferFactory,
@@ -187,11 +188,12 @@
return;
}
+ PersistableBundle carrierConfig = mTelephonyManager.getCarrierConfig();
CallComposerPictureTransfer transfer = transferFactory.create(mContext,
mSubscriptionId, remoteUrl, sExecutorService);
AtomicBoolean hasRetried = new AtomicBoolean(false);
- transfer.setCallback(new PictureCallback() {
+ transfer.setCallback(new CallComposerPictureTransfer.PictureCallback() {
@Override
public void onError(int error) {
callback.accept(Pair.create(null, error));
@@ -207,7 +209,7 @@
}
GbaCredentialsSupplier supplier =
(realm, executor) ->
- getGbaCredentials(credentialRefresh, realm, executor);
+ getGbaCredentials(credentialRefresh, carrierConfig, executor);
sExecutorService.schedule(() -> transfer.downloadPicture(supplier),
backoffMillis, TimeUnit.MILLISECONDS);
@@ -237,7 +239,8 @@
}
});
- transfer.downloadPicture(((realm, executor) -> getGbaCredentials(false, realm, executor)));
+ transfer.downloadPicture(((realm, executor) ->
+ getGbaCredentials(false, carrierConfig, executor)));
}
public void storeUploadedPictureToCallLog(UUID id, Consumer<Uri> callback) {
@@ -298,32 +301,36 @@
}
private CompletableFuture<GbaCredentials> getGbaCredentials(
- boolean forceRefresh, String nafId, Executor executor) {
- synchronized (mCachedCredentials) {
- if (!forceRefresh && mCachedCredentials.containsKey(nafId)) {
- return CompletableFuture.completedFuture(mCachedCredentials.get(nafId));
+ boolean forceRefresh, PersistableBundle config, Executor executor) {
+ synchronized (this) {
+ if (!forceRefresh && mCachedCredentials != null) {
+ return CompletableFuture.completedFuture(mCachedCredentials);
}
+
if (forceRefresh) {
- mCachedCredentials.remove(nafId);
+ mCachedCredentials = null;
}
}
UaSecurityProtocolIdentifier securityProtocolIdentifier =
new UaSecurityProtocolIdentifier.Builder()
- .setOrg(UaSecurityProtocolIdentifier.ORG_3GPP)
- .setProtocol(UaSecurityProtocolIdentifier
- .UA_SECURITY_PROTOCOL_3GPP_HTTP_DIGEST_AUTHENTICATION)
+ .setOrg(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_ORGANIZATION_INT))
+ .setProtocol(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_PROTOCOL_INT))
+ .setTlsCipherSuite(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_TLS_CIPHER_SUITE_INT))
.build();
CompletableFuture<GbaCredentials> resultFuture = new CompletableFuture<>();
- mTelephonyManager.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_UNKNOWN,
- Uri.parse(nafId), securityProtocolIdentifier, forceRefresh, executor,
+ mTelephonyManager.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_ISIM,
+ getNafUri(config), securityProtocolIdentifier, forceRefresh, executor,
new TelephonyManager.BootstrapAuthenticationCallback() {
@Override
public void onKeysAvailable(byte[] gbaKey, String transactionId) {
GbaCredentials creds = new GbaCredentials(transactionId, gbaKey);
- synchronized (mCachedCredentials) {
- mCachedCredentials.put(nafId, creds);
+ synchronized (CallComposerPictureManager.this) {
+ mCachedCredentials = creds;
}
resultFuture.complete(creds);
}
@@ -338,6 +345,30 @@
return resultFuture;
}
+ private static Uri getNafUri(PersistableBundle carrierConfig) {
+ String uploadUriString = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ Uri uploadUri = Uri.parse(uploadUriString);
+ String nafPrefix;
+ switch (carrierConfig.getInt(CarrierConfigManager.KEY_GBA_MODE_INT)) {
+ case CarrierConfigManager.GBA_U:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING + "-uicc";
+ break;
+ case CarrierConfigManager.GBA_DIGEST:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING + "-digest";
+ break;
+ case CarrierConfigManager.GBA_ME:
+ default:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING;
+ }
+ String newAuthority = nafPrefix + "@" + uploadUri.getAuthority();
+ Uri nafUri = new Uri.Builder().scheme(uploadUri.getScheme())
+ .encodedAuthority(newAuthority)
+ .build();
+ Log.i(TAG, "using NAF uri " + nafUri + " for GBA");
+ return nafUri;
+ }
+
@VisibleForTesting
static ScheduledExecutorService getExecutor() {
return sExecutorService;
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java b/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
index 1a176dd..e4458cd 100644
--- a/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
+++ b/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
@@ -21,6 +21,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -49,6 +50,9 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -140,7 +144,10 @@
mExecutorService);
networkUrlFuture.thenAcceptAsync((result) -> {
if (result != null) mCallback.onUploadSuccessful(result);
- }, mExecutorService);
+ }, mExecutorService).exceptionally((ex) -> {
+ logException("Exception uploading image" , ex);
+ return null;
+ });
}
public void downloadPicture(GbaCredentialsSupplier credentialsSupplier) {
@@ -203,6 +210,9 @@
}
if (fromAuth != null) mCallback.onDownloadSuccessful(fromAuth);
mCallback.onDownloadSuccessful(fromImmediate);
+ }).exceptionally((ex) -> {
+ logException("Exception downloading image" , ex);
+ return null;
});
}
@@ -223,7 +233,7 @@
return resultFuture;
}
- private static HttpURLConnection prepareInitialPost(Network network, String uploadUrl) {
+ private HttpURLConnection prepareInitialPost(Network network, String uploadUrl) {
try {
HttpURLConnection connection =
(HttpURLConnection) network.openConnection(new URL(uploadUrl));
@@ -231,7 +241,7 @@
connection.setInstanceFollowRedirects(false);
connection.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
connection.setReadTimeout(HTTP_TIMEOUT_MILLIS);
- connection.setRequestProperty("User-Agent", THREE_GPP_GBA);
+ connection.setRequestProperty("User-Agent", getUserAgent());
return connection;
} catch (MalformedURLException e) {
Log.e(TAG, "Malformed URL: " + uploadUrl);
@@ -242,14 +252,14 @@
}
}
- private static HttpURLConnection prepareImageDownloadRequest(Network network, String imageUrl) {
+ private HttpURLConnection prepareImageDownloadRequest(Network network, String imageUrl) {
try {
HttpURLConnection connection =
(HttpURLConnection) network.openConnection(new URL(imageUrl));
connection.setRequestMethod("GET");
connection.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
connection.setReadTimeout(HTTP_TIMEOUT_MILLIS);
- connection.setRequestProperty("User-Agent", THREE_GPP_GBA);
+ connection.setRequestProperty("User-Agent", getUserAgent());
return connection;
} catch (MalformedURLException e) {
Log.e(TAG, "Malformed URL: " + imageUrl);
@@ -387,7 +397,7 @@
public void sendDispositionHeader(OutputStream out) throws IOException {
super.sendDispositionHeader(out);
if (filename != null) {
- String fileNameSuffix = ";filename=\"" + filename + "\"";
+ String fileNameSuffix = "; filename=\"" + filename + "\"";
out.write(fileNameSuffix.getBytes());
}
}
@@ -416,6 +426,11 @@
HttpURLConnection connection = prepareInitialPost(network, mUrl);
connection.setDoOutput(true);
connection.addRequestProperty("Authorization", authHeader);
+ connection.addRequestProperty("Content-Length",
+ String.valueOf(multipartEntity.getContentLength()));
+ connection.addRequestProperty("Content-Type", multipartEntity.getContentType().getValue());
+ connection.addRequestProperty("Accept-Encoding", "*");
+
try (OutputStream requestBodyOut = connection.getOutputStream()) {
multipartEntity.writeTo(requestBodyOut);
} catch (IOException e) {
@@ -425,6 +440,8 @@
try {
int response = connection.getResponseCode();
+ Log.i(TAG, "Received response code: " + response
+ + ", message=" + connection.getResponseMessage());
if (response == 401 || response == 403) {
deliverFailure(TelephonyManager.CallComposerException.ERROR_AUTHENTICATION_FAILED);
return null;
@@ -493,6 +510,22 @@
return sb.toString();
}
+ private String getUserAgent() {
+ String carrierName = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mSubscriptionId)
+ .getSimOperatorName();
+ String buildId = Build.ID;
+ String buildDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(Build.TIME));
+ String buildVersion = Build.VERSION.RELEASE_OR_CODENAME;
+ String deviceName = Build.DEVICE;
+ return String.format("%s %s %s %s %s %s %s",
+ carrierName, buildId, buildDate, "Android", buildVersion,
+ deviceName, THREE_GPP_GBA);
+
+ }
+
private static void logException(String message, Throwable e) {
StringWriter log = new StringWriter();
log.append(message);
diff --git a/src/com/android/phone/callcomposer/DigestAuthUtils.java b/src/com/android/phone/callcomposer/DigestAuthUtils.java
index 52a278b..2f081f7 100644
--- a/src/com/android/phone/callcomposer/DigestAuthUtils.java
+++ b/src/com/android/phone/callcomposer/DigestAuthUtils.java
@@ -56,9 +56,13 @@
if (!TextUtils.isEmpty(parsedHeader.getAlgorithm())
&& !MD5_ALGORITHM.equals(parsedHeader.getAlgorithm().toLowerCase())) {
Log.e(TAG, "This client only supports MD5 auth");
+ return "";
}
-
- Log.i(TAG, "nonce=" + parsedHeader.getNonce());
+ if (!TextUtils.isEmpty(parsedHeader.getQop())
+ && !AUTH_QOP.equals(parsedHeader.getQop().toLowerCase())) {
+ Log.e(TAG, "This client only supports the auth qop");
+ return "";
+ }
String clientNonce = makeClientNonce();
@@ -71,7 +75,9 @@
replyHeader.setScheme(parsedHeader.getScheme());
replyHeader.setUsername(credentials.getTransactionId());
replyHeader.setURI(new WorkaroundURI(uri));
+ replyHeader.setRealm(parsedHeader.getRealm());
replyHeader.setQop(AUTH_QOP);
+ replyHeader.setNonce(parsedHeader.getNonce());
replyHeader.setCNonce(clientNonce);
replyHeader.setNonceCount(1);
replyHeader.setResponse(response);
@@ -83,7 +89,7 @@
return null;
}
- return replyHeader.encode();
+ return replyHeader.encodeBody();
}
public static String computeResponse(String serverNonce, String clientNonce, String qop,
diff --git a/tests/src/com/android/phone/callcomposer/PictureManagerTest.java b/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
index b52b297..f1ce3b8 100644
--- a/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
+++ b/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
@@ -34,6 +34,7 @@
import android.provider.CallLog;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
+import android.telephony.gba.TlsParams;
import android.telephony.gba.UaSecurityProtocolIdentifier;
import org.junit.After;
@@ -78,6 +79,14 @@
PersistableBundle b = new PersistableBundle();
b.putString(CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING,
FAKE_URL_BASE);
+ b.putInt(CarrierConfigManager.KEY_GBA_MODE_INT,
+ CarrierConfigManager.GBA_ME);
+ b.putInt(CarrierConfigManager.KEY_GBA_UA_SECURITY_ORGANIZATION_INT,
+ UaSecurityProtocolIdentifier.ORG_3GPP);
+ b.putInt(CarrierConfigManager.KEY_GBA_UA_SECURITY_PROTOCOL_INT,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_DEFAULT);
+ b.putInt(CarrierConfigManager.KEY_GBA_UA_TLS_CIPHER_SUITE_INT,
+ TlsParams.TLS_RSA_WITH_AES_128_CBC_SHA);
when(telephonyManager.getCarrierConfig()).thenReturn(b);
}
@@ -263,7 +272,7 @@
public void testGbaCredLookup(GbaCredentialsSupplier supplier, boolean forceExpected)
throws Exception {
- String fakeRealm = "3gpp-bootstraping@naf1.example.com";
+ String fakeNafId = "https://3GPP-bootstrapping@www.example.com";
byte[] fakeKey = new byte[] {1, 2, 3, 4, 5};
String fakeTxId = "89sdfjggf";
@@ -271,8 +280,9 @@
ArgumentCaptor.forClass(TelephonyManager.BootstrapAuthenticationCallback.class);
CompletableFuture<GbaCredentials> credsFuture =
- supplier.getCredentials(fakeRealm, CallComposerPictureManager.getExecutor());
- verify(telephonyManager).bootstrapAuthenticationRequest(anyInt(), eq(Uri.parse(fakeRealm)),
+ supplier.getCredentials(fakeNafId, CallComposerPictureManager.getExecutor());
+ verify(telephonyManager).bootstrapAuthenticationRequest(anyInt(),
+ eq(Uri.parse(fakeNafId)),
nullable(UaSecurityProtocolIdentifier.class), eq(forceExpected),
nullable(Executor.class),
authCallbackCaptor.capture());
@@ -285,9 +295,9 @@
// Do it again and see if we make another request, then make sure that matches up with what
// we expected.
CompletableFuture<GbaCredentials> credsFuture1 =
- supplier.getCredentials(fakeRealm, CallComposerPictureManager.getExecutor());
+ supplier.getCredentials(fakeNafId, CallComposerPictureManager.getExecutor());
verify(telephonyManager, times(forceExpected ? 2 : 1))
- .bootstrapAuthenticationRequest(anyInt(), eq(Uri.parse(fakeRealm)),
+ .bootstrapAuthenticationRequest(anyInt(), eq(Uri.parse(fakeNafId)),
nullable(UaSecurityProtocolIdentifier.class),
eq(forceExpected),
nullable(Executor.class),