aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2022-02-11 06:57:32 +0000
committerXin Li <delphij@google.com>2022-02-11 06:57:32 +0000
commitf7bd394f22e035c8c5be34ae9e3f8b43f299d22e (patch)
tree7dada9629976efaca4537893d3c6841bdee6f692
parentd683d7d4f36353ff6c40a719c785a0dd7e66e60d (diff)
parent265a3eca1141c83f1e7b58c781b3588d91a7c270 (diff)
downloadconnectedappssdk-f7bd394f22e035c8c5be34ae9e3f8b43f299d22e.tar.gz
Merge sc-v2-dev-plus-aosp-without-vendor@8084891sam_222710654
Bug: 214455710 Merged-In: Ib299774f0d1722c0a40c3d9f6657882e5b2e2498 Change-Id: I45469716d8fc4030da6c31a691e3207b2b2be2e5
-rw-r--r--processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherGenerator.java15
-rw-r--r--processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeOtherGenerator.java6
-rw-r--r--processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileClassGenerator.java318
-rw-r--r--processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileGenerator.java6
-rw-r--r--processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SupportedTypes.java525
-rw-r--r--processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TypeUtils.java19
-rw-r--r--sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileConnector.java1
-rw-r--r--sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileSender.java42
-rw-r--r--sdk/src/main/java/com/google/android/enterprise/connectedapps/exceptions/ProfileRuntimeException.java4
-rw-r--r--sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BackgroundExceptionThrower.java22
10 files changed, 519 insertions, 439 deletions
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherGenerator.java
index f5264b0..a4f8ef6 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherGenerator.java
@@ -187,6 +187,21 @@ final class DispatcherGenerator {
methodCode.addStatement("$T.throwInBackground(e)", BACKGROUND_EXCEPTION_THROWER_CLASSNAME);
methodCode.addStatement("return throwableBytes");
+ methodCode.nextControlFlow("catch ($T e)", Error.class);
+
+ // parcel is recycled in this method
+ methodCode.addStatement("$1T throwableParcel = $1T.obtain()", PARCEL_CLASSNAME);
+ methodCode.add("throwableParcel.writeInt(1); //errors\n");
+ methodCode.addStatement(
+ "$T.writeThrowableToParcel(throwableParcel, e)", PARCEL_UTILITIES_CLASSNAME);
+ methodCode.addStatement(
+ "$1T throwableBytes = parcelCallReceiver.prepareResponse(callId, throwableParcel)",
+ ArrayTypeName.of(byte.class));
+ methodCode.addStatement("throwableParcel.recycle()");
+
+ methodCode.addStatement("$T.throwInBackground(e)", BACKGROUND_EXCEPTION_THROWER_CLASSNAME);
+
+ methodCode.addStatement("return throwableBytes");
methodCode.endControlFlow();
MethodSpec callMethod =
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeOtherGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeOtherGenerator.java
index 20bf0fb..53e5c72 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeOtherGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeOtherGenerator.java
@@ -215,6 +215,8 @@ final class FakeOtherGenerator {
methodBuilder.addStatement(methodCall);
methodBuilder.nextControlFlow("catch ($T e)", RuntimeException.class);
methodBuilder.addStatement("throw new $T(e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
+ methodBuilder.nextControlFlow("catch ($T e)", Error.class);
+ methodBuilder.addStatement("throw new $T(e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
methodBuilder.endControlFlow();
classBuilder.addMethod(methodBuilder.build());
}
@@ -267,6 +269,8 @@ final class FakeOtherGenerator {
methodBuilder.addStatement(methodCall);
methodBuilder.nextControlFlow("catch ($T e)", RuntimeException.class);
methodBuilder.addStatement("throw new $T(e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
+ methodBuilder.nextControlFlow("catch ($T e)", Error.class);
+ methodBuilder.addStatement("throw new $T(e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
methodBuilder.endControlFlow();
classBuilder.addMethod(methodBuilder.build());
@@ -332,6 +336,8 @@ final class FakeOtherGenerator {
}
methodBuilder.nextControlFlow("catch ($T e)", RuntimeException.class);
methodBuilder.addStatement("throw new $T(e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
+ methodBuilder.nextControlFlow("catch ($T e)", Error.class);
+ methodBuilder.addStatement("throw new $T(e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
methodBuilder.endControlFlow();
classBuilder.addMethod(methodBuilder.build());
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileClassGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileClassGenerator.java
index f4aad75..bbbf0ab 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileClassGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileClassGenerator.java
@@ -64,9 +64,9 @@ final class InternalCrossProfileClassGenerator {
private final CrossProfileTypeInfo crossProfileType;
InternalCrossProfileClassGenerator(
- GeneratorContext generatorContext,
- ProviderClassInfo providerClass,
- CrossProfileTypeInfo crossProfileType) {
+ GeneratorContext generatorContext,
+ ProviderClassInfo providerClass,
+ CrossProfileTypeInfo crossProfileType) {
this.generatorContext = checkNotNull(generatorContext);
this.generatorUtilities = new GeneratorUtilities(generatorContext);
this.providerClass = checkNotNull(providerClass);
@@ -76,7 +76,7 @@ final class InternalCrossProfileClassGenerator {
void generate() {
if (generated) {
throw new IllegalStateException(
- "InternalCrossProfileClassGenerator#generate can only be called once");
+ "InternalCrossProfileClassGenerator#generate can only be called once");
}
generated = true;
@@ -87,60 +87,60 @@ final class InternalCrossProfileClassGenerator {
ClassName className = getInternalCrossProfileClassName(generatorContext, crossProfileType);
TypeSpec.Builder classBuilder =
- TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+ TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC, Modifier.FINAL);
classBuilder.addJavadoc(
- "Internal class for {@link $T}.\n\n"
- + "<p>This is used by the Connected Apps SDK to dispatch cross-profile calls.\n\n"
- + "<p>Cross-profile type identifier: $L.\n",
- crossProfileType.crossProfileTypeElement().asType(),
- crossProfileType.identifier());
+ "Internal class for {@link $T}.\n\n"
+ + "<p>This is used by the Connected Apps SDK to dispatch cross-profile calls.\n\n"
+ + "<p>Cross-profile type identifier: $L.\n",
+ crossProfileType.crossProfileTypeElement().asType(),
+ crossProfileType.identifier());
classBuilder.addField(
- FieldSpec.builder(className, "instance")
- .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
- .initializer("new $T()", className)
- .build());
+ FieldSpec.builder(className, "instance")
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .initializer("new $T()", className)
+ .build());
classBuilder.addField(
- FieldSpec.builder(BUNDLER_CLASSNAME, "bundler")
- .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
- .initializer(
- "new $T()",
- BundlerGenerator.getBundlerClassName(generatorContext, crossProfileType))
- .build());
+ FieldSpec.builder(BUNDLER_CLASSNAME, "bundler")
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .initializer(
+ "new $T()",
+ BundlerGenerator.getBundlerClassName(generatorContext, crossProfileType))
+ .build());
if (!crossProfileType.isStatic()) {
ExecutableElement providerMethod =
- providerClass.findProviderMethodFor(generatorContext, crossProfileType);
+ providerClass.findProviderMethodFor(generatorContext, crossProfileType);
String paramsString = providerMethod.getParameters().isEmpty() ? "()" : "(context)";
CodeBlock providerMethodCall =
- CodeBlock.of("$L$L", providerMethod.getSimpleName(), paramsString);
+ CodeBlock.of("$L$L", providerMethod.getSimpleName(), paramsString);
classBuilder.addMethod(
- MethodSpec.methodBuilder("crossProfileType")
- .addParameter(CONTEXT_CLASSNAME, "context")
- .returns(crossProfileType.className())
- .addStatement(
- "return $T.instance().providerClass(context).$L",
- InternalProviderClassGenerator.getInternalProviderClassName(
- generatorContext, providerClass),
- providerMethodCall)
- .build());
+ MethodSpec.methodBuilder("crossProfileType")
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .returns(crossProfileType.className())
+ .addStatement(
+ "return $T.instance().providerClass(context).$L",
+ InternalProviderClassGenerator.getInternalProviderClassName(
+ generatorContext, providerClass),
+ providerMethodCall)
+ .build());
}
classBuilder.addMethod(
- MethodSpec.methodBuilder("bundler")
- .returns(BUNDLER_CLASSNAME)
- .addStatement("return bundler")
- .build());
+ MethodSpec.methodBuilder("bundler")
+ .returns(BUNDLER_CLASSNAME)
+ .addStatement("return bundler")
+ .build());
classBuilder.addMethod(
- MethodSpec.methodBuilder("instance")
- .addModifiers(Modifier.STATIC)
- .returns(className)
- .addStatement("return instance")
- .build());
+ MethodSpec.methodBuilder("instance")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(className)
+ .addStatement("return instance")
+ .build());
classBuilder.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build());
@@ -152,23 +152,23 @@ final class InternalCrossProfileClassGenerator {
}
private void addMethodsField(
- TypeSpec.Builder classBuilder, CrossProfileTypeInfo crossProfileType) {
+ TypeSpec.Builder classBuilder, CrossProfileTypeInfo crossProfileType) {
int totalMethods = crossProfileType.crossProfileMethods().size();
classBuilder.addField(
- FieldSpec.builder(ArrayTypeName.of(METHOD_RUNNER_CLASSNAME), "methods")
- .addModifiers(Modifier.PRIVATE)
- .initializer(
- "new $T[]{$L}",
- METHOD_RUNNER_CLASSNAME,
- IntStream.range(0, totalMethods)
- .mapToObj(n -> "this::method" + n)
- .collect(joining(",")))
- .build());
+ FieldSpec.builder(ArrayTypeName.of(METHOD_RUNNER_CLASSNAME), "methods")
+ .addModifiers(Modifier.PRIVATE)
+ .initializer(
+ "new $T[]{$L}",
+ METHOD_RUNNER_CLASSNAME,
+ IntStream.range(0, totalMethods)
+ .mapToObj(n -> "this::method" + n)
+ .collect(joining(",")))
+ .build());
}
private void addCrossProfileTypeMethods(
- TypeSpec.Builder classBuilder, CrossProfileTypeInfo crossProfileType) {
+ TypeSpec.Builder classBuilder, CrossProfileTypeInfo crossProfileType) {
for (CrossProfileMethodInfo method : crossProfileType.crossProfileMethods()) {
if (method.isBlocking(generatorContext, crossProfileType)) {
addBlockingCrossProfileTypeMethod(classBuilder, method);
@@ -183,7 +183,7 @@ final class InternalCrossProfileClassGenerator {
}
private void addBlockingCrossProfileTypeMethod(
- TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
+ TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
CodeBlock.Builder methodCode = CodeBlock.builder();
// parcle is recycled by caller
@@ -192,12 +192,12 @@ final class InternalCrossProfileClassGenerator {
addExtractParametersCode(methodCode, method);
CodeBlock methodCall =
- CodeBlock.of(
- "$L.$L($L)",
- getCrossProfileTypeReference(method),
- method.simpleName(),
- method.commaSeparatedParameters(
- crossProfileType.supportedTypes(), REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS));
+ CodeBlock.of(
+ "$L.$L($L)",
+ getCrossProfileTypeReference(method),
+ method.simpleName(),
+ method.commaSeparatedParameters(
+ crossProfileType.supportedTypes(), REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS));
if (!method.thrownExceptions().isEmpty()) {
methodCode.beginControlFlow("try");
@@ -210,8 +210,8 @@ final class InternalCrossProfileClassGenerator {
methodCode.addStatement(methodCall);
methodCode.add("returnParcel.writeInt(0); // No errors\n");
methodCode.addStatement(
- "bundler.writeToParcel(returnParcel, returnValue, $L, /* flags= */ 0)",
- TypeUtils.generateBundlerType(method.returnType()));
+ "bundler.writeToParcel(returnParcel, returnValue, $L, /* flags= */ 0)",
+ TypeUtils.generateBundlerType(method.returnType()));
}
if (!method.thrownExceptions().isEmpty()) {
@@ -219,7 +219,7 @@ final class InternalCrossProfileClassGenerator {
methodCode.nextControlFlow("catch ($L e)", exceptionType);
methodCode.add("returnParcel.writeInt(1); // Errors\n");
methodCode.addStatement(
- "$T.writeThrowableToParcel(returnParcel, e)", PARCEL_UTILITIES_CLASSNAME);
+ "$T.writeThrowableToParcel(returnParcel, e)", PARCEL_UTILITIES_CLASSNAME);
}
methodCode.endControlFlow();
}
@@ -227,23 +227,23 @@ final class InternalCrossProfileClassGenerator {
methodCode.addStatement("return returnParcel");
classBuilder.addMethod(
- MethodSpec.methodBuilder("method" + method.identifier())
- .addModifiers(Modifier.PRIVATE)
- .returns(PARCEL_CLASSNAME)
- .addParameter(CONTEXT_CLASSNAME, "context")
- .addParameter(PARCEL_CLASSNAME, "params")
- .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
- .addCode(methodCode.build())
- .addJavadoc(
- "Call $1L and return a {@link $2T} containing the return value.\n\n"
- + "<p>The {@link $2T} must be recycled after use.\n",
- GeneratorUtilities.methodJavadocReference(method.methodElement()),
- PARCEL_CLASSNAME)
- .build());
+ MethodSpec.methodBuilder("method" + method.identifier())
+ .addModifiers(Modifier.PRIVATE)
+ .returns(PARCEL_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(PARCEL_CLASSNAME, "params")
+ .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
+ .addCode(methodCode.build())
+ .addJavadoc(
+ "Call $1L and return a {@link $2T} containing the return value.\n\n"
+ + "<p>The {@link $2T} must be recycled after use.\n",
+ GeneratorUtilities.methodJavadocReference(method.methodElement()),
+ PARCEL_CLASSNAME)
+ .build());
}
private void addCrossProfileCallbackCrossProfileTypeMethod(
- TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
+ TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
CodeBlock.Builder methodCode = CodeBlock.builder();
// parcel is recycled by caller
@@ -254,12 +254,12 @@ final class InternalCrossProfileClassGenerator {
createCrossProfileCallbackParameter(methodCode, method);
CodeBlock methodCall =
- CodeBlock.of(
- "$L.$L($L)",
- getCrossProfileTypeReference(method),
- method.simpleName(),
- method.commaSeparatedParameters(
- crossProfileType.supportedTypes(), REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS));
+ CodeBlock.of(
+ "$L.$L($L)",
+ getCrossProfileTypeReference(method),
+ method.simpleName(),
+ method.commaSeparatedParameters(
+ crossProfileType.supportedTypes(), REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS));
if (isPrimitiveOrObjectVoid(method.returnType())) {
methodCode.addStatement(methodCall);
@@ -268,31 +268,31 @@ final class InternalCrossProfileClassGenerator {
methodCode.addStatement(methodCall);
methodCode.add("returnParcel.writeInt(0); // No errors\n");
methodCode.addStatement(
- "bundler.writeToParcel(returnParcel, returnValue, $L, /* flags= */ 0)",
- TypeUtils.generateBundlerType(method.returnType()));
+ "bundler.writeToParcel(returnParcel, returnValue, $L, /* flags= */ 0)",
+ TypeUtils.generateBundlerType(method.returnType()));
}
methodCode.addStatement("return returnParcel");
classBuilder.addMethod(
- MethodSpec.methodBuilder("method" + method.identifier())
- .addModifiers(Modifier.PRIVATE)
- .returns(PARCEL_CLASSNAME)
- .addParameter(CONTEXT_CLASSNAME, "context")
- .addParameter(PARCEL_CLASSNAME, "params")
- // TODO: This should be renamed to "callback" once we prefix unpacked parameter names
- // (without doing this, a param named "callback" will cause a compile error)
- .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "crossProfileCallback")
- .addCode(methodCode.build())
- .addJavadoc(
- "Call $1L, and link the callback to {@code crossProfileCallback}.\n\n"
- + "@return An empty parcel. This must be recycled after use.\n",
- GeneratorUtilities.methodJavadocReference(method.methodElement()))
- .build());
+ MethodSpec.methodBuilder("method" + method.identifier())
+ .addModifiers(Modifier.PRIVATE)
+ .returns(PARCEL_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(PARCEL_CLASSNAME, "params")
+ // TODO: This should be renamed to "callback" once we prefix unpacked parameter names
+ // (without doing this, a param named "callback" will cause a compile error)
+ .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "crossProfileCallback")
+ .addCode(methodCode.build())
+ .addJavadoc(
+ "Call $1L, and link the callback to {@code crossProfileCallback}.\n\n"
+ + "@return An empty parcel. This must be recycled after use.\n",
+ GeneratorUtilities.methodJavadocReference(method.methodElement()))
+ .build());
}
private void addFutureCrossProfileTypeMethod(
- TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
+ TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
CodeBlock.Builder methodCode = CodeBlock.builder();
// parcel is recycled by caller
@@ -301,110 +301,110 @@ final class InternalCrossProfileClassGenerator {
addExtractParametersCode(methodCode, method);
CodeBlock methodCall =
- CodeBlock.of(
- "$L.$L($L)",
- getCrossProfileTypeReference(method),
- method.simpleName(),
- method.commaSeparatedParameters(
- crossProfileType.supportedTypes(), REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS));
+ CodeBlock.of(
+ "$L.$L($L)",
+ getCrossProfileTypeReference(method),
+ method.simpleName(),
+ method.commaSeparatedParameters(
+ crossProfileType.supportedTypes(), REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS));
methodCode.addStatement("$T future = $L", method.returnType(), methodCall);
TypeMirror rawFutureType = TypeUtils.removeTypeArguments(method.returnType());
FutureWrapper futureWrapper =
- crossProfileType.supportedTypes().getType(rawFutureType).getFutureWrapper().get();
+ crossProfileType.supportedTypes().getType(rawFutureType).getFutureWrapper().get();
// This assumes every Future is generic with one type argument
TypeMirror wrappedReturnType =
- TypeUtils.extractTypeArguments(method.returnType()).iterator().next();
+ TypeUtils.extractTypeArguments(method.returnType()).iterator().next();
methodCode.addStatement(
- "$T.writeFutureResult(future, new $T<>(callback, bundler, $L))",
- futureWrapper.wrapperClassName(),
- CROSS_PROFILE_FUTURE_RESULT_WRITER,
- TypeUtils.generateBundlerType(wrappedReturnType));
+ "$T.writeFutureResult(future, new $T<>(callback, bundler, $L))",
+ futureWrapper.wrapperClassName(),
+ CROSS_PROFILE_FUTURE_RESULT_WRITER,
+ TypeUtils.generateBundlerType(wrappedReturnType));
// TODO: Can this just return null? where does it go? that'd avoid having to obtain/recycle
methodCode.addStatement("return returnParcel");
classBuilder.addMethod(
- MethodSpec.methodBuilder("method" + method.identifier())
- .addModifiers(Modifier.PRIVATE)
- .returns(PARCEL_CLASSNAME)
- .addParameter(CONTEXT_CLASSNAME, "context")
- .addParameter(PARCEL_CLASSNAME, "params")
- .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
- .addCode(methodCode.build())
- .addJavadoc(
- "Call $1L, and link the returned future to {@code crossProfileCallback}.\n\n"
- + "@return An empty parcel. This must be recycled after use.\n",
- GeneratorUtilities.methodJavadocReference(method.methodElement()))
- .build());
+ MethodSpec.methodBuilder("method" + method.identifier())
+ .addModifiers(Modifier.PRIVATE)
+ .returns(PARCEL_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(PARCEL_CLASSNAME, "params")
+ .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
+ .addCode(methodCode.build())
+ .addJavadoc(
+ "Call $1L, and link the returned future to {@code crossProfileCallback}.\n\n"
+ + "@return An empty parcel. This must be recycled after use.\n",
+ GeneratorUtilities.methodJavadocReference(method.methodElement()))
+ .build());
}
private void createCrossProfileCallbackParameter(
- CodeBlock.Builder methodCode, CrossProfileMethodInfo method) {
+ CodeBlock.Builder methodCode, CrossProfileMethodInfo method) {
VariableElement asyncCallbackParam =
- method.getCrossProfileCallbackParam(generatorContext).get();
+ method.getCrossProfileCallbackParam(generatorContext).get();
TypeElement callbackType =
- generatorContext.elements().getTypeElement(asyncCallbackParam.asType().toString());
+ generatorContext.elements().getTypeElement(asyncCallbackParam.asType().toString());
CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(callbackType);
+ CrossProfileCallbackInterfaceInfo.create(callbackType);
methodCode.addStatement(
- "$T $L = new $L(crossProfileCallback, bundler)",
- asyncCallbackParam.asType(),
- asyncCallbackParam.getSimpleName(),
- CrossProfileCallbackCodeGenerator.getCrossProfileCallbackReceiverClassName(
- generatorContext, callbackInterface));
+ "$T $L = new $L(crossProfileCallback, bundler)",
+ asyncCallbackParam.asType(),
+ asyncCallbackParam.getSimpleName(),
+ CrossProfileCallbackCodeGenerator.getCrossProfileCallbackReceiverClassName(
+ generatorContext, callbackInterface));
}
private static boolean isPrimitiveOrObjectVoid(TypeMirror typeMirror) {
return typeMirror.getKind().equals(TypeKind.VOID)
- || typeMirror.toString().equals("java.lang.Void");
+ || typeMirror.toString().equals("java.lang.Void");
}
private void addExtractParametersCode(CodeBlock.Builder code, CrossProfileMethodInfo method) {
Optional<VariableElement> callbackParameter =
- method.getCrossProfileCallbackParam(generatorContext);
+ method.getCrossProfileCallbackParam(generatorContext);
for (VariableElement parameter : method.methodElement().getParameters()) {
if (callbackParameter.isPresent()
- && callbackParameter.get().getSimpleName().equals(parameter.getSimpleName())) {
+ && callbackParameter.get().getSimpleName().equals(parameter.getSimpleName())) {
continue; // Don't extract a callback parameter
}
if (crossProfileType.supportedTypes().isAutomaticallyResolved(parameter.asType())) {
continue;
}
code.addStatement(
- "@SuppressWarnings(\"unchecked\") $1T $2L = ($1T) bundler.readFromParcel(params, $3L)",
- parameter.asType(),
- parameter.getSimpleName().toString(),
- TypeUtils.generateBundlerType(parameter.asType()));
+ "@SuppressWarnings(\"unchecked\") $1T $2L = ($1T) bundler.readFromParcel(params, $3L)",
+ parameter.asType(),
+ parameter.getSimpleName().toString(),
+ TypeUtils.generateBundlerType(parameter.asType()));
}
}
private static void addCallMethod(TypeSpec.Builder classBuilder) {
classBuilder.addMethod(
- MethodSpec.methodBuilder("call")
- .addModifiers(Modifier.PUBLIC)
- .returns(PARCEL_CLASSNAME)
- .addParameter(CONTEXT_CLASSNAME, "context")
- .addParameter(int.class, "methodIdentifier")
- .addParameter(PARCEL_CLASSNAME, "params")
- .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
- .beginControlFlow("if (methodIdentifier >= methods.length)")
- .addStatement(
- "throw new $T(\"Invalid method identifier\" + methodIdentifier)",
- IllegalArgumentException.class)
- .endControlFlow()
- .addStatement("return methods[methodIdentifier].call(context, params, callback)")
- .addJavadoc(
- "Call the method referenced by {@code methodIdentifier}.\n\n"
- + "<p>If the method is synchronous, this will return a {@link $1T} containing"
- + " the return value, otherwise it will return an empty {@link $1T}. The"
- + " {@link $1T} must be recycled after use.\n",
- PARCEL_CLASSNAME)
- .build());
+ MethodSpec.methodBuilder("call")
+ .addModifiers(Modifier.PUBLIC)
+ .returns(PARCEL_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(int.class, "methodIdentifier")
+ .addParameter(PARCEL_CLASSNAME, "params")
+ .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
+ .beginControlFlow("if (methodIdentifier >= methods.length)")
+ .addStatement(
+ "throw new $T(\"Invalid method identifier\" + methodIdentifier)",
+ IllegalArgumentException.class)
+ .endControlFlow()
+ .addStatement("return methods[methodIdentifier].call(context, params, callback)")
+ .addJavadoc(
+ "Call the method referenced by {@code methodIdentifier}.\n\n"
+ + "<p>If the method is synchronous, this will return a {@link $1T} containing"
+ + " the return value, otherwise it will return an empty {@link $1T}. The"
+ + " {@link $1T} must be recycled after use.\n",
+ PARCEL_CLASSNAME)
+ .build());
}
private CodeBlock getCrossProfileTypeReference(CrossProfileMethodInfo method) {
@@ -415,7 +415,7 @@ final class InternalCrossProfileClassGenerator {
}
static ClassName getInternalCrossProfileClassName(
- GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
return GeneratorUtilities.appendToClassName(crossProfileType.profileClassName(), "_Internal");
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileGenerator.java
index ace2f9e..08011ca 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileGenerator.java
@@ -19,6 +19,7 @@ import static com.google.android.enterprise.connectedapps.processor.CommonClassN
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.LOCAL_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CONNECTOR_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_RUNTIME_EXCEPTION_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo.AutomaticallyResolvedParameterFilterBehaviour.REMOVE_AUTOMATICALLY_RESOLVED_PARAMETERS;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -209,9 +210,12 @@ final class OtherProfileGenerator {
methodBuilder.addStatement("throw e");
}
+ methodBuilder.nextControlFlow("catch ($T e)", PROFILE_RUNTIME_EXCEPTION_CLASSNAME);
+ methodBuilder.addStatement("throw e");
+
methodBuilder.nextControlFlow("catch ($T e)", Throwable.class);
methodBuilder.addStatement(
- "throw new $T($S)", IllegalStateException.class, "Unexpected exception thrown");
+ "throw new $T($S, e)", IllegalStateException.class, "Unexpected exception thrown");
methodBuilder.endControlFlow();
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SupportedTypes.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SupportedTypes.java
index 1238b4b..254c455 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SupportedTypes.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SupportedTypes.java
@@ -50,8 +50,8 @@ public final class SupportedTypes {
@Override
public String toString() {
return "SupportedTypes{" +
- "usableTypes=" + usableTypes +
- '}';
+ "usableTypes=" + usableTypes +
+ '}';
}
@Override
@@ -84,16 +84,16 @@ public final class SupportedTypes {
public static TypeCheckContext create() {
return new AutoValue_SupportedTypes_TypeCheckContext.Builder()
- .setWrapped(false)
- .setOnCrossProfileCallbackInterface(false)
- .build();
+ .setWrapped(false)
+ .setOnCrossProfileCallbackInterface(false)
+ .build();
}
public static TypeCheckContext createForCrossProfileCallbackInterface() {
return new AutoValue_SupportedTypes_TypeCheckContext.Builder()
- .setWrapped(false)
- .setOnCrossProfileCallbackInterface(true)
- .build();
+ .setWrapped(false)
+ .setOnCrossProfileCallbackInterface(true)
+ .build();
}
@AutoValue.Builder
@@ -123,9 +123,6 @@ public final class SupportedTypes {
if (TypeUtils.isGeneric(wrappedType)) {
return false; // We don't support generic arrays
}
- if (wrappedType.getKind().isPrimitive()) {
- return false; // We don't support primitive arrays
- }
if (TypeUtils.isArray(wrappedType)) {
return false; // We don't support multidimensional arrays
}
@@ -133,8 +130,8 @@ public final class SupportedTypes {
}
return TypeUtils.isGeneric(type)
- ? isValidGenericReturnType(type, context)
- : isValidReturnType(get(type), context);
+ ? isValidGenericReturnType(type, context)
+ : isValidReturnType(get(type), context);
}
private static boolean isValidReturnType(@Nullable Type supportedType, TypeCheckContext context) {
@@ -196,9 +193,6 @@ public final class SupportedTypes {
if (TypeUtils.isGeneric(wrappedType)) {
return false; // We don't support generic arrays
}
- if (wrappedType.getKind().isPrimitive()) {
- return false; // We don't support primitive arrays
- }
if (TypeUtils.isArray(wrappedType)) {
return false; // We don't support multidimensional arrays
}
@@ -219,8 +213,8 @@ public final class SupportedTypes {
}
return TypeUtils.isGeneric(type)
- ? isValidGenericParameterType(type, context)
- : isValidParameterType(get(type));
+ ? isValidGenericParameterType(type, context)
+ : isValidParameterType(get(type));
}
private static boolean isValidParameterType(Type supportedType) {
@@ -261,7 +255,7 @@ public final class SupportedTypes {
}
throw new IllegalArgumentException(
- String.format("%s can not write to parcel", type.getQualifiedName()));
+ String.format("%s can not write to parcel", type.getQualifiedName()));
}
CodeBlock generateReadFromParcelCode(String parcelName, Type type) {
@@ -270,7 +264,7 @@ public final class SupportedTypes {
}
throw new IllegalArgumentException(
- String.format("%s can not read from parcel", type.getQualifiedName()));
+ String.format("%s can not read from parcel", type.getQualifiedName()));
}
public Type getType(TypeMirror type) {
@@ -287,11 +281,11 @@ public final class SupportedTypes {
}
public static SupportedTypes createFromMethods(
- Types types,
- Elements elements,
- Collection<ParcelableWrapper> parcelableWrappers,
- Collection<FutureWrapper> futureWrappers,
- Collection<ExecutableElement> methods) {
+ Types types,
+ Elements elements,
+ Collection<ParcelableWrapper> parcelableWrappers,
+ Collection<FutureWrapper> futureWrappers,
+ Collection<ExecutableElement> methods) {
Map<String, Type> usableTypes = new HashMap<>();
addDefaultTypes(types, elements, usableTypes);
@@ -303,10 +297,10 @@ public final class SupportedTypes {
}
private static void addSupportForUsedTypes(
- Types types,
- Elements elements,
- Map<String, Type> usableTypes,
- Collection<ExecutableElement> methods) {
+ Types types,
+ Elements elements,
+ Map<String, Type> usableTypes,
+ Collection<ExecutableElement> methods) {
for (ExecutableElement method : methods) {
addSupportForUsedType(types, elements, usableTypes, method.getReturnType());
@@ -317,7 +311,7 @@ public final class SupportedTypes {
}
private static void addSupportForUsedType(
- Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
+ Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
if (TypeUtils.isArray(type)) {
addSupportForUsedType(types, elements, usableTypes, TypeUtils.extractTypeFromArray(type));
if (!TypeUtils.extractTypeFromArray(type).getKind().isPrimitive()) {
@@ -341,7 +335,7 @@ public final class SupportedTypes {
// We don't support generic callbacks so any callback interfaces can be picked up here
if (supportedType.isCrossProfileCallbackInterface()) {
for (TypeMirror typeMirror :
- supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
+ supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
addSupportForUsedType(types, elements, usableTypes, typeMirror);
}
}
@@ -350,11 +344,11 @@ public final class SupportedTypes {
}
private static void addSupportForGenericUsedType(
- Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
+ Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
TypeMirror genericType = TypeUtils.removeTypeArguments(type);
Optional<Type> optionalSupportedType =
- getSupportedType(types, elements, usableTypes, genericType);
+ getSupportedType(types, elements, usableTypes, genericType);
if (!optionalSupportedType.isPresent()) {
// The base type isn't supported
return;
@@ -372,7 +366,7 @@ public final class SupportedTypes {
}
private static Optional<Type> getSupportedType(
- Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
+ Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
if (usableTypes.containsKey(type.toString())) {
return Optional.of(usableTypes.get(type.toString()));
}
@@ -399,37 +393,37 @@ public final class SupportedTypes {
private static Type createCrossProfileCallbackType(TypeElement type) {
return Type.builder()
- .setTypeMirror(type.asType())
- .setAcceptableReturnType(false)
- .setAcceptableParameterType(true)
- .setSupportedInsideWrapper(false)
- .setSupportedInsideCrossProfileCallback(false)
- .setCrossProfileCallbackInterface(CrossProfileCallbackInterfaceInfo.create(type))
- .build();
+ .setTypeMirror(type.asType())
+ .setAcceptableReturnType(false)
+ .setAcceptableParameterType(true)
+ .setSupportedInsideWrapper(false)
+ .setSupportedInsideCrossProfileCallback(false)
+ .setCrossProfileCallbackInterface(CrossProfileCallbackInterfaceInfo.create(type))
+ .build();
}
private static Type createParcelableType(TypeMirror typeMirror) {
return Type.builder()
- .setTypeMirror(typeMirror)
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeParcelable($L, flags)")
- .setReadFromParcelCode("$L.readParcelable(Bundler.class.getClassLoader())")
- // Parcelables must take care of their own generic types
- .setSupportedWithAnyGenericType(true)
- .build();
+ .setTypeMirror(typeMirror)
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeParcelable($L, flags)")
+ .setReadFromParcelCode("$L.readParcelable(Bundler.class.getClassLoader())")
+ // Parcelables must take care of their own generic types
+ .setSupportedWithAnyGenericType(true)
+ .build();
}
private static Type createSerializableType(TypeMirror typeMirror) {
return Type.builder()
- .setTypeMirror(typeMirror)
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeSerializable($L)")
- .setReadFromParcelCode("$L.readSerializable()")
- // Serializables must take care of their own generic types
- .setSupportedWithAnyGenericType(true)
- .build();
+ .setTypeMirror(typeMirror)
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeSerializable($L)")
+ .setReadFromParcelCode("$L.readSerializable()")
+ // Serializables must take care of their own generic types
+ .setSupportedWithAnyGenericType(true)
+ .build();
}
/** Create a {@link Builder} to create a new {@link SupportedTypes} with modified entries. */
@@ -438,184 +432,193 @@ public final class SupportedTypes {
}
private static void addDefaultTypes(
- Types types, Elements elements, Map<String, Type> usableTypes) {
+ Types types, Elements elements, Map<String, Type> usableTypes) {
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getNoType(TypeKind.VOID))
- .setAcceptableReturnType(true)
- .setReadFromParcelCode("null")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getNoType(TypeKind.VOID))
+ .setAcceptableReturnType(true)
+ .setReadFromParcelCode("null")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(elements.getTypeElement("java.lang.Void").asType())
- .setAcceptableReturnType(true)
- .setReadFromParcelCode("null")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(elements.getTypeElement("java.lang.Void").asType())
+ .setAcceptableReturnType(true)
+ .setReadFromParcelCode("null")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(elements.getTypeElement("java.lang.String").asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeString($L)")
- .setReadFromParcelCode("$L.readString()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(elements.getTypeElement("java.lang.String").asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeString($L)")
+ .setReadFromParcelCode("$L.readString()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.BYTE))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeByte($L)")
- .setReadFromParcelCode("$L.readByte()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(elements.getTypeElement("java.lang.CharSequence").asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeString(String.valueOf($L))")
+ .setReadFromParcelCode("$L.readString()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.BYTE)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeByte($L)")
- .setReadFromParcelCode("$L.readByte()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.BYTE))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeByte($L)")
+ .setReadFromParcelCode("$L.readByte()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.SHORT))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L)")
- .setReadFromParcelCode("(short)$L.readInt()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.BYTE)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeByte($L)")
+ .setReadFromParcelCode("$L.readByte()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.SHORT)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L)")
- .setReadFromParcelCode("(short)$L.readInt()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.SHORT))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("(short)$L.readInt()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.INT))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L)")
- .setReadFromParcelCode("$L.readInt()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.SHORT)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("(short)$L.readInt()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.INT)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L)")
- .setReadFromParcelCode("$L.readInt()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.INT))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("$L.readInt()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.LONG))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeLong($L)")
- .setReadFromParcelCode("$L.readLong()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.INT)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("$L.readInt()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.LONG)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeLong($L)")
- .setReadFromParcelCode("$L.readLong()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.LONG))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeLong($L)")
+ .setReadFromParcelCode("$L.readLong()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.FLOAT))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeFloat($L)")
- .setReadFromParcelCode("$L.readFloat()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.LONG)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeLong($L)")
+ .setReadFromParcelCode("$L.readLong()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.FLOAT)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeFloat($L)")
- .setReadFromParcelCode("$L.readFloat()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.FLOAT))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeFloat($L)")
+ .setReadFromParcelCode("$L.readFloat()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.DOUBLE))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeDouble($L)")
- .setReadFromParcelCode("$L.readDouble()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.FLOAT)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeFloat($L)")
+ .setReadFromParcelCode("$L.readFloat()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.DOUBLE)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeDouble($L)")
- .setReadFromParcelCode("$L.readDouble()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.DOUBLE))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeDouble($L)")
+ .setReadFromParcelCode("$L.readDouble()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.CHAR))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L)")
- .setReadFromParcelCode("(char)$L.readInt()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.DOUBLE)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeDouble($L)")
+ .setReadFromParcelCode("$L.readDouble()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.CHAR)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L)")
- .setReadFromParcelCode("(char)$L.readInt()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.CHAR))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("(char)$L.readInt()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getPrimitiveType(TypeKind.BOOLEAN))
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
- .setReadFromParcelCode("($L.readInt() == 1)")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.CHAR)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("(char)$L.readInt()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.BOOLEAN)).asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
- .setReadFromParcelCode("($L.readInt() == 1)")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getPrimitiveType(TypeKind.BOOLEAN))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
+ .setReadFromParcelCode("($L.readInt() == 1)")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(elements.getTypeElement("android.content.Context").asType())
- .setAcceptableParameterType(true)
- .setAutomaticallyResolvedReplacement("context")
- .setAcceptableReturnType(false)
- .setSupportedInsideWrapper(false)
- .setSupportedInsideCrossProfileCallback(false)
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.BOOLEAN)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
+ .setReadFromParcelCode("($L.readInt() == 1)")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(elements.getTypeElement("android.content.Context").asType())
+ .setAcceptableParameterType(true)
+ .setAutomaticallyResolvedReplacement("context")
+ .setAcceptableReturnType(false)
+ .setSupportedInsideWrapper(false)
+ .setSupportedInsideCrossProfileCallback(false)
+ .build());
}
private static void addUsableType(Map<String, Type> usableTypes, Type type) {
@@ -623,49 +626,49 @@ public final class SupportedTypes {
}
private static void addParcelableWrapperTypes(
- Map<String, Type> usableTypes, Collection<ParcelableWrapper> parcelableWrappers) {
+ Map<String, Type> usableTypes, Collection<ParcelableWrapper> parcelableWrappers) {
for (ParcelableWrapper parcelableWrapper : parcelableWrappers) {
addParcelableWrapperType(usableTypes, parcelableWrapper);
}
}
private static void addParcelableWrapperType(
- Map<String, Type> usableTypes, ParcelableWrapper parcelableWrapper) {
+ Map<String, Type> usableTypes, ParcelableWrapper parcelableWrapper) {
String createParcelableCode = parcelableWrapper.wrapperClassName() + ".of(this, valueType, $L)";
// "this" will be a Bundler as this code is only run within a Bundler
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(parcelableWrapper.wrappedType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeParcelable(" + createParcelableCode + ", flags)")
- .setReadFromParcelCode(
- "(("
- + parcelableWrapper.wrapperClassName()
- + ") $L.readParcelable(Bundler.class.getClassLoader())).get()")
- .setParcelableWrapper(parcelableWrapper)
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(parcelableWrapper.wrappedType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setWriteToParcelCode("$L.writeParcelable(" + createParcelableCode + ", flags)")
+ .setReadFromParcelCode(
+ "(("
+ + parcelableWrapper.wrapperClassName()
+ + ") $L.readParcelable(Bundler.class.getClassLoader())).get()")
+ .setParcelableWrapper(parcelableWrapper)
+ .build());
}
private static void addFutureWrapperTypes(
- Map<String, Type> usableTypes, Collection<FutureWrapper> futureWrappers) {
+ Map<String, Type> usableTypes, Collection<FutureWrapper> futureWrappers) {
for (FutureWrapper futureWrapper : futureWrappers) {
addFutureWrapperType(usableTypes, futureWrapper);
}
}
private static void addFutureWrapperType(
- Map<String, Type> usableTypes, FutureWrapper futureWrapper) {
+ Map<String, Type> usableTypes, FutureWrapper futureWrapper) {
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(futureWrapper.wrappedType())
- .setAcceptableReturnType(true)
- .setSupportedInsideWrapper(false)
- .setFutureWrapper(futureWrapper)
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(futureWrapper.wrappedType())
+ .setAcceptableReturnType(true)
+ .setSupportedInsideWrapper(false)
+ .setFutureWrapper(futureWrapper)
+ .build());
}
public static final class Builder {
@@ -678,7 +681,7 @@ public final class SupportedTypes {
/** Filtering to only include used types. */
public Builder filterUsed(
- ValidatorContext context, Collection<CrossProfileMethodInfo> methods) {
+ ValidatorContext context, Collection<CrossProfileMethodInfo> methods) {
Map<String, Type> usedTypes = new HashMap<>();
@@ -692,7 +695,7 @@ public final class SupportedTypes {
}
private void copySupportedTypesForMethod(
- ValidatorContext context, Map<String, Type> usedTypes, CrossProfileMethodInfo method) {
+ ValidatorContext context, Map<String, Type> usedTypes, CrossProfileMethodInfo method) {
copySupportedType(context, usedTypes, method.returnType());
for (TypeMirror argumentType : method.parameterTypes()) {
copySupportedType(context, usedTypes, argumentType);
@@ -700,7 +703,7 @@ public final class SupportedTypes {
}
private void copySupportedType(
- ValidatorContext context, Map<String, Type> usedTypes, TypeMirror type) {
+ ValidatorContext context, Map<String, Type> usedTypes, TypeMirror type) {
if (TypeUtils.isGeneric(type)) {
copySupportedGenericType(context, usedTypes, type);
return;
@@ -710,9 +713,9 @@ public final class SupportedTypes {
copySupportedType(context, usedTypes, TypeUtils.extractTypeFromArray(type));
if (!TypeUtils.extractTypeFromArray(type).getKind().isPrimitive()) {
type =
- context
- .types()
- .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
+ context
+ .types()
+ .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
}
}
@@ -723,7 +726,7 @@ public final class SupportedTypes {
// We don't support generic callbacks so any callback interfaces can be picked up here
if (supportedType.isCrossProfileCallbackInterface()) {
for (TypeMirror typeMirror :
- supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
+ supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
copySupportedType(context, usedTypes, typeMirror);
}
}
@@ -736,7 +739,7 @@ public final class SupportedTypes {
}
private void copySupportedGenericType(
- ValidatorContext context, Map<String, Type> usedTypes, TypeMirror type) {
+ ValidatorContext context, Map<String, Type> usedTypes, TypeMirror type) {
TypeMirror genericType = TypeUtils.removeTypeArguments(type);
// The type must have been seen in when constructing the oldSupportedTypes so this should not
@@ -794,7 +797,7 @@ public final class SupportedTypes {
}
private void replaceParcelableWrapperPrefix(
- Map<String, Type> newUsableTypes, ClassName prefix, Type usableType) {
+ Map<String, Type> newUsableTypes, ClassName prefix, Type usableType) {
ParcelableWrapper parcelableWrapper = usableType.getParcelableWrapper().get();
if (parcelableWrapper.wrapperType().equals(ParcelableWrapper.WrapperType.CUSTOM)) {
@@ -804,16 +807,16 @@ public final class SupportedTypes {
}
addParcelableWrapperType(
- newUsableTypes,
- ParcelableWrapper.create(
- parcelableWrapper.wrappedType(),
- parcelableWrapper.defaultWrapperClassName(),
- prefix(prefix, parcelableWrapper.wrapperClassName()),
- parcelableWrapper.wrapperType()));
+ newUsableTypes,
+ ParcelableWrapper.create(
+ parcelableWrapper.wrappedType(),
+ parcelableWrapper.defaultWrapperClassName(),
+ prefix(prefix, parcelableWrapper.wrapperClassName()),
+ parcelableWrapper.wrapperType()));
}
private void replaceFutureWrapperPrefix(
- Map<String, Type> newUsableTypes, ClassName prefix, Type usableType) {
+ Map<String, Type> newUsableTypes, ClassName prefix, Type usableType) {
FutureWrapper futureWrapper = usableType.getFutureWrapper().get();
if (futureWrapper.wrapperType().equals(FutureWrapper.WrapperType.CUSTOM)) {
@@ -823,17 +826,17 @@ public final class SupportedTypes {
}
addFutureWrapperType(
- newUsableTypes,
- FutureWrapper.create(
- futureWrapper.wrappedType(),
- futureWrapper.defaultWrapperClassName(),
- prefix(prefix, futureWrapper.wrapperClassName()),
- futureWrapper.wrapperType()));
+ newUsableTypes,
+ FutureWrapper.create(
+ futureWrapper.wrappedType(),
+ futureWrapper.defaultWrapperClassName(),
+ prefix(prefix, futureWrapper.wrapperClassName()),
+ futureWrapper.wrapperType()));
}
private ClassName prefix(ClassName prefix, ClassName finalName) {
return ClassName.get(
- prefix.packageName(), prefix.simpleName() + "_" + finalName.simpleName());
+ prefix.packageName(), prefix.simpleName() + "_" + finalName.simpleName());
}
/** Build a new {@link SupportedTypes}. */
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TypeUtils.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TypeUtils.java
index 6d5d073..ffa68c9 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TypeUtils.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TypeUtils.java
@@ -95,18 +95,25 @@ public class TypeUtils {
private static CodeBlock generateArrayBundlerType(TypeMirror type) {
TypeMirror arrayType = extractTypeFromArray(type);
+ if (arrayType.getKind().isPrimitive()) {
+ return CodeBlock.of(
+ "$T.of($S)",
+ BUNDLER_TYPE_CLASSNAME,
+ arrayType.toString() + "[]");
+ }
+
return CodeBlock.of(
- "$T.of($S, $L)",
- BUNDLER_TYPE_CLASSNAME,
- "java.lang.Object[]",
- generateBundlerType(arrayType));
+ "$T.of($S, $L)",
+ BUNDLER_TYPE_CLASSNAME,
+ "java.lang.Object[]",
+ generateBundlerType(arrayType));
}
private static CodeBlock generateGenericBundlerType(TypeMirror type) {
CodeBlock.Builder typeArgs = CodeBlock.builder();
List<CodeBlock> typeArgBlocks =
- extractTypeArguments(type).stream().map(TypeUtils::generateBundlerType).collect(toList());
+ extractTypeArguments(type).stream().map(TypeUtils::generateBundlerType).collect(toList());
typeArgs.add(typeArgBlocks.get(0));
for (CodeBlock typeArgBlock : typeArgBlocks.subList(1, typeArgBlocks.size())) {
@@ -114,7 +121,7 @@ public class TypeUtils {
}
return CodeBlock.of(
- "$T.of($S, $L)", BUNDLER_TYPE_CLASSNAME, getRawTypeQualifiedName(type), typeArgs.build());
+ "$T.of($S, $L)", BUNDLER_TYPE_CLASSNAME, getRawTypeQualifiedName(type), typeArgs.build());
}
private TypeUtils() {}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileConnector.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileConnector.java
index ae24257..cc71c7b 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileConnector.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileConnector.java
@@ -110,7 +110,6 @@ public abstract class AbstractProfileConnector
/* availabilityListener= */ this,
scheduledExecutorService,
availabilityRestrictions);
- crossProfileSender.beginMonitoringAvailabilityChanges();
}
return crossProfileSender;
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileSender.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileSender.java
index fe34d09..9cc287b 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileSender.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileSender.java
@@ -18,6 +18,9 @@ package com.google.android.enterprise.connectedapps;
import static com.google.android.enterprise.connectedapps.CrossProfileSDKUtilities.filterUsersByAvailabilityRestrictions;
import static com.google.android.enterprise.connectedapps.CrossProfileSDKUtilities.selectUserHandleToBind;
+import static java.util.Collections.newSetFromMap;
+import static java.util.Collections.synchronizedSet;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -27,6 +30,7 @@ import android.content.ServiceConnection;
import android.content.pm.CrossProfileApps;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -41,8 +45,13 @@ import com.google.android.enterprise.connectedapps.internal.CrossProfileParcelCa
import com.google.android.enterprise.connectedapps.internal.ParcelCallReceiver;
import com.google.android.enterprise.connectedapps.internal.ParcelUtilities;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
@@ -238,6 +247,11 @@ public class CrossProfileSender {
@Nullable private volatile ScheduledFuture<Void> automaticDisconnectionFuture;
private final AvailabilityRestrictions availabilityRestrictions;
+ // This is synchronized which isn't massively performant but it only gets accessed once straight
+ // after creating a Sender, and once each time availability changes
+ private static final Set<CrossProfileSender> senders =
+ synchronizedSet(newSetFromMap(new WeakHashMap<>()));
+
private boolean isManuallyManagingConnection = false;
private ConcurrentLinkedDeque<OngoingCrossProfileCall> ongoingCrossProfileCalls =
new ConcurrentLinkedDeque<>();
@@ -277,13 +291,18 @@ public class CrossProfileSender {
canUseReflectedApis = ReflectionUtilities.canUseReflectedApis();
this.scheduledExecutorService = scheduledExecutorService;
this.availabilityRestrictions = availabilityRestrictions;
+
+ senders.add(this);
+ beginMonitoringAvailabilityChanges();
}
- private final BroadcastReceiver profileAvailabilityReceiver =
+ private static final BroadcastReceiver profileAvailabilityReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- checkAvailability();
+ for (CrossProfileSender sender : senders) {
+ sender.scheduledExecutorService.execute(sender::checkAvailability);
+ }
}
};
@@ -370,7 +389,13 @@ public class CrossProfileSender {
return null;
}
- void beginMonitoringAvailabilityChanges() {
+ private static final AtomicBoolean isMonitoringAvailabilityChanges = new AtomicBoolean(false);
+
+ private void beginMonitoringAvailabilityChanges() {
+ if (isMonitoringAvailabilityChanges.getAndSet(true)) {
+ return;
+ }
+
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
@@ -593,10 +618,17 @@ public class CrossProfileSender {
* @throws UnavailableProfileException if a connection is not already established
*/
public Parcel call(long crossProfileTypeIdentifier, int methodIdentifier, Parcel params)
- throws UnavailableProfileException {
+ throws UnavailableProfileException {
try {
return callWithExceptions(crossProfileTypeIdentifier, methodIdentifier, params);
- } catch (UnavailableProfileException | RuntimeException e) {
+ } catch (UnavailableProfileException | RuntimeException | Error e) {
+ StackTraceElement[] remoteStack = e.getStackTrace();
+ StackTraceElement[] localStack = Thread.currentThread().getStackTrace();
+ StackTraceElement[] totalStack =
+ Arrays.copyOf(remoteStack, remoteStack.length + localStack.length - 1);
+ // We cut off the first element of localStack as it is just getting the stack trace
+ System.arraycopy(localStack, 1, totalStack, remoteStack.length, localStack.length - 1);
+ e.setStackTrace(totalStack);
throw e;
} catch (Throwable e) {
throw new UnavailableProfileException("Unexpected checked exception", e);
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/exceptions/ProfileRuntimeException.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/exceptions/ProfileRuntimeException.java
index 6ea9005..511a77a 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/exceptions/ProfileRuntimeException.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/exceptions/ProfileRuntimeException.java
@@ -16,12 +16,12 @@
package com.google.android.enterprise.connectedapps.exceptions;
/**
- * Thrown when a {@link RuntimeException} is thrown during a cross-profile call.
+ * Thrown when a {@link Throwable} is thrown during a cross-profile call.
*
* <p>To get the original exception, call {@link #getCause()}.
*/
public class ProfileRuntimeException extends RuntimeException {
- public ProfileRuntimeException(RuntimeException cause) {
+ public ProfileRuntimeException(Throwable cause) {
super(cause);
}
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BackgroundExceptionThrower.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BackgroundExceptionThrower.java
index 0999a35..9511e19 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BackgroundExceptionThrower.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BackgroundExceptionThrower.java
@@ -24,15 +24,23 @@ public final class BackgroundExceptionThrower {
private BackgroundExceptionThrower() {}
private static class ThrowingRunnable implements Runnable {
- RuntimeException throwable;
+ RuntimeException runtimeException;
+ Error error;
- ThrowingRunnable(RuntimeException throwable) {
- this.throwable = throwable;
+ ThrowingRunnable(RuntimeException runtimeException) {
+ this.runtimeException = runtimeException;
+ }
+
+ ThrowingRunnable(Error error) {
+ this.error = error;
}
@Override
public void run() {
- throw throwable;
+ if (error != null) {
+ throw error;
+ }
+ throw runtimeException;
}
}
@@ -41,4 +49,10 @@ public final class BackgroundExceptionThrower {
// We add a small delay to ensure that the return can be completed before crashing
new Handler(Looper.getMainLooper()).postDelayed(new ThrowingRunnable(throwable), 1000);
}
+
+ /** Throw the given {@link Error} after a delay on the main looper. */
+ public static void throwInBackground(Error throwable) {
+ // We add a small delay to ensure that the return can be completed before crashing
+ new Handler(Looper.getMainLooper()).postDelayed(new ThrowingRunnable(throwable), 1000);
+ }
}