diff --git a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointClientFactory.java b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointClientFactory.java index 45396ff382..7be4abd5f0 100644 --- a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointClientFactory.java +++ b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointClientFactory.java @@ -20,7 +20,9 @@ import com.amplifyframework.analytics.AnalyticsException; import com.amplifyframework.core.Amplify; import com.amplifyframework.logging.Logger; +import com.amplifyframework.util.UserAgent; +import com.amazonaws.ClientConfiguration; import com.amazonaws.mobile.client.AWSMobileClient; import com.amazonaws.mobile.client.Callback; import com.amazonaws.mobile.client.UserStateDetails; @@ -76,6 +78,9 @@ public void onError(Exception exception) { throw new RuntimeException("Failed to initialize mobile client: " + exception.getLocalizedMessage()); } + ClientConfiguration clientConfiguration = new ClientConfiguration(); + clientConfiguration.setUserAgent(UserAgent.string()); + // Construct configuration using information from the configure method PinpointConfiguration pinpointConfiguration = new PinpointConfiguration( context, @@ -83,7 +88,7 @@ public void onError(Exception exception) { Regions.fromName(pinpointAnalyticsPluginConfiguration.getRegion()), ChannelType.GCM, AWSMobileClient.getInstance() - ); + ).withClientConfiguration(clientConfiguration); pinpointManager = new PinpointManager(pinpointConfiguration); return pinpointManager.getAnalyticsClient(); diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/AWSApiPlugin.java b/aws-api/src/main/java/com/amplifyframework/api/aws/AWSApiPlugin.java index 64badb9af0..2c30d87b4b 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/AWSApiPlugin.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/AWSApiPlugin.java @@ -39,6 +39,7 @@ import com.amplifyframework.core.Consumer; import com.amplifyframework.core.model.Model; import com.amplifyframework.core.model.query.predicate.QueryPredicate; +import com.amplifyframework.util.UserAgent; import org.json.JSONObject; @@ -113,6 +114,7 @@ public void configure(@NonNull JSONObject pluginConfigurationJson, @Nullable Con final ApiConfiguration apiConfiguration = entry.getValue(); final EndpointType endpointType = apiConfiguration.getEndpointType(); final OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.addNetworkInterceptor(UserAgentInterceptor.using(UserAgent::string)); if (apiConfiguration.getAuthorizationType() != AuthorizationType.NONE) { builder.addInterceptor(interceptorFactory.create(apiConfiguration)); } diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java b/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java index 70f7348027..6c0ba09b93 100644 --- a/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/SubscriptionEndpoint.java @@ -27,6 +27,7 @@ import com.amplifyframework.api.graphql.GraphQLResponse; import com.amplifyframework.core.Action; import com.amplifyframework.core.Consumer; +import com.amplifyframework.util.UserAgent; import org.json.JSONException; import org.json.JSONObject; @@ -157,6 +158,7 @@ private WebSocket createWebSocket() throws ApiException { .build(); return new OkHttpClient.Builder() + .addNetworkInterceptor(UserAgentInterceptor.using(UserAgent::string)) .retryOnConnectionFailure(true) .build() .newWebSocket(request, new WebSocketListener() { diff --git a/aws-api/src/main/java/com/amplifyframework/api/aws/UserAgentInterceptor.java b/aws-api/src/main/java/com/amplifyframework/api/aws/UserAgentInterceptor.java new file mode 100644 index 0000000000..ecd466f5f5 --- /dev/null +++ b/aws-api/src/main/java/com/amplifyframework/api/aws/UserAgentInterceptor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.amplifyframework.api.aws; + +import androidx.annotation.NonNull; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * An OkHttp3 interceptor which applies a User-Agent header to an outgoing request. + */ +final class UserAgentInterceptor implements Interceptor { + private final UserAgentProvider userAgentProvider; + + /** + * Constructs a UserAgentInterceptor. + * @param userAgentProvider A Provider of a user-agent string + */ + private UserAgentInterceptor(final UserAgentProvider userAgentProvider) { + this.userAgentProvider = userAgentProvider; + } + + /** + * Creates a user agent interceptor using a user-agent string provider. + * @param userAgentProvider Provider of user-agent string + * @return A UserAgentInterceptor + */ + static UserAgentInterceptor using(UserAgentProvider userAgentProvider) { + return new UserAgentInterceptor(userAgentProvider); + } + + @NonNull + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + Request originalRequest = chain.request(); + Request requestWithUserAgent = originalRequest.newBuilder() + .header("User-Agent", userAgentProvider.getUserAgent()) + .build(); + return chain.proceed(requestWithUserAgent); + } + + /** + * A provider of a user-agent string. + */ + interface UserAgentProvider { + /** + * Gets the User-Agent string. + * @return User-Agent string + */ + String getUserAgent(); + } +} diff --git a/aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/service/AWSS3StorageService.java b/aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/service/AWSS3StorageService.java index 141b5c5b71..e99d84e82f 100644 --- a/aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/service/AWSS3StorageService.java +++ b/aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/service/AWSS3StorageService.java @@ -19,7 +19,10 @@ import android.content.Intent; import com.amplifyframework.storage.result.StorageListResult; +import com.amplifyframework.util.UserAgent; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.mobile.client.AWSMobileClient; import com.amazonaws.mobileconnectors.s3.transferutility.TransferObserver; import com.amazonaws.mobileconnectors.s3.transferutility.TransferService; @@ -59,7 +62,7 @@ public final class AWSS3StorageService { public AWSS3StorageService(Region region, Context context, String bucket, boolean transferAcceleration) { this.context = context; this.bucket = bucket; - this.client = new AmazonS3Client(AWSMobileClient.getInstance(), region); + this.client = createS3Client(region); if (transferAcceleration) { client.setS3ClientOptions(S3ClientOptions.builder().setAccelerateModeEnabled(true).build()); @@ -71,6 +74,13 @@ public AWSS3StorageService(Region region, Context context, String bucket, boolea .build(); } + private AmazonS3Client createS3Client(Region region) { + AWSCredentialsProvider credentialsProvider = AWSMobileClient.getInstance(); + ClientConfiguration configuration = new ClientConfiguration(); + configuration.setUserAgent(UserAgent.string()); + return new AmazonS3Client(credentialsProvider, region, configuration); + } + /** * Begin downloading a file. * @param serviceKey S3 service key diff --git a/core/src/main/java/com/amplifyframework/util/UserAgent.java b/core/src/main/java/com/amplifyframework/util/UserAgent.java new file mode 100644 index 0000000000..efd86cf0ca --- /dev/null +++ b/core/src/main/java/com/amplifyframework/util/UserAgent.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.amplifyframework.util; + +import android.annotation.SuppressLint; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.amazonaws.amplify.core.BuildConfig; + +/** + * A utility to construct a User-Agent header, to be sent with all network operations. + */ +public final class UserAgent { + private static String instance = null; + + @SuppressWarnings("checkstyle:all") private UserAgent() {} + + /** + * Gets a String to use as the value of a User-Agent header. + * @return A value for a User-Agent header. + */ + @SuppressLint("SyntheticAccessor") + @NonNull + public static String string() { + if (instance == null) { + instance = new UserAgent.Builder() + .libraryName("amplify-android") + .libraryVersion(BuildConfig.VERSION_NAME) + .systemName(System.getProperty("os.name")) + .systemVersion(System.getProperty("os.version")) + .javaVmName(System.getProperty("java.vm.name")) + .javaVmVersion(System.getProperty("java.vm.version")) + .javaVersion(System.getProperty("java.version")) + .userLanguage(System.getProperty("user.language")) + .userRegion(System.getProperty("user.region")) + .toString(); + } + + return instance; + } + + @SuppressWarnings("SameParameterValue") + private static final class Builder { + private String libraryName; + private String libraryVersion; + private String systemName; + private String systemVersion; + private String javaVmName; + private String javaVmVersion; + private String javaVersion; + private String userLanguage; + private String userRegion; + + Builder libraryName(String libraryName) { + this.libraryName = sanitize(libraryName); + return this; + } + + Builder libraryVersion(String libraryVersion) { + this.libraryVersion = sanitize(libraryVersion); + return this; + } + + Builder systemName(String systemName) { + this.systemName = sanitize(systemName); + return this; + } + + Builder systemVersion(String systemVersion) { + this.systemVersion = sanitize(systemVersion); + return this; + } + + Builder javaVmName(String javaVmName) { + this.javaVmName = sanitize(javaVmName); + return this; + } + + Builder javaVmVersion(String javaVmVersion) { + this.javaVmVersion = sanitize(javaVmVersion); + return this; + } + + Builder javaVersion(String javaVersion) { + this.javaVersion = sanitize(javaVersion); + return this; + } + + Builder userLanguage(String userLanguage) { + this.userLanguage = sanitize(userLanguage); + return this; + } + + Builder userRegion(String userRegion) { + this.userRegion = sanitize(userRegion); + return this; + } + + @NonNull + @Override + public String toString() { + return String.format( + "%s/%s %s/%s %s/%s/%s %s_%s", + libraryName, libraryVersion, + systemName, systemVersion, + javaVmName, javaVmVersion, javaVersion, + userLanguage, userRegion + ); + } + + @NonNull + private static String sanitize(@Nullable String string) { + if (string == null) { + return "UNKNOWN"; + } + + return string.replace(' ', '_'); + } + } +}