DO NOT MERGE: [connectedappssdk] Update bedstead in AOSP. am: 5d5b0c656a
Original change: https://android-review.googlesource.com/c/platform/external/connectedappssdk/+/2317953
Change-Id: Ie6e37c5b1dc626ea8edf0a2f72d5c707e5fa2a8a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index e608e76..0eab8ec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -37,6 +37,7 @@
"ConnectedAppsSDK_Annotations",
"guava-android-annotation-stubs",
"auto_value_annotations",
+ "error_prone_annotations",
"guava",
"ConnectedAppsSDK_Annotations",
"ConnectedAppsSDK_Test_Annotations"
@@ -72,9 +73,10 @@
static_libs: [
"ConnectedAppsSDK_Annotations",
"guava-android-annotation-stubs",
+ "error_prone_annotations",
],
manifest: "sdk/src/main/AndroidManifest.xml",
- min_sdk_version: "27",
+ min_sdk_version: "28",
}
android_library {
@@ -88,45 +90,5 @@
"androidx.test.ext.junit",
],
manifest: "testing/sdk/src/main/AndroidManifest.xml",
- min_sdk_version: "27",
-}
-
-android_library {
- name: "ConnectedAppsSDK_SharedTestApp",
- sdk_version: "test_current",
- srcs: [
- "tests/shared/src/main/java/**/*.java"
- ],
- manifest: "tests/shared/src/main/AndroidManifest.xml",
- min_sdk_version: "27",
- static_libs: [
- "ConnectedAppsSDK_Annotations",
- "ConnectedAppsSDK",
- "guava",
- "truth-prebuilt"
- ],
- plugins: ["ConnectedAppsSDK_Processor"],
-}
-
-// We only run instrumented tests in AOSP
-android_test {
- name: "ConnectedAppsSDKTest",
- srcs: [
- "tests/instrumented/src/main/java/**/*.java"
- ],
- test_suites: [
- "general-tests",
- ],
- static_libs: [
- "ConnectedAppsSDK",
- "ConnectedAppsSDK_Annotations",
- "ConnectedAppsSDK_SharedTestApp",
- "ConnectedAppsSDK_Testing",
- "androidx.test.ext.junit",
- "ctstestrunner-axt",
- "truth-prebuilt",
- "testng", // for assertThrows
- ],
- manifest: "tests/instrumented/src/AndroidManifest.xml",
- min_sdk_version: "27"
-}
+ min_sdk_version: "28",
+}
\ No newline at end of file
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..0d82967
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,13 @@
+# HEAD
+ *
+# 1.1.2
+ * Fixed ANR caused by locking in connection holders
+ * Fixed race condition in CrossProfileCallbackMultiMerger when using .both() on multiple threads
+ * Allow providers to be in a different package to cross profile types
+ * Support Drawable in cross-profile calls
+ * Fixed NPE caused when no manifest permissions are requested
+ * Fixed dependency issue with gradle builds
+# 1.1
+ * Added connection holders to allow for ongoing callbacks
+# 1.0
+ * First Release
\ No newline at end of file
diff --git a/README.md b/README.md
index 252b9d6..2171193 100644
--- a/README.md
+++ b/README.md
@@ -7,4 +7,4 @@
permission.
For more information see
-https://developers.google.com/android/work/connected-apps
\ No newline at end of file
+https://developers.google.com/android/work/connected-apps
diff --git a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/Cacheable.java b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/Cacheable.java
new file mode 100644
index 0000000..672015b
--- /dev/null
+++ b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/Cacheable.java
@@ -0,0 +1,19 @@
+package com.google.android.enterprise.connectedapps.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotate a cross profile method to indicate the result should be cached.
+ *
+ * <p>Annotated methods must return non void types.
+ *
+ * <p>Annotated methods must also be annotated with the {@link CrossProfile} annotation.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Cacheable {
+
+}
diff --git a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossProfile.java b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossProfile.java
index dd88592..f6a00ba 100644
--- a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossProfile.java
+++ b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossProfile.java
@@ -33,15 +33,6 @@
public @interface CrossProfile {
/**
- * The name of the Profile class generated for this cross-profile type.
- *
- * <p>This argument can only be passed when annotating types, not methods.
- *
- * <p>Defaults to this type name prefixed with "Profile".
- */
- String profileClassName() default "";
-
- /**
* The {@link CustomProfileConnector} used by this type.
*
* <p>Setting this option for a cross-profile type ensures the generated code provides a better
@@ -73,11 +64,4 @@
* <p>This argument can only be passed when annotating types, not methods.
*/
boolean isStatic() default false;
-
- /**
- * The number of milliseconds to wait before timing out asynchronous calls to this method or type.
- *
- * <p>Defaults to {@link #DEFAULT_TIMEOUT_MILLIS}.
- */
- long timeoutMillis() default -1;
}
diff --git a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossUser.java b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossUser.java
index da95795..ff2c311 100644
--- a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossUser.java
+++ b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CrossUser.java
@@ -33,15 +33,6 @@
public @interface CrossUser {
/**
- * The name of the Profile class generated for this cross-profile type.
- *
- * <p>This argument can only be passed when annotating types, not methods.
- *
- * <p>Defaults to this type name prefixed with "Profile".
- */
- String profileClassName() default "";
-
- /**
* The {@link CustomProfileConnector} used by this type.
*
* <p>Setting this option for a cross-profile type ensures the generated code provides a better
@@ -73,11 +64,4 @@
* <p>This argument can only be passed when annotating types, not methods.
*/
boolean isStatic() default false;
-
- /**
- * The number of milliseconds to wait before timing out asynchronous calls to this method or type.
- *
- * <p>Defaults to {@link #DEFAULT_TIMEOUT_MILLIS}.
- */
- long timeoutMillis() default -1;
}
diff --git a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CustomProfileConnector.java b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CustomProfileConnector.java
index 807bb04..7265e72 100644
--- a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CustomProfileConnector.java
+++ b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/CustomProfileConnector.java
@@ -70,4 +70,13 @@
* <p>By default, this will require that a user be running, unlocked, and not in quiet mode.
*/
AvailabilityRestrictions availabilityRestrictions() default AvailabilityRestrictions.DEFAULT;
+
+ /**
+ * Determines what to do when a cross-profile method has an uncaught exception.
+ *
+ * <p>By default, the exception will be caught, communicated back to the calling process, then
+ * rethrown in the target process.
+ */
+ UncaughtExceptionsPolicy uncaughtExceptionsPolicy() default
+ UncaughtExceptionsPolicy.NOTIFY_RETHROW;
}
diff --git a/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/UncaughtExceptionsPolicy.java b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/UncaughtExceptionsPolicy.java
new file mode 100644
index 0000000..dfdf8a6
--- /dev/null
+++ b/annotations/src/main/java/com/google/android/enterprise/connectedapps/annotations/UncaughtExceptionsPolicy.java
@@ -0,0 +1,15 @@
+package com.google.android.enterprise.connectedapps.annotations;
+
+/** Determines what to do when a cross-profile method has an uncaught exception. */
+public enum UncaughtExceptionsPolicy {
+ /** Notify the caller about the uncaught exception, then rethrow it. */
+ NOTIFY_RETHROW(/* rethrowExceptions= */ true),
+ /** Notify the caller about the uncaught exception, then suppress it. */
+ NOTIFY_SUPPRESS(/* rethrowExceptions= */ false);
+
+ public final boolean rethrowExceptions;
+
+ UncaughtExceptionsPolicy(boolean rethrowExceptions) {
+ this.rethrowExceptions = rethrowExceptions;
+ }
+}
diff --git a/build.gradle b/build.gradle
index 8e106ac..75cb830 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,9 @@
autoservice: "com.google.auto.service:auto-service:1.0-rc6",
autoserviceAnnotations: "com.google.auto.service:auto-service-annotations:1.0-rc6",
javapoet: "com.squareup:javapoet:1.13.0",
- guava: "com.google.guava:guava:29.0-jre"
+ guava: "com.google.guava:guava:29.0-jre",
+ errorprone: "com.google.errorprone:error_prone_core:2.8.1",
+ robolectric: "org.robolectric:robolectric:4.7.2"
]
repositories {
jcenter()
diff --git a/connectedappssdk.iml b/connectedappssdk.iml
new file mode 100644
index 0000000..5fa0513
--- /dev/null
+++ b/connectedappssdk.iml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk">
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/annotations/src/main/java" isTestSource="false" />
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/processor/src/main/java" isTestSource="false" />
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/sdk/src/main/java" isTestSource="false" />
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/testing/annotations/src/main/java" isTestSource="false" />
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/testing/sdk/src/main/java" isTestSource="false" />
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/tests/instrumented/src/main/java" isTestSource="true" />
+ <sourceFolder url="file:///usr/local/google/home/scottjonathan/android-master/external/connectedappssdk/tests/shared/src/main/java" isTestSource="true" />
+ </content>
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="framework_srcjars" />
+ <orderEntry type="module" module-name="base" />
+ <orderEntry type="module" module-name="cts" />
+ <orderEntry type="module" module-name="dependencies" />
+ <orderEntry type="inheritedJdk" />
+ </component>
+</module>
diff --git a/gradle.properties b/gradle.properties
index 2b34218..8b62e01 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
android.useAndroidX=true
-version = 1.0.0-alpha04
+version = 1.1.3-alpha
org.gradle.jvmargs=-Xmx4g
\ No newline at end of file
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsGenerator.java
index 05a506e..6ad7219 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsGenerator.java
@@ -15,6 +15,8 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_CALLBACK_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;
@@ -24,7 +26,6 @@
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
@@ -91,19 +92,6 @@
.addStatement("this.errorMessage = errorMessage")
.build());
- classBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addAnnotation(Override.class)
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC)
- .returns(className)
- .addParameter(long.class, "timeout")
- .addStatement("return this")
- .build());
-
ClassName ifAvailableClass =
IfAvailableGenerator.getIfAvailableClassName(generatorContext, crossProfileType);
@@ -215,7 +203,6 @@
static ClassName getAlwaysThrowsClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_AlwaysThrows");
+ return transformClassName(crossProfileType.generatedClassName(), append("_AlwaysThrows"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerGenerator.java
index 8d421a3..c472003 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerGenerator.java
@@ -15,8 +15,11 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_TYPE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CLASSNAME;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;
@@ -87,6 +90,8 @@
.build());
makeParcelable(classBuilder, className);
+ addWriteToBundleMethod(classBuilder);
+ addReadFromBundleMethod(classBuilder);
addWriteToParcelMethod(classBuilder);
addReadFromParcelMethod(classBuilder);
addCreateArrayMethod(classBuilder);
@@ -106,7 +111,6 @@
generatorUtilities.addDefaultParcelableMethods(classBuilder, bundlerClassName);
}
-
private void addWriteToParcelMethod(TypeSpec.Builder classBuilder) {
CodeBlock.Builder methodCode = CodeBlock.builder();
@@ -122,9 +126,10 @@
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
// This is for passing rawtypes into the Parcelable*.of() methods
+ // ReflectedParcelable isn't a problem because it's the same APK on both sides
.addAnnotation(
AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "\"unchecked\"")
+ .addMember("value", "{\"unchecked\", \"ReflectedParcelable\"}")
.build())
.addParameter(PARCEL_CLASSNAME, "parcel")
.addParameter(Object.class, "value")
@@ -263,8 +268,124 @@
codeBuilder.addStatement("return new $T[size]", type.getTypeMirror());
}
+ private void addWriteToBundleMethod(TypeSpec.Builder classBuilder) {
+ CodeBlock.Builder methodCode = CodeBlock.builder();
+
+ List<Type> types =
+ crossProfileType.supportedTypes().usableTypes().stream()
+ .filter(Type::canBeBundled)
+ .filter(t -> !t.isPrimitive())
+ .collect(toList());
+
+ addWriteToBundleTypes(methodCode, types);
+
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("writeToBundle")
+ // This is for passing rawtypes into the Parcelable*.of() methods
+ .addAnnotation(
+ AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "{\"unchecked\", \"ReflectedParcelable\"}")
+ .build())
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(BUNDLE_CLASSNAME, "bundle")
+ .addParameter(String.class, "key")
+ .addParameter(Object.class, "value")
+ .addParameter(BUNDLER_TYPE_CLASSNAME, "valueType")
+ .addCode(methodCode.build())
+ .build());
+ }
+
+ private void addWriteToBundleTypes(CodeBlock.Builder codeBuilder, List<Type> types) {
+ codeBuilder.beginControlFlow(
+ "if ($S.equals(valueType.rawTypeQualifiedName()))", "java.lang.Void");
+ codeBuilder.addStatement("return");
+ for (Type type : types) {
+ codeBuilder.nextControlFlow(
+ "else if ($S.equals(valueType.rawTypeQualifiedName()))",
+ TypeUtils.getRawTypeQualifiedName(type.getTypeMirror()));
+ addWriteToBundleType(codeBuilder, type);
+ }
+ codeBuilder.endControlFlow();
+
+ codeBuilder.addStatement(
+ "throw new $T(\"Type \" + valueType.rawTypeQualifiedName() + \" cannot be written to"
+ + " Bundle\")",
+ IllegalArgumentException.class);
+ }
+
+ private void addWriteToBundleType(CodeBlock.Builder codeBuilder, Type type) {
+ CodeBlock convertedValue =
+ CodeBlock.of("($L) value", TypeUtils.getRawTypeQualifiedName(type.getTypeMirror()));
+ codeBuilder.addStatement(
+ crossProfileType
+ .supportedTypes()
+ .generatePutIntoBundleCode("bundle", type, "key", convertedValue.toString()));
+ codeBuilder.addStatement("return");
+ }
+
+ private void addReadFromBundleMethod(TypeSpec.Builder classBuilder) {
+ CodeBlock.Builder methodCode = CodeBlock.builder();
+
+ List<Type> types =
+ crossProfileType.supportedTypes().usableTypes().stream()
+ .filter(Type::canBeBundled)
+ .collect(toList());
+
+ methodCode.addStatement("bundle.setClassLoader($T.class.getClassLoader())", BUNDLER_CLASSNAME);
+
+ addReadFromBundleTypes(methodCode, types);
+
+ methodCode.addStatement(
+ "throw new $T(\"Type \" + valueType.rawTypeQualifiedName() + \" cannot be read from"
+ + " Bundle\")",
+ IllegalArgumentException.class);
+
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("readFromBundle")
+ .addAnnotation(
+ AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "\"unchecked\"")
+ .build())
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(Object.class)
+ .addParameter(BUNDLE_CLASSNAME, "bundle")
+ .addParameter(String.class, "key")
+ .addParameter(BUNDLER_TYPE_CLASSNAME, "valueType")
+ .addCode(methodCode.build())
+ .build());
+ }
+
+ private void addReadFromBundleTypes(CodeBlock.Builder codeBuilder, List<Type> types) {
+
+ codeBuilder.beginControlFlow(
+ "if ($S.equals(valueType.rawTypeQualifiedName()))", "java.lang.Void");
+ codeBuilder.addStatement("return null");
+ for (Type type : types) {
+ codeBuilder.nextControlFlow(
+ "else if ($S.equals(valueType.rawTypeQualifiedName()))",
+ TypeUtils.getRawTypeQualifiedName(type.getTypeMirror()));
+ addReadFromBundleType(codeBuilder, type);
+ }
+ codeBuilder.endControlFlow();
+ }
+
+ private void addReadFromBundleType(CodeBlock.Builder codeBuilder, Type type) {
+ TypeMirror objectType = type.getTypeMirror();
+ if (objectType.getKind().isPrimitive()) {
+ PrimitiveType primitiveType = (PrimitiveType) objectType;
+ objectType = generatorContext.types().boxedClass(primitiveType).asType();
+ }
+
+ codeBuilder.addStatement(
+ "return ($L) $L",
+ TypeUtils.getRawTypeQualifiedName(objectType),
+ crossProfileType.supportedTypes().generateGetFromBundleCode("bundle", type, "key"));
+ }
+
static ClassName getBundlerClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(crossProfileType.profileClassName(), "_Bundler");
+ return transformClassName(crossProfileType.generatedClassName(), append("_Bundler"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ClassNameUtilities.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ClassNameUtilities.java
new file mode 100644
index 0000000..d9c39f7
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ClassNameUtilities.java
@@ -0,0 +1,53 @@
+package com.google.android.enterprise.connectedapps.processor;
+
+import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.squareup.javapoet.ClassName;
+import java.util.function.Function;
+import javax.lang.model.element.TypeElement;
+
+final class ClassNameUtilities {
+
+ /**
+ * Given an {@code originalClassName}, creates a new {@link ClassName} which has an identical
+ * package, but with the type name transformed by a string function.
+ */
+ static ClassName transformClassName(
+ ClassName originalClassName, Function<String, String> transformation) {
+ return ClassName.get(
+ originalClassName.packageName(), transformation.apply(originalClassName.simpleName()));
+ }
+
+ /**
+ * When used with {@link #transformClassName(ClassName, Function)}, inserts the {@code string} in
+ * front of the type name of the type being transformed.
+ */
+ static Function<String, String> prepend(String string) {
+ return name -> string + name;
+ }
+
+ /**
+ * When used with {@link #transformClassName(ClassName, Function)}, inserts the {@code string}
+ * after the type name of the type being transformed.
+ */
+ static Function<String, String> append(String string) {
+ return name -> name + string;
+ }
+
+ static ClassName getBuilderClassName(ClassName originalClassName) {
+ return ClassName.get(
+ originalClassName.packageName() + "." + originalClassName.simpleName(), "Builder");
+ }
+
+ /**
+ * Creates a new {@link ClassName} which has the package of the {@link TypeElement} provided, and
+ * the specified {@code simpleName}.
+ */
+ static ClassName classNameInferringPackageFromElement(
+ GeneratorContext generatorContext, TypeElement packageElement, String simpleName) {
+ return ClassName.get(
+ generatorContext.elements().getPackageOf(packageElement).getQualifiedName().toString(),
+ simpleName);
+ }
+
+ private ClassNameUtilities() {}
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CommonClassNames.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CommonClassNames.java
index cebeebc..23fed95 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CommonClassNames.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CommonClassNames.java
@@ -25,8 +25,11 @@
*/
public class CommonClassNames {
static final ClassName CONTEXT_CLASSNAME = ClassName.get("android.content", "Context");
+ static final ClassName BUNDLE_CLASSNAME = ClassName.get("android.os", "Bundle");
static final ClassName PARCEL_CLASSNAME = ClassName.get("android.os", "Parcel");
static final ClassName PARCELABLE_CLASSNAME = ClassName.get("android.os", "Parcelable");
+ static final ClassName USER_HANDLE_CLASSNAME = ClassName.get("android.os", "UserHandle");
+ static final ClassName PROCESS_CLASSNAME = ClassName.get("android.os", "Process");
static final ClassName CROSS_PROFILE_FUTURE_RESULT_WRITER =
ClassName.get(
"com.google.android.enterprise.connectedapps.internal", "CrossProfileFutureResultWriter");
@@ -46,19 +49,18 @@
"com.google.android.enterprise.connectedapps.exceptions", "ProfileRuntimeException");
static final ClassName PROFILE_AWARE_UTILS_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps", "ConnectedAppsUtils");
- static final ClassName BACKGROUND_EXCEPTION_THROWER_CLASSNAME =
- ClassName.get(
- "com.google.android.enterprise.connectedapps.internal", "BackgroundExceptionThrower");
- static final ClassName PARCEL_UTILITIES_CLASSNAME =
- ClassName.get("com.google.android.enterprise.connectedapps.internal", "ParcelUtilities");
+ static final ClassName EXCEPTION_THROWER_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps.internal", "ExceptionThrower");
+ static final ClassName BUNDLE_UTILITIES_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps.internal", "BundleUtilities");
static final ClassName METHOD_RUNNER_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps.internal", "MethodRunner");
static final ClassName BUNDLER_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps.internal", "Bundler");
static final ClassName BUNDLER_TYPE_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps.internal", "BundlerType");
- static final ClassName PARCEL_CALL_RECEIVER_CLASSNAME =
- ClassName.get("com.google.android.enterprise.connectedapps.internal", "ParcelCallReceiver");
+ static final ClassName BUNDLE_CALL_RECEIVER_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps.internal", "BundleCallReceiver");
public static final ClassName BINDER_CLASSNAME = ClassName.get("android.os", "Binder");
public static final ClassName INTENT_CLASSNAME = ClassName.get("android.content", "Intent");
static final ClassName CROSS_PROFILE_SENDER_CLASSNAME =
@@ -77,14 +79,14 @@
ClassName.get(
"com.google.android.enterprise.connectedapps.internal",
"CrossProfileCallbackMultiMerger");
- static final ClassName CROSS_PROFILE_CALLBACK_PARCEL_CALL_SENDER_CLASSNAME =
+ static final ClassName CROSS_PROFILE_CALLBACK_BUNDLE_CALL_SENDER_CLASSNAME =
ClassName.get(
"com.google.android.enterprise.connectedapps.internal",
- "CrossProfileCallbackParcelCallSender");
- static final ClassName CROSS_PROFILE_CALLBACK_EXCEPTION_PARCEL_CALL_SENDER_CLASSNAME =
+ "CrossProfileCallbackBundleCallSender");
+ static final ClassName CROSS_PROFILE_CALLBACK_EXCEPTION_BUNDLE_CALL_SENDER_CLASSNAME =
ClassName.get(
"com.google.android.enterprise.connectedapps.internal",
- "CrossProfileCallbackExceptionParcelCallSender");
+ "CrossProfileCallbackExceptionBundleCallSender");
static final ClassName ASYNC_CALLBACK_PARAM_MULTIMERGER_COMPLETE_LISTENER_CLASSNAME =
ClassName.get(
"com.google.android.enterprise.connectedapps.internal.CrossProfileCallbackMultiMerger",
@@ -101,8 +103,12 @@
ClassName.get("com.google.android.enterprise.connectedapps", "ProfileConnector");
public static final ClassName ABSTRACT_PROFILE_CONNECTOR_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps", "AbstractProfileConnector");
+ public static final ClassName USER_CONNECTOR_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps", "UserConnector");
public static final ClassName ABSTRACT_USER_CONNECTOR_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps", "AbstractUserConnector");
+ public static final ClassName USER_CONNECTOR_WRAPPER_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps", "UserConnectorWrapper");
public static final ClassName ABSTRACT_PROFILE_CONNECTOR_BUILDER_CLASSNAME =
ClassName.get(
"com.google.android.enterprise.connectedapps.AbstractProfileConnector", "Builder");
@@ -110,11 +116,21 @@
ClassName.get("com.google.android.enterprise.connectedapps.AbstractUserConnector", "Builder");
public static final ClassName CONNECTION_BINDER_CLASSNAME =
ClassName.get("com.google.android.enterprise.connectedapps", "ConnectionBinder");
+ public static final ClassName USER_BINDER_FACTORY_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps", "UserBinderFactory");
public static final ClassName SCHEDULED_EXECUTOR_SERVICE_CLASSNAME =
ClassName.get("java.util.concurrent", "ScheduledExecutorService");
+ public static final ClassName FAKE_PROFILE_CONNECTOR_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps.testing", "FakeProfileConnector");
public static final ClassName ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME =
ClassName.get(
"com.google.android.enterprise.connectedapps.testing", "AbstractFakeProfileConnector");
+ public static final ClassName ABSTRACT_FAKE_USER_CONNECTOR_CLASSNAME =
+ ClassName.get(
+ "com.google.android.enterprise.connectedapps.testing", "AbstractFakeUserConnector");
+ public static final ClassName FAKE_USER_CONNECTOR_WRAPPER_CLASSNAME =
+ ClassName.get(
+ "com.google.android.enterprise.connectedapps.testing", "FakeUserConnectorWrapper");
public static final ClassName VERSION_CLASSNAME = ClassName.get("android.os.Build", "VERSION");
public static final ClassName VERSION_CODES_CLASSNAME =
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ConfigurationCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ConfigurationCodeGenerator.java
index a4647dd..ed72310 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ConfigurationCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ConfigurationCodeGenerator.java
@@ -30,13 +30,12 @@
*/
class ConfigurationCodeGenerator {
private boolean generated = false;
- private final CrossProfileConfigurationInfo configuration;
private final ServiceGenerator serviceGenerator;
private final DispatcherGenerator dispatcherGenerator;
ConfigurationCodeGenerator(
GeneratorContext generatorContext, CrossProfileConfigurationInfo configuration) {
- this.configuration = checkNotNull(configuration);
+ checkNotNull(configuration);
this.serviceGenerator = new ServiceGenerator(checkNotNull(generatorContext), configuration);
this.dispatcherGenerator = new DispatcherGenerator(generatorContext, configuration);
}
@@ -48,11 +47,6 @@
}
generated = true;
- if (configuration.profileConnector() == null) {
- // Without a connector we can't line things up so don't generate
- return;
- }
-
serviceGenerator.generate();
dispatcherGenerator.generate();
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackCodeGenerator.java
index 2822bf2..255d596 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackCodeGenerator.java
@@ -15,16 +15,17 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.classNameInferringPackageFromElement;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ASYNC_CALLBACK_PARAM_MULTIMERGER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ASYNC_CALLBACK_PARAM_MULTIMERGER_COMPLETE_LISTENER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_UTILITIES_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_BUNDLE_CALL_SENDER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_EXCEPTION_PARCEL_CALL_SENDER_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_PARCEL_CALL_SENDER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_EXCEPTION_BUNDLE_CALL_SENDER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_CALLBACK_CLASSNAME;
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.PARCEL_UTILITIES_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -43,7 +44,6 @@
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.PackageElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
@@ -255,7 +255,7 @@
+ " $2T}.\n",
callbackInterface.interfaceElement(),
CROSS_PROFILE_CALLBACK_CLASSNAME,
- PARCEL_CLASSNAME);
+ BUNDLE_CLASSNAME);
classBuilder.addField(
FieldSpec.builder(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
@@ -305,22 +305,20 @@
methodBuilder.addStatement(
"$1T callSender = new $1T(callback, /* methodIdentifier= */ $2L)",
- CROSS_PROFILE_CALLBACK_PARCEL_CALL_SENDER_CLASSNAME,
+ CROSS_PROFILE_CALLBACK_BUNDLE_CALL_SENDER_CLASSNAME,
callbackInterface.getIdentifier(method));
- // parcel is recycled in this method
- methodBuilder.addStatement("$1T parcel = $1T.obtain()", PARCEL_CLASSNAME);
+ methodBuilder.addStatement(
+ "$1T bundle = new $1T($2T.class.getClassLoader())", BUNDLE_CLASSNAME, BUNDLER_CLASSNAME);
for (VariableElement param : method.getParameters()) {
methodBuilder.addStatement(
- "bundler.writeToParcel(parcel, $1L, $2L, /* flags= */ 0)",
+ "bundler.writeToBundle(bundle, $1S, $1L, $2L)",
param.getSimpleName(),
TypeUtils.generateBundlerType(param.asType()));
}
- methodBuilder.addStatement("callSender.makeParcelCall(parcel)", PARCEL_CLASSNAME);
-
- methodBuilder.addStatement("parcel.recycle()");
+ methodBuilder.addStatement("callSender.makeBundleCall(bundle)");
methodBuilder
.nextControlFlow("catch ($T e)", Exception.class)
@@ -328,16 +326,16 @@
.addStatement(
"$1T unavailableProfileException = new $1T(\"Error when writing callback result\", e)",
UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME)
- // parcel is recycled in this method
- .addStatement("$1T parcel = $1T.obtain()", PARCEL_CLASSNAME)
.addStatement(
- "$T.writeThrowableToParcel(parcel, unavailableProfileException)",
- PARCEL_UTILITIES_CLASSNAME)
+ "$1T bundle = new $1T($2T.class.getClassLoader())", BUNDLE_CLASSNAME, BUNDLER_CLASSNAME)
+ .addStatement(
+ "$T.writeThrowableToBundle(bundle, $S, unavailableProfileException)",
+ BUNDLE_UTILITIES_CLASSNAME,
+ "throwable")
.addStatement(
"$1T callSender = new $1T(callback)",
- CROSS_PROFILE_CALLBACK_EXCEPTION_PARCEL_CALL_SENDER_CLASSNAME)
- .addStatement("callSender.makeParcelCall(parcel)", PARCEL_CLASSNAME)
- .addStatement("parcel.recycle()")
+ CROSS_PROFILE_CALLBACK_EXCEPTION_BUNDLE_CALL_SENDER_CLASSNAME)
+ .addStatement("callSender.makeBundleCall(bundle)")
.nextControlFlow("catch ($T r)", UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME)
.addComment(
"TODO: Decide what should happen if the connection is dropped between the call and"
@@ -404,7 +402,7 @@
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(int.class, "methodIdentifier")
- .addParameter(PARCEL_CLASSNAME, "params");
+ .addParameter(BUNDLE_CLASSNAME, "params");
methodBuilder.beginControlFlow("switch (methodIdentifier)$>");
for (ExecutableElement method : callbackInterface.methods()) {
@@ -424,11 +422,12 @@
MethodSpec.methodBuilder("onException")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
- .addParameter(PARCEL_CLASSNAME, "exception");
+ .addParameter(BUNDLE_CLASSNAME, "exception");
methodBuilder.addStatement(
- "$1T throwable = $2T.readThrowableFromParcel(exception)",
+ "$1T throwable = $2T.readThrowableFromBundle(exception, $3S)",
Throwable.class,
- PARCEL_UTILITIES_CLASSNAME);
+ BUNDLE_UTILITIES_CLASSNAME,
+ "throwable");
methodBuilder.addStatement("exceptionCallback.onException(throwable)");
@@ -438,7 +437,8 @@
private void addDispatchCode(MethodSpec.Builder methodBuilder, ExecutableElement method) {
for (VariableElement parameter : method.getParameters()) {
methodBuilder.addStatement(
- "@SuppressWarnings(\"unchecked\") $1T $2L = ($1T) bundler.readFromParcel(params, $3L)",
+ "@SuppressWarnings(\"unchecked\") $1T $2L = ($1T) bundler.readFromBundle(params, $2S,"
+ + " $3L)",
parameter.asType(),
parameter.getSimpleName().toString(),
TypeUtils.generateBundlerType(parameter.asType()));
@@ -454,48 +454,42 @@
static ClassName getCrossProfileCallbackMultiInterfaceClassName(
GeneratorContext generatorContext, CrossProfileCallbackInterfaceInfo callbackInterface) {
- PackageElement originalPackage =
- generatorContext.elements().getPackageOf(callbackInterface.interfaceElement());
- String interfaceName = String.format("%s_Multi", callbackInterface.simpleName());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), interfaceName);
+ return classNameInferringPackageFromElement(
+ generatorContext,
+ callbackInterface.interfaceElement(),
+ String.format("%s_Multi", callbackInterface.simpleName()));
}
static ClassName getCrossProfileCallbackMultiMergerResultClassName(
GeneratorContext generatorContext, CrossProfileCallbackInterfaceInfo callbackInterface) {
- PackageElement originalPackage =
- generatorContext.elements().getPackageOf(callbackInterface.interfaceElement());
- String interfaceName =
- String.format("Profile_%s_MultiMergerResult", callbackInterface.simpleName());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), interfaceName);
+ return classNameInferringPackageFromElement(
+ generatorContext,
+ callbackInterface.interfaceElement(),
+ String.format("Profile_%s_MultiMergerResult", callbackInterface.simpleName()));
}
static ClassName getCrossProfileCallbackMultiMergerInputClassName(
GeneratorContext generatorContext, CrossProfileCallbackInterfaceInfo callbackInterface) {
- PackageElement originalPackage =
- generatorContext.elements().getPackageOf(callbackInterface.interfaceElement());
- String interfaceName =
- String.format("Profile_%s_MultiMergerInput", callbackInterface.simpleName());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), interfaceName);
+ return classNameInferringPackageFromElement(
+ generatorContext,
+ callbackInterface.interfaceElement(),
+ String.format("Profile_%s_MultiMergerInput", callbackInterface.simpleName()));
}
static ClassName getCrossProfileCallbackReceiverClassName(
GeneratorContext generatorContext, CrossProfileCallbackInterfaceInfo callbackInterface) {
- PackageElement originalPackage =
- generatorContext.elements().getPackageOf(callbackInterface.interfaceElement());
- String interfaceName = String.format("Profile_%s_Receiver", callbackInterface.simpleName());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), interfaceName);
+ return classNameInferringPackageFromElement(
+ generatorContext,
+ callbackInterface.interfaceElement(),
+ String.format("Profile_%s_Receiver", callbackInterface.simpleName()));
}
static ClassName getCrossProfileCallbackSenderClassName(
GeneratorContext generatorContext, CrossProfileCallbackInterfaceInfo callbackInterface) {
- PackageElement originalPackage =
- generatorContext.elements().getPackageOf(callbackInterface.interfaceElement());
- String interfaceName = String.format("Profile_%s_Sender", callbackInterface.simpleName());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), interfaceName);
+ return classNameInferringPackageFromElement(
+ generatorContext,
+ callbackInterface.interfaceElement(),
+ String.format("Profile_%s_Sender", callbackInterface.simpleName()));
}
}
+
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeCodeGenerator.java
index 075ac6c..c1c0336 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeCodeGenerator.java
@@ -23,15 +23,9 @@
class CrossProfileTypeCodeGenerator {
private boolean generated = false;
- private final InterfaceGenerator interfaceGenerator;
- private final CurrentProfileGenerator currentProfileGenerator;
- private final OtherProfileGenerator otherProfileGenerator;
- private final IfAvailableGenerator ifAvailableGenerator;
- private final AlwaysThrowsGenerator alwaysThrowsGenerator;
+ private final CrossProfileTypeInterfaceGenerator crossProfileTypeInterfaceGenerator;
private final MultipleProfilesGenerator multipleProfilesGenerator;
private final DefaultProfileClassGenerator defaultProfileClassGenerator;
- private final InternalCrossProfileClassGenerator internalCrossProfileClassGenerator;
- private final BundlerGenerator bundlerGenerator;
public CrossProfileTypeCodeGenerator(
GeneratorContext generatorContext,
@@ -39,18 +33,12 @@
CrossProfileTypeInfo crossProfileType) {
checkNotNull(generatorContext);
checkNotNull(crossProfileType);
- this.interfaceGenerator = new InterfaceGenerator(generatorContext, crossProfileType);
- this.currentProfileGenerator = new CurrentProfileGenerator(generatorContext, crossProfileType);
- this.otherProfileGenerator = new OtherProfileGenerator(generatorContext, crossProfileType);
- this.ifAvailableGenerator = new IfAvailableGenerator(generatorContext, crossProfileType);
- this.alwaysThrowsGenerator = new AlwaysThrowsGenerator(generatorContext, crossProfileType);
+ this.crossProfileTypeInterfaceGenerator =
+ new CrossProfileTypeInterfaceGenerator(generatorContext, crossProfileType);
this.multipleProfilesGenerator =
new MultipleProfilesGenerator(generatorContext, crossProfileType);
this.defaultProfileClassGenerator =
new DefaultProfileClassGenerator(generatorContext, crossProfileType);
- this.internalCrossProfileClassGenerator =
- new InternalCrossProfileClassGenerator(generatorContext, providerClass, crossProfileType);
- this.bundlerGenerator = new BundlerGenerator(generatorContext, crossProfileType);
}
void generate() {
@@ -60,14 +48,8 @@
}
generated = true;
- interfaceGenerator.generate();
- currentProfileGenerator.generate();
- otherProfileGenerator.generate();
- ifAvailableGenerator.generate();
- alwaysThrowsGenerator.generate();
+ crossProfileTypeInterfaceGenerator.generate();
multipleProfilesGenerator.generate();
defaultProfileClassGenerator.generate();
- internalCrossProfileClassGenerator.generate();
- bundlerGenerator.generate();
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeInterfaceGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeInterfaceGenerator.java
new file mode 100644
index 0000000..b0db0db
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeInterfaceGenerator.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CONNECTOR_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getCrossProfileTypeInterfaceClassName;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getMultipleSenderInterfaceClassName;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getSingleSenderCanThrowInterfaceClassName;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getSingleSenderInterfaceClassName;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
+import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector;
+import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
+import com.google.android.enterprise.connectedapps.processor.containers.ConnectorInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.android.enterprise.connectedapps.processor.containers.ProfileConnectorInfo;
+import com.google.common.base.Ascii;
+import com.squareup.javapoet.ArrayTypeName;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import java.util.Optional;
+import javax.lang.model.element.Modifier;
+
+/** Generator of cross-profile code for a single {@link CrossProfile} type. */
+final class CrossProfileTypeInterfaceGenerator {
+
+ private boolean generated = false;
+ private final GeneratorContext generatorContext;
+ private final GeneratorUtilities generatorUtilities;
+ private final CrossProfileTypeInfo crossProfileType;
+ private final Optional<ProfileConnectorInfo> profileConnector;
+
+ CrossProfileTypeInterfaceGenerator(
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
+ this.generatorContext = checkNotNull(generatorContext);
+ this.generatorUtilities = new GeneratorUtilities(generatorContext);
+ this.crossProfileType = checkNotNull(crossProfileType);
+ this.profileConnector =
+ crossProfileType.connectorInfo().map(ConnectorInfo::profileConnector).map(Optional::get);
+ }
+
+ void generate() {
+ if (generated) {
+ throw new IllegalStateException(
+ "CrossProfileTypeInterfaceGenerator#generate can only be called once");
+ }
+ generated = true;
+
+ generateCrossProfileTypeInterface();
+ }
+
+ private void generateCrossProfileTypeInterface() {
+ ClassName interfaceName =
+ getCrossProfileTypeInterfaceClassName(generatorContext, crossProfileType);
+
+ TypeSpec.Builder interfaceBuilder =
+ TypeSpec.interfaceBuilder(interfaceName)
+ .addJavadoc(
+ "Entry point for cross-profile calls to {@link $T}.\n",
+ crossProfileType.className())
+ .addModifiers(Modifier.PUBLIC);
+
+ ClassName connectorClassName =
+ profileConnector.isPresent()
+ ? profileConnector.get().connectorClassName()
+ : PROFILE_CONNECTOR_CLASSNAME;
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("create")
+ .returns(interfaceName)
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addParameter(connectorClassName, "connector")
+ .addStatement(
+ "return new $T(connector)",
+ DefaultProfileClassGenerator.getDefaultProfileClassName(
+ generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("current")
+ .addJavadoc("Run a method on the current profile.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("other")
+ .addJavadoc("Run a method on the other profile, if accessible.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("personal")
+ .addJavadoc("Run a method on the personal profile, if accessible.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("work")
+ .addJavadoc("Run a method on the work profile, if accessible.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("profile")
+ .addJavadoc("Run a method on the given profile, if accessible.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addParameter(PROFILE_CLASSNAME, "profile")
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("profiles")
+ .addJavadoc(
+ CodeBlock.builder()
+ .add("Run a method on the given profiles, if accessible.\n\n")
+ .add(
+ "<p>This will deduplicate profiles to ensure that the method is only run"
+ + " at most once on each profile.\n")
+ .build())
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addParameter(ArrayTypeName.of(PROFILE_CLASSNAME), "profiles")
+ .varargs(true)
+ .returns(getMultipleSenderInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("both")
+ .addJavadoc("Run a method on both the personal and work profile, if accessible.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getMultipleSenderInterfaceClassName(generatorContext, crossProfileType))
+ .build());
+
+ if (!profileConnector.isPresent()
+ || profileConnector.get().primaryProfile() != ProfileType.NONE) {
+ generatePrimarySecondaryMethods(interfaceBuilder);
+ }
+
+ generatorUtilities.writeClassToFile(interfaceName.packageName(), interfaceBuilder);
+ }
+
+ private void generatePrimarySecondaryMethods(TypeSpec.Builder interfaceBuilder) {
+ generatePrimaryMethod(interfaceBuilder);
+ generateSecondaryMethod(interfaceBuilder);
+ generateSuppliersMethod(interfaceBuilder);
+ }
+
+ private void generatePrimaryMethod(TypeSpec.Builder interfaceBuilder) {
+ MethodSpec.Builder methodBuilder =
+ MethodSpec.methodBuilder("primary")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType));
+
+ if (profileConnector.isPresent()) {
+ methodBuilder.addJavadoc(
+ "Run a method on the primary ("
+ + Ascii.toLowerCase(profileConnector.get().primaryProfile().name())
+ + ") profile, if accessible.\n\n@see $T#primaryProfile()\n",
+ CustomProfileConnector.class);
+ } else {
+ methodBuilder.addJavadoc(
+ "Run a method on the primary profile, if accessible.\n\n"
+ + "@throws $1T if the {@link $2T} does not have a primary profile set\n"
+ + "@see $2T#primaryProfile()\n",
+ IllegalStateException.class,
+ CustomProfileConnector.class);
+ }
+
+ interfaceBuilder.addMethod(methodBuilder.build());
+ }
+
+ private void generateSecondaryMethod(TypeSpec.Builder interfaceBuilder) {
+ MethodSpec.Builder methodBuilder =
+ MethodSpec.methodBuilder("secondary")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType));
+
+ if (profileConnector.isPresent()) {
+ String secondaryProfileName =
+ profileConnector.get().primaryProfile().equals(ProfileType.WORK)
+ ? Ascii.toLowerCase(ProfileType.PERSONAL.name())
+ : Ascii.toLowerCase(ProfileType.WORK.name());
+ methodBuilder.addJavadoc(
+ "Run a method on the secondary ("
+ + secondaryProfileName
+ + ") profile, if accessible.\n\n@see $T#primaryProfile()\n",
+ CustomProfileConnector.class);
+ } else {
+ methodBuilder.addJavadoc(
+ "Run a method on the secondary profile, if accessible.\n\n"
+ + "@throws $1T if the {@link $2T} does not have a primary profile set\n"
+ + "@see $2T#primaryProfile()\n",
+ IllegalStateException.class,
+ CustomProfileConnector.class);
+ }
+
+ interfaceBuilder.addMethod(methodBuilder.build());
+ }
+
+ private void generateSuppliersMethod(TypeSpec.Builder interfaceBuilder) {
+ MethodSpec.Builder methodBuilder =
+ MethodSpec.methodBuilder("suppliers")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getMultipleSenderInterfaceClassName(generatorContext, crossProfileType));
+
+ if (profileConnector.isPresent()) {
+ String primaryProfileName =
+ profileConnector.get().primaryProfile().equals(ProfileType.WORK)
+ ? Ascii.toLowerCase(ProfileType.WORK.name())
+ : Ascii.toLowerCase(ProfileType.PERSONAL.name());
+ String secondaryProfileName =
+ profileConnector.get().primaryProfile().equals(ProfileType.WORK)
+ ? Ascii.toLowerCase(ProfileType.PERSONAL.name())
+ : Ascii.toLowerCase(ProfileType.WORK.name());
+ methodBuilder
+ .addJavadoc("Run a method on supplier profiles, if accessible.\n\n")
+ .addJavadoc(
+ "<p>When run from the primary ($1L) profile, supplier profiles are the primary ($1L)"
+ + " and secondary ($2L) profiles. When run from the secondary ($2L) profile,"
+ + " supplier profiles includes only the secondary ($2L) profile.\n\n",
+ primaryProfileName,
+ secondaryProfileName)
+ .addJavadoc("@see $T#primaryProfile()\n", CustomProfileConnector.class);
+ } else {
+ methodBuilder
+ .addJavadoc("Run a method on supplier profiles, if accessible.\n\n")
+ .addJavadoc(
+ "<p>When run from the primary profile, supplier profiles are the primary and"
+ + " secondary profiles. When run from the secondary profile, supplier profiles"
+ + " includes only the secondary profile.\n\n")
+ .addJavadoc(
+ "@throws $1T if the {@link $2T} does not have a primary profile set\n",
+ IllegalStateException.class,
+ CustomProfileConnector.class)
+ .addJavadoc("@see $T#primaryProfile()\n", CustomProfileConnector.class);
+ }
+
+ interfaceBuilder.addMethod(methodBuilder.build());
+ }
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserTypeCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserTypeCodeGenerator.java
new file mode 100644
index 0000000..d65524c
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserTypeCodeGenerator.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.AlwaysThrowsGenerator.getAlwaysThrowsClassName;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROCESS_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.USER_CONNECTOR_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.USER_CONNECTOR_WRAPPER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.USER_HANDLE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.VERSION_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.VERSION_CODES_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CurrentProfileGenerator.getCurrentProfileClassName;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getSingleSenderCanThrowInterfaceClassName;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getSingleSenderInterfaceClassName;
+import static com.google.android.enterprise.connectedapps.processor.OtherProfileGenerator.getOtherProfileClassName;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.android.enterprise.connectedapps.processor.containers.ProviderClassInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.UserConnectorInfo;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import java.util.Optional;
+import javax.lang.model.element.Modifier;
+
+class CrossUserTypeCodeGenerator {
+
+ private final GeneratorContext generatorContext;
+ private final GeneratorUtilities generatorUtilities;
+ private final CrossProfileTypeInfo crossUserType;
+ private final Optional<UserConnectorInfo> userConnector;
+
+ private boolean generated = false;
+
+ public CrossUserTypeCodeGenerator(
+ GeneratorContext generatorContext,
+ ProviderClassInfo providerClass,
+ CrossProfileTypeInfo crossUserType) {
+ checkNotNull(generatorContext);
+ checkNotNull(crossUserType);
+ this.generatorContext = generatorContext;
+ generatorUtilities = new GeneratorUtilities(generatorContext);
+ this.crossUserType = crossUserType;
+ this.userConnector =
+ crossUserType.connectorInfo().map(connectorInfo -> connectorInfo.userConnector().get());
+ }
+
+ void generate() {
+ if (generated) {
+ throw new IllegalStateException(
+ "CrossProfileTypeCodeGenerator#generate can only be called once");
+ }
+ generated = true;
+
+ generateCrossUserInterface();
+ generateCrossUserDefaultImplementation();
+ }
+
+ private void generateCrossUserInterface() {
+ ClassName interfaceName = getCrossUserTypeInterfaceClassName(generatorContext, crossUserType);
+ ClassName connectorClassName =
+ userConnector.map(UserConnectorInfo::connectorClassName).orElse(USER_CONNECTOR_CLASSNAME);
+
+ TypeSpec.Builder interfaceBuilder =
+ TypeSpec.interfaceBuilder(interfaceName)
+ .addJavadoc(
+ "Entry point for cross-user calls to {@link $T}.\n", crossUserType.className())
+ .addModifiers(Modifier.PUBLIC);
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("create")
+ .returns(interfaceName)
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addParameter(connectorClassName, "connector")
+ .addStatement(
+ "return new $T(connector)",
+ getDefaultUserClassName(generatorContext, crossUserType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("current")
+ .addJavadoc("Run a method on the current user.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .returns(getSingleSenderInterfaceClassName(generatorContext, crossUserType))
+ .build());
+
+ interfaceBuilder.addMethod(
+ MethodSpec.methodBuilder("user")
+ .addJavadoc("Run a method on a specific user.\n")
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addParameter(USER_HANDLE_CLASSNAME, "userHandle")
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossUserType))
+ .build());
+
+ generatorUtilities.writeClassToFile(interfaceName.packageName(), interfaceBuilder);
+ }
+
+ private void generateCrossUserDefaultImplementation() {
+ ClassName userClassName = getDefaultUserClassName(generatorContext, crossUserType);
+ ClassName crossUserTypeInterfaceClassName =
+ getCrossUserTypeInterfaceClassName(generatorContext, crossUserType);
+
+ TypeSpec.Builder classBuilder =
+ TypeSpec.classBuilder(userClassName)
+ .addJavadoc(
+ "Default implementation of {@link $T} to be used in production.\n",
+ crossUserTypeInterfaceClassName)
+ .addModifiers(Modifier.FINAL);
+
+ classBuilder.addSuperinterface(crossUserTypeInterfaceClassName);
+
+ classBuilder.addField(
+ FieldSpec.builder(USER_CONNECTOR_CLASSNAME, "connector")
+ .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+ .build());
+
+ classBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addParameter(USER_CONNECTOR_CLASSNAME, "connector")
+ .addModifiers(Modifier.PUBLIC)
+ .addStatement("this.connector = connector")
+ .build());
+
+ addCurrentMethod(classBuilder);
+ addUserMethod(classBuilder);
+ addCurrentProfileHelper(classBuilder);
+
+ generatorUtilities.writeClassToFile(userClassName.packageName(), classBuilder);
+ }
+
+ private void addCurrentProfileHelper(TypeSpec.Builder classBuilder) {
+ ClassName currentProfileConcreteType =
+ getCurrentProfileClassName(generatorContext, crossUserType);
+ MethodSpec.Builder currentProfileHelperMethodBuilder =
+ MethodSpec.methodBuilder("instanceOfCurrentProfile")
+ .addModifiers(Modifier.PRIVATE)
+ .returns(currentProfileConcreteType)
+ .addStatement(
+ "$T context = connector.applicationContext($T.myUserHandle())",
+ CONTEXT_CLASSNAME,
+ PROCESS_CLASSNAME);
+
+ if (crossUserType.isStatic()) {
+ currentProfileHelperMethodBuilder.addStatement(
+ "return new $1T(context)", currentProfileConcreteType);
+ } else {
+ currentProfileHelperMethodBuilder.addStatement(
+ "return new $1T(context, $2T.instance().crossProfileType(context))",
+ currentProfileConcreteType,
+ InternalCrossProfileClassGenerator.getInternalCrossProfileClassName(
+ generatorContext, crossUserType));
+ }
+
+ classBuilder.addMethod(currentProfileHelperMethodBuilder.build());
+ }
+
+ private void addCurrentMethod(TypeSpec.Builder classBuilder) {
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("current")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(getSingleSenderInterfaceClassName(generatorContext, crossUserType))
+ .addStatement("return instanceOfCurrentProfile()")
+ .build());
+ }
+
+ private void addUserMethod(TypeSpec.Builder classBuilder) {
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("user")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(USER_HANDLE_CLASSNAME, "userHandle")
+ .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossUserType))
+ .beginControlFlow("if ($T.SDK_INT < $T.O)", VERSION_CLASSNAME, VERSION_CODES_CLASSNAME)
+ .addStatement(
+ "return new $T($S)",
+ getAlwaysThrowsClassName(generatorContext, crossUserType),
+ "Cross-user calls are not supported on this version of Android")
+ .nextControlFlow("else if (userHandle == $T.myUserHandle())", PROCESS_CLASSNAME)
+ .addStatement("return instanceOfCurrentProfile()")
+ .nextControlFlow("else")
+ .addStatement(
+ "return new $T(new $T(connector, userHandle))",
+ getOtherProfileClassName(generatorContext, crossUserType),
+ USER_CONNECTOR_WRAPPER_CLASSNAME)
+ .endControlFlow()
+ .build());
+ }
+
+ static ClassName getCrossUserTypeInterfaceClassName(
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
+ return transformClassName(crossProfileType.generatedClassName(), prepend("User"));
+ }
+
+ static ClassName getDefaultUserClassName(
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
+ return transformClassName(
+ getCrossUserTypeInterfaceClassName(generatorContext, crossProfileType), prepend("Default"));
+ }
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CurrentProfileGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CurrentProfileGenerator.java
index 103b965..d16bdf8 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CurrentProfileGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CurrentProfileGenerator.java
@@ -15,6 +15,8 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME;
@@ -25,7 +27,6 @@
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
@@ -98,19 +99,6 @@
}
}
- classBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addAnnotation(Override.class)
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC)
- .returns(className)
- .addParameter(long.class, "timeout")
- .addStatement("return this")
- .build());
-
ClassName ifAvailableClass =
IfAvailableGenerator.getIfAvailableClassName(generatorContext, crossProfileType);
@@ -213,7 +201,6 @@
static ClassName getCurrentProfileClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_CurrentProfile");
+ return transformClassName(crossProfileType.generatedClassName(), append("_CurrentProfile"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DefaultProfileClassGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DefaultProfileClassGenerator.java
index 114a19c..af10095 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DefaultProfileClassGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DefaultProfileClassGenerator.java
@@ -16,6 +16,8 @@
package com.google.android.enterprise.connectedapps.processor;
import static com.google.android.enterprise.connectedapps.processor.AlwaysThrowsGenerator.getAlwaysThrowsClassName;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_AWARE_UTILS_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CONNECTOR_CLASSNAME;
@@ -30,8 +32,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
+import com.google.android.enterprise.connectedapps.processor.containers.ConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.android.enterprise.connectedapps.processor.containers.ProfileConnectorInfo;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
@@ -40,6 +44,7 @@
import com.squareup.javapoet.TypeSpec;
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
import javax.lang.model.element.Modifier;
/**
@@ -56,12 +61,15 @@
private final GeneratorContext generatorContext;
private final GeneratorUtilities generatorUtilities;
private final CrossProfileTypeInfo crossProfileType;
+ private final Optional<ProfileConnectorInfo> profileConnector;
DefaultProfileClassGenerator(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
this.generatorContext = checkNotNull(generatorContext);
this.generatorUtilities = new GeneratorUtilities(generatorContext);
this.crossProfileType = checkNotNull(crossProfileType);
+ this.profileConnector =
+ crossProfileType.connectorInfo().map(ConnectorInfo::profileConnector).map(Optional::get);
}
void generate() {
@@ -77,11 +85,6 @@
private void generateDefaultProfileClass() {
ClassName className = getDefaultProfileClassName(generatorContext, crossProfileType);
- ClassName connectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? crossProfileType.profileConnector().get().connectorClassName()
- : PROFILE_CONNECTOR_CLASSNAME;
-
ClassName crossProfileTypeInterfaceClassName =
InterfaceGenerator.getCrossProfileTypeInterfaceClassName(
generatorContext, crossProfileType);
@@ -96,13 +99,13 @@
classBuilder.addSuperinterface(crossProfileTypeInterfaceClassName);
classBuilder.addField(
- FieldSpec.builder(connectorClassName, "connector")
+ FieldSpec.builder(PROFILE_CONNECTOR_CLASSNAME, "connector")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.build());
classBuilder.addMethod(
MethodSpec.constructorBuilder()
- .addParameter(connectorClassName, "connector")
+ .addParameter(PROFILE_CONNECTOR_CLASSNAME, "connector")
.addModifiers(Modifier.PUBLIC)
.addStatement("this.connector = connector")
.build());
@@ -220,8 +223,8 @@
.addStatement("return profiles(currentProfileIdentifier, otherProfileIdentifier)")
.build());
- if (!crossProfileType.profileConnector().isPresent()
- || crossProfileType.profileConnector().get().primaryProfile() != ProfileType.NONE) {
+ if (!profileConnector.isPresent()
+ || profileConnector.get().primaryProfile() != ProfileType.NONE) {
generatePrimarySecondaryMethods(classBuilder);
}
@@ -323,8 +326,6 @@
static ClassName getDefaultProfileClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return ClassName.get(
- crossProfileType.profileClassName().packageName(),
- "Default" + crossProfileType.profileClassName().simpleName());
+ return transformClassName(crossProfileType.generatedClassName(), prepend("DefaultProfile"));
}
}
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 f8733d1..8ae6304 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
@@ -15,19 +15,23 @@
*/
package com.google.android.enterprise.connectedapps.processor;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BACKGROUND_EXCEPTION_THROWER_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BINDER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CALL_RECEIVER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_UTILITIES_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_SENDER_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CALL_RECEIVER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_THROWER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_UTILITIES_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.ServiceGenerator.getConnectedAppsServiceClassName;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.joining;
import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
+import com.google.android.enterprise.connectedapps.annotations.UncaughtExceptionsPolicy;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileConfigurationInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
import com.google.android.enterprise.connectedapps.processor.containers.ProviderClassInfo;
@@ -84,17 +88,19 @@
+ "<p>This uses a {@link $T} to construct calls before passing the completed"
+ " call\n"
+ "to a provider.\n",
- PARCEL_CALL_RECEIVER_CLASSNAME);
+ BUNDLE_CALL_RECEIVER_CLASSNAME);
classBuilder.addField(
- FieldSpec.builder(PARCEL_CALL_RECEIVER_CLASSNAME, "parcelCallReceiver")
+ FieldSpec.builder(BUNDLE_CALL_RECEIVER_CLASSNAME, "bundleCallReceiver")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
- .initializer("new $T()", PARCEL_CALL_RECEIVER_CLASSNAME)
+ .initializer("new $T()", BUNDLE_CALL_RECEIVER_CLASSNAME)
.build());
addCallMethod(classBuilder);
addPrepareCallMethod(classBuilder);
+ addPrepareBundleMethod(classBuilder);
addFetchResponseMethod(classBuilder);
+ addFetchResponseBundleMethod(classBuilder);
generatorUtilities.writeClassToFile(className.packageName(), classBuilder);
}
@@ -108,7 +114,7 @@
.addParameter(int.class, "blockId")
.addParameter(int.class, "numBytes")
.addParameter(ArrayTypeName.of(byte.class), "paramBytes")
- .addStatement("parcelCallReceiver.prepareCall(callId, blockId, numBytes, paramBytes)")
+ .addStatement("bundleCallReceiver.prepareCall(callId, blockId, numBytes, paramBytes)")
.addJavadoc(
"Store a block of bytes to be part of a future call to\n"
+ "{@link #call(Context, long, int, long, int, byte[], ICrossProfileCallback)}."
@@ -127,7 +133,31 @@
+ " $1T#MAX_BYTES_PER_BLOCK} bytes.\n\n"
+ "@see $2T#prepareCall(long, int, int, byte[])",
CROSS_PROFILE_SENDER_CLASSNAME,
- PARCEL_CALL_RECEIVER_CLASSNAME)
+ BUNDLE_CALL_RECEIVER_CLASSNAME)
+ .build();
+ classBuilder.addMethod(prepareCallMethod);
+ }
+
+ private static void addPrepareBundleMethod(TypeSpec.Builder classBuilder) {
+ MethodSpec prepareCallMethod =
+ MethodSpec.methodBuilder("prepareBundle")
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(long.class, "callId")
+ .addParameter(int.class, "bundleId")
+ .addParameter(BUNDLE_CLASSNAME, "bundle")
+ .addStatement("bundleCallReceiver.prepareBundle(callId, bundleId, bundle)")
+ .addJavadoc(
+ "Store a bundle to be part of a future call to\n"
+ + "{@link #call(Context, long, int, long, int, byte[], ICrossProfileCallback)}."
+ + "\n\n"
+ + "@param callId Arbitrary identifier used to link together\n"
+ + " {@link #prepareCall(Context, long, int, int, byte[])} and\n "
+ + "{@link #call(Context, long, int, long, int, byte[], ICrossProfileCallback)}"
+ + " calls.\n"
+ + "@param bundleId The (zero indexed) number of this bundle.\n"
+ + "@see $1T#prepareBundle(long, int, bundle)",
+ BUNDLE_CALL_RECEIVER_CLASSNAME)
.build();
classBuilder.addMethod(prepareCallMethod);
}
@@ -140,7 +170,7 @@
.addParameter(long.class, "callId")
.addParameter(int.class, "blockId")
.returns(ArrayTypeName.of(byte.class))
- .addStatement("return parcelCallReceiver.getPreparedResponse(callId, blockId)")
+ .addStatement("return bundleCallReceiver.getPreparedResponse(callId, blockId)")
.addJavadoc(
"Fetch a response block if a previous call to\n {@link #call(Context, long, int,"
+ " long, int, byte[], ICrossProfileCallback)} returned a\n byte array with"
@@ -149,7 +179,29 @@
+ " long, int, long, int, byte[], ICrossProfileCallback)}\n"
+ "@param blockId The (zero indexed) number of the block to fetch.\n\n"
+ "@see $1T#getPreparedResponse(long, int)\n",
- PARCEL_CALL_RECEIVER_CLASSNAME)
+ BUNDLE_CALL_RECEIVER_CLASSNAME)
+ .build();
+ classBuilder.addMethod(prepareCallMethod);
+ }
+
+ private static void addFetchResponseBundleMethod(TypeSpec.Builder classBuilder) {
+ MethodSpec prepareCallMethod =
+ MethodSpec.methodBuilder("fetchResponseBundle")
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(long.class, "callId")
+ .addParameter(int.class, "bundleId")
+ .returns(BUNDLE_CLASSNAME)
+ .addStatement("return bundleCallReceiver.getPreparedResponseBundle(callId, bundleId)")
+ .addJavadoc(
+ "Fetch a response bundle if a previous call to\n {@link #call(Context, long, int,"
+ + " long, int, byte[], ICrossProfileCallback)} returned a\n byte array with"
+ + " 2 as the first byte.\n\n"
+ + "@param callId should be the same callId used with\n {@link #call(Context,"
+ + " long, int, long, int, byte[], ICrossProfileCallback)}\n"
+ + "@param blockId The (zero indexed) number of the bundle to fetch.\n\n"
+ + "@see $1T#getPreparedResponseBundle(long, int)\n",
+ BUNDLE_CALL_RECEIVER_CLASSNAME)
.build();
classBuilder.addMethod(prepareCallMethod);
}
@@ -160,8 +212,8 @@
methodCode.beginControlFlow("try");
methodCode.addStatement(
- "$1T parcel = parcelCallReceiver.getPreparedCall(callId, blockId, paramBytes)",
- PARCEL_CLASSNAME);
+ "$1T bundle = bundleCallReceiver.getPreparedCall(callId, blockId, paramBytes)",
+ BUNDLE_CLASSNAME);
List<ProviderClassInfo> providers = configuration.providers().asList();
@@ -174,32 +226,42 @@
IllegalArgumentException.class);
methodCode.nextControlFlow("catch ($T e)", RuntimeException.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);
+ "$1T throwableBundle = new $1T($2T.class.getClassLoader())",
+ BUNDLE_CLASSNAME,
+ BUNDLER_CLASSNAME);
methodCode.addStatement(
- "$1T throwableBytes = parcelCallReceiver.prepareResponse(callId, throwableParcel)",
+ "$T.writeThrowableToBundle(throwableBundle, $S, e)",
+ BUNDLE_UTILITIES_CLASSNAME,
+ "throwable");
+ methodCode.addStatement(
+ "$1T throwableBytes = bundleCallReceiver.prepareResponse(callId, throwableBundle)",
ArrayTypeName.of(byte.class));
- methodCode.addStatement("throwableParcel.recycle()");
- // methodCode.addStatement("$T.throwInBackground(e)", BACKGROUND_EXCEPTION_THROWER_CLASSNAME);
+ final UncaughtExceptionsPolicy exceptionsPolicy =
+ configuration.connectorInfo().uncaughtExceptionsPolicy();
+
+ if (exceptionsPolicy.rethrowExceptions) {
+ methodCode.addStatement("$T.delayThrow(e)", 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);
+ "$1T throwableBundle = new $1T($2T.class.getClassLoader())",
+ BUNDLE_CLASSNAME,
+ BUNDLER_CLASSNAME);
methodCode.addStatement(
- "$1T throwableBytes = parcelCallReceiver.prepareResponse(callId, throwableParcel)",
- ArrayTypeName.of(byte.class));
- methodCode.addStatement("throwableParcel.recycle()");
+ "$T.writeThrowableToBundle(throwableBundle, $S, e)",
+ BUNDLE_UTILITIES_CLASSNAME,
+ "throwable");
+ methodCode.addStatement(
+ "$1T throwableBytes = bundleCallReceiver.prepareResponse(callId, throwableBundle)",
+ ArrayTypeName.of(byte.class));
- // methodCode.addStatement("$T.throwInBackground(e)", BACKGROUND_EXCEPTION_THROWER_CLASSNAME);
+ if (exceptionsPolicy.rethrowExceptions) {
+ methodCode.addStatement("$T.delayThrow(e)", EXCEPTION_THROWER_CLASSNAME);
+ }
methodCode.addStatement("return throwableBytes");
methodCode.endControlFlow();
@@ -208,10 +270,11 @@
MethodSpec.methodBuilder("call")
.addModifiers(Modifier.PUBLIC)
.returns(ArrayTypeName.of(byte.class))
- .addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
- // Allow catching of RuntimeException
- .addMember("value", "\"CatchSpecificExceptionsChecker\"")
- .build())
+ .addAnnotation(
+ AnnotationSpec.builder(SuppressWarnings.class)
+ // Allow catching of RuntimeException
+ .addMember("value", "\"CatchSpecificExceptionsChecker\"")
+ .build())
.addParameter(CONTEXT_CLASSNAME, "context")
.addParameter(long.class, "callId")
.addParameter(int.class, "blockId")
@@ -222,8 +285,8 @@
.addCode(methodCode.build())
.addJavadoc(
"Make a call, which will execute some annotated method and return a response.\n\n"
- + "<p>The parameters to the call should be contained in a {@link $1T}"
- + " marshalled into\n"
+ + "<p>The parameters to the call should be contained in a {@link $4T} written"
+ + " to a {@link $1T} and marshalled into\n"
+ "a byte array. If the byte array is larger than {@link"
+ " $2T#MAX_BYTES_PER_BLOCK},\n"
+ "then it should be separated into blocks of {@link"
@@ -245,22 +308,27 @@
+ " {@link #call(Context, long, int, long, int, byte[],"
+ " ICrossProfileCallback)} calls.\n"
+ "@param blockId The (zero indexed) number of this block. Each block should"
- + " be\n {@link CrossProfileSender#MAX_BYTES_PER_BLOCK} bytes so the total"
- + " number of blocks is\n {@code numBytes /"
- + " CrossProfileSender#MAX_BYTES_PER_BLOCK}.\n"
+ + " be\n"
+ + " {@link CrossProfileSender#MAX_BYTES_PER_BLOCK} bytes so the total"
+ + " number of blocks is\n"
+ + " {@code numBytes / CrossProfileSender#MAX_BYTES_PER_BLOCK}.\n"
+ "@param crossProfileTypeIdentifier The generated identifier for the type"
- + " which contains the\n method being called.\n"
+ + " which contains the\n"
+ + " method being called.\n"
+ "@param methodIdentifier The index of the method being called on the cross"
+ " profile type.\n"
+ "@param paramBytes The bytes for the final block, this will be merged with"
- + " any blocks\n previously set by a call to"
- + " {@link #prepareCall(Context, long, int, int, byte[])}.\n"
+ + " any blocks\n"
+ + " previously set by a call to {@link #prepareCall(Context, long, int,"
+ + " int, byte[])}.\n"
+ "@param callback A callback to be used if this is an asynchronous call."
- + " Otherwise this should be\n {@code null}.\n\n"
+ + " Otherwise this should be\n"
+ + " {@code null}.\n\n"
+ "@see $3T#getPreparedCall(long, int, byte[])\n",
PARCEL_CLASSNAME,
CROSS_PROFILE_SENDER_CLASSNAME,
- PARCEL_CALL_RECEIVER_CLASSNAME)
+ BUNDLE_CALL_RECEIVER_CLASSNAME,
+ BUNDLE_CLASSNAME)
.build();
classBuilder.addMethod(callMethod);
@@ -287,22 +355,20 @@
methodCode.beginControlFlow("if ($L)", condition);
methodCode.addStatement(
- "$1T returnParcel = $2T.instance().call(context.getApplicationContext(),"
- + " crossProfileTypeIdentifier, methodIdentifier, parcel, callback)",
- PARCEL_CLASSNAME,
+ "$1T returnBundle = $2T.instance().call(context.getApplicationContext(),"
+ + " crossProfileTypeIdentifier, methodIdentifier, bundle, callback)",
+ BUNDLE_CLASSNAME,
InternalProviderClassGenerator.getInternalProviderClassName(generatorContext, provider));
methodCode.addStatement(
- "$1T returnBytes = parcelCallReceiver.prepareResponse(callId, returnParcel)",
+ "$1T returnBytes = bundleCallReceiver.prepareResponse(callId, returnBundle)",
ArrayTypeName.of(byte.class));
- methodCode.addStatement("parcel.recycle()");
- methodCode.addStatement("returnParcel.recycle()");
methodCode.addStatement("return returnBytes");
methodCode.endControlFlow();
}
static ClassName getDispatcherClassName(
GeneratorContext generatorContext, CrossProfileConfigurationInfo configuration) {
- ClassName serviceName = getConnectedAppsServiceClassName(generatorContext, configuration);
- return ClassName.get(serviceName.packageName(), serviceName.simpleName() + "_Dispatcher");
+ return transformClassName(
+ getConnectedAppsServiceClassName(generatorContext, configuration), append("_Dispatcher"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/EarlyValidator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/EarlyValidator.java
index f1f566d..8c84a70 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/EarlyValidator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/EarlyValidator.java
@@ -17,6 +17,10 @@
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCELABLE_CREATOR_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.GeneratorUtilities.findCrossProfileMethodsInClass;
+import static com.google.android.enterprise.connectedapps.processor.TypeUtils.extractTypeArguments;
+import static com.google.android.enterprise.connectedapps.processor.TypeUtils.getRawTypeClassName;
+import static com.google.android.enterprise.connectedapps.processor.TypeUtils.getRawTypeQualifiedName;
+import static com.google.android.enterprise.connectedapps.processor.TypeUtils.removeTypeArguments;
import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileAnnotation;
import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileConfigurationAnnotation;
import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileConfigurationsAnnotation;
@@ -26,6 +30,7 @@
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
+import com.google.android.enterprise.connectedapps.annotations.Cacheable;
import com.google.android.enterprise.connectedapps.annotations.CustomFutureWrapper;
import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector;
@@ -48,6 +53,7 @@
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
+import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -100,7 +106,7 @@
"All classes specified in 'providers' must be provider classes";
private static final String CONNECTOR_MUST_BE_INTERFACE = "Connectors must be interfaces";
private static final String CONNECTOR_MUST_EXTEND_CONNECTOR =
- "Interfaces specified as a connector must extend ProfileConnector";
+ "Interfaces specified as a connector must extend ProfileConnector or UserConnector";
private static final String CUSTOM_PROFILE_CONNECTOR_MUST_BE_INTERFACE =
"@CustomProfileConnector must only be applied to interfaces";
private static final String CUSTOM_USER_CONNECTOR_MUST_BE_INTERFACE =
@@ -149,9 +155,6 @@
"@CROSS_PROFILE_ANNOTATION annotations on methods can not specify a connector";
private static final String METHOD_PARCELABLE_WRAPPERS_ERROR =
"@CROSS_PROFILE_ANNOTATION annotations on methods can not specify parcelable wrappers";
- private static final String METHOD_CLASSNAME_ERROR =
- "@CROSS_PROFILE_ANNOTATION annotations on methods can not specify a profile class name";
- private static final String INVALID_TIMEOUT_MILLIS = "timeoutMillis must be positive";
private static final String ADDITIONAL_PROFILE_CONNECTOR_METHODS_ERROR =
"Interfaces annotated with @GeneratedProfileConnector can not declare non-static methods";
private static final String ADDITIONAL_USER_CONNECTOR_METHODS_ERROR =
@@ -204,6 +207,16 @@
+ " methods annotated @CROSS_PROFILE_ANNOTATION";
private static final String METHOD_STATICTYPES_ERROR =
"@CROSS_PROFILE_PROVIDER_ANNOTATION annotations on methods can not specify staticTypes";
+ private static final String CACHEABLE_METHOD_RETURNS_VOID_ERROR =
+ "Methods annotated with @Cacheable must return a non-void type";
+ private static final String CACHEABLE_METHOD_RETURNS_NON_SERIALIZABLE_ERROR =
+ "Methods annotated with @Cacheable must return a type which implements Serializable, return"
+ + " a future with a Serializable result or return void with a simple callback parameter.";
+ private static final String CACHEABLE_METHOD_NON_SIMPLE_CALLBACK_ERROR =
+ "Methods annotated with @Cacheable may only have a callback parameter which is simple.";
+ private static final String CACHEABLE_METHOD_USES_INVALID_PARAMETERS_ERROR =
+ "Methods annotated with @Cacheable may only use callbacks that take a single Serializable"
+ + " parameter.";
private final ValidatorContext validatorContext;
private final TypeMirror contextType;
@@ -484,7 +497,8 @@
}
if (configuration.connector().isPresent()
- && !implementsInterface(configuration.connector().get(), profileConnectorType)) {
+ && !implementsInterface(configuration.connector().get(), profileConnectorType)
+ && !implementsInterface(configuration.connector().get(), userConnectorType)) {
showError(CONNECTOR_MUST_EXTEND_CONNECTOR, configuration.configurationElement());
isValid = false;
}
@@ -539,9 +553,9 @@
}
}
- if (crossProfileType.profileConnector().isPresent()
+ if (crossProfileType.connectorInfo().isPresent()
&& !crossProfileType
- .profileConnector()
+ .connectorInfo()
.get()
.connectorElement()
.getKind()
@@ -550,18 +564,15 @@
isValid = false;
}
- if (crossProfileType.profileConnector().isPresent()
+ if (crossProfileType.connectorInfo().isPresent()
&& !implementsInterface(
- crossProfileType.profileConnector().get().connectorElement(), profileConnectorType)) {
+ crossProfileType.connectorInfo().get().connectorElement(), profileConnectorType)
+ && !implementsInterface(
+ crossProfileType.connectorInfo().get().connectorElement(), userConnectorType)) {
showError(CONNECTOR_MUST_EXTEND_CONNECTOR, crossProfileType.crossProfileTypeElement());
isValid = false;
}
- if (crossProfileType.timeoutMillis() <= 0) {
- showError(INVALID_TIMEOUT_MILLIS, crossProfileType.crossProfileTypeElement());
- isValid = false;
- }
-
for (TypeElement parcelableWrapper : crossProfileType.parcelableWrapperClasses()) {
if (parcelableWrapper.getAnnotation(CustomParcelableWrapper.class) == null) {
showError(PARCELABLE_WRAPPER_ANNOTATION_ERROR, crossProfileType.crossProfileTypeElement());
@@ -652,7 +663,7 @@
CrossProfileProviderAnnotationInfo annotationInfo =
AnnotationFinder.extractCrossProfileProviderAnnotationInfo(
- providerMethod, validatorContext.types(), validatorContext.elements());
+ validatorContext, providerMethod);
if (!annotationInfo.staticTypes().isEmpty()) {
showError(METHOD_STATICTYPES_ERROR, providerMethod);
@@ -718,8 +729,7 @@
boolean isValid = true;
CrossProfileAnnotationInfo crossProfileAnnotation =
- AnnotationFinder.extractCrossProfileAnnotationInfo(
- crossProfileMethod, validatorContext.types(), validatorContext.elements());
+ AnnotationFinder.extractCrossProfileAnnotationInfo(validatorContext, crossProfileMethod);
if (!crossProfileAnnotation.connectorIsDefault()) {
showError(METHOD_CONNECTOR_ERROR, crossProfileMethod);
@@ -731,21 +741,10 @@
isValid = false;
}
- if (!crossProfileAnnotation.isProfileClassNameDefault()) {
- showError(METHOD_CLASSNAME_ERROR, crossProfileMethod);
- isValid = false;
- }
-
- if (crossProfileAnnotation.timeoutMillis().isPresent()
- && crossProfileAnnotation.timeoutMillis().get() <= 0) {
- showError(INVALID_TIMEOUT_MILLIS, crossProfileMethod);
- isValid = false;
- }
-
if (!crossProfileMethod.getThrownTypes().isEmpty()) {
if (CrossProfileMethodInfo.isFuture(crossProfileType.supportedTypes(), crossProfileMethod)
|| CrossProfileMethodInfo.getCrossProfileCallbackParam(
- validatorContext.elements(), crossProfileMethod)
+ validatorContext, crossProfileMethod)
.isPresent()) {
showError(ASYNC_DECLARED_EXCEPTION_ERROR, crossProfileMethod);
isValid = false;
@@ -761,6 +760,11 @@
isValid
&& validateReturnType(crossProfileType, crossProfileMethod)
&& validateParameterTypesForCrossProfileMethod(crossProfileType, crossProfileMethod);
+
+ if (crossProfileMethod.getAnnotation(Cacheable.class) != null) {
+ isValid = isValid && validateCacheableMethod(crossProfileType, crossProfileMethod);
+ }
+
return isValid;
}
@@ -874,7 +878,7 @@
CrossProfileCallbackAnnotationInfo annotationInfo =
AnnotationFinder.extractCrossProfileCallbackAnnotationInfo(
- crossProfileCallbackInterface, validatorContext.types(), validatorContext.elements());
+ validatorContext, crossProfileCallbackInterface);
PackageElement packageElement =
(PackageElement) crossProfileCallbackInterface.getEnclosingElement();
@@ -955,6 +959,89 @@
return isValid;
}
+ private boolean validateCacheableMethod(
+ ValidatorCrossProfileTypeInfo crossProfileTypeInfo, ExecutableElement cacheableMethod) {
+ boolean isValid = true;
+
+ TypeMirror returnType = cacheableMethod.getReturnType();
+
+ if (returnType.getKind().equals(TypeKind.VOID)) {
+ isValid = isValid && validateCallbackOnCacheableMethod(cacheableMethod);
+ } else if (!isSerializable(crossProfileTypeInfo, returnType)) {
+ showError(CACHEABLE_METHOD_RETURNS_NON_SERIALIZABLE_ERROR, cacheableMethod);
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ private boolean isSerializable(
+ ValidatorCrossProfileTypeInfo crossProfileTypeInfo, TypeMirror type) {
+ return isSerializable(type) || isFutureWithSerializableResult(crossProfileTypeInfo, type);
+ }
+
+ private boolean isSerializable(TypeMirror type) {
+ TypeMirror serializable =
+ validatorContext.elements().getTypeElement(Serializable.class.getCanonicalName()).asType();
+
+ return validatorContext.types().isAssignable(type, serializable);
+ }
+
+ private boolean isFutureWithSerializableResult(
+ ValidatorCrossProfileTypeInfo crossProfileType, TypeMirror type) {
+
+ if (!crossProfileType.supportedTypes().isFuture(removeTypeArguments(type))) {
+ return false;
+ }
+
+ TypeMirror futureResult = extractTypeArguments(type).get(0);
+
+ return isSerializable(futureResult);
+ }
+
+ private boolean validateCallbackOnCacheableMethod(ExecutableElement cacheableMethod) {
+ boolean isValid = true;
+
+ if (!hasCallback(cacheableMethod)) {
+ showError(CACHEABLE_METHOD_RETURNS_VOID_ERROR, cacheableMethod);
+ return false;
+ }
+
+ TypeElement callback =
+ cacheableMethod.getParameters().stream()
+ .map(v -> validatorContext.elements().getTypeElement(v.asType().toString()))
+ .filter(Objects::nonNull)
+ .filter(AnnotationFinder::hasCrossProfileCallbackAnnotation)
+ .findFirst()
+ .get();
+
+ CrossProfileCallbackAnnotationInfo annotationInfo =
+ AnnotationFinder.extractCrossProfileCallbackAnnotationInfo(validatorContext, callback);
+ if (!annotationInfo.simple()) {
+ showError(CACHEABLE_METHOD_NON_SIMPLE_CALLBACK_ERROR, cacheableMethod);
+ isValid = false;
+ }
+
+ ExecutableElement method = getMethods(callback).stream().findFirst().get();
+ if (!hasSingleSerializableParameterOnly(method)) {
+ showError(CACHEABLE_METHOD_USES_INVALID_PARAMETERS_ERROR, cacheableMethod);
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ private boolean hasCallback(ExecutableElement method) {
+ return method.getParameters().stream()
+ .map(v -> validatorContext.elements().getTypeElement(v.asType().toString()))
+ .filter(Objects::nonNull)
+ .anyMatch(AnnotationFinder::hasCrossProfileCallbackAnnotation);
+ }
+
+ private boolean hasSingleSerializableParameterOnly(ExecutableElement method) {
+ return method.getParameters().stream().filter(p -> isSerializable(p.asType())).count() == 1;
+ }
+
private boolean validateCrossProfileTests(
Collection<ValidatorCrossProfileTestInfo> crossProfileTests) {
return crossProfileTests.stream().allMatch(this::validateCrossProfileTest);
@@ -983,12 +1070,11 @@
isValid = false;
}
- ClassName parcelableWrapperRawType =
- TypeUtils.getRawTypeClassName(customParcelableWrapper.asType());
+ ClassName parcelableWrapperRawType = getRawTypeClassName(customParcelableWrapper.asType());
ClassName wrappedParamRawType =
- TypeUtils.getRawTypeClassName(
+ getRawTypeClassName(
ParcelableWrapperAnnotationInfo.extractFromParcelableWrapperAnnotation(
- validatorContext.types(),
+ validatorContext,
customParcelableWrapper.getAnnotation(CustomParcelableWrapper.class))
.originalType()
.asType());
@@ -1002,8 +1088,8 @@
// the method is returning the correct generic type
.filter(
p ->
- TypeUtils.getRawTypeClassName(p.getReturnType())
- .equals(TypeUtils.getRawTypeClassName(customParcelableWrapper.asType())))
+ getRawTypeClassName(p.getReturnType())
+ .equals(getRawTypeClassName(customParcelableWrapper.asType())))
.filter(p -> ofMethodHasExpectedArguments(wrappedParamRawType, p))
.findFirst();
@@ -1019,8 +1105,7 @@
.filter(p -> p.getSimpleName().contentEquals("get"))
// We drop generics as without being overly prescriptive it's impossible to know that
// the method is returning the correct generic type
- .filter(
- p -> TypeUtils.getRawTypeClassName(p.getReturnType()).equals(wrappedParamRawType))
+ .filter(p -> getRawTypeClassName(p.getReturnType()).equals(wrappedParamRawType))
.findFirst();
if (!getMethod.isPresent()) {
@@ -1067,7 +1152,7 @@
return false;
}
- if (!TypeUtils.getRawTypeClassName(parameters.get(2).asType()).equals(wrappedParamRawType)) {
+ if (!getRawTypeClassName(parameters.get(2).asType()).equals(wrappedParamRawType)) {
return false;
}
@@ -1082,14 +1167,13 @@
boolean isValid = true;
ClassName wrappedFutureRawType =
- TypeUtils.getRawTypeClassName(
+ getRawTypeClassName(
FutureWrapperAnnotationInfo.extractFromFutureWrapperAnnotation(
- validatorContext.types(),
- futureWrapper.getAnnotation(CustomFutureWrapper.class))
+ validatorContext, futureWrapper.getAnnotation(CustomFutureWrapper.class))
.originalType()
.asType());
- if (!TypeUtils.getRawTypeQualifiedName(futureWrapper.getSuperclass())
+ if (!getRawTypeQualifiedName(futureWrapper.getSuperclass())
.equals("com.google.android.enterprise.connectedapps.FutureWrapper")) {
showError(DOES_NOT_EXTEND_FUTURE_WRAPPER_ERROR, futureWrapper);
isValid = false;
@@ -1111,8 +1195,8 @@
// the method is returning the correct generic type
.filter(
e ->
- TypeUtils.getRawTypeClassName(e.getReturnType())
- .equals(TypeUtils.getRawTypeClassName(futureWrapper.asType())))
+ getRawTypeClassName(e.getReturnType())
+ .equals(getRawTypeClassName(futureWrapper.asType())))
.filter(this::createMethodHasExpectedArguments)
.findFirst();
@@ -1130,8 +1214,7 @@
.filter(e -> !e.getModifiers().contains(Modifier.STATIC))
// We drop generics as without being overly prescriptive it's impossible to know that
// the method is returning the correct generic type
- .filter(
- e -> TypeUtils.getRawTypeClassName(e.getReturnType()).equals(wrappedFutureRawType))
+ .filter(e -> getRawTypeClassName(e.getReturnType()).equals(wrappedFutureRawType))
.filter(e -> e.getParameters().isEmpty())
.findFirst();
@@ -1178,19 +1261,17 @@
private boolean groupResultsMethodHasExpectedReturnType(
ExecutableElement groupResultsMethod, ClassName wrappedFutureRawType) {
- if (!TypeUtils.getRawTypeClassName(groupResultsMethod.getReturnType())
- .equals(wrappedFutureRawType)) {
+ if (!getRawTypeClassName(groupResultsMethod.getReturnType()).equals(wrappedFutureRawType)) {
return false;
}
- TypeMirror wrappedReturnType =
- TypeUtils.extractTypeArguments(groupResultsMethod.getReturnType()).get(0);
+ TypeMirror wrappedReturnType = extractTypeArguments(groupResultsMethod.getReturnType()).get(0);
- if (!TypeUtils.getRawTypeClassName(wrappedReturnType).equals(ClassName.get(Map.class))) {
+ if (!getRawTypeClassName(wrappedReturnType).equals(ClassName.get(Map.class))) {
return false;
}
- TypeMirror wrappedReturnTypeKey = TypeUtils.extractTypeArguments(wrappedReturnType).get(0);
+ TypeMirror wrappedReturnTypeKey = extractTypeArguments(wrappedReturnType).get(0);
if (!validatorContext.types().isSameType(wrappedReturnTypeKey, profileType)) {
return false;
@@ -1207,11 +1288,11 @@
TypeMirror param = groupResultsMethod.getParameters().get(0).asType();
- if (!TypeUtils.getRawTypeClassName(param).equals(ClassName.get(Map.class))) {
+ if (!getRawTypeClassName(param).equals(ClassName.get(Map.class))) {
return false;
}
- List<TypeMirror> params = TypeUtils.extractTypeArguments(param);
+ List<TypeMirror> params = extractTypeArguments(param);
TypeMirror keyParam = params.get(0);
TypeMirror valueParam = params.get(1);
@@ -1220,7 +1301,7 @@
return false;
}
- if (!TypeUtils.getRawTypeClassName(valueParam).equals(wrappedFutureRawType)) {
+ if (!getRawTypeClassName(valueParam).equals(wrappedFutureRawType)) {
return false;
}
@@ -1253,16 +1334,14 @@
return false;
}
- if (!TypeUtils.getRawTypeClassName(method.getParameters().get(0).asType())
- .equals(wrappedFutureRawType)) {
+ if (!getRawTypeClassName(method.getParameters().get(0).asType()).equals(wrappedFutureRawType)) {
return false;
}
if (!validatorContext
.types()
.isAssignable(
- TypeUtils.removeTypeArguments(method.getParameters().get(1).asType()),
- futureResultWriterType)) {
+ removeTypeArguments(method.getParameters().get(1).asType()), futureResultWriterType)) {
return false;
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossProfileTypeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossProfileTypeGenerator.java
index 0abba95..9a3d441 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossProfileTypeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossProfileTypeGenerator.java
@@ -15,10 +15,14 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.getBuilderClassName;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_AWARE_UTILS_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CurrentProfileGenerator.getCurrentProfileClassName;
+import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getCrossProfileTypeInterfaceClassName;
import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getMultipleSenderInterfaceClassName;
import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getSingleSenderCanThrowInterfaceClassName;
import static com.google.android.enterprise.connectedapps.processor.InterfaceGenerator.getSingleSenderInterfaceClassName;
@@ -29,6 +33,7 @@
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
@@ -45,12 +50,19 @@
private final CrossProfileTypeInfo crossProfileType;
private final GeneratorContext generatorContext;
private final GeneratorUtilities generatorUtilities;
+ private final ClassName fakeProfileConnectorClassName;
public FakeCrossProfileTypeGenerator(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
this.generatorContext = checkNotNull(generatorContext);
this.generatorUtilities = new GeneratorUtilities(generatorContext);
this.crossProfileType = checkNotNull(crossProfileType);
+ this.fakeProfileConnectorClassName =
+ crossProfileType.connectorInfo().isPresent()
+ && crossProfileType.connectorInfo().get().profileConnector().isPresent()
+ ? FakeProfileConnectorGenerator.getFakeProfileConnectorClassName(
+ crossProfileType.connectorInfo().get().profileConnector().get())
+ : ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
}
void generate() {
@@ -68,13 +80,7 @@
ClassName builderClassName =
getFakeCrossProfileTypeBuilderClassName(generatorContext, crossProfileType);
ClassName crossProfileTypeInterfaceClassName =
- InterfaceGenerator.getCrossProfileTypeInterfaceClassName(
- generatorContext, crossProfileType);
- ClassName fakeProfileConnectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? FakeProfileConnectorGenerator.getFakeProfileConnectorClassName(
- crossProfileType.profileConnector().get())
- : ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
+ getCrossProfileTypeInterfaceClassName(generatorContext, crossProfileType);
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(className)
@@ -188,8 +194,10 @@
.addStatement("return profiles(currentProfileIdentifier, otherProfileIdentifier)")
.build());
- if (!crossProfileType.profileConnector().isPresent()
- || crossProfileType.profileConnector().get().primaryProfile() != ProfileType.NONE) {
+ if (!crossProfileType.connectorInfo().isPresent()
+ || !crossProfileType.connectorInfo().get().profileConnector().isPresent()
+ || crossProfileType.connectorInfo().get().profileConnector().get().primaryProfile()
+ != ProfileType.NONE) {
generatePrimarySecondaryMethods(classBuilder);
}
@@ -199,12 +207,6 @@
}
private void addConstructor(TypeSpec.Builder classBuilder) {
- ClassName fakeProfileConnectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? FakeProfileConnectorGenerator.getFakeProfileConnectorClassName(
- crossProfileType.profileConnector().get())
- : ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
-
if (crossProfileType.isStatic()) {
classBuilder.addMethod(
MethodSpec.constructorBuilder()
@@ -300,11 +302,6 @@
getFakeCrossProfileTypeClassName(generatorContext, crossProfileType);
ClassName builderClassName =
getFakeCrossProfileTypeBuilderClassName(generatorContext, crossProfileType);
- ClassName fakeProfileConnectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? FakeProfileConnectorGenerator.getFakeProfileConnectorClassName(
- crossProfileType.profileConnector().get())
- : ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(builderClassName)
@@ -327,6 +324,7 @@
.addJavadoc(
"Set the {@link $T} to be used to manage the state of this fake.\n",
fakeProfileConnectorClassName)
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
.returns(builderClassName)
.addParameter(fakeProfileConnectorClassName, "connector")
@@ -369,6 +367,7 @@
"Set the {@link $T} to be used when a call needs to be made to the personal"
+ " profile.\n",
crossProfileType.className())
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
.returns(builderClassName)
.addParameter(crossProfileType.className(), "personal")
@@ -386,6 +385,7 @@
.addJavadoc(
"Set the {@link $T} to be used when a call needs to be made to the work profile.\n",
crossProfileType.className())
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
.returns(builderClassName)
.addParameter(crossProfileType.className(), "work")
@@ -455,23 +455,13 @@
static ClassName getFakeCrossProfileTypeClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- ClassName crossProfileTypeClassName =
- InterfaceGenerator.getCrossProfileTypeInterfaceClassName(
- generatorContext, crossProfileType);
- return ClassName.get(
- crossProfileTypeClassName.packageName(), "Fake" + crossProfileTypeClassName.simpleName());
+ return transformClassName(
+ getCrossProfileTypeInterfaceClassName(generatorContext, crossProfileType), prepend("Fake"));
}
static ClassName getFakeCrossProfileTypeBuilderClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- ClassName crossProfileTypeClassName =
- InterfaceGenerator.getCrossProfileTypeInterfaceClassName(
- generatorContext, crossProfileType);
- return ClassName.get(
- crossProfileTypeClassName.packageName()
- + "."
- + "Fake"
- + crossProfileTypeClassName.simpleName(),
- "Builder");
+ return getBuilderClassName(
+ getFakeCrossProfileTypeClassName(generatorContext, crossProfileType));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossUserTypeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossUserTypeGenerator.java
new file mode 100644
index 0000000..d04c38a
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeCrossUserTypeGenerator.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.getBuilderClassName;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_FAKE_USER_CONNECTOR_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.FAKE_USER_CONNECTOR_WRAPPER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.USER_HANDLE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CrossUserTypeCodeGenerator.getCrossUserTypeInterfaceClassName;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import java.util.HashMap;
+import java.util.Map;
+import javax.lang.model.element.Modifier;
+
+final class FakeCrossUserTypeGenerator {
+
+ private final GeneratorContext generatorContext;
+ private final GeneratorUtilities generatorUtilities;
+ private final CrossProfileTypeInfo crossUserType;
+
+ private final ClassName fakeUserConnectorClassName;
+ private final TypeName mapType;
+
+ private boolean generated = false;
+
+ public FakeCrossUserTypeGenerator(
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossUserType) {
+ this.generatorContext = checkNotNull(generatorContext);
+ this.generatorUtilities = new GeneratorUtilities(generatorContext);
+ this.crossUserType = checkNotNull(crossUserType);
+ this.fakeUserConnectorClassName =
+ crossUserType.connectorInfo().isPresent()
+ && crossUserType.connectorInfo().get().userConnector().isPresent()
+ ? FakeUserConnectorGenerator.getFakeUserConnectorClassName(
+ crossUserType.connectorInfo().get().userConnector().get())
+ : ABSTRACT_FAKE_USER_CONNECTOR_CLASSNAME;
+ this.mapType =
+ ParameterizedTypeName.get(
+ ClassName.get(Map.class), USER_HANDLE_CLASSNAME, crossUserType.className());
+ }
+
+ void generate() {
+ if (generated) {
+ throw new IllegalStateException(
+ "FakeCrossUserTypeGenerator#generate can only be called once");
+ }
+ generated = true;
+
+ generateFakeCrossUserType();
+ }
+
+ private void generateFakeCrossUserType() {
+ ClassName className = getFakeCrossUserTypeClassName(generatorContext, crossUserType);
+ ClassName builderClassName =
+ getFakeCrossUserTypeBuilderClassName(generatorContext, crossUserType);
+ ClassName crossUserTypeInterfaceClassName =
+ getCrossUserTypeInterfaceClassName(generatorContext, crossUserType);
+
+ TypeSpec.Builder classBuilder =
+ TypeSpec.classBuilder(className)
+ .addJavadoc(
+ "Fake implementation of {@link $T} for use during tests.\n\n"
+ + "<p>This should be injected into your code under test and the {@link $T}\n"
+ + "used to control the fake state. Calls will be routed to the correct {@link"
+ + " $T}.\n",
+ crossUserTypeInterfaceClassName,
+ fakeUserConnectorClassName,
+ crossUserType.className())
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addSuperinterface(crossUserTypeInterfaceClassName);
+
+ generateConstructor(classBuilder);
+ generateInterfaceMethods(classBuilder);
+ generateBuilder(classBuilder, className, builderClassName);
+
+ generatorUtilities.writeClassToFile(className.packageName(), classBuilder);
+ }
+
+ private void generateConstructor(TypeSpec.Builder classBuilder) {
+ classBuilder.addField(
+ FieldSpec.builder(fakeUserConnectorClassName, "connector")
+ .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+ .build());
+ classBuilder.addField(
+ FieldSpec.builder(mapType, "targetTypeForUserHandle")
+ .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+ .build());
+ classBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PRIVATE)
+ .addParameter(fakeUserConnectorClassName, "connector")
+ .addParameter(mapType, "targetTypeForUserHandle")
+ .addStatement("this.connector = connector")
+ .addStatement("this.targetTypeForUserHandle = targetTypeForUserHandle")
+ .build());
+ }
+
+ private void generateInterfaceMethods(TypeSpec.Builder classBuilder) {
+ generateCurrentMethod(classBuilder);
+ generateSpecificUserMethod(classBuilder);
+ generateTargetTypeGetter(classBuilder);
+ }
+
+ private void generateTargetTypeGetter(TypeSpec.Builder classBuilder) {
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("getTargetType")
+ .addModifiers(Modifier.PRIVATE)
+ .addParameter(USER_HANDLE_CLASSNAME, "userHandle")
+ .returns(crossUserType.className())
+ .beginControlFlow("if (userHandle == null)")
+ .addStatement("throw new $T(\"Null user handle.\")", IllegalArgumentException.class)
+ .nextControlFlow("else if (!targetTypeForUserHandle.containsKey(userHandle))")
+ .addStatement(
+ "throw new $T(\"No $L type specified for target user handle.\")",
+ UnsupportedOperationException.class,
+ crossUserType.generatedClassName().simpleName())
+ .endControlFlow()
+ .addStatement("return targetTypeForUserHandle.get(userHandle)")
+ .build());
+ }
+
+ private void generateCurrentMethod(TypeSpec.Builder classBuilder) {
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("current")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(
+ InterfaceGenerator.getSingleSenderInterfaceClassName(
+ generatorContext, crossUserType))
+ .beginControlFlow("if (connector.runningOnUser() == null)")
+ .addStatement(
+ "throw new $T(\"Current user not specified - you must call setRunningOnUser on your"
+ + " connector.\")",
+ UnsupportedOperationException.class)
+ .endControlFlow()
+ .addStatement("$T userHandle = connector.runningOnUser()", USER_HANDLE_CLASSNAME)
+ .addStatement(
+ "return new $T(connector.applicationContext(userHandle),"
+ + " getTargetType(userHandle))",
+ CurrentProfileGenerator.getCurrentProfileClassName(generatorContext, crossUserType))
+ .build());
+ }
+
+ private void generateSpecificUserMethod(TypeSpec.Builder classBuilder) {
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("user")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(USER_HANDLE_CLASSNAME, "userHandle")
+ .returns(
+ InterfaceGenerator.getSingleSenderCanThrowInterfaceClassName(
+ generatorContext, crossUserType))
+ .beginControlFlow("if (connector.runningOnUser() == userHandle)")
+ .addStatement(
+ "return new $T(connector.applicationContext(userHandle),"
+ + " getTargetType(userHandle))",
+ CurrentProfileGenerator.getCurrentProfileClassName(generatorContext, crossUserType))
+ .endControlFlow()
+ .addStatement(
+ "return new $T(new $T(connector, userHandle), getTargetType(userHandle))",
+ FakeOtherGenerator.getFakeOtherClassName(generatorContext, crossUserType),
+ FAKE_USER_CONNECTOR_WRAPPER_CLASSNAME)
+ .build());
+ }
+
+ private void generateBuilder(
+ TypeSpec.Builder classBuilder, ClassName className, ClassName builderClassName) {
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("builder")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(builderClassName)
+ .addStatement("return new $T()", builderClassName)
+ .build());
+
+ String targetTypeName = "target" + crossUserType.className().simpleName();
+ classBuilder.addType(
+ TypeSpec.classBuilder("Builder")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+ .addField(
+ FieldSpec.builder(mapType, "targetTypeForUserHandle")
+ .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
+ .initializer(
+ "new $T()",
+ ParameterizedTypeName.get(
+ ClassName.get(HashMap.class),
+ USER_HANDLE_CLASSNAME,
+ crossUserType.generatedClassName()))
+ .build())
+ .addField(
+ FieldSpec.builder(fakeUserConnectorClassName, "connector")
+ .addModifiers(Modifier.PRIVATE)
+ .build())
+ .addMethod(
+ MethodSpec.methodBuilder("user")
+ .addAnnotation(CanIgnoreReturnValue.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(USER_HANDLE_CLASSNAME, "userHandle")
+ .addParameter(crossUserType.className(), targetTypeName)
+ .returns(builderClassName)
+ .addStatement("targetTypeForUserHandle.put(userHandle, $L)", targetTypeName)
+ .addStatement("return this")
+ .build())
+ .addMethod(
+ MethodSpec.methodBuilder("connector")
+ .addAnnotation(CanIgnoreReturnValue.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(fakeUserConnectorClassName, "connector")
+ .returns(builderClassName)
+ .addStatement("this.connector = connector")
+ .addStatement("return this")
+ .build())
+ .addMethod(
+ MethodSpec.methodBuilder("build")
+ .addModifiers(Modifier.PUBLIC)
+ .returns(className)
+ .beginControlFlow("if (connector == null)")
+ .addStatement(
+ "throw new $T(\"Cannot build $L with no connector specified.\")",
+ IllegalStateException.class,
+ className.simpleName())
+ .endControlFlow()
+ .addStatement("return new $T(connector, targetTypeForUserHandle)", className)
+ .build())
+ .build());
+ }
+
+ static ClassName getFakeCrossUserTypeClassName(
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossUserType) {
+ return transformClassName(
+ getCrossUserTypeInterfaceClassName(generatorContext, crossUserType), prepend("Fake"));
+ }
+
+ static ClassName getFakeCrossUserTypeBuilderClassName(
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossUserType) {
+ return getBuilderClassName(getFakeCrossUserTypeClassName(generatorContext, crossUserType));
+ }
+}
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 53e5c72..9726689 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
@@ -15,9 +15,11 @@
*/
package com.google.android.enterprise.connectedapps.processor;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_CALLBACK_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.FAKE_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;
@@ -28,7 +30,6 @@
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
@@ -73,12 +74,6 @@
InterfaceGenerator.getSingleSenderCanThrowInterfaceClassName(
generatorContext, crossProfileType);
- ClassName fakeProfileConnectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? FakeProfileConnectorGenerator.getFakeProfileConnectorClassName(
- crossProfileType.profileConnector().get())
- : ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
-
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(className)
.addJavadoc(
@@ -86,28 +81,15 @@
+ "<p>This acts based on the state of the passed in {@link $T} and acts as if"
+ " making a call on the other profile.\n",
singleSenderCanThrowInterface,
- fakeProfileConnectorClassName)
+ FAKE_PROFILE_CONNECTOR_CLASSNAME)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(singleSenderCanThrowInterface);
classBuilder.addField(
- fakeProfileConnectorClassName, "connector", Modifier.PRIVATE, Modifier.FINAL);
+ FAKE_PROFILE_CONNECTOR_CLASSNAME, "connector", Modifier.PRIVATE, Modifier.FINAL);
addConstructor(classBuilder);
- classBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addAnnotation(Override.class)
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC)
- .returns(className)
- .addParameter(long.class, "timeout")
- .addStatement("return this")
- .build());
-
ClassName ifAvailableClass =
IfAvailableGenerator.getIfAvailableClassName(generatorContext, crossProfileType);
@@ -135,19 +117,13 @@
}
private void addConstructor(TypeSpec.Builder classBuilder) {
- ClassName fakeProfileConnectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? FakeProfileConnectorGenerator.getFakeProfileConnectorClassName(
- crossProfileType.profileConnector().get())
- : ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
-
classBuilder.addField(CONTEXT_CLASSNAME, "context", Modifier.PRIVATE, Modifier.FINAL);
if (crossProfileType.isStatic()) {
classBuilder.addMethod(
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
- .addParameter(fakeProfileConnectorClassName, "connector")
+ .addParameter(FAKE_PROFILE_CONNECTOR_CLASSNAME, "connector")
.addStatement("this.context = connector.applicationContext()")
.addStatement("this.connector = connector")
.build());
@@ -158,7 +134,7 @@
classBuilder.addMethod(
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
- .addParameter(fakeProfileConnectorClassName, "connector")
+ .addParameter(FAKE_PROFILE_CONNECTOR_CLASSNAME, "connector")
.addParameter(crossProfileType.className(), "crossProfileType")
.addStatement("this.context = connector.applicationContext()")
.addStatement("this.connector = connector")
@@ -204,7 +180,7 @@
"Could not access other profile");
methodBuilder.endControlFlow();
- methodBuilder.beginControlFlow("if (!connector.isManuallyManagingConnection())");
+ methodBuilder.beginControlFlow("if (!connector.hasExplicitConnectionHolders())");
methodBuilder.addStatement(
"throw new $T($S)",
UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME,
@@ -345,6 +321,6 @@
static ClassName getFakeOtherClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(crossProfileType.profileClassName(), "_FakeOther");
+ return transformClassName(crossProfileType.generatedClassName(), append("_FakeOther"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeProfileConnectorGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeProfileConnectorGenerator.java
index 5ff51ea..0a41745 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeProfileConnectorGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeProfileConnectorGenerator.java
@@ -15,6 +15,8 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -29,19 +31,19 @@
class FakeProfileConnectorGenerator {
private boolean generated = false;
- private final ProfileConnectorInfo connector;
private final GeneratorUtilities generatorUtilities;
+ private final ProfileConnectorInfo profileConnector;
public FakeProfileConnectorGenerator(
GeneratorContext generatorContext, ProfileConnectorInfo connector) {
this.generatorUtilities = new GeneratorUtilities(checkNotNull(generatorContext));
- this.connector = checkNotNull(connector);
+ this.profileConnector = checkNotNull(connector);
}
void generate() {
if (generated) {
throw new IllegalStateException(
- "FakeProfileConectorGenerator#generate can only be called once");
+ "FakeProfileConnectorGenerator#generate can only be called once");
}
generated = true;
@@ -49,7 +51,7 @@
}
private void generateFakeProfileConnector() {
- ClassName className = getFakeProfileConnectorClassName(connector);
+ ClassName className = getFakeProfileConnectorClassName(profileConnector);
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(className)
@@ -57,13 +59,13 @@
"Fake Profile Connector for {@link $1T}.\n\n"
+ "<p>All functionality is implemented by {@link $2T}, this class is just used"
+ " for compatibility with the {@link $1T} interface.\n",
- connector.connectorClassName(),
+ profileConnector.connectorClassName(),
ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addSuperinterface(connector.connectorClassName())
+ .addSuperinterface(profileConnector.connectorClassName())
.superclass(ABSTRACT_FAKE_PROFILE_CONNECTOR_CLASSNAME);
- if (connector.primaryProfile().equals(ProfileType.UNKNOWN)) {
+ if (profileConnector.primaryProfile().equals(ProfileType.UNKNOWN)) {
// Special case - we need to provide the profile type to the fake.
classBuilder.addMethod(
MethodSpec.constructorBuilder()
@@ -85,16 +87,16 @@
.addModifiers(Modifier.PUBLIC)
.addParameter(CONTEXT_CLASSNAME, "context")
.addStatement(
- "super(context, $T.$L)", ProfileType.class, connector.primaryProfile().name())
+ "super(context, $T.$L)",
+ ProfileType.class,
+ profileConnector.primaryProfile().name())
.build());
}
generatorUtilities.writeClassToFile(className.packageName(), classBuilder);
}
- static ClassName getFakeProfileConnectorClassName(ProfileConnectorInfo connector) {
- return ClassName.get(
- connector.connectorClassName().packageName(),
- "Fake" + connector.connectorClassName().simpleName());
+ static ClassName getFakeProfileConnectorClassName(ProfileConnectorInfo profileConnector) {
+ return transformClassName(profileConnector.connectorClassName(), prepend("Fake"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeUserConnectorGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeUserConnectorGenerator.java
new file mode 100644
index 0000000..3d99434
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/FakeUserConnectorGenerator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_FAKE_USER_CONNECTOR_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.android.enterprise.connectedapps.processor.containers.UserConnectorInfo;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import javax.lang.model.element.Modifier;
+
+class FakeUserConnectorGenerator {
+
+ private final GeneratorUtilities generatorUtilities;
+ private final UserConnectorInfo userConnector;
+
+ private boolean generated = false;
+
+ public FakeUserConnectorGenerator(
+ GeneratorContext generatorContext, UserConnectorInfo connector) {
+ this.generatorUtilities = new GeneratorUtilities(checkNotNull(generatorContext));
+ this.userConnector = checkNotNull(connector);
+ }
+
+ void generate() {
+ if (generated) {
+ throw new IllegalStateException(
+ "FakeUserConnectorGenerator#generate can only be called once");
+ }
+ generated = true;
+
+ generateFakeUserConnector();
+ }
+
+ private void generateFakeUserConnector() {
+ ClassName className = getFakeUserConnectorClassName(userConnector);
+
+ TypeSpec.Builder classBuilder =
+ TypeSpec.classBuilder(className)
+ .addJavadoc(
+ "Fake User Connector for {@link $1T}.\n\n"
+ + "<p>All functionality is implemented by {@link $2T}, this class is just used"
+ + " for compatibility with the {@link $1T} interface.\n",
+ userConnector.connectorClassName(),
+ ABSTRACT_FAKE_USER_CONNECTOR_CLASSNAME)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addSuperinterface(userConnector.connectorClassName())
+ .superclass(ABSTRACT_FAKE_USER_CONNECTOR_CLASSNAME);
+
+ classBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addStatement("super(context)")
+ .build());
+
+ generatorUtilities.writeClassToFile(className.packageName(), classBuilder);
+ }
+
+ static ClassName getFakeUserConnectorClassName(UserConnectorInfo userConnector) {
+ return transformClassName(userConnector.connectorClassName(), prepend("Fake"));
+ }
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/GeneratorUtilities.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/GeneratorUtilities.java
index 7d4dbd7..9c600c8 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/GeneratorUtilities.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/GeneratorUtilities.java
@@ -22,13 +22,13 @@
import static com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo.AutomaticallyResolvedParameterFilterBehaviour.REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
import com.google.android.enterprise.connectedapps.processor.containers.Context;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo.AutomaticallyResolvedParameterFilterBehaviour;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ArrayTypeName;
@@ -44,7 +44,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
-import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@@ -116,22 +115,27 @@
throw new AssertionError("Could not extract classes from annotation");
}
- public static Set<ExecutableElement> findCrossProfileMethodsInClass(TypeElement clazz) {
- return clazz.getEnclosedElements().stream()
+ public static ImmutableSet<ExecutableElement> findCrossProfileMethodsInClass(TypeElement clazz) {
+ ImmutableSet.Builder<ExecutableElement> result = ImmutableSet.builder();
+ clazz.getEnclosedElements().stream()
.filter(e -> e instanceof ExecutableElement)
.map(e -> (ExecutableElement) e)
.filter(e -> e.getKind() == ElementKind.METHOD)
.filter(AnnotationFinder::hasCrossProfileAnnotation)
- .collect(toSet());
+ .forEach(result::add);
+ return result.build();
}
- public static Set<ExecutableElement> findCrossProfileProviderMethodsInClass(TypeElement clazz) {
- return clazz.getEnclosedElements().stream()
+ public static ImmutableSet<ExecutableElement> findCrossProfileProviderMethodsInClass(
+ TypeElement clazz) {
+ ImmutableSet.Builder<ExecutableElement> result = ImmutableSet.builder();
+ clazz.getEnclosedElements().stream()
.filter(e -> e instanceof ExecutableElement)
.map(e -> (ExecutableElement) e)
.filter(e -> e.getKind() == ElementKind.METHOD)
.filter(AnnotationFinder::hasCrossProfileProviderAnnotation)
- .collect(toSet());
+ .forEach(result::add);
+ return result.build();
}
/** Generate a {@code @link} reference to a given method. */
@@ -292,8 +296,4 @@
private static List<TypeMirror> convertParametersToTypes(CrossProfileMethodInfo method) {
return method.methodElement().getParameters().stream().map(Element::asType).collect(toList());
}
-
- static ClassName appendToClassName(ClassName originalClassName, String suffix) {
- return ClassName.get(originalClassName.packageName(), originalClassName.simpleName() + suffix);
- }
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableGenerator.java
index 68d84f4..d3b4270 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableGenerator.java
@@ -15,6 +15,8 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.IF_AVAILABLE_FUTURE_RESULT_WRITER;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.GeneratorUtilities.generateMethodReference;
@@ -22,6 +24,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackParameterInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
@@ -31,7 +34,6 @@
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -149,13 +151,10 @@
return;
}
+ CrossProfileCallbackParameterInfo callbackParam =
+ method.getCrossProfileCallbackParam(generatorContext).get();
CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(
- (TypeElement)
- generatorContext
- .types()
- .asElement(
- method.getCrossProfileCallbackParam(generatorContext).get().asType()));
+ callbackParam.crossProfileCallbackInterface();
if (callbackInterface.argumentTypes().isEmpty()) {
// Void
// This assumes a single callback method
@@ -167,7 +166,7 @@
method.simpleName(),
method.commaSeparatedParameters(
crossProfileType.supportedTypes(), REMOVE_AUTOMATICALLY_RESOLVED_PARAMETERS),
- method.getCrossProfileCallbackParam(generatorContext).get().getSimpleName(),
+ callbackParam.getSimpleName(),
callbackInterface.methods().get(0).getSimpleName());
} else {
// This assumes a single callback method
@@ -226,7 +225,6 @@
static ClassName getIfAvailableClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_IfAvailable");
+ return transformClassName(crossProfileType.generatedClassName(), append("_IfAvailable"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceGenerator.java
index a8c76c6..a8d94c1 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceGenerator.java
@@ -15,9 +15,11 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_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.GeneratorUtilities.generateMethodReference;
@@ -26,15 +28,11 @@
import static java.util.stream.Collectors.toList;
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
-import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector;
-import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackParameterInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.google.common.base.Ascii;
-import com.squareup.javapoet.AnnotationSpec;
-import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
@@ -45,8 +43,6 @@
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -70,204 +66,11 @@
}
generated = true;
- generateCrossProfileTypeInterface();
generateSingleSenderInterface();
generateSingleSenderCanThrowInterface();
generateMultipleSenderInterface();
}
- private void generateCrossProfileTypeInterface() {
- ClassName interfaceName =
- getCrossProfileTypeInterfaceClassName(generatorContext, crossProfileType);
-
- TypeSpec.Builder interfaceBuilder =
- TypeSpec.interfaceBuilder(interfaceName)
- .addJavadoc(
- "Entry point for cross-profile calls to {@link $T}.\n",
- crossProfileType.className())
- .addModifiers(Modifier.PUBLIC);
-
- ClassName connectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? crossProfileType.profileConnector().get().connectorClassName()
- : PROFILE_CONNECTOR_CLASSNAME;
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("create")
- .returns(interfaceName)
- .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
- .addParameter(connectorClassName, "connector")
- .addStatement(
- "return new $T(connector)",
- DefaultProfileClassGenerator.getDefaultProfileClassName(
- generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("current")
- .addJavadoc("Run a method on the current profile.\n")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getSingleSenderInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("other")
- .addJavadoc("Run a method on the other profile, if accessible.\n")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("personal")
- .addJavadoc("Run a method on the personal profile, if accessible.\n")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("work")
- .addJavadoc("Run a method on the work profile, if accessible.\n")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("profile")
- .addJavadoc("Run a method on the given profile, if accessible.\n")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .addParameter(PROFILE_CLASSNAME, "profile")
- .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("profiles")
- .addJavadoc(
- CodeBlock.builder()
- .add("Run a method on the given profiles, if accessible.\n\n")
- .add(
- "<p>This will deduplicate profiles to ensure that the method is only run"
- + " at most once on each profile.\n")
- .build())
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .addParameter(ArrayTypeName.of(PROFILE_CLASSNAME), "profiles")
- .varargs(true)
- .returns(getMultipleSenderInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("both")
- .addJavadoc("Run a method on both the personal and work profile, if accessible.\n")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getMultipleSenderInterfaceClassName(generatorContext, crossProfileType))
- .build());
-
- if (!crossProfileType.profileConnector().isPresent()
- || crossProfileType.profileConnector().get().primaryProfile() != ProfileType.NONE) {
- generatePrimarySecondaryMethods(interfaceBuilder);
- }
-
- generatorUtilities.writeClassToFile(interfaceName.packageName(), interfaceBuilder);
- }
-
- private void generatePrimarySecondaryMethods(TypeSpec.Builder interfaceBuilder) {
- generatePrimaryMethod(interfaceBuilder);
- generateSecondaryMethod(interfaceBuilder);
- generateSuppliersMethod(interfaceBuilder);
- }
-
- private void generatePrimaryMethod(TypeSpec.Builder interfaceBuilder) {
- MethodSpec.Builder methodBuilder =
- MethodSpec.methodBuilder("primary")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType));
-
- if (crossProfileType.profileConnector().isPresent()) {
- methodBuilder.addJavadoc(
- "Run a method on the primary ("
- + Ascii.toLowerCase(crossProfileType.profileConnector().get().primaryProfile().name())
- + ") profile, if accessible.\n\n@see $T#primaryProfile()\n",
- CustomProfileConnector.class);
- } else {
- methodBuilder.addJavadoc(
- "Run a method on the primary profile, if accessible.\n\n"
- + "@throws $1T if the {@link $2T} does not have a primary profile set\n"
- + "@see $2T#primaryProfile()\n",
- IllegalStateException.class,
- CustomProfileConnector.class);
- }
-
- interfaceBuilder.addMethod(methodBuilder.build());
- }
-
- private void generateSecondaryMethod(TypeSpec.Builder interfaceBuilder) {
- MethodSpec.Builder methodBuilder =
- MethodSpec.methodBuilder("secondary")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getSingleSenderCanThrowInterfaceClassName(generatorContext, crossProfileType));
-
- if (crossProfileType.profileConnector().isPresent()) {
- String secondaryProfileName =
- crossProfileType.profileConnector().get().primaryProfile().equals(ProfileType.WORK)
- ? Ascii.toLowerCase(ProfileType.PERSONAL.name())
- : Ascii.toLowerCase(ProfileType.WORK.name());
- methodBuilder.addJavadoc(
- "Run a method on the secondary ("
- + secondaryProfileName
- + ") profile, if accessible.\n\n@see $T#primaryProfile()\n",
- CustomProfileConnector.class);
- } else {
- methodBuilder.addJavadoc(
- "Run a method on the secondary profile, if accessible.\n\n"
- + "@throws $1T if the {@link $2T} does not have a primary profile set\n"
- + "@see $2T#primaryProfile()\n",
- IllegalStateException.class,
- CustomProfileConnector.class);
- }
-
- interfaceBuilder.addMethod(methodBuilder.build());
- }
-
- private void generateSuppliersMethod(TypeSpec.Builder interfaceBuilder) {
- MethodSpec.Builder methodBuilder =
- MethodSpec.methodBuilder("suppliers")
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(getMultipleSenderInterfaceClassName(generatorContext, crossProfileType));
-
- if (crossProfileType.profileConnector().isPresent()) {
- String primaryProfileName =
- crossProfileType.profileConnector().get().primaryProfile().equals(ProfileType.WORK)
- ? Ascii.toLowerCase(ProfileType.WORK.name())
- : Ascii.toLowerCase(ProfileType.PERSONAL.name());
- String secondaryProfileName =
- crossProfileType.profileConnector().get().primaryProfile().equals(ProfileType.WORK)
- ? Ascii.toLowerCase(ProfileType.PERSONAL.name())
- : Ascii.toLowerCase(ProfileType.WORK.name());
- methodBuilder
- .addJavadoc("Run a method on supplier profiles, if accessible.\n\n")
- .addJavadoc(
- "<p>When run from the primary ($1L) profile, supplier profiles are the primary ($1L)"
- + " and secondary ($2L) profiles. When run from the secondary ($2L) profile,"
- + " supplier profiles includes only the secondary ($2L) profile.\n\n",
- primaryProfileName,
- secondaryProfileName)
- .addJavadoc("@see $T#primaryProfile()\n", CustomProfileConnector.class);
- } else {
- methodBuilder
- .addJavadoc("Run a method on supplier profiles, if accessible.\n\n")
- .addJavadoc(
- "<p>When run from the primary profile, supplier profiles are the primary and"
- + " secondary profiles. When run from the secondary profile, supplier profiles"
- + " includes only the secondary profile.\n\n")
- .addJavadoc(
- "@throws $1T if the {@link $2T} does not have a primary profile set\n",
- IllegalStateException.class,
- CustomProfileConnector.class)
- .addJavadoc("@see $T#primaryProfile()\n", CustomProfileConnector.class);
- }
-
- interfaceBuilder.addMethod(methodBuilder.build());
- }
-
private void generateSingleSenderInterface() {
ClassName interfaceName = getSingleSenderInterfaceClassName(generatorContext, crossProfileType);
@@ -349,20 +152,6 @@
IfAvailableGenerator.getIfAvailableClassName(generatorContext, crossProfileType))
.build());
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addJavadoc(
- "Set a timeout to be used when making asynchronous calls to other profiles.\n\n"
- + "<p>This overrides any timeout set on the type or method being called.\n")
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(interfaceName)
- .addParameter(long.class, "timeout")
- .build());
-
generatorUtilities.writeClassToFile(interfaceName.packageName(), interfaceBuilder);
}
@@ -459,20 +248,6 @@
}
}
- interfaceBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addJavadoc(
- "Set a timeout to be used when making asynchronous calls to other profiles.\n\n"
- + "<p>This overrides any timeout set on the type or method being called.")
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
- .returns(interfaceName)
- .addParameter(long.class, "timeout")
- .build());
-
generatorUtilities.writeClassToFile(interfaceName.packageName(), interfaceBuilder);
}
@@ -543,20 +318,13 @@
return;
}
- VariableElement callbackParameter = method.getCrossProfileCallbackParam(generatorContext).get();
- TypeElement callbackType =
- generatorContext.elements().getTypeElement(callbackParameter.asType().toString());
- CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(callbackType);
-
List<ParameterSpec> parameters =
convertCallbackParametersIntoMulti(
GeneratorUtilities.extractParametersFromMethod(
crossProfileType.supportedTypes(),
method.methodElement(),
REMOVE_AUTOMATICALLY_RESOLVED_PARAMETERS),
- callbackParameter,
- callbackInterface);
+ method.getCrossProfileCallbackParam(generatorContext).get());
CodeBlock methodReference = generateMethodReference(crossProfileType, method);
@@ -636,20 +404,22 @@
+ " a {@link $T} will be thrown on another thread with the original exception"
+ " as the cause.\n\n",
PROFILE_RUNTIME_EXCEPTION_CLASSNAME)
+ .addJavadoc(
+ "<p>Only the first result passed in for each profile will be passed into the "
+ + "callback.\n\n"
+ )
.addJavadoc("@see $L\n", methodReference);
interfaceBuilder.addMethod(methodBuilder.build());
}
private List<ParameterSpec> convertCallbackParametersIntoMulti(
- List<ParameterSpec> parameters,
- VariableElement callbackParameter,
- CrossProfileCallbackInterfaceInfo callbackInterface) {
+ List<ParameterSpec> parameters, CrossProfileCallbackParameterInfo callbackParameter) {
return parameters.stream()
.map(
e ->
e.name.equals(callbackParameter.getSimpleName().toString())
- ? convertCallbackToMulti(e, callbackInterface)
+ ? convertCallbackToMulti(e, callbackParameter.crossProfileCallbackInterface())
: e)
.collect(toList());
}
@@ -667,24 +437,22 @@
static ClassName getCrossProfileTypeInterfaceClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return crossProfileType.profileClassName();
+ return transformClassName(crossProfileType.generatedClassName(), prepend("Profile"));
}
static ClassName getSingleSenderInterfaceClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_SingleSender");
+ return transformClassName(crossProfileType.generatedClassName(), append("_SingleSender"));
}
static ClassName getSingleSenderCanThrowInterfaceClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_SingleSenderCanThrow");
+ return transformClassName(
+ crossProfileType.generatedClassName(), append("_SingleSenderCanThrow"));
}
static ClassName getMultipleSenderInterfaceClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_MultipleSender");
+ return transformClassName(crossProfileType.generatedClassName(), append("_MultipleSender"));
}
}
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 bbbf0ab..c9e1e72 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
@@ -15,19 +15,21 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_UTILITIES_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_FUTURE_RESULT_WRITER;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.METHOD_RUNNER_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_UTILITIES_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo.AutomaticallyResolvedParameterFilterBehaviour.REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.joining;
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
-import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackParameterInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
@@ -44,7 +46,6 @@
import java.util.stream.IntStream;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -64,9 +65,9 @@
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 +77,7 @@
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 +88,62 @@
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.PUBLIC, Modifier.STATIC)
- .returns(className)
- .addStatement("return instance")
- .build());
+ MethodSpec.methodBuilder("instance")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addJavadoc(
+ "Connected-Apps SDK internal use only. This API may be removed at any time.")
+ .returns(className)
+ .addStatement("return instance")
+ .build());
classBuilder.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build());
@@ -152,23 +155,23 @@
}
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,21 +186,23 @@
}
private void addBlockingCrossProfileTypeMethod(
- TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
+ TypeSpec.Builder classBuilder, CrossProfileMethodInfo method) {
CodeBlock.Builder methodCode = CodeBlock.builder();
- // parcle is recycled by caller
- methodCode.addStatement("$1T returnParcel = $1T.obtain()", PARCEL_CLASSNAME);
+ methodCode.addStatement(
+ "$1T returnBundle = new $1T($2T.class.getClassLoader())",
+ BUNDLE_CLASSNAME,
+ BUNDLER_CLASSNAME);
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");
@@ -208,203 +213,203 @@
} else {
methodCall = CodeBlock.of("$T returnValue = $L", method.returnType(), methodCall);
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.writeToBundle(returnBundle, $S, returnValue, $L)",
+ "return",
+ TypeUtils.generateBundlerType(method.returnType()));
}
if (!method.thrownExceptions().isEmpty()) {
for (TypeName exceptionType : method.thrownExceptions()) {
methodCode.nextControlFlow("catch ($L e)", exceptionType);
- methodCode.add("returnParcel.writeInt(1); // Errors\n");
methodCode.addStatement(
- "$T.writeThrowableToParcel(returnParcel, e)", PARCEL_UTILITIES_CLASSNAME);
+ "$T.writeThrowableToBundle(returnBundle, $S, e)",
+ BUNDLE_UTILITIES_CLASSNAME,
+ "throwable");
}
methodCode.endControlFlow();
}
- methodCode.addStatement("return returnParcel");
+ methodCode.addStatement("return returnBundle");
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(BUNDLE_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(BUNDLE_CLASSNAME, "params")
+ .addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
+ .addCode(methodCode.build())
+ .addJavadoc(
+ "Call $1L and return a {@link $2T} containing the return value under the \"return\""
+ + "key.\n",
+ GeneratorUtilities.methodJavadocReference(method.methodElement()),
+ BUNDLE_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
- methodCode.addStatement("$1T returnParcel = $1T.obtain()", PARCEL_CLASSNAME);
+ methodCode.addStatement(
+ "$1T returnBundle = new $1T($2T.class.getClassLoader())",
+ BUNDLE_CLASSNAME,
+ BUNDLER_CLASSNAME);
addExtractParametersCode(methodCode, method);
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);
} else {
methodCall = CodeBlock.of("$T returnValue = $L", method.returnType(), methodCall);
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.writeToBundle(returnBundle, $1S, returnValue, $2L)",
+ "return",
+ TypeUtils.generateBundlerType(method.returnType()));
}
- methodCode.addStatement("return returnParcel");
+ methodCode.addStatement("return returnBundle");
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(BUNDLE_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(BUNDLE_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 bundle.\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
- methodCode.addStatement("$1T returnParcel = $1T.obtain()", PARCEL_CLASSNAME);
+ methodCode.addStatement(
+ "$1T returnBundle = new $1T($2T.class.getClassLoader())",
+ BUNDLE_CLASSNAME,
+ BUNDLER_CLASSNAME);
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");
+ methodCode.addStatement("return returnBundle");
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(BUNDLE_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(BUNDLE_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 bundle.\n",
+ GeneratorUtilities.methodJavadocReference(method.methodElement()))
+ .build());
}
private void createCrossProfileCallbackParameter(
- CodeBlock.Builder methodCode, CrossProfileMethodInfo method) {
- VariableElement asyncCallbackParam =
- method.getCrossProfileCallbackParam(generatorContext).get();
-
- TypeElement callbackType =
- generatorContext.elements().getTypeElement(asyncCallbackParam.asType().toString());
- CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(callbackType);
+ CodeBlock.Builder methodCode, CrossProfileMethodInfo method) {
+ CrossProfileCallbackParameterInfo callbackParameter =
+ method.getCrossProfileCallbackParam(generatorContext).get();
methodCode.addStatement(
- "$T $L = new $L(crossProfileCallback, bundler)",
- asyncCallbackParam.asType(),
- asyncCallbackParam.getSimpleName(),
- CrossProfileCallbackCodeGenerator.getCrossProfileCallbackReceiverClassName(
- generatorContext, callbackInterface));
+ "$T $L = new $L(crossProfileCallback, bundler)",
+ callbackParameter.variable().asType(),
+ callbackParameter.getSimpleName(),
+ CrossProfileCallbackCodeGenerator.getCrossProfileCallbackReceiverClassName(
+ generatorContext, callbackParameter.crossProfileCallbackInterface()));
}
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);
+ Optional<CrossProfileCallbackParameterInfo> callbackParameter =
+ method.getCrossProfileCallbackParam(generatorContext);
for (VariableElement parameter : method.methodElement().getParameters()) {
if (callbackParameter.isPresent()
- && callbackParameter.get().getSimpleName().equals(parameter.getSimpleName())) {
+ && callbackParameter.get().variable().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.readFromBundle(params, $2S,"
+ + " $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(BUNDLE_CLASSNAME)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addParameter(int.class, "methodIdentifier")
+ .addParameter(BUNDLE_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 under the key \"return\", otherwise it will return an"
+ + " empty {@link $1T}.\n",
+ BUNDLE_CLASSNAME)
+ .build());
}
private CodeBlock getCrossProfileTypeReference(CrossProfileMethodInfo method) {
@@ -415,7 +420,8 @@
}
static ClassName getInternalCrossProfileClassName(
- GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(crossProfileType.profileClassName(), "_Internal");
+ GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
+ return transformClassName(crossProfileType.generatedClassName(), append("_Internal"));
}
}
+
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassGenerator.java
index c02bbfb..b5bcb82 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassGenerator.java
@@ -15,9 +15,10 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.classNameInferringPackageFromElement;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CLASSNAME;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
@@ -29,7 +30,6 @@
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.PackageElement;
/**
* Generate the {@code Profile_*_Internal} class for a single provider class.
@@ -146,20 +146,19 @@
classBuilder.addMethod(
MethodSpec.methodBuilder("call")
.addModifiers(Modifier.PUBLIC)
- .returns(PARCEL_CLASSNAME)
+ .returns(BUNDLE_CLASSNAME)
.addParameter(CONTEXT_CLASSNAME, "context")
.addParameter(long.class, "crossProfileTypeIdentifier")
.addParameter(int.class, "methodIdentifier")
- .addParameter(PARCEL_CLASSNAME, "params")
+ .addParameter(BUNDLE_CLASSNAME, "params")
.addParameter(CROSS_PROFILE_CALLBACK_CLASSNAME, "callback")
.addCode(methodCode.build())
.addJavadoc(
"Call the {@code call} method on the internal type referenced by the {@code"
+ " crossProfileTypeIdentifier}.\n\n"
- + "@return A {@link $1T} which contains the return value (if a synchronous"
- + " call) or is empty\n (if asynchronous). This {@link $1T} must be recycled"
- + " after use.\n",
- PARCEL_CLASSNAME)
+ + "@return A {@link $1T} which contains the return value under the key "
+ + "\"return\" (if a synchronous call) or is empty\n (if asynchronous).\n",
+ BUNDLE_CLASSNAME)
.build());
}
@@ -176,11 +175,9 @@
static ClassName getInternalProviderClassName(
GeneratorContext generatorContext, ProviderClassInfo providerClass) {
- PackageElement originalPackage =
- generatorContext.elements().getPackageOf(providerClass.providerClassElement());
- String internalProviderClassName =
- String.format("Profile_%s_Internal", providerClass.simpleName());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), internalProviderClassName);
+ return classNameInferringPackageFromElement(
+ generatorContext,
+ providerClass.providerClassElement(),
+ String.format("Profile_%s_Internal", providerClass.simpleName()));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/LateValidator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/LateValidator.java
index 4b55483..4b30134 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/LateValidator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/LateValidator.java
@@ -23,10 +23,10 @@
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
+import com.google.android.enterprise.connectedapps.processor.containers.ConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileConfigurationInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.google.android.enterprise.connectedapps.processor.containers.ProfileConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.ProviderClassInfo;
import com.google.common.collect.Streams;
import java.util.Collection;
@@ -137,7 +137,7 @@
.serviceClass()
.get()
.toString()
- .equals(configuration.profileConnector().serviceName().toString())) {
+ .equals(configuration.connectorInfo().serviceName().toString())) {
showError(INCORRECT_SERVICE_CLASS, configuration.configurationElement());
isValid = false;
}
@@ -152,7 +152,7 @@
.flatMap(m -> getConnectorQualifiedNamesUsedInProviderClass(m).stream())
.collect(toSet());
connectorQualifiedNames.add(
- configuration.profileConnector().connectorElement().asType().toString());
+ configuration.connectorInfo().connectorElement().asType().toString());
return connectorQualifiedNames;
}
@@ -184,9 +184,9 @@
private static Collection<String> getConnectorQualifiedNamesUsedInProviderClass(
ProviderClassInfo providerClass) {
return providerClass.allCrossProfileTypes().stream()
- .map(CrossProfileTypeInfo::profileConnector)
+ .map(CrossProfileTypeInfo::connectorInfo)
.flatMap(Streams::stream)
- .map(ProfileConnectorInfo::connectorElement)
+ .map(ConnectorInfo::connectorElement)
.map(Element::asType)
.map(TypeMirror::toString)
.collect(toSet());
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/MultipleProfilesGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/MultipleProfilesGenerator.java
index 38b8038..e1dbd69 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/MultipleProfilesGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/MultipleProfilesGenerator.java
@@ -15,6 +15,8 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ASYNC_CALLBACK_PARAM_MULTIMERGER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CALLBACK_MERGER_EXCEPTION_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PROFILE_CLASSNAME;
@@ -24,11 +26,11 @@
import static java.util.stream.Collectors.toList;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackParameterInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
@@ -40,8 +42,6 @@
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -108,22 +108,6 @@
.addStatement("this.senders = senders")
.build());
- classBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addAnnotation(Override.class)
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC)
- .returns(className)
- .addParameter(long.class, "timeout")
- .beginControlFlow("for ($T senderProfile : senders.keySet())", PROFILE_CLASSNAME)
- .addStatement("senders.put(senderProfile, senders.get(senderProfile).timeout(timeout))")
- .endControlFlow()
- .addStatement("return this")
- .build());
-
for (CrossProfileMethodInfo method : crossProfileType.crossProfileMethods()) {
if (method.isBlocking(generatorContext, crossProfileType)) {
generateBlockingMethodOnMultipleProfilesClass(classBuilder, method, crossProfileType);
@@ -225,11 +209,10 @@
String methodName = method.simpleName();
- VariableElement callbackParameter = method.getCrossProfileCallbackParam(generatorContext).get();
- TypeElement callbackType =
- generatorContext.elements().getTypeElement(callbackParameter.asType().toString());
+ CrossProfileCallbackParameterInfo callbackParameter =
+ method.getCrossProfileCallbackParam(generatorContext).get();
CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(callbackType);
+ callbackParameter.crossProfileCallbackInterface();
List<ParameterSpec> parameters =
convertCallbackParametersIntoMulti(
@@ -237,8 +220,7 @@
crossProfileType.supportedTypes(),
method.methodElement(),
REMOVE_AUTOMATICALLY_RESOLVED_PARAMETERS),
- callbackParameter,
- callbackInterface);
+ callbackParameter);
TypeMirror paramType =
callbackInterface.methods().get(0).getParameters().isEmpty()
@@ -352,14 +334,12 @@
}
private List<ParameterSpec> convertCallbackParametersIntoMulti(
- List<ParameterSpec> parameters,
- VariableElement callbackParameter,
- CrossProfileCallbackInterfaceInfo callbackInterface) {
+ List<ParameterSpec> parameters, CrossProfileCallbackParameterInfo callbackParam) {
return parameters.stream()
.map(
e ->
- e.name.equals(callbackParameter.getSimpleName().toString())
- ? convertCallbackToMulti(e, callbackInterface)
+ e.name.equals(callbackParam.getSimpleName().toString())
+ ? convertCallbackToMulti(e, callbackParam.crossProfileCallbackInterface())
: e)
.collect(toList());
}
@@ -386,7 +366,6 @@
static ClassName getMultipleProfilesClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_MultipleProfiles");
+ return transformClassName(crossProfileType.generatedClassName(), append("_MultipleProfiles"));
}
}
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 08011ca..0188ea7 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
@@ -15,17 +15,19 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.append;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.EXCEPTION_CALLBACK_CLASSNAME;
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;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileAnnotation;
-import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackParameterInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
@@ -33,12 +35,10 @@
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
-import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -86,47 +86,19 @@
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(singleSenderCanThrowInterface);
- ClassName connectorClassName =
- crossProfileType.profileConnector().isPresent()
- ? crossProfileType.profileConnector().get().connectorClassName()
- : PROFILE_CONNECTOR_CLASSNAME;
-
- classBuilder.addField(connectorClassName, "connector", Modifier.PRIVATE, Modifier.FINAL);
+ classBuilder.addField(
+ PROFILE_CONNECTOR_CLASSNAME, "connector", Modifier.PRIVATE, Modifier.FINAL);
classBuilder.addMethod(
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
- .addParameter(connectorClassName, "connector")
+ .addParameter(PROFILE_CONNECTOR_CLASSNAME, "connector")
.beginControlFlow("if (connector == null)")
.addStatement("throw new $T()", NullPointerException.class)
.endControlFlow()
.addStatement("this.connector = connector")
.build());
- classBuilder.addField(
- FieldSpec.builder(long.class, "timeout")
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PRIVATE)
- .initializer("$L", CrossProfileAnnotation.TIMEOUT_MILLIS_NOT_SET)
- .build());
-
- classBuilder.addMethod(
- MethodSpec.methodBuilder("timeout")
- .addAnnotation(Override.class)
- .addAnnotation(
- AnnotationSpec.builder(SuppressWarnings.class)
- .addMember("value", "$S", "GoodTime")
- .build())
- .addModifiers(Modifier.PUBLIC)
- .returns(className)
- .addParameter(long.class, "timeout")
- .addStatement("this.timeout = timeout")
- .addStatement("return this")
- .build());
-
ClassName ifAvailableClass =
IfAvailableGenerator.getIfAvailableClassName(generatorContext, crossProfileType);
@@ -177,29 +149,29 @@
InternalCrossProfileClassGenerator.getInternalCrossProfileClassName(
generatorContext, crossProfileType));
- // parcel is recycled in this method
- methodBuilder.addStatement("$1T params = $1T.obtain()", PARCEL_CLASSNAME);
+ methodBuilder.addStatement(
+ "$1T params = new $1T($2T.class.getClassLoader())", BUNDLE_CLASSNAME, BUNDLER_CLASSNAME);
for (VariableElement param : method.methodElement().getParameters()) {
if (crossProfileType.supportedTypes().isAutomaticallyResolved(param.asType())) {
continue;
}
methodBuilder.addStatement(
- "internalCrossProfileClass.bundler().writeToParcel(params, $1L, $2L, /* flags= */ 0)",
+ "internalCrossProfileClass.bundler().writeToBundle(params, $1S, $1L, $2L)",
param.getSimpleName(),
TypeUtils.generateBundlerType(param.asType()));
}
if (method.thrownExceptions().isEmpty()) {
methodBuilder.addStatement(
- "$1T returnParcel = connector.crossProfileSender().call($2LL, $3L, params)",
- PARCEL_CLASSNAME,
+ "$1T returnBundle = connector.crossProfileSender().call($2LL, $3L, params)",
+ BUNDLE_CLASSNAME,
crossProfileType.identifier(),
method.identifier());
} else {
- methodBuilder.addStatement("$1T returnParcel", PARCEL_CLASSNAME);
+ methodBuilder.addStatement("$1T returnBundle", BUNDLE_CLASSNAME);
methodBuilder.beginControlFlow("try");
methodBuilder.addStatement(
- "returnParcel = connector.crossProfileSender().callWithExceptions($1LL, $2L, params)",
+ "returnBundle = connector.crossProfileSender().callWithExceptions($1LL, $2L, params)",
crossProfileType.identifier(),
method.identifier());
methodBuilder.nextControlFlow("catch ($T e)", UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME);
@@ -219,20 +191,16 @@
methodBuilder.endControlFlow();
}
- methodBuilder.addStatement("params.recycle()");
-
if (!method.returnType().getKind().equals(TypeKind.VOID)) {
methodBuilder.addStatement(
CodeBlock.of(
"@SuppressWarnings(\"unchecked\") $1T returnValue = ($1T)"
- + " internalCrossProfileClass.bundler().readFromParcel(returnParcel,"
- + " $2L)",
+ + " internalCrossProfileClass.bundler().readFromBundle(returnBundle, $2S, "
+ + " $3L)",
method.returnType(),
+ "return",
TypeUtils.generateBundlerType(method.returnType())));
- methodBuilder.addStatement("returnParcel.recycle()");
methodBuilder.addStatement("return returnValue");
- } else {
- methodBuilder.addStatement("returnParcel.recycle()");
}
classBuilder.addMethod(methodBuilder.build());
@@ -242,11 +210,8 @@
TypeSpec.Builder classBuilder,
CrossProfileMethodInfo method,
CrossProfileTypeInfo crossProfileType) {
- VariableElement callbackParameter = method.getCrossProfileCallbackParam(generatorContext).get();
- TypeElement callbackType =
- generatorContext.elements().getTypeElement(callbackParameter.asType().toString());
- CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(callbackType);
+ CrossProfileCallbackParameterInfo callbackParameter =
+ method.getCrossProfileCallbackParam(generatorContext).get();
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(method.simpleName())
@@ -265,8 +230,8 @@
InternalCrossProfileClassGenerator.getInternalCrossProfileClassName(
generatorContext, crossProfileType));
- // parcel is passed into callAsync where it will be cached and recycled afterwards
- methodBuilder.addStatement("$1T params = $1T.obtain()", PARCEL_CLASSNAME);
+ methodBuilder.addStatement(
+ "$1T params = new $1T($2T.class.getClassLoader())", BUNDLE_CLASSNAME, BUNDLER_CLASSNAME);
for (VariableElement param : method.methodElement().getParameters()) {
if (crossProfileType.supportedTypes().isAutomaticallyResolved(param.asType())) {
@@ -276,7 +241,7 @@
continue;
}
methodBuilder.addStatement(
- "internalCrossProfileClass.bundler().writeToParcel(params, $1L, $2L, /* flags= */ 0)",
+ "internalCrossProfileClass.bundler().writeToBundle(params, $1S, $1L, $2L)",
param.getSimpleName(),
TypeUtils.generateBundlerType(param.asType()));
}
@@ -285,7 +250,7 @@
"$1T sender = new $2T($3L, exceptionCallback, internalCrossProfileClass.bundler())",
LOCAL_CALLBACK_CLASSNAME,
CrossProfileCallbackCodeGenerator.getCrossProfileCallbackSenderClassName(
- generatorContext, callbackInterface),
+ generatorContext, callbackParameter.crossProfileCallbackInterface()),
callbackParameter.getSimpleName());
// Suppress GoodTime warning for unboxing Duration.
@@ -294,16 +259,10 @@
.addMember("value", "$S", "GoodTime")
.build());
methodBuilder.addStatement(
- "connector.crossProfileSender().callAsync($1LL, $2L, params, sender, timeout =="
- + " $3L ? $4L : timeout)",
+ "connector.crossProfileSender().callAsync($1LL, $2L, params, sender, $3L)",
crossProfileType.identifier(),
method.identifier(),
- CrossProfileAnnotation.TIMEOUT_MILLIS_NOT_SET,
- method.timeoutMillis());
-
- methodBuilder.addComment(
- "We don't recycle the params as they will be stored for the async call and recycled"
- + " afterwards");
+ callbackParameter.getSimpleName());
classBuilder.addMethod(methodBuilder.build());
}
@@ -329,14 +288,14 @@
InternalCrossProfileClassGenerator.getInternalCrossProfileClassName(
generatorContext, crossProfileType));
- // parcel is passed into callAsync where it will be cached and recycled afterwards
- methodBuilder.addStatement("$1T params = $1T.obtain()", PARCEL_CLASSNAME);
+ methodBuilder.addStatement(
+ "$1T params = new $1T($2T.class.getClassLoader())", BUNDLE_CLASSNAME, BUNDLER_CLASSNAME);
for (VariableElement param : method.methodElement().getParameters()) {
if (crossProfileType.supportedTypes().isAutomaticallyResolved(param.asType())) {
continue;
}
methodBuilder.addStatement(
- "internalCrossProfileClass.bundler().writeToParcel(params, $1L, $2L, /* flags= */ 0)",
+ "internalCrossProfileClass.bundler().writeToBundle(params, $1S, $1L, $2L)",
param.getSimpleName(),
TypeUtils.generateBundlerType(param.asType()));
}
@@ -360,15 +319,9 @@
.build());
methodBuilder.addStatement(
"connector.crossProfileSender().callAsync($1LL, $2L, params, futureWrapper,"
- + " timeout == $3L ? $4L : timeout)",
+ + "futureWrapper.getFuture())",
crossProfileType.identifier(),
- method.identifier(),
- CrossProfileAnnotation.TIMEOUT_MILLIS_NOT_SET,
- method.timeoutMillis());
-
- methodBuilder.addComment(
- "We don't recycle the params as they will be stored for the async call and recycled"
- + " afterwards");
+ method.identifier());
methodBuilder.addStatement("return futureWrapper.getFuture()");
@@ -377,7 +330,6 @@
static ClassName getOtherProfileClassName(
GeneratorContext generatorContext, CrossProfileTypeInfo crossProfileType) {
- return GeneratorUtilities.appendToClassName(
- crossProfileType.profileClassName(), "_OtherProfile");
+ return transformClassName(crossProfileType.generatedClassName(), append("_OtherProfile"));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/Processor.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/Processor.java
index 4948196..729f27e 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/Processor.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/Processor.java
@@ -30,9 +30,11 @@
import com.google.android.enterprise.connectedapps.annotations.CustomUserConnector;
import com.google.android.enterprise.connectedapps.annotations.GeneratedProfileConnector;
import com.google.android.enterprise.connectedapps.annotations.GeneratedUserConnector;
+import com.google.android.enterprise.connectedapps.processor.containers.Context;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
import com.google.android.enterprise.connectedapps.processor.containers.ParcelableWrapper;
+import com.google.android.enterprise.connectedapps.processor.containers.PreValidatorContext;
import com.google.android.enterprise.connectedapps.processor.containers.ProfileConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.UserConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.ValidatorContext;
@@ -47,7 +49,6 @@
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
@@ -55,11 +56,10 @@
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
/** Processor for generation of cross-profile code. */
@SupportedAnnotationTypes({
+ "com.google.android.enterprise.connectedapps.annotations.Cacheable",
"com.google.android.enterprise.connectedapps.annotations.CrossProfile",
"com.google.android.enterprise.connectedapps.annotations.CrossProfileCallback",
"com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration",
@@ -82,8 +82,6 @@
@AutoService(javax.annotation.processing.Processor.class)
public final class Processor extends AbstractProcessor {
- private Types types;
-
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
@@ -91,27 +89,26 @@
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- Elements elements = processingEnv.getElementUtils();
- types = processingEnv.getTypeUtils();
+ PreValidatorContext preValidatorContext = new PreValidatorContext(processingEnv);
Collection<ValidatorCrossProfileTestInfo> newCrossProfileTests =
- findNewCrossProfileTests(roundEnv);
+ findNewCrossProfileTests(preValidatorContext, roundEnv);
// Only new configurations need code generating - but we need to support types used by methods
// included in configurations under test
Collection<ValidatorCrossProfileConfigurationInfo> newConfigurations =
- findNewConfigurations(roundEnv);
+ findNewConfigurations(preValidatorContext, roundEnv);
Collection<ValidatorCrossProfileConfigurationInfo> allConfigurations =
- findAllConfigurations(newConfigurations, newCrossProfileTests);
+ findAllConfigurations(preValidatorContext, newConfigurations, newCrossProfileTests);
- Collection<ValidatorProviderClassInfo> newProviderClasses = findNewProviderClasses(roundEnv);
+ Collection<ValidatorProviderClassInfo> newProviderClasses =
+ findNewProviderClasses(preValidatorContext, roundEnv);
Collection<ExecutableElement> newProviderMethods = findNewProviderMethods(roundEnv);
Collection<TypeElement> newGeneratedConnectors = findNewGeneratedConnectors(roundEnv);
Collection<TypeElement> newGeneratedUserConnectors = findNewGeneratedUserConnectors(roundEnv);
Collection<ExecutableElement> newCrossProfileMethods = findNewCrossProfileMethods(roundEnv);
Collection<ExecutableElement> allCrossProfileMethods =
findAllCrossProfileMethods(
- processingEnv,
- elements,
+ preValidatorContext,
newCrossProfileMethods,
allConfigurations,
newProviderMethods,
@@ -132,26 +129,23 @@
Collection<TypeElement> newCustomFutureWrappers = findNewFutureWrappers(roundEnv);
Collection<FutureWrapper> globalFutureWrappers =
- FutureWrapper.createGlobalFutureWrappers(elements);
+ FutureWrapper.createGlobalFutureWrappers(preValidatorContext);
Collection<ParcelableWrapper> globalParcelableWrappers =
- ParcelableWrapper.createGlobalParcelableWrappers(types, elements, methods);
+ ParcelableWrapper.createGlobalParcelableWrappers(preValidatorContext, methods);
SupportedTypes globalSupportedTypes =
SupportedTypes.createFromMethods(
- types, elements, globalParcelableWrappers, globalFutureWrappers, methods);
+ preValidatorContext, globalParcelableWrappers, globalFutureWrappers, methods);
Collection<ValidatorCrossProfileTypeInfo> newCrossProfileTypes =
- findNewCrossProfileTypes(roundEnv, globalSupportedTypes);
+ findNewCrossProfileTypes(preValidatorContext, roundEnv, globalSupportedTypes);
Collection<ProfileConnectorInfo> newProfileConnectorInterfaces =
- findNewProfileConnectorInterfaces(roundEnv, globalSupportedTypes);
+ findNewProfileConnectorInterfaces(preValidatorContext, roundEnv, globalSupportedTypes);
Collection<UserConnectorInfo> newUserConnectorInterfaces =
- findNewUserConnectorInterfaces(roundEnv, globalSupportedTypes);
+ findNewUserConnectorInterfaces(preValidatorContext, roundEnv, globalSupportedTypes);
ValidatorContext validatorContext =
- ValidatorContext.builder()
- .setProcessingEnv(processingEnv)
- .setElements(elements)
- .setTypes(types)
+ ValidatorContext.builderFromPreValidatorContext(preValidatorContext)
.setGlobalSupportedTypes(globalSupportedTypes)
.setNewProfileConnectorInterfaces(newProfileConnectorInterfaces)
.setNewUserConnectorInterfaces(newUserConnectorInterfaces)
@@ -189,27 +183,28 @@
}
private Collection<ValidatorCrossProfileConfigurationInfo> findNewConfigurations(
- RoundEnvironment roundEnv) {
+ Context context, RoundEnvironment roundEnv) {
Set<ValidatorCrossProfileConfigurationInfo> annotations = new HashSet<>();
elementsAnnotatedWithCrossProfileConfiguration(roundEnv)
.map(
element ->
ValidatorCrossProfileConfigurationInfo.createFromElement(
- processingEnv, (TypeElement) element))
+ context, (TypeElement) element))
.forEach(annotations::add);
elementsAnnotatedWithCrossProfileConfigurations(roundEnv)
.map(
element ->
ValidatorCrossProfileConfigurationInfo.createMultipleFromElement(
- processingEnv, (TypeElement) element))
+ context, (TypeElement) element))
.forEach(annotations::addAll);
return annotations;
}
private Collection<ValidatorCrossProfileConfigurationInfo> findAllConfigurations(
+ Context context,
Collection<ValidatorCrossProfileConfigurationInfo> newConfigurations,
Collection<ValidatorCrossProfileTestInfo> crossProfileTests) {
Set<ValidatorCrossProfileConfigurationInfo> allConfigurations = new HashSet<>();
@@ -219,18 +214,19 @@
.flatMap(
t ->
ValidatorCrossProfileConfigurationInfo.createMultipleFromElement(
- processingEnv, t.configurationElement())
+ context, t.configurationElement())
.stream())
.collect(toSet()));
return allConfigurations;
}
- private Collection<ValidatorProviderClassInfo> findNewProviderClasses(RoundEnvironment roundEnv) {
+ private Collection<ValidatorProviderClassInfo> findNewProviderClasses(
+ Context context, RoundEnvironment roundEnv) {
Set<ValidatorProviderClassInfo> annotatedClasses =
elementsAnnotatedWithCrossProfileProvider(roundEnv)
.filter(m -> m instanceof TypeElement)
.map(m -> (TypeElement) m)
- .map(m -> ValidatorProviderClassInfo.create(processingEnv, m))
+ .map(m -> ValidatorProviderClassInfo.create(context, m))
.collect(toSet());
Set<ValidatorProviderClassInfo> unannotatedClasses =
@@ -240,7 +236,7 @@
.map(Element::getEnclosingElement)
.map(m -> (TypeElement) m)
.filter(m -> !hasCrossProfileProviderAnnotation(m))
- .map(m -> ValidatorProviderClassInfo.create(processingEnv, m))
+ .map(m -> ValidatorProviderClassInfo.create(context, m))
.collect(toSet());
Collection<ValidatorProviderClassInfo> allProviders = new HashSet<>();
@@ -257,12 +253,12 @@
}
private Collection<ValidatorCrossProfileTypeInfo> findNewCrossProfileTypes(
- RoundEnvironment roundEnv, SupportedTypes globalSupportedTypes) {
+ Context context, RoundEnvironment roundEnv, SupportedTypes globalSupportedTypes) {
Collection<ValidatorCrossProfileTypeInfo> annotatedTypes =
elementsAnnotatedWithCrossProfile(roundEnv)
.filter(m -> m instanceof TypeElement)
.map(m -> (TypeElement) m)
- .map(m -> ValidatorCrossProfileTypeInfo.create(processingEnv, m, globalSupportedTypes))
+ .map(m -> ValidatorCrossProfileTypeInfo.create(context, m, globalSupportedTypes))
.collect(toSet());
Collection<ValidatorCrossProfileTypeInfo> unannotatedTypes =
@@ -272,7 +268,7 @@
.map(ExecutableElement::getEnclosingElement)
.filter(m -> m instanceof TypeElement)
.map(m -> (TypeElement) m)
- .map(m -> ValidatorCrossProfileTypeInfo.create(processingEnv, m, globalSupportedTypes))
+ .map(m -> ValidatorCrossProfileTypeInfo.create(context, m, globalSupportedTypes))
.collect(toSet());
Collection<ValidatorCrossProfileTypeInfo> allTypes = new HashSet<>();
@@ -289,7 +285,7 @@
}
private Collection<ProfileConnectorInfo> findNewProfileConnectorInterfaces(
- RoundEnvironment roundEnv, SupportedTypes globalSupportedTypes) {
+ Context context, RoundEnvironment roundEnv, SupportedTypes globalSupportedTypes) {
Collection<TypeElement> connectorInterfaces =
roundEnv.getElementsAnnotatedWith(CustomProfileConnector.class).stream()
.map(m -> (TypeElement) m)
@@ -302,12 +298,12 @@
.getTypeElement("com.google.android.enterprise.connectedapps.CrossProfileConnector"));
return connectorInterfaces.stream()
- .map(t -> ProfileConnectorInfo.create(processingEnv, t, globalSupportedTypes))
+ .map(t -> ProfileConnectorInfo.create(context, t, globalSupportedTypes))
.collect(Collectors.toSet());
}
private Collection<UserConnectorInfo> findNewUserConnectorInterfaces(
- RoundEnvironment roundEnv, SupportedTypes globalSupportedTypes) {
+ Context context, RoundEnvironment roundEnv, SupportedTypes globalSupportedTypes) {
Collection<TypeElement> connectorInterfaces =
roundEnv.getElementsAnnotatedWith(CustomUserConnector.class).stream()
.map(m -> (TypeElement) m)
@@ -320,7 +316,7 @@
.getTypeElement("com.google.android.enterprise.connectedapps.CrossUserConnector"));
return connectorInterfaces.stream()
- .map(t -> UserConnectorInfo.create(processingEnv, t, globalSupportedTypes))
+ .map(t -> UserConnectorInfo.create(context, t, globalSupportedTypes))
.collect(Collectors.toSet());
}
@@ -343,8 +339,7 @@
}
private static Collection<ExecutableElement> findAllCrossProfileMethods(
- ProcessingEnvironment processingEnvironment,
- Elements elements,
+ Context context,
Collection<ExecutableElement> newCrossProfileMethods,
Collection<ValidatorCrossProfileConfigurationInfo> configurations,
Collection<ExecutableElement> newProviderMethods,
@@ -354,7 +349,7 @@
Collection<ValidatorProviderClassInfo> foundProviderClasses =
configurations.stream()
.flatMap(a -> a.providerClassElements().stream())
- .map(m -> ValidatorProviderClassInfo.create(processingEnvironment, m))
+ .map(m -> ValidatorProviderClassInfo.create(context, m))
.collect(toSet());
Collection<ExecutableElement> providerMethods =
@@ -370,7 +365,7 @@
Collection<TypeElement> crossProfileTypes =
providerMethods.stream()
- .map(e -> elements.getTypeElement(e.getReturnType().toString()))
+ .map(e -> context.elements().getTypeElement(e.getReturnType().toString()))
.filter(Objects::nonNull)
.collect(toSet());
crossProfileTypes.addAll(
@@ -388,10 +383,10 @@
}
private Collection<ValidatorCrossProfileTestInfo> findNewCrossProfileTests(
- RoundEnvironment roundEnv) {
+ Context context, RoundEnvironment roundEnv) {
return elementsAnnotatedWithCrossProfileTest(roundEnv)
.map(e -> (TypeElement) e)
- .map(e -> ValidatorCrossProfileTestInfo.create(processingEnv, e))
+ .map(e -> ValidatorCrossProfileTestInfo.create(context, e))
.collect(toSet());
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileConnectorCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileConnectorCodeGenerator.java
index 68c14f2..87be4fa 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileConnectorCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileConnectorCodeGenerator.java
@@ -15,6 +15,9 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.getBuilderClassName;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_PROFILE_CONNECTOR_BUILDER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_PROFILE_CONNECTOR_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.AVAILABILITY_RESTRICTIONS_CLASSNAME;
@@ -27,6 +30,7 @@
import com.google.android.enterprise.connectedapps.annotations.GeneratedProfileConnector;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
import com.google.android.enterprise.connectedapps.processor.containers.ProfileConnectorInfo;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
@@ -143,6 +147,7 @@
classBuilder.addMethod(
MethodSpec.methodBuilder("setScheduledExecutorService")
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(SCHEDULED_EXECUTOR_SERVICE_CLASSNAME, "scheduledExecutorService")
.returns(builderClassName)
@@ -153,6 +158,7 @@
classBuilder.addMethod(
MethodSpec.methodBuilder("setBinder")
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(CONNECTION_BINDER_CLASSNAME, "binder")
.returns(builderClassName)
@@ -172,18 +178,11 @@
static ClassName getGeneratedProfileConnectorClassName(
GeneratorContext generatorContext, ProfileConnectorInfo connector) {
- return ClassName.get(
- connector.connectorClassName().packageName(),
- "Generated" + connector.connectorClassName().simpleName());
+ return transformClassName(connector.connectorClassName(), prepend("Generated"));
}
static ClassName getGeneratedProfileConnectorBuilderClassName(
GeneratorContext generatorContext, ProfileConnectorInfo connector) {
- return ClassName.get(
- connector.connectorClassName().packageName()
- + "."
- + "Generated"
- + connector.connectorClassName().simpleName(),
- "Builder");
+ return getBuilderClassName(getGeneratedProfileConnectorClassName(generatorContext, connector));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProtoParcelableWrapperGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProtoParcelableWrapperGenerator.java
index 302b420..99753d5 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProtoParcelableWrapperGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProtoParcelableWrapperGenerator.java
@@ -77,7 +77,7 @@
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(wrapperClassName)
- .addModifiers(Modifier.PUBLIC)
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(PARCELABLE_CLASSNAME)
.addJavadoc(
"Wrapper for reading & writing {@link $T} instances to and from {@link $T}"
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProviderClassCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProviderClassCodeGenerator.java
index 06f6b71..ae25706 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProviderClassCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProviderClassCodeGenerator.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.android.enterprise.connectedapps.processor.containers.ConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
import com.google.android.enterprise.connectedapps.processor.containers.ProviderClassInfo;
@@ -28,12 +29,14 @@
private final GeneratorContext generatorContext;
private final InternalProviderClassGenerator internalProviderClassGenerator;
private final ProviderClassInfo providerClass;
+ private final ConnectorInfo connectorInfo;
ProviderClassCodeGenerator(GeneratorContext generatorContext, ProviderClassInfo providerClass) {
this.generatorContext = checkNotNull(generatorContext);
this.providerClass = checkNotNull(providerClass);
this.internalProviderClassGenerator =
new InternalProviderClassGenerator(generatorContext, providerClass);
+ this.connectorInfo = providerClass.connectorInfo();
}
void generate() {
@@ -45,9 +48,31 @@
internalProviderClassGenerator.generate();
+ generatedSharedCrossConnectionApi();
+ if (connectorInfo.hasCrossProfileConnector()) {
+ generateCrossProfileApi();
+ }
+ if (connectorInfo.hasCrossUserConnector()) {
+ generateCrossUserApi();
+ }
+ }
+
+ private void generatedSharedCrossConnectionApi() {
+ for (CrossProfileTypeInfo crossProfileType : providerClass.allCrossProfileTypes()) {
+ new SharedTypeCodeGenerator(generatorContext, providerClass, crossProfileType).generate();
+ }
+ }
+
+ private void generateCrossProfileApi() {
for (CrossProfileTypeInfo crossProfileType : providerClass.allCrossProfileTypes()) {
new CrossProfileTypeCodeGenerator(generatorContext, providerClass, crossProfileType)
.generate();
}
}
+
+ private void generateCrossUserApi() {
+ for (CrossProfileTypeInfo crossProfileType : providerClass.allCrossProfileTypes()) {
+ new CrossUserTypeCodeGenerator(generatorContext, providerClass, crossProfileType).generate();
+ }
+ }
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ServiceGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ServiceGenerator.java
index 28fa128..fd8a2ce 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ServiceGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ServiceGenerator.java
@@ -16,6 +16,7 @@
package com.google.android.enterprise.connectedapps.processor;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BINDER_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLE_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSSPROFILESERVICE_STUB_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CROSS_PROFILE_CALLBACK_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.INTENT_CLASSNAME;
@@ -82,7 +83,7 @@
+ "<p>This service must be exposed in a <service> tag in your"
+ " AndroidManifest.xml\n",
configuration.configurationElement(),
- configuration.profileConnector().connectorClassName(),
+ configuration.connectorInfo().connectorClassName(),
getDispatcherClassName(generatorContext, configuration));
addBinder(classBuilder);
@@ -112,8 +113,10 @@
.build());
addPrepareCallMethod(binderBuilder);
+ addPrepareBundleMethod(binderBuilder);
addCallMethod(binderBuilder);
addFetchResponseMethod(binderBuilder);
+ addFetchResponseBundleMethod(binderBuilder);
classBuilder.addField(
FieldSpec.builder(CROSSPROFILESERVICE_STUB_CLASSNAME, "binder", Modifier.PRIVATE)
@@ -137,6 +140,20 @@
classBuilder.addMethod(prepareCallMethod);
}
+ private static void addPrepareBundleMethod(TypeSpec.Builder classBuilder) {
+ MethodSpec prepareCallMethod =
+ MethodSpec.methodBuilder("prepareBundle")
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class)
+ .addParameter(long.class, "callId")
+ .addParameter(int.class, "bundleId")
+ .addParameter(BUNDLE_CLASSNAME, "bundle")
+ .addStatement(
+ "dispatcher.prepareBundle(getApplicationContext(), callId, bundleId, bundle)")
+ .build();
+ classBuilder.addMethod(prepareCallMethod);
+ }
+
private static void addCallMethod(TypeSpec.Builder classBuilder) {
MethodSpec callMethod =
MethodSpec.methodBuilder("call")
@@ -171,8 +188,22 @@
classBuilder.addMethod(prepareCallMethod);
}
+ private static void addFetchResponseBundleMethod(TypeSpec.Builder classBuilder) {
+ MethodSpec prepareCallMethod =
+ MethodSpec.methodBuilder("fetchResponseBundle")
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class)
+ .addParameter(long.class, "callId")
+ .addParameter(int.class, "bundleId")
+ .returns(BUNDLE_CLASSNAME)
+ .addStatement(
+ "return dispatcher.fetchResponseBundle(getApplicationContext(), callId, bundleId)")
+ .build();
+ classBuilder.addMethod(prepareCallMethod);
+ }
+
static ClassName getConnectedAppsServiceClassName(
GeneratorContext generatorContext, CrossProfileConfigurationInfo configuration) {
- return configuration.profileConnector().serviceName();
+ return configuration.connectorInfo().serviceName();
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SharedTypeCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SharedTypeCodeGenerator.java
new file mode 100644
index 0000000..ca6c105
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/SharedTypeCodeGenerator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
+import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
+import com.google.android.enterprise.connectedapps.processor.containers.ProviderClassInfo;
+
+class SharedTypeCodeGenerator {
+ private boolean generated = false;
+ private final InterfaceGenerator interfaceGenerator;
+ private final CurrentProfileGenerator currentProfileGenerator;
+ private final OtherProfileGenerator otherProfileGenerator;
+ private final IfAvailableGenerator ifAvailableGenerator;
+ private final AlwaysThrowsGenerator alwaysThrowsGenerator;
+ private final InternalCrossProfileClassGenerator internalCrossProfileClassGenerator;
+ private final BundlerGenerator bundlerGenerator;
+
+ public SharedTypeCodeGenerator(
+ GeneratorContext generatorContext,
+ ProviderClassInfo providerClass,
+ CrossProfileTypeInfo crossProfileType) {
+ checkNotNull(generatorContext);
+ checkNotNull(crossProfileType);
+ this.interfaceGenerator = new InterfaceGenerator(generatorContext, crossProfileType);
+ this.currentProfileGenerator = new CurrentProfileGenerator(generatorContext, crossProfileType);
+ this.otherProfileGenerator = new OtherProfileGenerator(generatorContext, crossProfileType);
+ this.ifAvailableGenerator = new IfAvailableGenerator(generatorContext, crossProfileType);
+ this.alwaysThrowsGenerator = new AlwaysThrowsGenerator(generatorContext, crossProfileType);
+ this.internalCrossProfileClassGenerator =
+ new InternalCrossProfileClassGenerator(generatorContext, providerClass, crossProfileType);
+ this.bundlerGenerator = new BundlerGenerator(generatorContext, crossProfileType);
+ }
+
+ void generate() {
+ if (generated) {
+ throw new IllegalStateException("SharedTypeCodeGenerator#generate can only be called once");
+ }
+ generated = true;
+
+ interfaceGenerator.generate();
+ currentProfileGenerator.generate();
+ otherProfileGenerator.generate();
+ ifAvailableGenerator.generate();
+ alwaysThrowsGenerator.generate();
+ internalCrossProfileClassGenerator.generate();
+ bundlerGenerator.generate();
+ }
+}
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 254c455..51eae3d 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
@@ -17,12 +17,12 @@
import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileCallbackAnnotation;
+import com.google.android.enterprise.connectedapps.processor.containers.Context;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
import com.google.android.enterprise.connectedapps.processor.containers.ParcelableWrapper;
import com.google.android.enterprise.connectedapps.processor.containers.Type;
-import com.google.android.enterprise.connectedapps.processor.containers.ValidatorContext;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
@@ -50,8 +50,8 @@
@Override
public String toString() {
return "SupportedTypes{" +
- "usableTypes=" + usableTypes +
- '}';
+ "usableTypes=" + usableTypes +
+ '}';
}
@Override
@@ -84,16 +84,16 @@
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
@@ -124,14 +124,15 @@
return false; // We don't support generic arrays
}
if (TypeUtils.isArray(wrappedType)) {
- return false; // We don't support multidimensional arrays
+ // We don't support non-primitive multidimensional arrays
+ return TypeUtils.isPrimitiveArray(wrappedType);
}
return isValidReturnType(wrappedType, context);
}
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) {
@@ -194,7 +195,8 @@
return false; // We don't support generic arrays
}
if (TypeUtils.isArray(wrappedType)) {
- return false; // We don't support multidimensional arrays
+ // We don't support non-primitive multidimensional arrays
+ return TypeUtils.isPrimitiveArray(wrappedType);
}
return isValidParameterType(wrappedType, context.toBuilder().setWrapped(true).build());
}
@@ -213,8 +215,8 @@
}
return TypeUtils.isGeneric(type)
- ? isValidGenericParameterType(type, context)
- : isValidParameterType(get(type));
+ ? isValidGenericParameterType(type, context)
+ : isValidParameterType(get(type));
}
private static boolean isValidParameterType(Type supportedType) {
@@ -249,13 +251,33 @@
return usableTypes.getOrDefault(type.toString(), null);
}
+ CodeBlock generatePutIntoBundleCode(
+ String bundleName, Type type, String keyCode, String valueCode) {
+
+ if (type.getPutIntoBundleCode().isPresent()) {
+ return CodeBlock.of(type.getPutIntoBundleCode().get(), bundleName, keyCode, valueCode);
+ }
+
+ throw new IllegalArgumentException(
+ String.format("%s can not put into bundle", type.getQualifiedName()));
+ }
+
+ CodeBlock generateGetFromBundleCode(String bundleName, Type type, String keyCode) {
+ if (type.getGetFromBundleCode().isPresent()) {
+ return CodeBlock.of(type.getGetFromBundleCode().get(), bundleName, keyCode);
+ }
+
+ throw new IllegalArgumentException(
+ String.format("%s can not get from bundle", type.getQualifiedName()));
+ }
+
CodeBlock generateWriteToParcelCode(String parcelName, Type type, String valueCode) {
if (type.getWriteToParcelCode().isPresent()) {
return CodeBlock.of(type.getWriteToParcelCode().get(), parcelName, valueCode);
}
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) {
@@ -264,7 +286,7 @@
}
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) {
@@ -281,50 +303,48 @@
}
public static SupportedTypes createFromMethods(
- Types types,
- Elements elements,
- Collection<ParcelableWrapper> parcelableWrappers,
- Collection<FutureWrapper> futureWrappers,
- Collection<ExecutableElement> methods) {
+ Context context,
+ Collection<ParcelableWrapper> parcelableWrappers,
+ Collection<FutureWrapper> futureWrappers,
+ Collection<ExecutableElement> methods) {
Map<String, Type> usableTypes = new HashMap<>();
- addDefaultTypes(types, elements, usableTypes);
+ addDefaultTypes(context, usableTypes);
addParcelableWrapperTypes(usableTypes, parcelableWrappers);
addFutureWrapperTypes(usableTypes, futureWrappers);
- addSupportForUsedTypes(types, elements, usableTypes, methods);
+ addSupportForUsedTypes(context, usableTypes, methods);
return new SupportedTypes(usableTypes);
}
private static void addSupportForUsedTypes(
- Types types,
- Elements elements,
- Map<String, Type> usableTypes,
- Collection<ExecutableElement> methods) {
+ Context context, Map<String, Type> usableTypes, Collection<ExecutableElement> methods) {
for (ExecutableElement method : methods) {
- addSupportForUsedType(types, elements, usableTypes, method.getReturnType());
+ addSupportForUsedType(context, usableTypes, method.getReturnType());
for (VariableElement parameter : method.getParameters()) {
- addSupportForUsedType(types, elements, usableTypes, parameter.asType());
+ addSupportForUsedType(context, usableTypes, parameter.asType());
}
}
}
private static void addSupportForUsedType(
- Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
+ Context context, Map<String, Type> usableTypes, TypeMirror type) {
if (TypeUtils.isArray(type)) {
- addSupportForUsedType(types, elements, usableTypes, TypeUtils.extractTypeFromArray(type));
- if (!TypeUtils.extractTypeFromArray(type).getKind().isPrimitive()) {
- type = types.getArrayType(elements.getTypeElement("java.lang.Object").asType());
+ if (!TypeUtils.isPrimitiveArray(type)) {
+ addSupportForUsedType(context, usableTypes, TypeUtils.extractTypeFromArray(type));
+ type =
+ context
+ .types()
+ .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
}
}
-
if (TypeUtils.isGeneric(type)) {
- addSupportForGenericUsedType(types, elements, usableTypes, type);
+ addSupportForGenericUsedType(context, usableTypes, type);
return;
}
- Optional<Type> optionalSupportedType = getSupportedType(types, elements, usableTypes, type);
+ Optional<Type> optionalSupportedType = getSupportedType(context, usableTypes, type);
if (!optionalSupportedType.isPresent()) {
// The type isn't supported
return;
@@ -335,8 +355,8 @@
// 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()) {
- addSupportForUsedType(types, elements, usableTypes, typeMirror);
+ supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
+ addSupportForUsedType(context, usableTypes, typeMirror);
}
}
@@ -344,11 +364,10 @@
}
private static void addSupportForGenericUsedType(
- Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
+ Context context, Map<String, Type> usableTypes, TypeMirror type) {
TypeMirror genericType = TypeUtils.removeTypeArguments(type);
- Optional<Type> optionalSupportedType =
- getSupportedType(types, elements, usableTypes, genericType);
+ Optional<Type> optionalSupportedType = getSupportedType(context, usableTypes, genericType);
if (!optionalSupportedType.isPresent()) {
// The base type isn't supported
return;
@@ -360,13 +379,15 @@
if (!supportedType.isSupportedWithAnyGenericType()) {
for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(type)) {
- addSupportForUsedType(types, elements, usableTypes, typeArgument);
+ addSupportForUsedType(context, usableTypes, typeArgument);
}
}
}
private static Optional<Type> getSupportedType(
- Types types, Elements elements, Map<String, Type> usableTypes, TypeMirror type) {
+ Context context, Map<String, Type> usableTypes, TypeMirror type) {
+ Elements elements = context.elements();
+ Types types = context.types();
if (usableTypes.containsKey(type.toString())) {
return Optional.of(usableTypes.get(type.toString()));
}
@@ -393,37 +414,41 @@
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)
+ .setPutIntoBundleCode("$L.putParcelable($L, $L)")
+ .setGetFromBundleCode("$L.getParcelable($L)")
+ .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)
+ .setPutIntoBundleCode("$L.putSerializable($L, $L)")
+ .setGetFromBundleCode("$L.getSerializable($L)")
+ .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. */
@@ -431,194 +456,326 @@
return new Builder(usableTypes);
}
- private static void addDefaultTypes(
- Types types, Elements elements, Map<String, Type> usableTypes) {
+ private static void addDefaultTypes(Context context, Map<String, Type> usableTypes) {
+ Elements elements = context.elements();
+ Types types = context.types();
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(types.getNoType(TypeKind.VOID))
- .setAcceptableReturnType(true)
- .setReadFromParcelCode("null")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getNoType(TypeKind.VOID))
+ .setAcceptableReturnType(true)
+ .setGetFromBundleCode("null")
+ .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)
+ .setGetFromBundleCode("null")
+ .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)
+ .setPutIntoBundleCode("$L.putString($L, $L)")
+ .setGetFromBundleCode("$L.getString($L)")
+ .setWriteToParcelCode("$L.writeString($L)")
+ .setReadFromParcelCode("$L.readString()")
+ .build());
addUsableType(
- usableTypes,
- Type.builder()
- .setTypeMirror(elements.getTypeElement("java.lang.CharSequence").asType())
- .setAcceptableReturnType(true)
- .setAcceptableParameterType(true)
- .setWriteToParcelCode("$L.writeString(String.valueOf($L))")
- .setReadFromParcelCode("$L.readString()")
- .build());
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(elements.getTypeElement("java.lang.CharSequence").asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putString($L, String.valueOf($L))")
+ .setGetFromBundleCode("$L.getString($L)")
+ .setWriteToParcelCode("$L.writeString(String.valueOf($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(types.getPrimitiveType(TypeKind.BYTE))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putByte($L, $L)")
+ .setGetFromBundleCode("$L.getByte($L)")
+ .setWriteToParcelCode("$L.writeByte($L)")
+ .setReadFromParcelCode("$L.readByte()")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.BYTE)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putByte($L, $L)")
+ .setGetFromBundleCode("$L.getByte($L)")
+ .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.getPrimitiveType(TypeKind.SHORT))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putShort($L, $L)")
+ .setGetFromBundleCode("$L.getShort($L)")
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("(short)$L.readInt()")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.SHORT)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putShort($L, $L)")
+ .setGetFromBundleCode("$L.getShort($L)")
+ .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.getPrimitiveType(TypeKind.INT))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putInt($L, $L)")
+ .setGetFromBundleCode("$L.getInt($L)")
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("$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.boxedClass(types.getPrimitiveType(TypeKind.INT)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putInt($L, $L)")
+ .setGetFromBundleCode("$L.getInt($L)")
+ .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.getPrimitiveType(TypeKind.LONG))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putLong($L, $L)")
+ .setGetFromBundleCode("$L.getLong($L)")
+ .setWriteToParcelCode("$L.writeLong($L)")
+ .setReadFromParcelCode("$L.readLong()")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.LONG)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putLong($L, $L)")
+ .setGetFromBundleCode("$L.getLong($L)")
+ .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.getPrimitiveType(TypeKind.FLOAT))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putFloat($L, $L)")
+ .setGetFromBundleCode("$L.getFloat($L)")
+ .setWriteToParcelCode("$L.writeFloat($L)")
+ .setReadFromParcelCode("$L.readFloat()")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.FLOAT)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putFloat($L, $L)")
+ .setGetFromBundleCode("$L.getFloat($L)")
+ .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.getPrimitiveType(TypeKind.DOUBLE))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putDouble($L, $L)")
+ .setGetFromBundleCode("$L.getDouble($L)")
+ .setWriteToParcelCode("$L.writeDouble($L)")
+ .setReadFromParcelCode("$L.readDouble()")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.DOUBLE)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putDouble($L, $L)")
+ .setGetFromBundleCode("$L.getDouble($L)")
+ .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.getPrimitiveType(TypeKind.CHAR))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putChar($L, $L)")
+ .setGetFromBundleCode("$L.getChar($L)")
+ .setWriteToParcelCode("$L.writeInt($L)")
+ .setReadFromParcelCode("(char)$L.readInt()")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.CHAR)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putChar($L, $L)")
+ .setGetFromBundleCode("$L.getChar($L)")
+ .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.getPrimitiveType(TypeKind.BOOLEAN))
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putBoolean($L, $L)")
+ .setGetFromBundleCode("$L.getBoolean($L)")
+ .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
+ .setReadFromParcelCode("($L.readInt() == 1)")
+ .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.boxedClass(types.getPrimitiveType(TypeKind.BOOLEAN)).asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putBoolean($L, $L)")
+ .setGetFromBundleCode("$L.getBoolean($L)")
+ .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(elements.getTypeElement("android.content.Context").asType())
+ .setAcceptableParameterType(true)
+ .setAutomaticallyResolvedReplacement("context")
+ .setAcceptableReturnType(false)
+ .setSupportedInsideWrapper(false)
+ .setSupportedInsideCrossProfileCallback(false)
+ .build());
+
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(elements.getTypeElement("android.os.Parcelable").asType())
+ .setAcceptableReturnType(true)
+ .setAcceptableParameterType(true)
+ .setPutIntoBundleCode("$L.putParcelable($L, $L)")
+ .setGetFromBundleCode("$L.getParcelable($L)")
+ .setWriteToParcelCode("$L.writeParcelable($L, flags)")
+ .setReadFromParcelCode("$L.readParcelable(Bundler.class.getClassLoader())")
+ .build());
+
+ //region **** Primitive Array Types ****
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.BOOLEAN)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putBooleanArray($L, $L)")
+ .setGetFromBundleCode("$L.getBooleanArray($L)")
+ .setWriteToParcelCode("$L.writeBooleanArray($L)")
+ .setReadFromParcelCode("$L.createBooleanArray()")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.BYTE)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putByteArray($L, $L)")
+ .setGetFromBundleCode("$L.getByteArray($L)")
+ .setWriteToParcelCode("$L.writeByteArray($L)")
+ .setReadFromParcelCode("$L.createByteArray()")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.CHAR)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putCharArray($L, $L)")
+ .setGetFromBundleCode("$L.getCharArray($L)")
+ .setWriteToParcelCode("$L.writeCharArray($L)")
+ .setReadFromParcelCode("$L.createCharArray()")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.DOUBLE)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putDoubleArray($L, $L)")
+ .setGetFromBundleCode("$L.getDoubleArray($L)")
+ .setWriteToParcelCode("$L.writeDoubleArray($L)")
+ .setReadFromParcelCode("$L.createDoubleArray()")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.FLOAT)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putFloatArray($L, $L)")
+ .setGetFromBundleCode("$L.getFloatArray($L)")
+ .setWriteToParcelCode("$L.writeFloatArray($L)")
+ .setReadFromParcelCode("$L.createFloatArray()")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.INT)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putIntArray($L, $L)")
+ .setGetFromBundleCode("$L.getIntArray($L)")
+ .setWriteToParcelCode("$L.writeIntArray($L)")
+ .setReadFromParcelCode("$L.createIntArray()")
+ .build());
+ addUsableType(
+ usableTypes,
+ Type.builder()
+ .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.LONG)))
+ .setAcceptableParameterType(true)
+ .setAcceptableReturnType(true)
+ .setPutIntoBundleCode("$L.putLongArray($L, $L)")
+ .setGetFromBundleCode("$L.getLongArray($L)")
+ .setWriteToParcelCode("$L.writeLongArray($L)")
+ .setReadFromParcelCode("$L.createLongArray()")
+ .build());
+ //endregion **** Primitive Array Types ****
+
}
private static void addUsableType(Map<String, Type> usableTypes, Type type) {
@@ -626,49 +783,52 @@
}
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)
+ .setPutIntoBundleCode("$L.putParcelable($L, " + createParcelableCode + ")")
+ .setGetFromBundleCode(
+ "((" + parcelableWrapper.wrapperClassName() + ") $L.getParcelable($L)).get()")
+ .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 {
@@ -680,8 +840,7 @@
}
/** Filtering to only include used types. */
- public Builder filterUsed(
- ValidatorContext context, Collection<CrossProfileMethodInfo> methods) {
+ public Builder filterUsed(Context context, Collection<CrossProfileMethodInfo> methods) {
Map<String, Type> usedTypes = new HashMap<>();
@@ -695,27 +854,27 @@
}
private void copySupportedTypesForMethod(
- ValidatorContext context, Map<String, Type> usedTypes, CrossProfileMethodInfo method) {
+ Context context, Map<String, Type> usedTypes, CrossProfileMethodInfo method) {
copySupportedType(context, usedTypes, method.returnType());
for (TypeMirror argumentType : method.parameterTypes()) {
copySupportedType(context, usedTypes, argumentType);
}
}
- private void copySupportedType(
- ValidatorContext context, Map<String, Type> usedTypes, TypeMirror type) {
+ private void copySupportedType(Context context, Map<String, Type> usedTypes, TypeMirror type) {
if (TypeUtils.isGeneric(type)) {
copySupportedGenericType(context, usedTypes, type);
return;
}
if (TypeUtils.isArray(type)) {
- copySupportedType(context, usedTypes, TypeUtils.extractTypeFromArray(type));
- if (!TypeUtils.extractTypeFromArray(type).getKind().isPrimitive()) {
+ if (!TypeUtils.isPrimitiveArray(type)) {
+ // Primitive arrays aren't resolved recursively
+ copySupportedType(context, usedTypes, TypeUtils.extractTypeFromArray(type));
type =
- context
- .types()
- .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
+ context
+ .types()
+ .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
}
}
@@ -726,7 +885,7 @@
// 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);
}
}
@@ -739,7 +898,7 @@
}
private void copySupportedGenericType(
- ValidatorContext context, Map<String, Type> usedTypes, TypeMirror type) {
+ Context 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
@@ -797,7 +956,7 @@
}
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)) {
@@ -807,16 +966,16 @@
}
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)) {
@@ -826,17 +985,17 @@
}
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/TestCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestCodeGenerator.java
index 77483f5..e4f229a 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestCodeGenerator.java
@@ -17,11 +17,11 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.android.enterprise.connectedapps.processor.containers.ConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileConfigurationInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTestInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTypeInfo;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
-import com.google.android.enterprise.connectedapps.processor.containers.ProfileConnectorInfo;
import com.google.android.enterprise.connectedapps.processor.containers.ProviderClassInfo;
import java.util.HashSet;
import java.util.Set;
@@ -37,8 +37,10 @@
final class TestCodeGenerator {
private boolean generated = false;
private final GeneratorContext generatorContext;
- private final Set<CrossProfileTypeInfo> fakedTypes = new HashSet<>();
- private final Set<ProfileConnectorInfo> fakedConnectors = new HashSet<>();
+ private final Set<ConnectorInfo> fakedConnectors = new HashSet<>();
+ private final Set<CrossProfileTypeInfo> allFakedTypes = new HashSet<>();
+ private final Set<CrossProfileTypeInfo> crossProfileFakedTypes = new HashSet<>();
+ private final Set<CrossProfileTypeInfo> crossUserFakedTypes = new HashSet<>();
TestCodeGenerator(GeneratorContext generatorContext) {
this.generatorContext = checkNotNull(generatorContext);
@@ -55,14 +57,29 @@
}
private void generateFakes() {
- for (ProfileConnectorInfo connector : fakedConnectors) {
- new FakeProfileConnectorGenerator(generatorContext, connector).generate();
+ for (ConnectorInfo connectorInfo : fakedConnectors) {
+ if (connectorInfo.hasCrossProfileConnector()) {
+ new FakeProfileConnectorGenerator(generatorContext, connectorInfo.profileConnector().get())
+ .generate();
+ }
+
+ if (connectorInfo.hasCrossUserConnector()) {
+ new FakeUserConnectorGenerator(generatorContext, connectorInfo.userConnector().get())
+ .generate();
+ }
}
- for (CrossProfileTypeInfo type : fakedTypes) {
- new FakeCrossProfileTypeGenerator(generatorContext, type).generate();
+ for (CrossProfileTypeInfo type : allFakedTypes) {
new FakeOtherGenerator(generatorContext, type).generate();
}
+
+ for (CrossProfileTypeInfo type : crossProfileFakedTypes) {
+ new FakeCrossProfileTypeGenerator(generatorContext, type).generate();
+ }
+
+ for (CrossProfileTypeInfo type : crossUserFakedTypes) {
+ new FakeCrossUserTypeGenerator(generatorContext, type).generate();
+ }
}
private void collectTestTypes() {
@@ -78,20 +95,28 @@
}
private void collectTestTypes(CrossProfileConfigurationInfo configuration) {
- for (ProviderClassInfo provider : configuration.providers()) {
- collectTestTypes(provider);
+ if (configuration.connectorInfo().hasCrossProfileConnector()) {
+ for (ProviderClassInfo provider : configuration.providers()) {
+ collectTestTypes(crossProfileFakedTypes, provider);
+ }
+ }
+ if (configuration.connectorInfo().hasCrossUserConnector()) {
+ for (ProviderClassInfo provider : configuration.providers()) {
+ collectTestTypes(crossUserFakedTypes, provider);
+ }
}
- fakedConnectors.add(configuration.profileConnector());
+ fakedConnectors.add(configuration.connectorInfo());
}
- private void collectTestTypes(ProviderClassInfo provider) {
+ private void collectTestTypes(Set<CrossProfileTypeInfo> targetSet, ProviderClassInfo provider) {
for (CrossProfileTypeInfo type : provider.allCrossProfileTypes()) {
- collectTestTypes(type);
+ collectTestTypes(targetSet, type);
}
}
- private void collectTestTypes(CrossProfileTypeInfo type) {
- fakedTypes.add(type);
+ private void collectTestTypes(Set<CrossProfileTypeInfo> targetSet, CrossProfileTypeInfo type) {
+ allFakedTypes.add(type);
+ targetSet.add(type);
}
}
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 ffa68c9..8ec5269 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
@@ -92,28 +92,37 @@
return CodeBlock.of("$T.of($S)", BUNDLER_TYPE_CLASSNAME, getRawTypeQualifiedName(type));
}
+ static TypeMirror getArrayRootType(TypeMirror type) {
+ while (TypeUtils.isArray(type)) {
+ type = TypeUtils.extractTypeFromArray(type);
+ }
+
+ return type;
+ }
+
+ static boolean isPrimitiveArray(TypeMirror type) {
+ return getArrayRootType(type).getKind().isPrimitive();
+ }
+
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() + "[]");
+ if (isPrimitiveArray(arrayType)) {
+ return CodeBlock.of("$T.of($S)", BUNDLER_TYPE_CLASSNAME, type);
}
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())) {
@@ -121,7 +130,7 @@
}
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/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/UserConnectorCodeGenerator.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/UserConnectorCodeGenerator.java
index 415765b..646f1c7 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/UserConnectorCodeGenerator.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/UserConnectorCodeGenerator.java
@@ -15,17 +15,21 @@
*/
package com.google.android.enterprise.connectedapps.processor;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.getBuilderClassName;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.prepend;
+import static com.google.android.enterprise.connectedapps.processor.ClassNameUtilities.transformClassName;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_USER_CONNECTOR_BUILDER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.ABSTRACT_USER_CONNECTOR_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.AVAILABILITY_RESTRICTIONS_CLASSNAME;
-import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONNECTION_BINDER_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.CONTEXT_CLASSNAME;
import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.SCHEDULED_EXECUTOR_SERVICE_CLASSNAME;
+import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.USER_BINDER_FACTORY_CLASSNAME;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.android.enterprise.connectedapps.annotations.GeneratedUserConnector;
import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
import com.google.android.enterprise.connectedapps.processor.containers.UserConnectorInfo;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
@@ -56,7 +60,7 @@
void generate() {
if (generated) {
throw new IllegalStateException(
- "ProfileConnectorCodeGenerator#generate can only be called once");
+ "UserConnectorCodeGenerator#generate can only be called once");
}
generated = true;
@@ -92,7 +96,7 @@
.addModifiers(Modifier.PRIVATE)
.addParameter(builderClassName, "builder")
.addStatement(
- "super($1T.class, builder.profileConnectorBuilder)", connector.connectorClassName())
+ "super($1T.class, builder.userConnectorBuilder)", connector.connectorClassName())
.build());
generateUserConnectorBuilder(classBuilder);
@@ -100,7 +104,7 @@
generatorUtilities.writeClassToFile(className.packageName(), classBuilder);
}
- private void generateUserConnectorBuilder(TypeSpec.Builder profileConnector) {
+ private void generateUserConnectorBuilder(TypeSpec.Builder userConnector) {
ClassName connectorClassName = getGeneratedUserConnectorClassName(generatorContext, connector);
ClassName builderClassName =
getGeneratedUserConnectorBuilderClassName(generatorContext, connector);
@@ -121,31 +125,33 @@
classBuilder.addMethod(
MethodSpec.constructorBuilder()
.addParameter(CONTEXT_CLASSNAME, "context")
- .addStatement("profileConnectorBuilder.setContext(context)")
+ .addStatement("userConnectorBuilder.setContext(context)")
.build());
classBuilder.addField(
- FieldSpec.builder(ABSTRACT_USER_CONNECTOR_BUILDER_CLASSNAME, "profileConnectorBuilder")
+ FieldSpec.builder(ABSTRACT_USER_CONNECTOR_BUILDER_CLASSNAME, "userConnectorBuilder")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer(initialiser)
.build());
classBuilder.addMethod(
- MethodSpec.methodBuilder("setScheduledExecutorService")
+ MethodSpec.methodBuilder("setBinderFactory")
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
- .addParameter(SCHEDULED_EXECUTOR_SERVICE_CLASSNAME, "scheduledExecutorService")
+ .addParameter(USER_BINDER_FACTORY_CLASSNAME, "binderFactory")
.returns(builderClassName)
- .addStatement(
- "profileConnectorBuilder.setScheduledExecutorService(scheduledExecutorService)")
+ .addStatement("userConnectorBuilder.setBinderFactory(binderFactory)")
.addStatement("return this")
.build());
classBuilder.addMethod(
- MethodSpec.methodBuilder("setBinder")
+ MethodSpec.methodBuilder("setScheduledExecutorService")
+ .addAnnotation(CanIgnoreReturnValue.class)
.addModifiers(Modifier.PUBLIC)
- .addParameter(CONNECTION_BINDER_CLASSNAME, "binder")
+ .addParameter(SCHEDULED_EXECUTOR_SERVICE_CLASSNAME, "scheduledExecutorService")
.returns(builderClassName)
- .addStatement("profileConnectorBuilder.setBinder(binder)")
+ .addStatement(
+ "userConnectorBuilder.setScheduledExecutorService(scheduledExecutorService)")
.addStatement("return this")
.build());
@@ -156,23 +162,16 @@
.addStatement("return new $1T(this)", connectorClassName)
.build());
- profileConnector.addType(classBuilder.build());
+ userConnector.addType(classBuilder.build());
}
static ClassName getGeneratedUserConnectorClassName(
GeneratorContext generatorContext, UserConnectorInfo connector) {
- return ClassName.get(
- connector.connectorClassName().packageName(),
- "Generated" + connector.connectorClassName().simpleName());
+ return transformClassName(connector.connectorClassName(), prepend("Generated"));
}
static ClassName getGeneratedUserConnectorBuilderClassName(
GeneratorContext generatorContext, UserConnectorInfo connector) {
- return ClassName.get(
- connector.connectorClassName().packageName()
- + "."
- + "Generated"
- + connector.connectorClassName().simpleName(),
- "Builder");
+ return getBuilderClassName(getGeneratedUserConnectorClassName(generatorContext, connector));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationFinder.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationFinder.java
index 4923567..e051f53 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationFinder.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationFinder.java
@@ -29,6 +29,7 @@
import com.google.android.enterprise.connectedapps.annotations.CrossUserConfigurations;
import com.google.android.enterprise.connectedapps.annotations.CrossUserProvider;
import com.google.android.enterprise.connectedapps.processor.ValidationMessageFormatter;
+import com.google.android.enterprise.connectedapps.processor.containers.Context;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileAnnotationInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackAnnotationInfo;
import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileConfigurationAnnotationInfo;
@@ -46,8 +47,6 @@
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
/** Helper methods to discover all cross-profile annotations of a specific type on elements. */
public final class AnnotationFinder {
@@ -164,41 +163,39 @@
}
public static CrossProfileAnnotationInfo extractCrossProfileAnnotationInfo(
- Element annotatedElement, Types types, Elements elements) {
+ Context context, Element annotatedElement) {
return new CrossProfileAnnotationInfoExtractor()
- .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, annotatedElement, types, elements);
+ .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
}
public static CrossProfileCallbackAnnotationInfo extractCrossProfileCallbackAnnotationInfo(
- Element annotatedElement, Types types, Elements elements) {
+ Context context, Element annotatedElement) {
return new CrossProfileCallbackAnnotationInfoExtractor()
- .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, annotatedElement, types, elements);
+ .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
}
public static CrossProfileConfigurationAnnotationInfo
- extractCrossProfileConfigurationAnnotationInfo(
- Element annotatedElement, Types types, Elements elements) {
+ extractCrossProfileConfigurationAnnotationInfo(Context context, Element annotatedElement) {
return new CrossProfileConfigurationAnnotationInfoExtractor()
- .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, annotatedElement, types, elements);
+ .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
}
public static CrossProfileConfigurationsAnnotationInfo
- extractCrossProfileConfigurationsAnnotationInfo(
- Element annotatedElement, Types types, Elements elements) {
+ extractCrossProfileConfigurationsAnnotationInfo(Context context, Element annotatedElement) {
return new CrossProfileConfigurationsAnnotationInfoExtractor()
- .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, annotatedElement, types, elements);
+ .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
}
public static CrossProfileProviderAnnotationInfo extractCrossProfileProviderAnnotationInfo(
- Element annotatedElement, Types types, Elements elements) {
+ Context context, Element annotatedElement) {
return new CrossProfileProviderAnnotationInfoExtractor()
- .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, annotatedElement, types, elements);
+ .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
}
public static CrossProfileTestAnnotationInfo extractCrossProfileTestAnnotationInfo(
- Element annotatedElement, Types types, Elements elements) {
+ Context context, Element annotatedElement) {
return new CrossProfileTestAnnotationInfoExtractor()
- .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, annotatedElement, types, elements);
+ .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
}
public static boolean hasCrossProfileAnnotation(Element element) {
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationInfoExtractor.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationInfoExtractor.java
index 08022c4..06fe9a5 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationInfoExtractor.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/AnnotationInfoExtractor.java
@@ -18,6 +18,7 @@
import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
import com.google.android.enterprise.connectedapps.annotations.CrossUserProvider;
import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileProviderAnnotation;
+import com.google.android.enterprise.connectedapps.processor.containers.Context;
import java.lang.annotation.Annotation;
import java.lang.reflect.Proxy;
import javax.lang.model.element.Element;
@@ -42,20 +43,19 @@
*/
AnnotationInfoT extractAnnotationInfo(
Iterable<? extends AnnotationClasses> availableAnnotations,
- Element annotatedElement,
- Types types,
- Elements elements) {
+ Context context,
+ Element annotatedElement) {
for (AnnotationClasses annotationClasses : availableAnnotations) {
Annotation annotation =
annotatedElement.getAnnotation(supportedAnnotationClass(annotationClasses));
if (annotation != null) {
return annotationInfoFromAnnotation(
- wrapAnnotationWithInterface(annotationInterfaceClass, annotation), types);
+ wrapAnnotationWithInterface(annotationInterfaceClass, annotation), context.types());
}
}
- return emptyAnnotationInfo(elements);
+ return emptyAnnotationInfo(context.elements());
}
/**
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/CrossProfileAnnotationInfoExtractor.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/CrossProfileAnnotationInfoExtractor.java
index 49e737d..7403ddb 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/CrossProfileAnnotationInfoExtractor.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/CrossProfileAnnotationInfoExtractor.java
@@ -43,7 +43,6 @@
CrossProfileAnnotationInfo.builder()
.setConnectorClass(
GeneratorUtilities.extractClassFromAnnotation(types, annotation::connector))
- .setProfileClassName(annotation.profileClassName())
.setParcelableWrapperClasses(
ImmutableSet.copyOf(
GeneratorUtilities.extractClassesFromAnnotation(
@@ -54,12 +53,6 @@
types, annotation::futureWrappers)))
.setIsStatic(annotation.isStatic());
- long timeoutMillis = annotation.timeoutMillis();
-
- if (timeoutMillis != CrossProfileAnnotation.TIMEOUT_MILLIS_NOT_SET) {
- builder.setTimeoutMillis(timeoutMillis);
- }
-
return builder.build();
}
@@ -69,7 +62,6 @@
.setConnectorClass(
elements.getTypeElement(
"com.google.android.enterprise.connectedapps.annotations.CrossProfile"))
- .setProfileClassName("")
.setParcelableWrapperClasses(ImmutableSet.of())
.setFutureWrapperClasses(ImmutableSet.of())
.setIsStatic(false)
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/interfaces/CrossProfileAnnotation.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/interfaces/CrossProfileAnnotation.java
index 8e76d9c..0d3226e 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/interfaces/CrossProfileAnnotation.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/annotationdiscovery/interfaces/CrossProfileAnnotation.java
@@ -17,13 +17,6 @@
/** Elements that can be populated on annotations of type CrossProfile. */
public interface CrossProfileAnnotation {
-
- long DEFAULT_TIMEOUT_MILLIS = 10000;
-
- long TIMEOUT_MILLIS_NOT_SET = -1;
-
- String profileClassName();
-
Class<?> connector();
Class<?>[] parcelableWrappers();
@@ -31,6 +24,4 @@
Class<?>[] futureWrappers();
boolean isStatic();
-
- long timeoutMillis();
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ConnectorInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ConnectorInfo.java
new file mode 100644
index 0000000..df1ce5e
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ConnectorInfo.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor.containers;
+
+import com.google.android.enterprise.connectedapps.annotations.UncaughtExceptionsPolicy;
+import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
+import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
+import java.util.Optional;
+import java.util.function.Function;
+import javax.lang.model.element.TypeElement;
+
+/** Wrapper of the connectors specified for a connected app. */
+@AutoValue
+public abstract class ConnectorInfo {
+
+ private static final String CROSS_PROFILE_CONNECTOR_QUALIFIED_NAME =
+ "com.google.android.enterprise.connectedapps.CrossProfileConnector";
+ private static final String CROSS_USER_CONNECTOR_QUALIFIED_NAME =
+ "com.google.android.enterprise.connectedapps.CrossUserConnector";
+ private static final String PROFILE_CONNECTOR_QUALIFIED_NAME =
+ "com.google.android.enterprise.connectedapps.ProfileConnector";
+ private static final String USER_CONNECTOR_QUALIFIED_NAME =
+ "com.google.android.enterprise.connectedapps.UserConnector";
+
+ public static boolean isProfileConnector(Context context, TypeElement connectorElement) {
+ return isConnectorOfType(context, connectorElement, PROFILE_CONNECTOR_QUALIFIED_NAME);
+ }
+
+ public static boolean isUserConnector(Context context, TypeElement connectorElement) {
+ return isConnectorOfType(context, connectorElement, USER_CONNECTOR_QUALIFIED_NAME);
+ }
+
+ private static boolean isConnectorOfType(
+ Context context, TypeElement connectorElement, String requiredType) {
+ return context
+ .types()
+ .isAssignable(
+ connectorElement.asType(), context.elements().getTypeElement(requiredType).asType());
+ }
+
+ public boolean hasCrossProfileConnector() {
+ return profileConnector().isPresent();
+ }
+
+ public boolean hasCrossUserConnector() {
+ return userConnector().isPresent();
+ }
+
+ public abstract Optional<ProfileConnectorInfo> profileConnector();
+
+ public abstract Optional<UserConnectorInfo> userConnector();
+
+ public TypeElement connectorElement() {
+ return getElement(ProfileConnectorInfo::connectorElement, UserConnectorInfo::connectorElement);
+ }
+
+ public ClassName connectorClassName() {
+ return getElement(
+ ProfileConnectorInfo::connectorClassName, UserConnectorInfo::connectorClassName);
+ }
+
+ public ClassName serviceName() {
+ return getElement(ProfileConnectorInfo::serviceName, UserConnectorInfo::serviceName);
+ }
+
+ public SupportedTypes supportedTypes() {
+ return getElement(ProfileConnectorInfo::supportedTypes, UserConnectorInfo::supportedTypes);
+ }
+
+ public UncaughtExceptionsPolicy uncaughtExceptionsPolicy() {
+ return profileConnector()
+ .map(ProfileConnectorInfo::uncaughtExceptionsPolicy)
+ .orElse(UncaughtExceptionsPolicy.NOTIFY_RETHROW);
+ }
+
+ /**
+ * Tries to get an element from {@link #profileConnector()} if present, or from {@link
+ * #userConnector()} otherwise.
+ *
+ * <p>Throws an exception if no connectors are specified, but this should not be possible (now and
+ * in the future).
+ */
+ private <T> T getElement(
+ Function<ProfileConnectorInfo, T> getFromProfileConnector,
+ Function<UserConnectorInfo, T> getFromUserConnector) {
+ return profileConnector()
+ .map(getFromProfileConnector)
+ .orElseGet(
+ () ->
+ userConnector()
+ .map(getFromUserConnector)
+ .orElseThrow(
+ () -> new UnsupportedOperationException("No connectors specified")));
+ }
+
+ public static ConnectorInfo invalid(
+ Context context, TypeElement connector, SupportedTypes globalSupportedTypes) {
+ return noSpecificConnector(context, globalSupportedTypes, connector, connector);
+ }
+
+ public static ConnectorInfo unspecified(Context context, SupportedTypes globalSupportedTypes) {
+ return noSpecificConnector(
+ context,
+ globalSupportedTypes,
+ context.elements().getTypeElement(CROSS_PROFILE_CONNECTOR_QUALIFIED_NAME),
+ context.elements().getTypeElement(CROSS_USER_CONNECTOR_QUALIFIED_NAME));
+ }
+
+ private static ConnectorInfo noSpecificConnector(
+ Context context,
+ SupportedTypes globalSupportedTypes,
+ TypeElement profileConnector,
+ TypeElement userConnector) {
+ return new AutoValue_ConnectorInfo(
+ Optional.of(ProfileConnectorInfo.create(context, profileConnector, globalSupportedTypes)),
+ Optional.of(UserConnectorInfo.create(context, userConnector, globalSupportedTypes)));
+ }
+
+ public static ConnectorInfo forProfileConnector(
+ Context context, TypeElement connectorElement, SupportedTypes globalSupportedTypes) {
+ return new AutoValue_ConnectorInfo(
+ Optional.of(ProfileConnectorInfo.create(context, connectorElement, globalSupportedTypes)),
+ Optional.empty());
+ }
+
+ public static ConnectorInfo forUserConnector(
+ Context context, TypeElement connectorElement, SupportedTypes globalSupportedTypes) {
+ return new AutoValue_ConnectorInfo(
+ Optional.empty(),
+ Optional.of(UserConnectorInfo.create(context, connectorElement, globalSupportedTypes)));
+ }
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileAnnotationInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileAnnotationInfo.java
index f083a69..c75cfc2 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileAnnotationInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileAnnotationInfo.java
@@ -18,7 +18,6 @@
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
-import java.util.Optional;
import javax.lang.model.element.TypeElement;
/** Wrapper around information contained in an annotation of type {@link CrossProfile}. */
@@ -30,10 +29,6 @@
public abstract TypeElement connectorClass();
- public abstract String profileClassName();
-
- public abstract Optional<Long> timeoutMillis();
-
public abstract ImmutableCollection<TypeElement> parcelableWrapperClasses();
public abstract ImmutableCollection<TypeElement> futureWrapperClasses();
@@ -44,10 +39,6 @@
return connectorClass().asType().toString().equals(DEFAULT_CONNECTOR_NAME);
}
- public boolean isProfileClassNameDefault() {
- return profileClassName().isEmpty();
- }
-
public static Builder builder() {
return new AutoValue_CrossProfileAnnotationInfo.Builder();
}
@@ -57,10 +48,6 @@
public abstract Builder setConnectorClass(TypeElement value);
- public abstract Builder setProfileClassName(String value);
-
- public abstract Builder setTimeoutMillis(Long value);
-
public abstract Builder setParcelableWrapperClasses(ImmutableCollection<TypeElement> value);
public abstract Builder setFutureWrapperClasses(ImmutableCollection<TypeElement> value);
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileCallbackParameterInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileCallbackParameterInfo.java
new file mode 100644
index 0000000..4a3279a
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileCallbackParameterInfo.java
@@ -0,0 +1,27 @@
+package com.google.android.enterprise.connectedapps.processor.containers;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossProfileCallback;
+import com.google.auto.value.AutoValue;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.VariableElement;
+
+/** Wrapper of a {@link CrossProfileCallback} parameter on a method. */
+@AutoValue
+public abstract class CrossProfileCallbackParameterInfo {
+
+ public abstract CrossProfileCallbackInterfaceInfo crossProfileCallbackInterface();
+
+ public abstract VariableElement variable();
+
+ public Name getSimpleName() {
+ return variable().getSimpleName();
+ }
+
+ public static CrossProfileCallbackParameterInfo create(
+ Context context, VariableElement variableElement) {
+ return new AutoValue_CrossProfileCallbackParameterInfo(
+ CrossProfileCallbackInterfaceInfo.create(
+ context.elements().getTypeElement(variableElement.asType().toString())),
+ variableElement);
+ }
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileConfigurationInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileConfigurationInfo.java
index 2d4ed43..245a6c0 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileConfigurationInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileConfigurationInfo.java
@@ -15,30 +15,19 @@
*/
package com.google.android.enterprise.connectedapps.processor.containers;
-import static java.util.stream.Collectors.toSet;
-
import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
-import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
-import com.google.android.enterprise.connectedapps.processor.TypeUtils;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.squareup.javapoet.ClassName;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.Optional;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeMirror;
/** Wrapper of a {@link CrossProfileConfiguration} annotated class. */
@AutoValue
public abstract class CrossProfileConfigurationInfo {
- public static final String CROSS_PROFILE_CONNECTOR_QUALIFIED_NAME =
- "com.google.android.enterprise.connectedapps.CrossProfileConnector";
-
public abstract TypeElement configurationElement();
public abstract ImmutableCollection<ProviderClassInfo> providers();
@@ -55,65 +44,44 @@
return ClassName.get(configurationElement());
}
- public abstract ProfileConnectorInfo profileConnector();
+ public abstract ConnectorInfo connectorInfo();
public static CrossProfileConfigurationInfo create(
ValidatorContext context, ValidatorCrossProfileConfigurationInfo configuration) {
- Collection<ProviderClassInfo> providerClasses =
- configuration.providerClassElements().stream()
- .map(
- m ->
- ProviderClassInfo.create(
- context, ValidatorProviderClassInfo.create(context.processingEnv(), m)))
- .collect(toSet());
+ ImmutableSet.Builder<ProviderClassInfo> providerClassesBuilder = ImmutableSet.builder();
+ configuration.providerClassElements().stream()
+ .map(m -> ProviderClassInfo.create(context, ValidatorProviderClassInfo.create(context, m)))
+ .forEach(providerClassesBuilder::add);
+ ImmutableSet<ProviderClassInfo> providerClasses = providerClassesBuilder.build();
- ProfileConnectorInfo profileConnectorInfo =
+ ConnectorInfo connectorInfo =
providerClasses.stream()
.flatMap(m -> m.allCrossProfileTypes().stream())
- .map(CrossProfileTypeInfo::profileConnector)
+ .map(CrossProfileTypeInfo::connectorInfo)
.flatMap(Streams::stream)
.findFirst()
- .orElseGet(
- () ->
- ProfileConnectorInfo.create(
- context.processingEnv(),
- getConfiguredConnectorOrDefault(context, configuration),
- context.globalSupportedTypes()));
+ .orElseGet(() -> defaultConnector(context, configuration));
return new AutoValue_CrossProfileConfigurationInfo(
configuration.configurationElement(),
- ImmutableSet.copyOf(providerClasses),
+ providerClasses,
configuration.serviceSuperclass(),
configuration.serviceClass(),
- profileConnectorInfo);
+ connectorInfo);
}
- private static TypeElement getConfiguredConnectorOrDefault(
+ private static ConnectorInfo defaultConnector(
ValidatorContext context, ValidatorCrossProfileConfigurationInfo configuration) {
- return configuration
- .connector()
- .orElseGet(() -> context.elements().getTypeElement(CROSS_PROFILE_CONNECTOR_QUALIFIED_NAME));
- }
-
- private static Collection<Type> convertTypeMirrorToSupportedTypes(
- SupportedTypes supportedTypes, TypeMirror typeMirror) {
- if (TypeUtils.isGeneric(typeMirror)) {
- return convertGenericTypeMirrorToSupportedTypes(supportedTypes, typeMirror);
- }
- return Collections.singleton(supportedTypes.getType(typeMirror));
- }
-
- private static Collection<Type> convertGenericTypeMirrorToSupportedTypes(
- SupportedTypes supportedTypes, TypeMirror typeMirror) {
- Collection<Type> types = new HashSet<>();
- TypeMirror genericType = TypeUtils.removeTypeArguments(typeMirror);
- Type supportedType = supportedTypes.getType(genericType);
- if (!supportedType.isSupportedWithAnyGenericType()) {
- for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(typeMirror)) {
- types.addAll(convertTypeMirrorToSupportedTypes(supportedTypes, typeArgument));
+ if (configuration.connector().isPresent()) {
+ if (ConnectorInfo.isProfileConnector(context, configuration.connector().get())) {
+ return ConnectorInfo.forProfileConnector(
+ context, configuration.connector().get(), context.globalSupportedTypes());
+ } else if (ConnectorInfo.isUserConnector(context, configuration.connector().get())) {
+ return ConnectorInfo.forUserConnector(
+ context, configuration.connector().get(), context.globalSupportedTypes());
}
}
- types.add(supportedTypes.getType(genericType));
- return types;
+
+ return ConnectorInfo.unspecified(context, context.globalSupportedTypes());
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileMethodInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileMethodInfo.java
index 72831a9..6a90652 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileMethodInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileMethodInfo.java
@@ -15,17 +15,15 @@
*/
package com.google.android.enterprise.connectedapps.processor.containers;
-import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileAnnotation;
import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileCallbackAnnotation;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
+import com.google.android.enterprise.connectedapps.annotations.Cacheable;
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
import com.google.android.enterprise.connectedapps.annotations.CrossProfileCallback;
import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
import com.google.android.enterprise.connectedapps.processor.TypeUtils;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileAnnotation;
import com.google.auto.value.AutoValue;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
@@ -50,6 +48,8 @@
public abstract boolean isStatic();
+ public abstract boolean isCacheable();
+
public String simpleName() {
return methodElement().getSimpleName().toString();
}
@@ -75,12 +75,6 @@
}
/**
- * The number of milliseconds to timeout async calls. This is either set on the method, the type,
- * or defaults to {@link CrossProfileAnnotation#DEFAULT_TIMEOUT_MILLIS}.
- */
- public abstract long timeoutMillis();
-
- /**
* Specify behaviour when encountering parameters of a type which is automatically resolved by the
* SDK.
*/
@@ -165,13 +159,11 @@
/** True if there is only a single {@link CrossProfileCallback} argument and it is simple. */
public boolean isSimpleCrossProfileCallback(GeneratorContext generatorContext) {
- Optional<VariableElement> param = getCrossProfileCallbackParam(generatorContext);
+ Optional<CrossProfileCallbackParameterInfo> param =
+ getCrossProfileCallbackParam(generatorContext);
if (param.isPresent()) {
- CrossProfileCallbackInterfaceInfo callbackInterface =
- CrossProfileCallbackInterfaceInfo.create(
- (TypeElement) generatorContext.types().asElement(param.get().asType()));
- return callbackInterface.isSimple();
+ return param.get().crossProfileCallbackInterface().isSimple();
}
return false;
@@ -187,16 +179,18 @@
}
/** Return the {@link CrossProfileCallback} annotated parameter, if any. */
- public Optional<VariableElement> getCrossProfileCallbackParam(GeneratorContext generatorContext) {
- return getCrossProfileCallbackParam(generatorContext.elements(), methodElement());
+ public Optional<CrossProfileCallbackParameterInfo> getCrossProfileCallbackParam(
+ GeneratorContext generatorContext) {
+ return getCrossProfileCallbackParam(generatorContext, methodElement());
}
- public static Optional<VariableElement> getCrossProfileCallbackParam(
- Elements elements, ExecutableElement method) {
+ public static Optional<CrossProfileCallbackParameterInfo> getCrossProfileCallbackParam(
+ Context context, ExecutableElement method) {
return method.getParameters().stream()
- .filter(v -> isCrossProfileCallbackInterface(elements, v.asType()))
+ .filter(v -> isCrossProfileCallbackInterface(context.elements(), v.asType()))
.findFirst()
- .map(e -> (VariableElement) e);
+ .map(e -> (VariableElement) e)
+ .map(e -> CrossProfileCallbackParameterInfo.create(context, e));
}
private static boolean isCrossProfileCallbackInterface(Elements elements, TypeMirror type) {
@@ -213,19 +207,6 @@
methodElement,
identifier,
methodElement.getModifiers().contains(Modifier.STATIC),
- findTimeoutMillis(type, methodElement, context));
- }
-
- private static long findTimeoutMillis(
- ValidatorCrossProfileTypeInfo type, ExecutableElement methodElement, Context context) {
- if (hasCrossProfileAnnotation(methodElement)) {
- return AnnotationFinder.extractCrossProfileAnnotationInfo(
- methodElement, context.types(), context.elements())
- .timeoutMillis()
- .filter(timeout -> timeout > 0)
- .orElse(type.timeoutMillis());
- }
-
- return type.timeoutMillis();
+ methodElement.getAnnotation(Cacheable.class) != null);
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTestInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTestInfo.java
index a2219be..51d6c49 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTestInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTestInfo.java
@@ -36,7 +36,7 @@
Set<CrossProfileConfigurationInfo> configurations =
ValidatorCrossProfileConfigurationInfo.createMultipleFromElement(
- context.processingEnv(), validatorCrossProfileTest.configurationElement())
+ context, validatorCrossProfileTest.configurationElement())
.stream()
.map(b -> CrossProfileConfigurationInfo.create(context, b))
.collect(toSet());
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTypeInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTypeInfo.java
index fffe4a1..869eb15 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTypeInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/CrossProfileTypeInfo.java
@@ -15,14 +15,11 @@
*/
package com.google.android.enterprise.connectedapps.processor.containers;
-import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileAnnotation;
-import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Collectors.toCollection;
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
import com.google.android.enterprise.connectedapps.processor.ProcessorConfiguration;
import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
-import com.google.android.enterprise.connectedapps.processor.TypeUtils;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileAnnotation;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
@@ -30,15 +27,12 @@
import com.squareup.javapoet.ClassName;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeMirror;
/** Wrapper of a {@link CrossProfile} type. */
@AutoValue
@@ -50,15 +44,13 @@
public abstract SupportedTypes supportedTypes();
- public abstract Optional<ProfileConnectorInfo> profileConnector();
-
- public abstract ClassName profileClassName();
+ public abstract Optional<ConnectorInfo> connectorInfo();
/**
- * The specified timeout for async calls, or {@link CrossProfileAnnotation#DEFAULT_TIMEOUT_MILLIS}
- * if unspecified.
+ * The verbatim (not prefixed) name of the interface class used to make cross-profile or
+ * cross-user calls.
*/
- public abstract long timeoutMillis();
+ public abstract ClassName generatedClassName();
public String simpleName() {
return crossProfileTypeElement().getSimpleName().toString();
@@ -96,7 +88,7 @@
t ->
CrossProfileMethodInfo.create(
t, crossProfileType, crossProfileMethodElements.get(t), context))
- .collect(toSet());
+ .collect(toCollection(LinkedHashSet::new));
SupportedTypes.Builder supportedTypesBuilder = crossProfileType.supportedTypes().asBuilder();
@@ -112,60 +104,14 @@
crossProfileTypeElement,
ImmutableSet.copyOf(crossProfileMethods),
supportedTypesBuilder.build(),
- crossProfileType.profileConnector(),
- findProfileClassName(context, crossProfileTypeElement, crossProfileType),
- crossProfileType.timeoutMillis());
+ crossProfileType.connectorInfo(),
+ findGeneratedClassName(context, crossProfileTypeElement));
}
- private static ClassName findProfileClassName(
- ValidatorContext context,
- TypeElement typeElement,
- ValidatorCrossProfileTypeInfo crossProfileType) {
- return hasCrossProfileAnnotation(typeElement)
- ? findAnnotatedProfileClassName(context, typeElement, crossProfileType)
- : createDefaultProfileClassName(context, typeElement);
- }
-
- private static ClassName createDefaultProfileClassName(
+ private static ClassName findGeneratedClassName(
ValidatorContext context, TypeElement typeElement) {
- PackageElement originalPackage = context.elements().getPackageOf(typeElement);
- String profileAwareClassName =
- String.format("Profile%s", typeElement.getSimpleName().toString());
-
- return ClassName.get(originalPackage.getQualifiedName().toString(), profileAwareClassName);
- }
-
- private static ClassName findAnnotatedProfileClassName(
- ValidatorContext context,
- TypeElement typeElement,
- ValidatorCrossProfileTypeInfo crossProfileType) {
- String profileClassName = crossProfileType.profileClassName();
- if (!profileClassName.isEmpty()) {
- return ClassName.bestGuess(profileClassName);
- }
-
- return createDefaultProfileClassName(context, typeElement);
- }
-
- private static Collection<Type> convertTypeMirrorToSupportedTypes(
- SupportedTypes supportedTypes, TypeMirror typeMirror) {
- if (TypeUtils.isGeneric(typeMirror)) {
- return convertGenericTypeMirrorToSupportedTypes(supportedTypes, typeMirror);
- }
- return Collections.singleton(supportedTypes.getType(typeMirror));
- }
-
- private static Collection<Type> convertGenericTypeMirrorToSupportedTypes(
- SupportedTypes supportedTypes, TypeMirror typeMirror) {
- Collection<Type> types = new HashSet<>();
- TypeMirror genericType = TypeUtils.removeTypeArguments(typeMirror);
- Type supportedType = supportedTypes.getType(genericType);
- if (!supportedType.isSupportedWithAnyGenericType()) {
- for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(typeMirror)) {
- types.addAll(convertTypeMirrorToSupportedTypes(supportedTypes, typeArgument));
- }
- }
- types.add(supportedTypes.getType(genericType));
- return types;
+ return ClassName.get(
+ context.elements().getPackageOf(typeElement).getQualifiedName().toString(),
+ typeElement.getSimpleName().toString());
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapper.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapper.java
index 5208e7c..e5f705a 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapper.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapper.java
@@ -22,8 +22,6 @@
import java.util.Collection;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
/** Information about future wrapper. */
@AutoValue
@@ -60,18 +58,18 @@
wrappedType, defaultWrapperClassName, wrapperClassName, wrapperType);
}
- public static Collection<FutureWrapper> createGlobalFutureWrappers(Elements elements) {
+ public static Collection<FutureWrapper> createGlobalFutureWrappers(Context context) {
Collection<FutureWrapper> wrappers = new ArrayList<>();
- addDefaultFutureWrappers(elements, wrappers);
+ addDefaultFutureWrappers(context, wrappers);
return wrappers;
}
private static void addDefaultFutureWrappers(
- Elements elements, Collection<FutureWrapper> wrappers) {
+ Context context, Collection<FutureWrapper> wrappers) {
tryAddWrapper(
- elements,
+ context,
wrappers,
"com.google.common.util.concurrent.ListenableFuture",
ClassName.get(FUTURE_WRAPPER_PACKAGE, "ListenableFutureWrapper"),
@@ -79,29 +77,25 @@
}
public static Collection<FutureWrapper> createCustomFutureWrappers(
- Types types, Elements elements, Collection<TypeElement> customFutureWrappers) {
+ Context context, Collection<TypeElement> customFutureWrappers) {
Collection<FutureWrapper> wrappers = new ArrayList<>();
- addCustomFutureWrappers(types, elements, wrappers, customFutureWrappers);
+ addCustomFutureWrappers(context, wrappers, customFutureWrappers);
return wrappers;
}
private static void addCustomFutureWrappers(
- Types types,
- Elements elements,
+ Context context,
Collection<FutureWrapper> wrappers,
Collection<TypeElement> customFutureWrappers) {
for (TypeElement customFutureWrapper : customFutureWrappers) {
- addCustomFutureWrapper(types, elements, wrappers, customFutureWrapper);
+ addCustomFutureWrapper(context, wrappers, customFutureWrapper);
}
}
private static void addCustomFutureWrapper(
- Types types,
- Elements elements,
- Collection<FutureWrapper> wrappers,
- TypeElement customFutureWrapper) {
+ Context context, Collection<FutureWrapper> wrappers, TypeElement customFutureWrapper) {
CustomFutureWrapper customFutureWrapperAnnotation =
customFutureWrapper.getAnnotation(CustomFutureWrapper.class);
@@ -111,10 +105,10 @@
}
tryAddWrapper(
- elements,
+ context,
wrappers,
FutureWrapperAnnotationInfo.extractFromFutureWrapperAnnotation(
- types, customFutureWrapperAnnotation)
+ context, customFutureWrapperAnnotation)
.originalType()
.toString(),
ClassName.get(customFutureWrapper),
@@ -122,12 +116,12 @@
}
private static void tryAddWrapper(
- Elements elements,
+ Context context,
Collection<FutureWrapper> wrappers,
String typeQualifiedName,
ClassName wrapperClassName,
WrapperType wrapperType) {
- TypeElement typeElement = elements.getTypeElement(typeQualifiedName);
+ TypeElement typeElement = context.elements().getTypeElement(typeQualifiedName);
if (typeElement == null) {
// The type isn't supported at compile-time - so won't be included in this app
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapperAnnotationInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapperAnnotationInfo.java
index 18fb34b..169ac33 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapperAnnotationInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/FutureWrapperAnnotationInfo.java
@@ -19,7 +19,6 @@
import com.google.android.enterprise.connectedapps.processor.GeneratorUtilities;
import com.google.auto.value.AutoValue;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Types;
/**
* Wrapper around information contained in a {@link
@@ -31,14 +30,14 @@
public abstract TypeElement originalType();
public static FutureWrapperAnnotationInfo extractFromFutureWrapperAnnotation(
- Types types, CustomFutureWrapper customFutureWrapperAnnotation) {
+ Context context, CustomFutureWrapper customFutureWrapperAnnotation) {
if (customFutureWrapperAnnotation == null) {
throw new NullPointerException("customFutureWrapperAnnotation must not be null");
}
TypeElement originalType =
GeneratorUtilities.extractClassFromAnnotation(
- types, customFutureWrapperAnnotation::originalType);
+ context.types(), customFutureWrapperAnnotation::originalType);
return new AutoValue_FutureWrapperAnnotationInfo(originalType);
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/GeneratorContext.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/GeneratorContext.java
index ac47218..d61621c 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/GeneratorContext.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/GeneratorContext.java
@@ -55,9 +55,7 @@
.map(
t ->
ProfileConnectorInfo.create(
- validatorContext.processingEnv(),
- t,
- validatorContext.globalSupportedTypes()))
+ validatorContext, t, validatorContext.globalSupportedTypes()))
.collect(toSet());
Collection<UserConnectorInfo> generatedUserConnectors =
@@ -65,9 +63,7 @@
.map(
t ->
UserConnectorInfo.create(
- validatorContext.processingEnv(),
- t,
- validatorContext.globalSupportedTypes()))
+ validatorContext, t, validatorContext.globalSupportedTypes()))
.collect(toSet());
Collection<CrossProfileTestInfo> crossProfileTests =
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapper.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapper.java
index 114abfb..87f699f 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapper.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapper.java
@@ -31,7 +31,6 @@
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
/** Information about a Parcelable Wrapper. */
@AutoValue
@@ -70,23 +69,23 @@
}
public static Collection<ParcelableWrapper> createCustomParcelableWrappers(
- Types types, Elements elements, Collection<TypeElement> customParcelableWrappers) {
+ Context context, Collection<TypeElement> customParcelableWrappers) {
Collection<ParcelableWrapper> wrappers = new ArrayList<>();
- addCustomParcelableWrappers(types, wrappers, customParcelableWrappers);
+ addCustomParcelableWrappers(context, wrappers, customParcelableWrappers);
return wrappers;
}
public static Collection<ParcelableWrapper> createGlobalParcelableWrappers(
- Types types, Elements elements, Collection<ExecutableElement> methods) {
+ Context context, Collection<ExecutableElement> methods) {
Collection<ParcelableWrapper> wrappers = new ArrayList<>();
- addDefaultParcelableWrappers(types, elements, wrappers);
+ addDefaultParcelableWrappers(context, wrappers);
Collection<TypeMirror> usedTypes = extractTypesFromMethods(methods);
- addGeneratedProtoParcelableWrappers(types, elements, wrappers, usedTypes);
+ addGeneratedProtoParcelableWrappers(context, wrappers, usedTypes);
return wrappers;
}
@@ -129,16 +128,16 @@
}
private static void addCustomParcelableWrappers(
- Types types,
+ Context context,
Collection<ParcelableWrapper> wrappers,
Collection<TypeElement> customParcelableWrappers) {
for (TypeElement parcelableWrapper : customParcelableWrappers) {
- addCustomParcelableWrapper(types, wrappers, parcelableWrapper);
+ addCustomParcelableWrapper(context, wrappers, parcelableWrapper);
}
}
private static void addCustomParcelableWrapper(
- Types types, Collection<ParcelableWrapper> wrappers, TypeElement parcelableWrapper) {
+ Context context, Collection<ParcelableWrapper> wrappers, TypeElement parcelableWrapper) {
CustomParcelableWrapper customParcelableWrapperAnnotation =
parcelableWrapper.getAnnotation(CustomParcelableWrapper.class);
@@ -150,7 +149,7 @@
ParcelableWrapperAnnotationInfo annotationInfo =
ParcelableWrapperAnnotationInfo.extractFromParcelableWrapperAnnotation(
- types, customParcelableWrapperAnnotation);
+ context, customParcelableWrapperAnnotation);
wrappers.add(
ParcelableWrapper.create(
annotationInfo.originalType().asType(),
@@ -159,7 +158,8 @@
}
private static void addDefaultParcelableWrappers(
- Types types, Elements elements, Collection<ParcelableWrapper> wrappers) {
+ Context context, Collection<ParcelableWrapper> wrappers) {
+ Elements elements = context.elements();
tryAddWrapper(
elements,
wrappers,
@@ -214,15 +214,18 @@
"android.graphics.Bitmap",
ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableBitmap"));
- addArrayWrappers(types, elements, wrappers);
+ tryAddWrapper(
+ elements,
+ wrappers,
+ "android.graphics.drawable.Drawable",
+ ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableDrawable"));
+
+ addArrayWrappers(context, wrappers);
}
private static void addGeneratedProtoParcelableWrappers(
- Types types,
- Elements elements,
- Collection<ParcelableWrapper> wrappers,
- Collection<TypeMirror> usedTypes) {
- TypeElement protoElement = elements.getTypeElement("com.google.protobuf.MessageLite");
+ Context context, Collection<ParcelableWrapper> wrappers, Collection<TypeMirror> usedTypes) {
+ TypeElement protoElement = context.elements().getTypeElement("com.google.protobuf.MessageLite");
if (protoElement == null) {
// Protos are not included at compile-time
return;
@@ -231,12 +234,12 @@
Collection<TypeMirror> protoTypes =
usedTypes.stream()
- // <any> is the value when the compiler encounters a type which isn't accessible
- // or does not exist. This passes the types.isAssignable filter, which makes such
- // bugs hard to debug. This will already fail because the Java compiler won't allow
- // it - so this is just to suppress strange test failures
+ // <any> is the value when the compiler encounters a type which isn't accessible
+ // or does not exist. This passes the types.isAssignable filter, which makes such
+ // bugs hard to debug. This will already fail because the Java compiler won't allow
+ // it - so this is just to suppress strange test failures
.filter(t -> !t.toString().equals("<any>"))
- .filter(t -> types.isAssignable(t, proto))
+ .filter(t -> context.types().isAssignable(t, proto))
.collect(toSet());
for (TypeMirror protoType : protoTypes) {
@@ -246,10 +249,9 @@
}
}
- private static void addArrayWrappers(
- Types types, Elements elements, Collection<ParcelableWrapper> wrappers) {
- TypeElement typeElement = elements.getTypeElement("java.lang.Object");
- TypeMirror typeMirror = types.getArrayType(typeElement.asType());
+ private static void addArrayWrappers(Context context, Collection<ParcelableWrapper> wrappers) {
+ TypeElement typeElement = context.elements().getTypeElement("java.lang.Object");
+ TypeMirror typeMirror = context.types().getArrayType(typeElement.asType());
ClassName wrapperClassName = ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableArray");
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapperAnnotationInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapperAnnotationInfo.java
index d9e7949..2d87109 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapperAnnotationInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ParcelableWrapperAnnotationInfo.java
@@ -19,7 +19,6 @@
import com.google.android.enterprise.connectedapps.processor.GeneratorUtilities;
import com.google.auto.value.AutoValue;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Types;
/** Wrapper around information contained in a {@link CustomParcelableWrapper} annotation. */
@AutoValue
@@ -28,14 +27,14 @@
public abstract TypeElement originalType();
public static ParcelableWrapperAnnotationInfo extractFromParcelableWrapperAnnotation(
- Types types, CustomParcelableWrapper customParcelableWrapperAnnotation) {
+ Context context, CustomParcelableWrapper customParcelableWrapperAnnotation) {
if (customParcelableWrapperAnnotation == null) {
throw new NullPointerException("parcelableWrapperAnnotation must not be null");
}
TypeElement originalType =
GeneratorUtilities.extractClassFromAnnotation(
- types, customParcelableWrapperAnnotation::originalType);
+ context.types(), customParcelableWrapperAnnotation::originalType);
return new AutoValue_ParcelableWrapperAnnotationInfo(originalType);
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/PreValidatorContext.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/PreValidatorContext.java
new file mode 100644
index 0000000..803db78
--- /dev/null
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/PreValidatorContext.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor.containers;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/** Context for connected apps code run before validation. */
+public final class PreValidatorContext extends Context {
+
+ private final ProcessingEnvironment processingEnv;
+
+ public PreValidatorContext(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public ProcessingEnvironment processingEnv() {
+ return processingEnv;
+ }
+
+ @Override
+ public Elements elements() {
+ return processingEnv.getElementUtils();
+ }
+
+ @Override
+ public Types types() {
+ return processingEnv.getTypeUtils();
+ }
+}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProfileConnectorInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProfileConnectorInfo.java
index 9a68099..6f4f71d 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProfileConnectorInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProfileConnectorInfo.java
@@ -18,6 +18,7 @@
import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector;
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
+import com.google.android.enterprise.connectedapps.annotations.UncaughtExceptionsPolicy;
import com.google.android.enterprise.connectedapps.processor.GeneratorUtilities;
import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
import com.google.auto.value.AutoValue;
@@ -27,7 +28,6 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
@@ -49,6 +49,8 @@
abstract ImmutableCollection<TypeElement> importsClasses();
abstract AvailabilityRestrictions availabilityRestrictions();
+
+ abstract UncaughtExceptionsPolicy uncaughtExceptionsPolicy();
}
public abstract TypeElement connectorElement();
@@ -71,22 +73,21 @@
public abstract AvailabilityRestrictions availabilityRestrictions();
- public static ProfileConnectorInfo create(
- ProcessingEnvironment processingEnv,
- TypeElement connectorElement,
- SupportedTypes globalSupportedTypes) {
+ public abstract UncaughtExceptionsPolicy uncaughtExceptionsPolicy();
- Elements elements = processingEnv.getElementUtils();
+ public static ProfileConnectorInfo create(
+ Context context, TypeElement connectorElement, SupportedTypes globalSupportedTypes) {
+ Elements elements = context.elements();
CustomProfileConnectorAnnotationInfo annotationInfo =
- extractFromCustomProfileConnectorAnnotation(processingEnv, elements, connectorElement);
+ extractFromCustomProfileConnectorAnnotation(context, elements, connectorElement);
Set<TypeElement> parcelableWrappers = new HashSet<>(annotationInfo.parcelableWrapperClasses());
Set<TypeElement> futureWrappers = new HashSet<>(annotationInfo.futureWrapperClasses());
for (TypeElement importConnectorClass : annotationInfo.importsClasses()) {
ProfileConnectorInfo importConnector =
- ProfileConnectorInfo.create(processingEnv, importConnectorClass, globalSupportedTypes);
+ ProfileConnectorInfo.create(context, importConnectorClass, globalSupportedTypes);
parcelableWrappers.addAll(importConnector.parcelableWrapperClasses());
futureWrappers.addAll(importConnector.futureWrapperClasses());
}
@@ -98,22 +99,18 @@
globalSupportedTypes
.asBuilder()
.addParcelableWrappers(
- ParcelableWrapper.createCustomParcelableWrappers(
- processingEnv.getTypeUtils(),
- processingEnv.getElementUtils(),
- parcelableWrappers))
- .addFutureWrappers(
- FutureWrapper.createCustomFutureWrappers(
- processingEnv.getTypeUtils(), processingEnv.getElementUtils(), futureWrappers))
+ ParcelableWrapper.createCustomParcelableWrappers(context, parcelableWrappers))
+ .addFutureWrappers(FutureWrapper.createCustomFutureWrappers(context, futureWrappers))
.build(),
ImmutableSet.copyOf(parcelableWrappers),
ImmutableSet.copyOf(futureWrappers),
annotationInfo.importsClasses(),
- annotationInfo.availabilityRestrictions());
+ annotationInfo.availabilityRestrictions(),
+ annotationInfo.uncaughtExceptionsPolicy());
}
private static CustomProfileConnectorAnnotationInfo extractFromCustomProfileConnectorAnnotation(
- ProcessingEnvironment processingEnv, Elements elements, TypeElement connectorElement) {
+ Context context, Elements elements, TypeElement connectorElement) {
CustomProfileConnector customProfileConnector =
connectorElement.getAnnotation(CustomProfileConnector.class);
@@ -124,18 +121,19 @@
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableSet.of(),
- AvailabilityRestrictions.DEFAULT);
+ AvailabilityRestrictions.DEFAULT,
+ UncaughtExceptionsPolicy.NOTIFY_RETHROW);
}
Collection<TypeElement> parcelableWrappers =
GeneratorUtilities.extractClassesFromAnnotation(
- processingEnv.getTypeUtils(), customProfileConnector::parcelableWrappers);
+ context.types(), customProfileConnector::parcelableWrappers);
Collection<TypeElement> futureWrappers =
GeneratorUtilities.extractClassesFromAnnotation(
- processingEnv.getTypeUtils(), customProfileConnector::futureWrappers);
+ context.types(), customProfileConnector::futureWrappers);
Collection<TypeElement> imports =
GeneratorUtilities.extractClassesFromAnnotation(
- processingEnv.getTypeUtils(), customProfileConnector::imports);
+ context.types(), customProfileConnector::imports);
String serviceClassName = customProfileConnector.serviceClassName();
@@ -147,7 +145,8 @@
ImmutableSet.copyOf(parcelableWrappers),
ImmutableSet.copyOf(futureWrappers),
ImmutableSet.copyOf(imports),
- customProfileConnector.availabilityRestrictions());
+ customProfileConnector.availabilityRestrictions(),
+ customProfileConnector.uncaughtExceptionsPolicy());
}
public static ClassName getDefaultServiceName(Elements elements, TypeElement connectorElement) {
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProviderClassInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProviderClassInfo.java
index 02adf0c..d3cc922 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProviderClassInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ProviderClassInfo.java
@@ -16,16 +16,13 @@
package com.google.android.enterprise.connectedapps.processor.containers;
import static com.google.android.enterprise.connectedapps.processor.GeneratorUtilities.findCrossProfileProviderMethodsInClass;
-import static java.util.stream.Collectors.toSet;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.stream.Stream;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
@@ -40,10 +37,10 @@
public abstract TypeElement providerClassElement();
public ImmutableCollection<CrossProfileTypeInfo> allCrossProfileTypes() {
- Set<CrossProfileTypeInfo> types = new HashSet<>();
+ ImmutableSet.Builder<CrossProfileTypeInfo> types = ImmutableSet.builder();
types.addAll(nonStaticTypes());
types.addAll(staticTypes());
- return ImmutableSet.copyOf(types);
+ return types.build();
}
public abstract ImmutableCollection<CrossProfileTypeInfo> nonStaticTypes();
@@ -58,6 +55,8 @@
return ClassName.get(providerClassElement());
}
+ public abstract ConnectorInfo connectorInfo();
+
public ImmutableCollection<VariableElement> publicConstructorArgumentTypes() {
return ImmutableList.copyOf(
providerClassElement().getEnclosedElements().stream()
@@ -91,40 +90,52 @@
public static ProviderClassInfo create(
ValidatorContext context, ValidatorProviderClassInfo provider) {
- Set<CrossProfileTypeInfo> nonStaticTypes =
- extractCrossProfileTypeElementsFromReturnValues(
- context.elements(), provider.providerClassElement())
- .stream()
- .map(
- crossProfileTypeElement ->
- ValidatorCrossProfileTypeInfo.create(
- context.processingEnv(),
- crossProfileTypeElement,
- context.globalSupportedTypes()))
- .map(crossProfileType -> CrossProfileTypeInfo.create(context, crossProfileType))
- .collect(toSet());
+ ImmutableSet.Builder<CrossProfileTypeInfo> nonStaticTypesBuilder = ImmutableSet.builder();
+ extractCrossProfileTypeElementsFromReturnValues(
+ context.elements(), provider.providerClassElement())
+ .stream()
+ .map(
+ crossProfileTypeElement ->
+ ValidatorCrossProfileTypeInfo.create(
+ context, crossProfileTypeElement, context.globalSupportedTypes()))
+ .map(crossProfileType -> CrossProfileTypeInfo.create(context, crossProfileType))
+ .forEach(nonStaticTypesBuilder::add);
+ ImmutableSet<CrossProfileTypeInfo> nonStaticTypes = nonStaticTypesBuilder.build();
- Set<CrossProfileTypeInfo> staticTypes =
- provider.staticTypes().stream()
- .map(
- crossProfileTypeElement ->
- ValidatorCrossProfileTypeInfo.create(
- context.processingEnv(),
- crossProfileTypeElement,
- context.globalSupportedTypes()))
- .map(crossProfileType -> CrossProfileTypeInfo.create(context, crossProfileType))
- .collect(toSet());
+ ImmutableSet.Builder<CrossProfileTypeInfo> staticTypesBuilder = ImmutableSet.builder();
+ provider.staticTypes().stream()
+ .map(
+ crossProfileTypeElement ->
+ ValidatorCrossProfileTypeInfo.create(
+ context, crossProfileTypeElement, context.globalSupportedTypes()))
+ .map(crossProfileType -> CrossProfileTypeInfo.create(context, crossProfileType))
+ .forEach(staticTypesBuilder::add);
+ ImmutableSet<CrossProfileTypeInfo> staticTypes = staticTypesBuilder.build();
return new AutoValue_ProviderClassInfo(
provider.providerClassElement(),
- ImmutableSet.copyOf(nonStaticTypes),
- ImmutableSet.copyOf(staticTypes));
+ nonStaticTypes,
+ staticTypes,
+ findConnector(context, staticTypes, nonStaticTypes));
}
- public static Collection<TypeElement> extractCrossProfileTypeElementsFromReturnValues(
+ public static ImmutableSet<TypeElement> extractCrossProfileTypeElementsFromReturnValues(
Elements elements, TypeElement providerClassElement) {
- return findCrossProfileProviderMethodsInClass(providerClassElement).stream()
+ ImmutableSet.Builder<TypeElement> result = ImmutableSet.builder();
+ findCrossProfileProviderMethodsInClass(providerClassElement).stream()
.map(e -> elements.getTypeElement(e.getReturnType().toString()))
- .collect(toSet());
+ .forEach(result::add);
+ return result.build();
+ }
+
+ private static ConnectorInfo findConnector(
+ ValidatorContext context,
+ ImmutableSet<CrossProfileTypeInfo> staticTypes,
+ ImmutableSet<CrossProfileTypeInfo> nonStaticTypes) {
+ return Stream.concat(staticTypes.stream(), nonStaticTypes.stream())
+ .filter(typeInfo -> typeInfo.connectorInfo().isPresent())
+ .map(typeInfo -> typeInfo.connectorInfo().get())
+ .findFirst()
+ .orElse(ConnectorInfo.unspecified(context, context.globalSupportedTypes()));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/Type.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/Type.java
index d81f6cd..d52a1e6 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/Type.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/Type.java
@@ -51,7 +51,7 @@
}
public boolean canBeBundled() {
- return getWriteToParcelCode().isPresent() && getReadFromParcelCode().isPresent();
+ return getPutIntoBundleCode().isPresent() && getGetFromBundleCode().isPresent();
}
public boolean isPrimitive() {
@@ -96,6 +96,10 @@
// (e.g. ParcelableList)
public abstract Optional<ParcelableWrapper> getParcelableWrapper();
+ public abstract Optional<String> getPutIntoBundleCode();
+
+ public abstract Optional<String> getGetFromBundleCode();
+
public abstract Optional<String> getWriteToParcelCode();
public abstract Optional<String> getReadFromParcelCode();
@@ -125,6 +129,10 @@
public abstract Builder setCrossProfileCallbackInterface(
CrossProfileCallbackInterfaceInfo crossProfileCallbackInterface);
+ public abstract Builder setPutIntoBundleCode(String putIntoBundleCode);
+
+ public abstract Builder setGetFromBundleCode(String getFromBundleCode);
+
public abstract Builder setWriteToParcelCode(String writeToParcelCode);
public abstract Builder setReadFromParcelCode(String readFromParcelCode);
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/UserConnectorInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/UserConnectorInfo.java
index 33a4d56..82395f9 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/UserConnectorInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/UserConnectorInfo.java
@@ -26,10 +26,8 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
/** Wrapper of an interface used as a user connector. */
@AutoValue
@@ -67,19 +65,16 @@
public abstract AvailabilityRestrictions availabilityRestrictions();
public static UserConnectorInfo create(
- ProcessingEnvironment processingEnv,
- TypeElement connectorElement,
- SupportedTypes globalSupportedTypes) {
- Elements elements = processingEnv.getElementUtils();
+ Context context, TypeElement connectorElement, SupportedTypes globalSupportedTypes) {
CustomUserConnectorAnnotationInfo annotationInfo =
- extractFromCustomUserConnectorAnnotation(processingEnv, elements, connectorElement);
+ extractFromCustomUserConnectorAnnotation(context, connectorElement);
Set<TypeElement> parcelableWrappers = new HashSet<>(annotationInfo.parcelableWrapperClasses());
Set<TypeElement> futureWrappers = new HashSet<>(annotationInfo.futureWrapperClasses());
for (TypeElement importConnectorClass : annotationInfo.importsClasses()) {
UserConnectorInfo importConnector =
- UserConnectorInfo.create(processingEnv, importConnectorClass, globalSupportedTypes);
+ UserConnectorInfo.create(context, importConnectorClass, globalSupportedTypes);
parcelableWrappers.addAll(importConnector.parcelableWrapperClasses());
futureWrappers.addAll(importConnector.futureWrapperClasses());
}
@@ -90,13 +85,8 @@
globalSupportedTypes
.asBuilder()
.addParcelableWrappers(
- ParcelableWrapper.createCustomParcelableWrappers(
- processingEnv.getTypeUtils(),
- processingEnv.getElementUtils(),
- parcelableWrappers))
- .addFutureWrappers(
- FutureWrapper.createCustomFutureWrappers(
- processingEnv.getTypeUtils(), processingEnv.getElementUtils(), futureWrappers))
+ ParcelableWrapper.createCustomParcelableWrappers(context, parcelableWrappers))
+ .addFutureWrappers(FutureWrapper.createCustomFutureWrappers(context, futureWrappers))
.build(),
ImmutableSet.copyOf(parcelableWrappers),
ImmutableSet.copyOf(futureWrappers),
@@ -105,13 +95,13 @@
}
private static CustomUserConnectorAnnotationInfo extractFromCustomUserConnectorAnnotation(
- ProcessingEnvironment processingEnv, Elements elements, TypeElement connectorElement) {
+ Context context, TypeElement connectorElement) {
CustomUserConnector customUserConnector =
connectorElement.getAnnotation(CustomUserConnector.class);
if (customUserConnector == null) {
return new AutoValue_UserConnectorInfo_CustomUserConnectorAnnotationInfo(
- getDefaultServiceName(elements, connectorElement),
+ getDefaultServiceName(context, connectorElement),
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableSet.of(),
@@ -120,19 +110,19 @@
Collection<TypeElement> parcelableWrappers =
GeneratorUtilities.extractClassesFromAnnotation(
- processingEnv.getTypeUtils(), customUserConnector::parcelableWrappers);
+ context.types(), customUserConnector::parcelableWrappers);
Collection<TypeElement> futureWrappers =
GeneratorUtilities.extractClassesFromAnnotation(
- processingEnv.getTypeUtils(), customUserConnector::futureWrappers);
+ context.types(), customUserConnector::futureWrappers);
Collection<TypeElement> imports =
GeneratorUtilities.extractClassesFromAnnotation(
- processingEnv.getTypeUtils(), customUserConnector::imports);
+ context.types(), customUserConnector::imports);
String serviceClassName = customUserConnector.serviceClassName();
return new AutoValue_UserConnectorInfo_CustomUserConnectorAnnotationInfo(
serviceClassName.isEmpty()
- ? getDefaultServiceName(elements, connectorElement)
+ ? getDefaultServiceName(context, connectorElement)
: ClassName.bestGuess(serviceClassName),
ImmutableSet.copyOf(parcelableWrappers),
ImmutableSet.copyOf(futureWrappers),
@@ -140,8 +130,8 @@
customUserConnector.availabilityRestrictions());
}
- public static ClassName getDefaultServiceName(Elements elements, TypeElement connectorElement) {
- PackageElement originalPackage = elements.getPackageOf(connectorElement);
+ public static ClassName getDefaultServiceName(Context context, TypeElement connectorElement) {
+ PackageElement originalPackage = context.elements().getPackageOf(connectorElement);
return ClassName.get(
originalPackage.getQualifiedName().toString(),
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorContext.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorContext.java
index e13f0c5..8b6c98f 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorContext.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorContext.java
@@ -34,8 +34,11 @@
@AutoValue
public abstract class ValidatorContext extends Context {
- public static Builder builder() {
- return new AutoValue_ValidatorContext.Builder();
+ public static Builder builderFromPreValidatorContext(PreValidatorContext preValidatorContext) {
+ return new AutoValue_ValidatorContext.Builder()
+ .setProcessingEnv(preValidatorContext.processingEnv())
+ .setElements(preValidatorContext.elements())
+ .setTypes(preValidatorContext.types());
}
public abstract SupportedTypes globalSupportedTypes();
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileConfigurationInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileConfigurationInfo.java
index 973714c..2109108 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileConfigurationInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileConfigurationInfo.java
@@ -24,7 +24,6 @@
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
import java.util.Optional;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@@ -43,18 +42,15 @@
public abstract Optional<TypeElement> connector();
public static ImmutableSet<ValidatorCrossProfileConfigurationInfo> createMultipleFromElement(
- ProcessingEnvironment processingEnvironment, TypeElement annotatedElement) {
+ Context context, TypeElement annotatedElement) {
ImmutableSet<CrossProfileConfigurationAnnotationInfo> infos =
- AnnotationFinder.extractCrossProfileConfigurationsAnnotationInfo(
- annotatedElement,
- processingEnvironment.getTypeUtils(),
- processingEnvironment.getElementUtils())
+ AnnotationFinder.extractCrossProfileConfigurationsAnnotationInfo(context, annotatedElement)
.configurations();
ImmutableSet.Builder<ValidatorCrossProfileConfigurationInfo> configurations =
ImmutableSet.builder();
if (infos.isEmpty()) {
- configurations.add(createFromElement(processingEnvironment, annotatedElement));
+ configurations.add(createFromElement(context, annotatedElement));
} else {
for (CrossProfileConfigurationAnnotationInfo info : infos) {
configurations.add(createFromAnnotationInfo(info, annotatedElement));
@@ -65,9 +61,9 @@
}
public static ValidatorCrossProfileConfigurationInfo createFromElement(
- ProcessingEnvironment processingEnv, TypeElement annotatedElement) {
+ Context context, TypeElement annotatedElement) {
CrossProfileConfigurationAnnotationInfo annotationInfo =
- extractFromCrossProfileConfigurationAnnotation(annotatedElement, processingEnv);
+ extractFromCrossProfileConfigurationAnnotation(context, annotatedElement);
return createFromAnnotationInfo(annotationInfo, annotatedElement);
}
@@ -117,9 +113,8 @@
}
private static CrossProfileConfigurationAnnotationInfo
- extractFromCrossProfileConfigurationAnnotation(
- Element annotatedElement, ProcessingEnvironment processingEnv) {
+ extractFromCrossProfileConfigurationAnnotation(Context context, Element annotatedElement) {
return AnnotationFinder.extractCrossProfileConfigurationAnnotationInfo(
- annotatedElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+ context, annotatedElement);
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTestInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTestInfo.java
index 172c8ed..d7a93e3 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTestInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTestInfo.java
@@ -18,7 +18,6 @@
import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
import com.google.android.enterprise.connectedapps.testing.annotations.CrossProfileTest;
import com.google.auto.value.AutoValue;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.TypeElement;
/** Wrapper of a {@link CrossProfileTest} annotated class. */
@@ -30,10 +29,9 @@
public abstract TypeElement configurationElement();
public static ValidatorCrossProfileTestInfo create(
- ProcessingEnvironment processingEnv, TypeElement crossProfileTestElement) {
+ Context context, TypeElement crossProfileTestElement) {
CrossProfileTestAnnotationInfo annotationInfo =
- AnnotationFinder.extractCrossProfileTestAnnotationInfo(
- crossProfileTestElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+ AnnotationFinder.extractCrossProfileTestAnnotationInfo(context, crossProfileTestElement);
return new AutoValue_ValidatorCrossProfileTestInfo(
crossProfileTestElement, annotationInfo.configuration());
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTypeInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTypeInfo.java
index c757ba4..bc828ae 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTypeInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorCrossProfileTypeInfo.java
@@ -21,14 +21,12 @@
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileAnnotation;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
@@ -40,7 +38,7 @@
public abstract ImmutableList<ExecutableElement> crossProfileMethods();
- public abstract Optional<ProfileConnectorInfo> profileConnector();
+ public abstract Optional<ConnectorInfo> connectorInfo();
public abstract SupportedTypes supportedTypes();
@@ -48,30 +46,15 @@
public abstract ImmutableCollection<TypeElement> futureWrapperClasses();
- public abstract String profileClassName();
-
public abstract boolean isStatic();
- /**
- * The specified timeout for async calls, or {@link CrossProfileAnnotation#DEFAULT_TIMEOUT_MILLIS}
- * if unspecified.
- */
- public abstract long timeoutMillis();
-
public static ValidatorCrossProfileTypeInfo create(
- ProcessingEnvironment processingEnv,
- TypeElement crossProfileTypeElement,
- SupportedTypes globalSupportedTypes) {
+ Context context, TypeElement crossProfileTypeElement, SupportedTypes globalSupportedTypes) {
CrossProfileAnnotationInfo annotationInfo =
- AnnotationFinder.extractCrossProfileAnnotationInfo(
- crossProfileTypeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+ AnnotationFinder.extractCrossProfileAnnotationInfo(context, crossProfileTypeElement);
- Optional<ProfileConnectorInfo> profileConnectorElement =
- annotationInfo.connectorIsDefault()
- ? Optional.empty()
- : Optional.of(
- ProfileConnectorInfo.create(
- processingEnv, annotationInfo.connectorClass(), globalSupportedTypes));
+ Optional<ConnectorInfo> connectorInfo =
+ createConnectorInfo(context, annotationInfo, globalSupportedTypes);
List<ExecutableElement> crossProfileMethodElements =
findCrossProfileMethodsInClass(crossProfileTypeElement).stream()
@@ -79,37 +62,46 @@
.collect(toList());
SupportedTypes incomingSupportedTypes =
- profileConnectorElement.isPresent()
- ? profileConnectorElement.get().supportedTypes()
- : globalSupportedTypes;
+ connectorInfo.isPresent() ? connectorInfo.get().supportedTypes() : globalSupportedTypes;
SupportedTypes supportedTypes =
incomingSupportedTypes
.asBuilder()
.addParcelableWrappers(
ParcelableWrapper.createCustomParcelableWrappers(
- processingEnv.getTypeUtils(),
- processingEnv.getElementUtils(),
- annotationInfo.parcelableWrapperClasses()))
+ context, annotationInfo.parcelableWrapperClasses()))
.addFutureWrappers(
FutureWrapper.createCustomFutureWrappers(
- processingEnv.getTypeUtils(),
- processingEnv.getElementUtils(),
- annotationInfo.futureWrapperClasses()))
+ context, annotationInfo.futureWrapperClasses()))
.build();
return new AutoValue_ValidatorCrossProfileTypeInfo(
crossProfileTypeElement,
ImmutableList.copyOf(crossProfileMethodElements),
- profileConnectorElement,
+ connectorInfo,
supportedTypes,
annotationInfo.parcelableWrapperClasses(),
annotationInfo.futureWrapperClasses(),
- annotationInfo.profileClassName(),
- annotationInfo.isStatic(),
- annotationInfo
- .timeoutMillis()
- .filter(value -> value != CrossProfileAnnotation.TIMEOUT_MILLIS_NOT_SET)
- .orElse(CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS));
+ annotationInfo.isStatic());
+ }
+
+ private static Optional<ConnectorInfo> createConnectorInfo(
+ Context context,
+ CrossProfileAnnotationInfo annotationInfo,
+ SupportedTypes globalSupportedTypes) {
+ if (annotationInfo.connectorIsDefault()) {
+ return Optional.empty();
+ } else if (ConnectorInfo.isProfileConnector(context, annotationInfo.connectorClass())) {
+ return Optional.of(
+ ConnectorInfo.forProfileConnector(
+ context, annotationInfo.connectorClass(), globalSupportedTypes));
+ } else if (ConnectorInfo.isUserConnector(context, annotationInfo.connectorClass())) {
+ return Optional.of(
+ ConnectorInfo.forUserConnector(
+ context, annotationInfo.connectorClass(), globalSupportedTypes));
+ }
+
+ return Optional.of(
+ ConnectorInfo.invalid(context, annotationInfo.connectorClass(), globalSupportedTypes));
}
}
diff --git a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorProviderClassInfo.java b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorProviderClassInfo.java
index c3ffc28..99427fa 100644
--- a/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorProviderClassInfo.java
+++ b/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/containers/ValidatorProviderClassInfo.java
@@ -20,7 +20,6 @@
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
-import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.TypeElement;
/** Wrapper of basic information for a cross-profile provider class. */
@@ -40,10 +39,9 @@
}
public static ValidatorProviderClassInfo create(
- ProcessingEnvironment processingEnv, TypeElement providerClassElement) {
+ Context context, TypeElement providerClassElement) {
CrossProfileProviderAnnotationInfo annotationInfo =
- AnnotationFinder.extractCrossProfileProviderAnnotationInfo(
- providerClassElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
+ AnnotationFinder.extractCrossProfileProviderAnnotationInfo(context, providerClassElement);
return new AutoValue_ValidatorProviderClassInfo(
providerClassElement, ImmutableSet.copyOf(annotationInfo.staticTypes()));
diff --git a/processor/src/main/resources/futurewrappers/ListenableFutureWrapper.java b/processor/src/main/resources/futurewrappers/ListenableFutureWrapper.java
index f9fe728..6af3332 100644
--- a/processor/src/main/resources/futurewrappers/ListenableFutureWrapper.java
+++ b/processor/src/main/resources/futurewrappers/ListenableFutureWrapper.java
@@ -75,7 +75,7 @@
directExecutor());
}
- private static class MergerFutureCallback<E> implements FutureCallback<E> {
+ private static final class MergerFutureCallback<E> implements FutureCallback<E> {
private final Profile profileId;
private final CrossProfileCallbackMultiMerger<E> merger;
diff --git a/processor/src/main/resources/parcelablewrappers/ParcelableDrawable.java b/processor/src/main/resources/parcelablewrappers/ParcelableDrawable.java
new file mode 100644
index 0000000..b9852aa
--- /dev/null
+++ b/processor/src/main/resources/parcelablewrappers/ParcelableDrawable.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.parcelablewrappers;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * Wrapper for reading & writing {@link Drawable} instances from and to {@link Parcel} instances.
+ *
+ * <p>Note that all {@link Drawable} instances are converted to {@link Bitmap} when parcelling.
+ */
+public class ParcelableDrawable implements Parcelable {
+
+ private static final int NULL = -1;
+ private static final int NOT_NULL = 1;
+
+ private final Drawable drawable;
+
+ /** Create a wrapper for a given drawable. */
+ public static ParcelableDrawable of(Bundler bundler, BundlerType type, Drawable drawable) {
+ return new ParcelableDrawable(drawable);
+ }
+
+ public Drawable get() {
+ return drawable;
+ }
+
+ private ParcelableDrawable(Drawable drawable) {
+ this.drawable = drawable;
+ }
+
+ private ParcelableDrawable(Parcel in) {
+ int present = in.readInt();
+
+ if (present == NULL) {
+ drawable = null;
+ return;
+ }
+
+ Bitmap bitmap = (Bitmap) in.readParcelable(Bundler.class.getClassLoader());
+ drawable = new BitmapDrawable(bitmap);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (drawable == null) {
+ dest.writeInt(NULL);
+ return;
+ }
+
+ dest.writeInt(NOT_NULL);
+
+ Bitmap bitmap = drawableToBitmap(drawable);
+ dest.writeParcelable(bitmap, /* flags= */ flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final Creator<ParcelableDrawable> CREATOR =
+ new Creator<ParcelableDrawable>() {
+ @Override
+ public ParcelableDrawable createFromParcel(Parcel in) {
+ return new ParcelableDrawable(in);
+ }
+
+ @Override
+ public ParcelableDrawable[] newArray(int size) {
+ return new ParcelableDrawable[size];
+ }
+ };
+
+ private static Bitmap drawableToBitmap(Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ Bitmap bitmap =
+ Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
+ drawable.draw(new Canvas(bitmap));
+
+ return bitmap;
+ }
+}
diff --git a/proguard.pgcfg b/proguard.pgcfg
index e69de29..b92adab 100644
--- a/proguard.pgcfg
+++ b/proguard.pgcfg
@@ -0,0 +1,8 @@
+-keepclasseswithmembers class com.google.android.enterprise.connectedapps.internal.BundleCallReceiver {
+ android.os.Bundle getPreparedCall(long, int, byte[]);
+ byte[] prepareResponse(long, android.os.Bundle);
+}
+
+-keepclasseswithmembers class com.google.android.enterprise.connectedapps.internal.CrossProfileBundleCallSender {
+ android.os.Bundle makeBundleCall(android.os.Bundle);
+}
\ No newline at end of file
diff --git a/sdk/build.gradle b/sdk/build.gradle
index f9a6886..e3b7203 100644
--- a/sdk/build.gradle
+++ b/sdk/build.gradle
@@ -4,10 +4,15 @@
}
dependencies {
- api deps.checkerFramework
+ api(deps.errorprone, {
+ exclude group: 'org.checkerframework', module: 'dataflow-errorprone'
+ })
implementation project(path: ':connectedapps-annotations')
+ implementation(deps.errorprone, {
+ exclude group: 'org.checkerframework', module: 'dataflow-errorprone'
+ })
testImplementation project(path: ':connectedapps-sharedtests')
- testImplementation 'org.robolectric:robolectric:4.4'
+ testImplementation deps.robolectric
testImplementation 'junit:junit:4.13.1'
testImplementation 'com.google.truth:truth:1.1.2'
testImplementation 'androidx.test:core:1.3.0'
diff --git a/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileCallback.aidl b/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileCallback.aidl
index 581da13..443baa4 100644
--- a/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileCallback.aidl
+++ b/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileCallback.aidl
@@ -15,8 +15,11 @@
*/
package com.google.android.enterprise.connectedapps;
+import android.os.Bundle;
+
interface ICrossProfileCallback {
void prepareResult(long callId, int blockId, int numBytes, in byte[] params);
+ void prepareBundle(long callId, int bundleId, in Bundle bundle);
void onResult(long callId, int blockId, int methodIdentifier, in byte[] params);
void onException(long callId, int blockId, in byte[] params);
}
\ No newline at end of file
diff --git a/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileService.aidl b/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileService.aidl
index 126f2d4..22e30b4 100644
--- a/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileService.aidl
+++ b/sdk/src/main/aidl/com/google/android/enterprise/connectedapps/ICrossProfileService.aidl
@@ -16,6 +16,7 @@
package com.google.android.enterprise.connectedapps;
import com.google.android.enterprise.connectedapps.ICrossProfileCallback;
+import android.os.Bundle;
interface ICrossProfileService {
// When making a call containing params larger than
@@ -29,6 +30,9 @@
// and is used to prepare the cache with the first use of prepareCall
void prepareCall(long callId, int blockId, int numBytes, in byte[] params);
+
+ void prepareBundle(long callId, int bundleId, in Bundle bundle);
+
// When making a call with params smaller than
// CrossProfileSender.MAX_BYTES_PER_BLOCK bytes bytes, or with the final
// block in a larger call, this method is used.
@@ -38,4 +42,6 @@
ICrossProfileCallback callback);
byte[] fetchResponse(long callId, int blockId);
+
+ Bundle fetchResponseBundle(long callId, int bundleId);
}
\ No newline at end of file
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileBinder.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileBinder.java
index b4e908f..15b4ae0 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileBinder.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractProfileBinder.java
@@ -117,6 +117,11 @@
PackageInfo packageInfo =
packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
+ if (packageInfo == null || packageInfo.requestedPermissions == null) {
+ hasCachedPermissionRequests = true;
+ return;
+ }
+
for (String permission : packageInfo.requestedPermissions) {
if (permission.equals(INTERACT_ACROSS_PROFILES)) {
requestsInteractAcrossProfiles = true;
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 dd84c7d..92184a5 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
@@ -81,28 +81,21 @@
}
@Override
- public void startConnecting() {
- if (VERSION.SDK_INT < VERSION_CODES.O) {
- return;
- }
- crossProfileSender().startManuallyBinding();
+ public ProfileConnectionHolder connect() throws UnavailableProfileException {
+ return connect(CrossProfileSender.MANUAL_MANAGEMENT_CONNECTION_HOLDER);
}
@Override
- public void connect() throws UnavailableProfileException {
+
+ public ProfileConnectionHolder connect(Object connectionHolder)
+ throws UnavailableProfileException {
if (VERSION.SDK_INT < VERSION_CODES.O) {
throw new UnavailableProfileException(
"Cross-profile calls are not supported on this version of Android");
}
- crossProfileSender().manuallyBind();
- }
+ crossProfileSender().manuallyBind(connectionHolder);
- @Override
- public void stopManualConnectionManagement() {
- if (VERSION.SDK_INT < VERSION_CODES.O) {
- return;
- }
- crossProfileSender().stopManualConnectionManagement();
+ return ProfileConnectionHolder.create(this, connectionHolder);
}
@Override
@@ -125,12 +118,12 @@
}
@Override
- public void registerConnectionListener(ConnectionListener listener) {
+ public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
@Override
- public void unregisterConnectionListener(ConnectionListener listener) {
+ public void removeConnectionListener(ConnectionListener listener) {
connectionListeners.remove(listener);
}
@@ -146,12 +139,12 @@
}
@Override
- public void registerAvailabilityListener(AvailabilityListener listener) {
+ public void addAvailabilityListener(AvailabilityListener listener) {
availabilityListeners.add(listener);
}
@Override
- public void unregisterAvailabilityListener(AvailabilityListener listener) {
+ public void removeAvailabilityListener(AvailabilityListener listener) {
availabilityListeners.remove(listener);
}
@@ -214,8 +207,37 @@
}
@Override
- public boolean isManuallyManagingConnection() {
- return crossProfileSender().isManuallyManagingConnection();
+ public ProfileConnectionHolder addConnectionHolder(Object connectionHolder) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return ProfileConnectionHolder.create(this, connectionHolder);
+ }
+ crossProfileSender().addConnectionHolder(connectionHolder);
+
+ return ProfileConnectionHolder.create(this, connectionHolder);
+ }
+
+ @Override
+ public void addConnectionHolderAlias(Object key, Object value) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return;
+ }
+ crossProfileSender().addConnectionHolderAlias(key, value);
+ }
+
+ @Override
+ public void removeConnectionHolder(Object connectionHolder) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return;
+ }
+ crossProfileSender().removeConnectionHolder(connectionHolder);
+ }
+
+ @Override
+ public void clearConnectionHolders() {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return;
+ }
+ crossProfileSender().clearConnectionHolders();
}
/** A builder for an {@link AbstractProfileConnector}. */
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractUserConnector.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractUserConnector.java
index fa07fe2..544b6af 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractUserConnector.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/AbstractUserConnector.java
@@ -16,89 +16,261 @@
package com.google.android.enterprise.connectedapps;
import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.UserHandle;
import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.checkerframework.checker.nullness.qual.Nullable;
/** Standard implementation of {@link UserConnector}. */
-public abstract class AbstractUserConnector
- implements UserConnector, ConnectionListener, AvailabilityListener {
+public abstract class AbstractUserConnector implements UserConnector {
+
+ private final Context context;
+ private final UserBinderFactory binderFactory;
+ private final ScheduledExecutorService scheduledExecutorService;
+ private final String serviceClassName;
+ private final AvailabilityRestrictions availabilityRestrictions;
+
+ private final Map<UserHandle, UserConnection> userConnections = new HashMap<>();
+
+ private static final class UserConnection {
+ private final ConnectionBinder binder;
+
+ private final CrossProfileSender crossProfileSender;
+
+ private final Set<ConnectionListener> connectionListeners;
+
+ private final Set<AvailabilityListener> availabilityListeners;
+
+ private UserConnection(
+ ConnectionBinder binder,
+ CrossProfileSender crossProfileSender,
+ Set<ConnectionListener> connectionListeners,
+ Set<AvailabilityListener> availabilityListeners) {
+ this.binder = binder;
+ this.crossProfileSender = crossProfileSender;
+ this.connectionListeners = connectionListeners;
+ this.availabilityListeners = availabilityListeners;
+ }
+
+ public CrossProfileSender crossProfileSender() {
+ return crossProfileSender;
+ }
+
+ public Set<ConnectionListener> connectionListeners() {
+ return connectionListeners;
+ }
+
+ public Set<AvailabilityListener> availabilityListeners() {
+ return availabilityListeners;
+ }
+
+ public static UserConnection create(
+ ConnectionBinder binder, CrossProfileSender crossProfileSender) {
+ return new UserConnection(
+ binder, crossProfileSender, new CopyOnWriteArraySet<>(), new CopyOnWriteArraySet<>());
+ }
+ }
public AbstractUserConnector(Class<? extends UserConnector> userConnectorClass, Builder builder) {
if (userConnectorClass == null || builder == null || builder.context == null) {
throw new NullPointerException();
}
+
+ if (builder.binderFactory == null) {
+ binderFactory = DefaultUserBinder::new;
+ } else {
+ binderFactory = builder.binderFactory;
+ }
+
+ if (builder.scheduledExecutorService == null) {
+ scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ } else {
+ scheduledExecutorService = builder.scheduledExecutorService;
+ }
+
+ context = builder.context.getApplicationContext();
+ availabilityRestrictions = builder.availabilityRestrictions;
+
+ if (builder.serviceClassName == null) {
+ throw new NullPointerException("serviceClassName must be specified");
+ }
+ serviceClassName = builder.serviceClassName;
}
@Override
- public void availabilityChanged() {}
-
- @Override
- public void connectionChanged() {}
-
- @Override
- public void startConnecting(UserHandle userHandle) {}
-
- @Override
- public void connect(UserHandle userHandle) throws UnavailableProfileException {}
-
- @Override
- public void stopManualConnectionManagement(UserHandle userHandle) {}
-
- @Override
- public CrossProfileSender crossProfileSender(UserHandle userHandle) {
- return null;
- }
-
- @Override
- public void registerConnectionListener(UserHandle userHandle, ConnectionListener listener) {}
-
- @Override
- public void unregisterConnectionListener(UserHandle userHandle, ConnectionListener listener) {}
-
- @Override
- public void registerAvailabilityListener(UserHandle userHandle, AvailabilityListener listener) {}
-
- @Override
- public void unregisterAvailabilityListener(
- UserHandle userHandle, AvailabilityListener listener) {}
-
- @Override
public boolean isAvailable(UserHandle userHandle) {
- return false;
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return false;
+ }
+ return crossProfileSender(userHandle).isBindingPossible();
+ }
+
+ @Override
+ public UserConnectionHolder connect(UserHandle userHandle) throws UnavailableProfileException {
+ return connect(userHandle, new Object());
+ }
+
+ @Override
+ public UserConnectionHolder connect(UserHandle userHandle, Object connectionHolder)
+ throws UnavailableProfileException {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ throw new UnavailableProfileException(
+ "Cross-user calls are not supported on this version of Android");
+ }
+ crossProfileSender(userHandle).manuallyBind(connectionHolder);
+
+ return UserConnectionHolder.create(this, userHandle, connectionHolder);
}
@Override
public boolean isConnected(UserHandle userHandle) {
- return false;
- }
-
- @Override
- public ConnectedAppsUtils utils(UserHandle userHandle) {
- return null;
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return false;
+ }
+ return crossProfileSender(userHandle).isBound();
}
@Override
public Permissions permissions(UserHandle userHandle) {
- return null;
+ return new PermissionsImpl(context, userConnection(userHandle).binder);
}
@Override
public Context applicationContext(UserHandle userHandle) {
- return null;
+ return context;
}
@Override
- public boolean isManuallyManagingConnection(UserHandle userHandle) {
- return false;
+ public CrossProfileSender crossProfileSender(UserHandle userHandle) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return null;
+ }
+ return userConnection(userHandle).crossProfileSender();
+ }
+
+ private UserConnection userConnection(UserHandle userHandle) {
+ if (userConnections.containsKey(userHandle)) {
+ return userConnections.get(userHandle);
+ }
+
+ ConnectionBinder binder = binderFactory.createBinder(userHandle);
+ UserHandleEventForwarder userHandleEventForwarder =
+ new UserHandleEventForwarder(this, userHandle);
+ CrossProfileSender crossProfileSender =
+ new CrossProfileSender(
+ context.getApplicationContext(),
+ serviceClassName,
+ binder,
+ /* connectionListener= */ userHandleEventForwarder,
+ /* availabilityListener= */ userHandleEventForwarder,
+ scheduledExecutorService,
+ availabilityRestrictions);
+ UserConnection userConnection = UserConnection.create(binder, crossProfileSender);
+ userConnections.put(userHandle, userConnection);
+ return userConnection;
+ }
+
+ private static final class UserHandleEventForwarder
+ implements ConnectionListener, AvailabilityListener {
+
+ private final AbstractUserConnector connector;
+
+ private final UserHandle userHandle;
+
+ private UserHandleEventForwarder(AbstractUserConnector connector, UserHandle userHandle) {
+ this.connector = connector;
+ this.userHandle = userHandle;
+ }
+
+ @Override
+ public void connectionChanged() {
+ connector.connectionChanged(userHandle);
+ }
+
+ @Override
+ public void availabilityChanged() {
+ connector.availabilityChanged(userHandle);
+ }
+ }
+
+ @Override
+ public void addConnectionListener(UserHandle userHandle, ConnectionListener listener) {
+ userConnection(userHandle).connectionListeners().add(listener);
+ }
+
+ @Override
+ public void removeConnectionListener(UserHandle userHandle, ConnectionListener listener) {
+ userConnection(userHandle).connectionListeners().remove(listener);
+ }
+
+ private void availabilityChanged(UserHandle userHandle) {
+ for (AvailabilityListener listener : userConnection(userHandle).availabilityListeners()) {
+ listener.availabilityChanged();
+ }
+ }
+
+ @Override
+ public void addAvailabilityListener(UserHandle userHandle, AvailabilityListener listener) {
+ userConnection(userHandle).availabilityListeners().add(listener);
+ }
+
+ @Override
+ public void removeAvailabilityListener(UserHandle userHandle, AvailabilityListener listener) {
+ userConnection(userHandle).availabilityListeners().remove(listener);
+ }
+
+ private void connectionChanged(UserHandle userHandle) {
+ for (ConnectionListener listener : userConnection(userHandle).connectionListeners()) {
+ listener.connectionChanged();
+ }
+ }
+
+ @Override
+ public UserConnectionHolder addConnectionHolder(UserHandle userHandle, Object connectionHolder) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return UserConnectionHolder.create(this, userHandle, connectionHolder);
+ }
+ crossProfileSender(userHandle).addConnectionHolder(connectionHolder);
+
+ return UserConnectionHolder.create(this, userHandle, connectionHolder);
+ }
+
+ @Override
+ public void addConnectionHolderAlias(UserHandle userHandle, Object key, Object value) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return;
+ }
+ crossProfileSender(userHandle).addConnectionHolderAlias(key, value);
+ }
+
+ @Override
+ public void removeConnectionHolder(UserHandle userHandle, Object connectionHolder) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return;
+ }
+ crossProfileSender(userHandle).removeConnectionHolder(connectionHolder);
+ }
+
+ @Override
+ public void clearConnectionHolders(UserHandle userHandle) {
+ if (VERSION.SDK_INT < VERSION_CODES.O) {
+ return;
+ }
+ crossProfileSender(userHandle).clearConnectionHolders();
}
/** A builder for an {@link AbstractUserConnector}. */
public static final class Builder {
+ @Nullable UserBinderFactory binderFactory;
@Nullable ScheduledExecutorService scheduledExecutorService;
- @Nullable ConnectionBinder binder;
@Nullable AvailabilityRestrictions availabilityRestrictions;
Context context;
String serviceClassName;
@@ -113,8 +285,8 @@
return this;
}
- public Builder setBinder(ConnectionBinder binder) {
- this.binder = binder;
+ public Builder setBinderFactory(UserBinderFactory binderFactory) {
+ this.binderFactory = binderFactory;
return this;
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/ConnectionBinder.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/ConnectionBinder.java
index 0d793f4..0438b65 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/ConnectionBinder.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/ConnectionBinder.java
@@ -20,6 +20,7 @@
import android.content.ServiceConnection;
import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
import com.google.android.enterprise.connectedapps.exceptions.MissingApiException;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
/** {@link ConnectionBinder} instances are used to establish bindings with other profiles. */
public interface ConnectionBinder {
@@ -37,7 +38,7 @@
ComponentName bindToService,
ServiceConnection connection,
AvailabilityRestrictions availabilityRestrictions)
- throws MissingApiException;
+ throws MissingApiException, UnavailableProfileException;
/**
* Return true if there is a profile available to bind to, while enforcing the passed in {@link
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileConnector.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileConnector.java
index b534152..2a332d5 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileConnector.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossProfileConnector.java
@@ -45,6 +45,9 @@
* Use an alternative {@link ScheduledExecutorService}.
*
* <p>Defaults to {@link Executors#newSingleThreadScheduledExecutor()}.
+ *
+ * <p>This {@link ScheduledExecutorService} must be single threaded or sequential. Failure to do
+ * so will result in undefined behavior when using the SDK.
*/
public Builder setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
implBuilder.setScheduledExecutorService(scheduledExecutorService);
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 9cc287b..0432696 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
@@ -17,9 +17,11 @@
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 static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -33,7 +35,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Parcel;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -41,42 +42,44 @@
import com.google.android.enterprise.connectedapps.exceptions.MissingApiException;
import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
-import com.google.android.enterprise.connectedapps.internal.CrossProfileParcelCallSender;
-import com.google.android.enterprise.connectedapps.internal.ParcelCallReceiver;
-import com.google.android.enterprise.connectedapps.internal.ParcelUtilities;
+import com.google.android.enterprise.connectedapps.internal.BundleCallReceiver;
+import com.google.android.enterprise.connectedapps.internal.BundleUtilities;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.CrossProfileBundleCallSender;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.qual.Nullable;
-/** This class is used internally by the Connected Apps SDK to send messages cross-profile. */
-public class CrossProfileSender {
+/**
+ * This class is used internally by the Connected Apps SDK to send messages across users and
+ * profiles.
+ */
+public final class CrossProfileSender {
- private static final class CrossProfileCall {
+ private static final class CrossProfileCall implements ExceptionCallback {
private final long crossProfileTypeIdentifier;
private final int methodIdentifier;
- private final Parcel params;
+ private final Bundle params;
private final LocalCallback callback;
- private final long timeoutMillis;
CrossProfileCall(
long crossProfileTypeIdentifier,
int methodIdentifier,
- Parcel params,
- LocalCallback callback,
- long timeoutMillis) {
+ Bundle params,
+ LocalCallback callback) {
if (params == null || callback == null) {
throw new NullPointerException();
}
@@ -84,15 +87,10 @@
this.methodIdentifier = methodIdentifier;
this.params = params;
this.callback = callback;
- this.timeoutMillis = timeoutMillis;
- }
-
- void recycle() {
- params.recycle();
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
@@ -103,108 +101,70 @@
return crossProfileTypeIdentifier == that.crossProfileTypeIdentifier
&& methodIdentifier == that.methodIdentifier
&& params.equals(that.params)
- && callback.equals(that.callback)
- && timeoutMillis == that.timeoutMillis;
+ && callback.equals(that.callback);
}
@Override
public int hashCode() {
- return Objects.hash(
- crossProfileTypeIdentifier, methodIdentifier, params, callback, timeoutMillis);
+ return Objects.hash(crossProfileTypeIdentifier, methodIdentifier, params, callback);
+ }
+
+ @Override
+ public void onException(Throwable throwable) {
+ callback.onException(createThrowableBundle(throwable));
}
}
private static final class OngoingCrossProfileCall extends ICrossProfileCallback.Stub {
private final CrossProfileSender sender;
- private final LocalCallback originalCallback;
- private final AtomicBoolean complete = new AtomicBoolean(false);
- private ScheduledFuture<?> timeoutFuture;
- private final long timeoutMillis;
- private final ParcelCallReceiver parcelCallReceiver = new ParcelCallReceiver();
+ private final CrossProfileCall call;
+ private final BundleCallReceiver bundleCallReceiver = new BundleCallReceiver();
- private OngoingCrossProfileCall(
- CrossProfileSender sender, LocalCallback originalCallback, long timeoutMillis) {
- if (sender == null || originalCallback == null) {
+ private OngoingCrossProfileCall(CrossProfileSender sender, CrossProfileCall call) {
+ if (sender == null || call == null) {
throw new NullPointerException();
}
this.sender = sender;
- this.originalCallback = originalCallback;
- this.timeoutMillis = timeoutMillis;
- }
-
- void scheduleTimeout(ScheduledExecutorService timeoutExecutor) {
- if (this.timeoutFuture != null) {
- throw new IllegalStateException("Each call can only have a single timeout scheduled.");
- }
- if (complete.get()) {
- return;
- }
- this.timeoutFuture =
- timeoutExecutor.schedule(this::onTimeout, timeoutMillis, TimeUnit.MILLISECONDS);
- }
-
- private void onTimeout() {
- if (complete.get()) {
- return;
- }
- Parcel throwableParcel =
- createThrowableParcel(
- new UnavailableProfileException(
- "The call timed out after " + timeoutMillis + " milliseconds"));
-
- onException(throwableParcel);
- throwableParcel.recycle();
+ this.call = call;
}
@Override
public void prepareResult(long callId, int blockId, int numBytes, byte[] params) {
- parcelCallReceiver.prepareCall(callId, blockId, numBytes, params);
+ bundleCallReceiver.prepareCall(callId, blockId, numBytes, params);
+ }
+
+ @Override
+ public void prepareBundle(long callId, int bundleId, Bundle bundle) {
+ bundleCallReceiver.prepareBundle(callId, bundleId, bundle);
}
@Override
public void onResult(long callId, int blockId, int methodIdentifier, byte[] paramsBytes) {
- if (complete.getAndSet(true)) {
- return;
- }
- if (timeoutFuture != null) {
- timeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
- }
- sender.ongoingCallComplete(this);
+ sender.removeConnectionHolder(call);
- Parcel parcel = parcelCallReceiver.getPreparedCall(callId, blockId, paramsBytes);
+ Bundle bundle = bundleCallReceiver.getPreparedCall(callId, blockId, paramsBytes);
- originalCallback.onResult(methodIdentifier, parcel);
- parcel.recycle();
-
- sender.maybeScheduleAutomaticDisconnection();
+ call.callback.onResult(methodIdentifier, bundle);
}
@Override
public void onException(long callId, int blockId, byte[] paramsBytes) {
- Parcel parcel = parcelCallReceiver.getPreparedCall(callId, blockId, paramsBytes);
+ Bundle bundle = bundleCallReceiver.getPreparedCall(callId, blockId, paramsBytes);
- onException(parcel);
-
- parcel.recycle();
+ onException(bundle);
}
- public void onException(Parcel exception) {
- if (complete.getAndSet(true)) {
- return;
- }
- if (timeoutFuture != null) {
- timeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
- }
- sender.ongoingCallComplete(this);
+ public void onException(Bundle exception) {
+ sender.removeConnectionHolder(call);
- originalCallback.onException(exception);
+ call.callback.onException(exception);
- sender.maybeScheduleAutomaticDisconnection();
+ sender.scheduledExecutorService.execute(sender::maybeScheduleAutomaticDisconnection);
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
@@ -212,20 +172,17 @@
return false;
}
OngoingCrossProfileCall that = (OngoingCrossProfileCall) o;
- return sender.equals(that.sender)
- && originalCallback.equals(that.originalCallback)
- && complete.equals(that.complete);
+ return sender.equals(that.sender) && call.equals(that.call);
}
@Override
public int hashCode() {
- return Objects.hash(sender, originalCallback, complete);
+ return Objects.hash(sender, call);
}
}
- private void ongoingCallComplete(OngoingCrossProfileCall call) {
- ongoingCrossProfileCalls.removeFirstOccurrence(call);
- }
+ // Temporary variable until deprecated methods are removed
+ public static final Object MANUAL_MANAGEMENT_CONNECTION_HOLDER = new Object();
public static final int MAX_BYTES_PER_BLOCK = 250000;
@@ -233,38 +190,115 @@
private static final long INITIAL_BIND_RETRY_DELAY_MS = 500;
private static final int DEFAULT_AUTOMATIC_DISCONNECTION_TIMEOUT_SECONDS = 30;
- private final ScheduledExecutorService scheduledExecutorService;
- private final Context context;
- private final ComponentName bindToService;
- private boolean canUseReflectedApis;
- private long bindRetryDelayMs = 500;
- private AtomicBoolean isBinding = new AtomicBoolean(false);
- private final AtomicReference<ICrossProfileService> iCrossProfileService =
- new AtomicReference<>();
- private final ConnectionListener connectionListener;
- private final AvailabilityListener availabilityListener;
- private final ConnectionBinder binder;
- @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<>();
- private ConcurrentLinkedDeque<CrossProfileCall> asyncCallQueue = new ConcurrentLinkedDeque<>();
-
private static final int NONE = 0;
private static final int UNAVAILABLE = 1;
private static final int AVAILABLE = 2;
private static final int DISCONNECTED = UNAVAILABLE;
private static final int CONNECTED = AVAILABLE;
- private ScheduledFuture<?> scheduledTryBind;
+ private final ScheduledExecutorService scheduledExecutorService;
+ private final Context context;
+ private final ComponentName bindToService;
+ private final boolean canUseReflectedApis;
+ private final ConnectionListener connectionListener;
+ private final AvailabilityListener availabilityListener;
+ private final ConnectionBinder binder;
+ private final AvailabilityRestrictions availabilityRestrictions;
+ private final AtomicReference<@Nullable ICrossProfileService> iCrossProfileService =
+ new AtomicReference<>();
+ private final AtomicReference<@Nullable ScheduledFuture<?>> scheduledTryBind =
+ new AtomicReference<>();
+ private final AtomicReference<ScheduledFuture<?>> scheduledBindTimeout = new AtomicReference<>();
+
+ // Interaction with connectionHolders, and connectionHolderAliases must
+ // take place on the scheduled executor thread
+ private final Set<Object> connectionHolders = Collections.newSetFromMap(new WeakHashMap<>());
+ private final Map<Object, Set<Object>> connectionHolderAliases = new WeakHashMap<>();
+ private final Set<ExceptionCallback> unavailableProfileExceptionWatchers =
+ Collections.newSetFromMap(new ConcurrentHashMap<>());
+ private final ConcurrentLinkedDeque<CrossProfileCall> asyncCallQueue =
+ new ConcurrentLinkedDeque<>();
+
+ private final ServiceConnection connection =
+ new ServiceConnection() {
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.e(LOG_TAG, "onBindingDied for component " + name);
+ scheduledExecutorService.execute(
+ () -> onBindingAttemptFailed("onBindingDied", /* terminal= */ true));
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.e(LOG_TAG, "onNullBinding for component " + name);
+ scheduledExecutorService.execute(
+ () -> onBindingAttemptFailed("onNullBinding", /* terminal= */ true));
+ }
+
+ // Called when the connection with the service is established
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(LOG_TAG, "onServiceConnected for component " + name);
+ scheduledExecutorService.execute(
+ () -> {
+ if (connectionHolders.isEmpty()) {
+ Log.i(LOG_TAG, "Connected but no holders. Disconnecting.");
+ unbind();
+ return;
+ }
+ iCrossProfileService.set(ICrossProfileService.Stub.asInterface(service));
+
+ tryMakeAsyncCalls();
+ checkConnected();
+ onBindingAttemptSucceeded();
+ });
+ }
+
+ // Called when the connection with the service disconnects unexpectedly
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.e(LOG_TAG, "Unexpected disconnection for component " + name);
+ attemptReconnect();
+ }
+
+ private void attemptReconnect() {
+ scheduledExecutorService.execute(
+ () -> {
+ unbind();
+ throwUnavailableException(
+ new UnavailableProfileException("Lost connection to other profile"));
+ // These disconnections can be temporary - so to avoid an exception on an async
+ // call leading to bad user experience - we send the availability update again
+ // to prompt a retry/refresh
+ updateAvailability();
+ checkConnected();
+ cancelAutomaticDisconnection();
+ bind();
+ });
+ }
+ };
+
+ // 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 static final BroadcastReceiver profileAvailabilityReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ for (CrossProfileSender sender : senders) {
+ sender.scheduledExecutorService.execute(sender::checkAvailability);
+ }
+ }
+ };
+
+ private final AtomicReference<ScheduledFuture<Void>> automaticDisconnectionFuture =
+ new AtomicReference<>();
+ private volatile @Nullable CountDownLatch manuallyBindLatch;
+
+ private long bindRetryDelayMs = INITIAL_BIND_RETRY_DELAY_MS;
private int lastReportedAvailabilityStatus = NONE;
private int lastReportedConnectedStatus = NONE;
@@ -296,94 +330,33 @@
beginMonitoringAvailabilityChanges();
}
- private static final BroadcastReceiver profileAvailabilityReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- for (CrossProfileSender sender : senders) {
- sender.scheduledExecutorService.execute(sender::checkAvailability);
- }
- }
- };
-
- private final ServiceConnection connection =
- new ServiceConnection() {
- // Called when the connection with the service is established
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- scheduledExecutorService.execute(
- () -> {
- if (!isBinding.get()) {
- unbind();
- return;
- }
- iCrossProfileService.set(ICrossProfileService.Stub.asInterface(service));
-
- tryMakeAsyncCalls();
- checkConnected();
- onBindingAttemptSucceeded();
- });
- }
-
- // Called when the connection with the service disconnects unexpectedly
- @Override
- public void onServiceDisconnected(ComponentName className) {
- scheduledExecutorService.execute(
- () -> {
- Log.e(LOG_TAG, "Unexpected disconnection");
- if (!asyncCallQueue.isEmpty() || !ongoingCrossProfileCalls.isEmpty()) {
- Log.d(LOG_TAG, "Found in progress calls");
- throwExceptionForAsyncCalls(
- new UnavailableProfileException("Lost connection to other profile"));
- // These disconnections can be temporary - so to avoid an exception on an async
- // call leading to bad user experience - we send the availability update again
- // to prompt a retry/refresh
- updateAvailability();
- }
- iCrossProfileService.set(null);
- checkConnected();
- cancelAutomaticDisconnection();
- startTryBinding();
- });
- }
- };
-
- private final Object automaticDisconnectionFutureLock = new Object();
-
private void cancelAutomaticDisconnection() {
- if (automaticDisconnectionFuture != null) {
- synchronized (automaticDisconnectionFutureLock) {
- if (automaticDisconnectionFuture != null) {
- automaticDisconnectionFuture.cancel(/* mayInterruptIfRunning= */ true);
- automaticDisconnectionFuture = null;
- }
- }
+ ScheduledFuture<?> disconnectionFuture = automaticDisconnectionFuture.getAndSet(null);
+ if (disconnectionFuture != null) {
+ disconnectionFuture.cancel(/* mayInterruptIfRunning= */ true);
}
}
private void maybeScheduleAutomaticDisconnection() {
- if (!isManuallyManagingConnection
- && asyncCallQueue.isEmpty()
- && ongoingCrossProfileCalls.isEmpty()
- && isBound()
- && automaticDisconnectionFuture == null) {
- synchronized (automaticDisconnectionFutureLock) {
- if (automaticDisconnectionFuture == null) {
- automaticDisconnectionFuture =
- scheduledExecutorService.schedule(
- this::automaticallyDisconnect,
- DEFAULT_AUTOMATIC_DISCONNECTION_TIMEOUT_SECONDS,
- TimeUnit.SECONDS);
- }
+ // Always called on scheduled executor service thread
+ if (connectionHolders.isEmpty() && isBound()) {
+ Log.i(LOG_TAG, "Scheduling automatic disconnection");
+ ScheduledFuture<Void> scheduledDisconnection =
+ scheduledExecutorService.schedule(
+ this::automaticallyDisconnect,
+ DEFAULT_AUTOMATIC_DISCONNECTION_TIMEOUT_SECONDS,
+ SECONDS);
+
+ if (!automaticDisconnectionFuture.compareAndSet(null, scheduledDisconnection)) {
+ Log.i(LOG_TAG, "Already scheduled");
+ scheduledDisconnection.cancel(/* mayInterruptIfRunning= */ true);
}
}
}
private Void automaticallyDisconnect() {
- if (!isManuallyManagingConnection
- && asyncCallQueue.isEmpty()
- && ongoingCrossProfileCalls.isEmpty()
- && isBound()) {
+ // Always called on scheduled executor service thread
+ if (connectionHolders.isEmpty() && isBound()) {
unbind();
}
return null;
@@ -403,9 +376,7 @@
context.registerReceiver(profileAvailabilityReceiver, filter);
}
- private volatile CountDownLatch manuallyBindLatch;
-
- void manuallyBind() throws UnavailableProfileException {
+ void manuallyBind(Object connectionHolder) throws UnavailableProfileException {
Log.e(LOG_TAG, "Calling manuallyBind");
if (isRunningOnUIThread()) {
throw new IllegalStateException("connect()/manuallyBind() cannot be called from UI thread");
@@ -420,7 +391,11 @@
}
cancelAutomaticDisconnection();
- isManuallyManagingConnection = true;
+
+ scheduledExecutorService.execute(
+ () -> {
+ connectionHolders.add(connectionHolder);
+ });
if (isBound()) {
// If we're already bound there's no need to block the thread
@@ -447,8 +422,8 @@
}
if (!isBound()) {
- unbind(); // ensure we don't continue trying to connect if we throw an exception
- isManuallyManagingConnection = false;
+ unbind();
+ scheduledExecutorService.execute(() -> removeConnectionHolderAndAliases(connectionHolder));
throw new UnavailableProfileException("Profile not available");
}
}
@@ -457,58 +432,46 @@
return Looper.myLooper() == Looper.getMainLooper();
}
- /**
- * Start trying to bind to the other profile and start manually managing the connection.
- *
- * <p>This will mean that the connection will not be dropped automatically to save resources.
- *
- * <p>Must be called before interacting with synchronous cross-profile methods.
- */
- void startManuallyBinding() {
- cancelAutomaticDisconnection();
- isManuallyManagingConnection = true;
- bind();
- }
-
- /**
- * Stop manual connection management.
- *
- * <p>This can be called after {@link #startManuallyBinding()} or {@link #manuallyBind()} to
- * return connection management responsibilities to the SDK.
- *
- * <p>You should not make any synchronous cross-profile calls after calling this method.
- */
- public void stopManualConnectionManagement() {
- isManuallyManagingConnection = false;
- maybeScheduleAutomaticDisconnection();
- }
-
- /**
- * Attempt to bind to the other profile.
- *
- * <p>This will continually attempt to form a binding to the other profile in a background thread.
- */
private void bind() {
- if (isBinding.getAndSet(true)) {
- return;
- }
-
- startTryBinding();
+ bindRetryDelayMs = INITIAL_BIND_RETRY_DELAY_MS;
+ scheduledExecutorService.execute(this::tryBind);
}
private void onBindingAttemptSucceeded() {
+ clearScheduledBindTimeout();
Log.i(LOG_TAG, "Binding attempt succeeded");
checkTriggerManualConnectionLock();
}
private void onBindingAttemptFailed(String reason) {
- onBindingAttemptFailed(reason, /* terminal= */ false);
+ onBindingAttemptFailed(reason, /* exception= */ null, /* terminal= */ false);
+ }
+
+ private void onBindingAttemptFailed(Exception exception) {
+ onBindingAttemptFailed(exception.getMessage(), exception, /* terminal= */ false);
+ }
+
+ private void onBindingAttemptFailed(String reason, Exception exception) {
+ onBindingAttemptFailed(reason, exception, /* terminal= */ false);
}
private void onBindingAttemptFailed(String reason, boolean terminal) {
- Log.i(LOG_TAG, "Binding attempt failed: " + reason);
- throwExceptionForAsyncCalls(new UnavailableProfileException(reason));
- if (terminal || !isManuallyManagingConnection || manuallyBindLatch != null) {
+ onBindingAttemptFailed(reason, /* exception= */ null, terminal);
+ }
+
+ private void onBindingAttemptFailed(
+ String reason, @Nullable Exception exception, boolean terminal) {
+ // Always called on scheduled executor service thread
+ clearScheduledBindTimeout();
+ if (exception == null) {
+ Log.i(LOG_TAG, "Binding attempt failed: " + reason);
+ throwUnavailableException(new UnavailableProfileException(reason));
+ } else {
+ Log.i(LOG_TAG, "Binding attempt failed: " + reason, exception);
+ throwUnavailableException(new UnavailableProfileException(reason, exception));
+ }
+
+ if (terminal || connectionHolders.isEmpty() || manuallyBindLatch != null) {
unbind();
checkTriggerManualConnectionLock();
} else {
@@ -516,6 +479,13 @@
}
}
+ private void clearScheduledBindTimeout() {
+ ScheduledFuture<?> scheduledTimeout = scheduledBindTimeout.getAndSet(null);
+ if (scheduledTimeout != null) {
+ scheduledTimeout.cancel(/* mayInterruptIfRunning= */ true);
+ }
+ }
+
private void checkTriggerManualConnectionLock() {
if (manuallyBindLatch != null) {
synchronized (this) {
@@ -532,16 +502,16 @@
*
* <p>If there is already a binding present, it will be killed.
*/
- void unbind() {
+ private void unbind() {
Log.i(LOG_TAG, "Unbind");
- throwExceptionForAsyncCalls(new UnavailableProfileException("No profile available"));
- isBinding.set(false);
if (isBound()) {
context.unbindService(connection);
iCrossProfileService.set(null);
checkConnected();
cancelAutomaticDisconnection();
}
+ clearScheduledBindTimeout();
+ throwUnavailableException(new UnavailableProfileException("No profile available"));
checkTriggerManualConnectionLock();
}
@@ -549,17 +519,13 @@
return binder.bindingIsPossible(context, availabilityRestrictions);
}
- private void startTryBinding() {
- bindRetryDelayMs = INITIAL_BIND_RETRY_DELAY_MS;
- scheduledExecutorService.execute(this::tryBind);
- }
-
private void tryBind() {
+ // Always called on scheduled executor service thread
Log.i(LOG_TAG, "Attempting to bind");
- if (scheduledTryBind != null) {
- scheduledTryBind.cancel(/* mayInterruptIfRunning= */ false);
- scheduledTryBind = null;
+ ScheduledFuture<?> scheduledFuture = scheduledTryBind.getAndSet(null);
+ if (scheduledFuture != null) {
+ scheduledFuture.cancel(/* mayInterruptIfRunning= */ false);
}
if (!canUseReflectedApis) {
@@ -567,13 +533,14 @@
return;
}
- if (!isBinding.get()) {
- onBindingAttemptFailed("Not trying to bind");
+ if (isBound()) {
+ Log.i(LOG_TAG, "Already bound");
+ onBindingAttemptSucceeded();
return;
}
- if (isBound()) {
- onBindingAttemptSucceeded();
+ if (connectionHolders.isEmpty()) {
+ onBindingAttemptFailed("Not trying to bind");
return;
}
@@ -587,24 +554,43 @@
return;
}
+ if (scheduledBindTimeout.get() != null) {
+ Log.i(LOG_TAG, "Already waiting to bind");
+ return;
+ }
+
try {
+ // Schedule a timeout in case something happens and we never reach onServiceConnected
+ scheduledBindTimeout.set(scheduledExecutorService.schedule(this::timeoutBinding, 1, MINUTES));
if (!binder.tryBind(context, bindToService, connection, availabilityRestrictions)) {
- onBindingAttemptFailed("No profile available or app not installed in other profile");
+ onBindingAttemptFailed(
+ "No profile available, app not installed in other profile, or service not included in"
+ + " manifest");
+ } else {
+ Log.i(LOG_TAG, "binder.tryBind returned true, expecting onServiceConnected");
}
} catch (MissingApiException e) {
Log.e(LOG_TAG, "MissingApiException when trying to bind", e);
- onBindingAttemptFailed("Missing API");
+ onBindingAttemptFailed("Missing API", e);
+ } catch (UnavailableProfileException e) {
+ Log.e(LOG_TAG, "Error while trying to bind", e);
+ onBindingAttemptFailed(e);
}
}
+ private void timeoutBinding() {
+ onBindingAttemptFailed("Timed out while waiting for onServiceConnected");
+ }
+
private void scheduleBindAttempt() {
- if (scheduledTryBind != null && !scheduledTryBind.isDone()) {
+ ScheduledFuture<?> scheduledFuture = scheduledTryBind.get();
+ if (scheduledFuture != null && !scheduledFuture.isDone()) {
return;
}
bindRetryDelayMs *= 2;
- scheduledTryBind =
- scheduledExecutorService.schedule(this::tryBind, bindRetryDelayMs, TimeUnit.MILLISECONDS);
+ scheduledTryBind.set(
+ scheduledExecutorService.schedule(this::tryBind, bindRetryDelayMs, MILLISECONDS));
}
boolean isBound() {
@@ -614,18 +600,18 @@
/**
* Make a synchronous cross-profile call.
*
- * @return A {@link Parcel} containing the return value. This must be recycled after use.
+ * @return A {@link Bundle} containing the return value under the key \"return\".
* @throws UnavailableProfileException if a connection is not already established
*/
- public Parcel call(long crossProfileTypeIdentifier, int methodIdentifier, Parcel params)
- throws UnavailableProfileException {
+ public Bundle call(long crossProfileTypeIdentifier, int methodIdentifier, Bundle params)
+ throws UnavailableProfileException {
try {
return callWithExceptions(crossProfileTypeIdentifier, methodIdentifier, params);
} catch (UnavailableProfileException | RuntimeException | Error e) {
StackTraceElement[] remoteStack = e.getStackTrace();
StackTraceElement[] localStack = Thread.currentThread().getStackTrace();
StackTraceElement[] totalStack =
- Arrays.copyOf(remoteStack, remoteStack.length + localStack.length - 1);
+ 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);
@@ -638,100 +624,71 @@
/**
* Make a synchronous cross-profile call which expects some checked exceptions to be thrown.
*
- * <p>Behaves the same as {@link #call(long, int, Parcel)} except that it deals with checked
+ * <p>Behaves the same as {@link #call(long, int, Bundle)} except that it deals with checked
* exceptions by throwing {@link Throwable}.
*
- * @return A {@link Parcel} containing the return value. This must be recycled after use.
+ * @return A {@link Bundle} containing the return value under the "return" key.
* @throws UnavailableProfileException if a connection is not already established
*/
- public Parcel callWithExceptions(
- long crossProfileTypeIdentifier, int methodIdentifier, Parcel params) throws Throwable {
-
- if (!isBound()) {
+ public Bundle callWithExceptions(
+ long crossProfileTypeIdentifier, int methodIdentifier, Bundle params) throws Throwable {
+ ICrossProfileService service = iCrossProfileService.get();
+ if (service == null) {
throw new UnavailableProfileException("Could not access other profile");
}
- if (!isManuallyManagingConnection) {
- throw new UnavailableProfileException(
- "Synchronous calls can only be used when manually connected");
- }
+ CrossProfileBundleCallSender callSender =
+ new CrossProfileBundleCallSender(
+ service, crossProfileTypeIdentifier, methodIdentifier, /* callback= */ null);
+ Bundle returnBundle = callSender.makeBundleCall(params);
- CrossProfileParcelCallSender callSender =
- new CrossProfileParcelCallSender(
- iCrossProfileService.get(),
- crossProfileTypeIdentifier,
- methodIdentifier,
- /* callback= */ null);
- Parcel parcel = callSender.makeParcelCall(params); // Recycled by caller
- boolean hasError = parcel.readInt() == 1;
-
- if (hasError) {
- Throwable t = ParcelUtilities.readThrowableFromParcel(parcel);
+ if (returnBundle.containsKey("throwable")) {
+ Throwable t = BundleUtilities.readThrowableFromBundle(returnBundle, "throwable");
if (t instanceof RuntimeException) {
- throw new ProfileRuntimeException((RuntimeException) t);
+ throw new ProfileRuntimeException(t);
}
throw t;
}
- return parcel;
+ return returnBundle;
}
- /**
- * Make an asynchronous cross-profile call.
- *
- * @param params These will be cached and will be recycled after the call is complete.
- */
+ /** Make an asynchronous cross-profile call. */
public void callAsync(
long crossProfileTypeIdentifier,
int methodIdentifier,
- Parcel params,
+ Bundle params,
LocalCallback callback,
- long timeoutMillis) {
-
- cancelAutomaticDisconnection();
-
- asyncCallQueue.add(
- new CrossProfileCall(
- crossProfileTypeIdentifier, methodIdentifier, params, callback, timeoutMillis));
-
- tryMakeAsyncCalls();
- if (isManuallyManagingConnection) {
- if (!isBindingPossible()) {
- throwExceptionForAsyncCalls(new UnavailableProfileException("Profile not available"));
- }
- } else {
- bind();
+ Object connectionHolderAlias) {
+ if (!isBindingPossible()) {
+ throwUnavailableException(new UnavailableProfileException("Profile not available"));
}
+
+ scheduledExecutorService.execute(
+ () -> {
+ CrossProfileCall crossProfileCall =
+ new CrossProfileCall(crossProfileTypeIdentifier, methodIdentifier, params, callback);
+ connectionHolders.add(crossProfileCall);
+ cancelAutomaticDisconnection();
+ addConnectionHolderAlias(connectionHolderAlias, crossProfileCall);
+ unavailableProfileExceptionWatchers.add(crossProfileCall);
+
+ asyncCallQueue.add(crossProfileCall);
+
+ tryMakeAsyncCalls();
+ bind();
+ });
}
- private void throwExceptionForAsyncCalls(Throwable throwable) {
- Parcel throwableParcel = createThrowableParcel(throwable);
-
- while (true) {
- CrossProfileCall call = asyncCallQueue.pollFirst();
- if (call == null) {
- break;
- }
-
- call.callback.onException(throwableParcel);
- throwableParcel.setDataPosition(0);
- call.recycle();
+ private void throwUnavailableException(Throwable throwable) {
+ for (ExceptionCallback callback : unavailableProfileExceptionWatchers) {
+ removeConnectionHolder(callback);
+ callback.onException(throwable);
}
-
- while (true) {
- OngoingCrossProfileCall call = ongoingCrossProfileCalls.pollFirst();
- if (call == null) {
- break;
- }
-
- call.onException(throwableParcel);
- throwableParcel.setDataPosition(0);
- }
-
- throwableParcel.recycle();
}
private void tryMakeAsyncCalls() {
+ Log.i(LOG_TAG, "tryMakeAsyncCalls");
if (!isBound()) {
return;
}
@@ -740,46 +697,43 @@
}
private void drainAsyncQueue() {
+ Log.i(LOG_TAG, "drainAsyncQueue");
while (true) {
CrossProfileCall call = asyncCallQueue.pollFirst();
if (call == null) {
- break;
+ return;
}
- OngoingCrossProfileCall ongoingCall =
- new OngoingCrossProfileCall(this, call.callback, call.timeoutMillis);
- ongoingCrossProfileCalls.add(ongoingCall);
+ OngoingCrossProfileCall ongoingCall = new OngoingCrossProfileCall(this, call);
try {
- CrossProfileParcelCallSender callSender =
- new CrossProfileParcelCallSender(
- iCrossProfileService.get(),
- call.crossProfileTypeIdentifier,
- call.methodIdentifier,
- ongoingCall);
- Parcel p = callSender.makeParcelCall(call.params);
+ ICrossProfileService service = iCrossProfileService.get();
+ if (service == null) {
+ Log.w(LOG_TAG, "OngoingCrossProfileCall: not bound anymore, adding back to queue");
+ asyncCallQueue.add(call);
+ return;
+ }
+ CrossProfileBundleCallSender callSender =
+ new CrossProfileBundleCallSender(
+ service, call.crossProfileTypeIdentifier, call.methodIdentifier, ongoingCall);
- boolean hasError = p.readInt() == 1;
- call.recycle();
+ Bundle p = callSender.makeBundleCall(call.params);
- if (hasError) {
+ if (p.containsKey("throwable")) {
RuntimeException exception =
- (RuntimeException) ParcelUtilities.readThrowableFromParcel(p);
- p.recycle();
- ongoingCrossProfileCalls.remove(ongoingCall);
+ (RuntimeException) BundleUtilities.readThrowableFromBundle(p, "throwable");
+ removeConnectionHolder(ongoingCall.call);
throw new ProfileRuntimeException(exception);
}
-
- p.recycle();
- ongoingCall.scheduleTimeout(scheduledExecutorService);
} catch (UnavailableProfileException e) {
- ongoingCrossProfileCalls.remove(ongoingCall);
+ Log.w(
+ LOG_TAG, "OngoingCrossProfileCall: UnavailableProfileException, adding back to queue");
asyncCallQueue.add(call);
return;
}
}
}
- void checkAvailability() {
+ private void checkAvailability() {
if (isBindingPossible() && (lastReportedAvailabilityStatus != AVAILABLE)) {
updateAvailability();
} else if (!isBindingPossible() && (lastReportedAvailabilityStatus != UNAVAILABLE)) {
@@ -787,39 +741,31 @@
}
}
- void updateAvailability() {
- scheduledExecutorService.execute(availabilityListener::availabilityChanged);
+ private void updateAvailability() {
+ // This is only executed on the executor thread
+ availabilityListener.availabilityChanged();
lastReportedAvailabilityStatus = isBindingPossible() ? AVAILABLE : UNAVAILABLE;
}
- void checkConnected() {
+ private void checkConnected() {
+ // This is only executed on the executor thread
if (isBound() && lastReportedConnectedStatus != CONNECTED) {
- scheduledExecutorService.execute(connectionListener::connectionChanged);
+ connectionListener.connectionChanged();
lastReportedConnectedStatus = CONNECTED;
} else if (!isBound() && lastReportedConnectedStatus != DISCONNECTED) {
- scheduledExecutorService.execute(connectionListener::connectionChanged);
+ connectionListener.connectionChanged();
lastReportedConnectedStatus = DISCONNECTED;
}
}
- boolean isManuallyManagingConnection() {
- return isManuallyManagingConnection;
+ /** Create a {@link Bundle} containing a {@link Throwable}. */
+ private static Bundle createThrowableBundle(Throwable throwable) {
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ BundleUtilities.writeThrowableToBundle(bundle, "throwable", throwable);
+ return bundle;
}
- /**
- * Create a {@link Parcel} containing a {@link Throwable}.
- *
- * <p>The {@link Parcel} must be recycled after use.
- */
- private static Parcel createThrowableParcel(Throwable throwable) {
- Parcel parcel = Parcel.obtain(); // Recycled by caller
- ParcelUtilities.writeThrowableToParcel(parcel, throwable);
- parcel.setDataPosition(0);
- return parcel;
- }
-
- @Nullable
- static UserHandle getOtherUserHandle(
+ static @Nullable UserHandle getOtherUserHandle(
Context context, AvailabilityRestrictions availabilityRestrictions) {
if (VERSION.SDK_INT < VERSION_CODES.P) {
// CrossProfileApps was introduced in P
@@ -835,8 +781,7 @@
return selectUserHandleToBind(context, otherUsers);
}
- @Nullable
- private static UserHandle findDifferentRunningUser(
+ private static @Nullable UserHandle findDifferentRunningUser(
Context context,
UserHandle ignoreUserHandle,
AvailabilityRestrictions availabilityRestrictions) {
@@ -854,4 +799,81 @@
return selectUserHandleToBind(context, otherUsers);
}
+
+ void addConnectionHolder(Object o) {
+ scheduledExecutorService.execute(
+ () -> {
+ connectionHolders.add(o);
+
+ cancelAutomaticDisconnection();
+ bind();
+ });
+ }
+
+ void removeConnectionHolder(Object o) {
+ if (o == null) {
+ throw new NullPointerException("Connection holder cannot be null");
+ }
+
+ scheduledExecutorService.execute(
+ () -> {
+ removeConnectionHolderAndAliases(o);
+
+ maybeScheduleAutomaticDisconnection();
+ });
+ }
+
+ void clearConnectionHolders() {
+ scheduledExecutorService.execute(
+ () -> {
+ connectionHolders.clear();
+ connectionHolderAliases.clear();
+
+ maybeScheduleAutomaticDisconnection();
+ });
+ }
+
+ private void removeConnectionHolderAndAliases(Object o) {
+ // Always called on scheduled executor thread
+ Set<Object> aliases = connectionHolderAliases.get(o);
+ if (aliases != null) {
+ connectionHolderAliases.remove(o);
+ for (Object alias : aliases) {
+ removeConnectionHolderAndAliases(alias);
+ }
+ }
+
+ connectionHolders.remove(o);
+ unavailableProfileExceptionWatchers.remove(o);
+ }
+
+ /**
+ * Registers a connection holder alias.
+ *
+ * <p>This means that if the key is removed, then the value will also be removed. If the value is
+ * removed, the key will not be removed.
+ */
+ void addConnectionHolderAlias(Object key, Object value) {
+ scheduledExecutorService.execute(
+ () -> {
+ Set<Object> aliases = connectionHolderAliases.get(key);
+ if (aliases == null) {
+ aliases = Collections.newSetFromMap(new WeakHashMap<>());
+ }
+
+ aliases.add(value);
+
+ connectionHolderAliases.put(key, aliases);
+ });
+ }
+
+ /**
+ * Clear static state.
+ *
+ * <p>This should not be required in production.
+ */
+ public static void clearStaticState() {
+ isMonitoringAvailabilityChanges.set(false);
+ senders.clear();
+ }
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossUserConnector.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossUserConnector.java
index 2e7dc66..67d1567 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossUserConnector.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/CrossUserConnector.java
@@ -42,6 +42,9 @@
* Use an alternative {@link ScheduledExecutorService}.
*
* <p>Defaults to {@link Executors#newSingleThreadScheduledExecutor()}.
+ *
+ * <p>This {@link ScheduledExecutorService} must be single threaded or sequential. Failure to do
+ * so will result in undefined behavior when using the SDK.
*/
public Builder setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
implBuilder.setScheduledExecutorService(scheduledExecutorService);
@@ -49,16 +52,6 @@
}
/**
- * Specify an alternative {@link ConnectionBinder} for managing the connection.
- *
- * <p>Defaults to {@link DefaultProfileBinder}.
- */
- public Builder setBinder(ConnectionBinder binder) {
- implBuilder.setBinder(binder);
- return this;
- }
-
- /**
* Specify which set of restrictions should be applied to checking availability.
*
* <p>Defaults to {@link AvailabilityRestrictions#DEFAULT}, which requires that a user be
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/DefaultUserBinder.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/DefaultUserBinder.java
new file mode 100644
index 0000000..09cf908
--- /dev/null
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/DefaultUserBinder.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps;
+
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.CrossProfileApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
+import com.google.android.enterprise.connectedapps.exceptions.MissingApiException;
+
+/**
+ * A default {@link ConnectionBinder} for connecting to a specific user.
+ *
+ * <p>Methods expect that the app has INTERACT_ACROSS_USERS or INTERACT_ACROSS_PROFILES permission.
+ */
+public class DefaultUserBinder implements ConnectionBinder {
+
+ private static final String LOG_TAG = "DefaultUserBinder";
+
+ private static final String INTERACT_ACROSS_USERS_FULL =
+ "android.permission.INTERACT_ACROSS_USERS_FULL";
+
+ private boolean hasCachedPermissionRequests = false;
+ private boolean requestsInteractAcrossProfiles = false;
+ private boolean requestsInteractAcrossUsersFull = false;
+
+ private final UserHandle userHandle;
+
+ public DefaultUserBinder(UserHandle userHandle) {
+ this.userHandle = userHandle;
+ }
+
+ @Override
+ public boolean tryBind(
+ Context context,
+ ComponentName bindToService,
+ ServiceConnection connection,
+ AvailabilityRestrictions availabilityRestrictions)
+ throws MissingApiException {
+ Intent bindIntent = new Intent();
+ bindIntent.setComponent(bindToService);
+
+ boolean hasBound =
+ ReflectionUtilities.bindServiceAsUser(context, bindIntent, connection, userHandle);
+ if (!hasBound) {
+ context.unbindService(connection);
+ }
+ return hasBound;
+ }
+
+ @Override
+ public boolean bindingIsPossible(
+ Context context, AvailabilityRestrictions availabilityRestrictions) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ return userManager.isUserRunning(userHandle)
+ && userManager.isUserUnlocked(userHandle)
+ && !userManager.isQuietModeEnabled(userHandle);
+ }
+
+ @Override
+ public boolean hasPermissionToBind(Context context) {
+ cachePermissionRequests(context);
+
+ if (VERSION.SDK_INT >= VERSION_CODES.R
+ && requestsInteractAcrossProfiles
+ && context
+ .getSystemService(CrossProfileApps.class)
+ .getTargetUserProfiles()
+ .contains(userHandle)
+ && context.getSystemService(CrossProfileApps.class).canInteractAcrossProfiles()) {
+ return true;
+ }
+
+ if (requestsInteractAcrossUsersFull
+ && context.checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void cachePermissionRequests(Context context) {
+ if (hasCachedPermissionRequests) {
+ return;
+ }
+
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ PackageInfo packageInfo =
+ packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
+
+ if (packageInfo == null || packageInfo.requestedPermissions == null) {
+ hasCachedPermissionRequests = true;
+ return;
+ }
+
+ for (String permission : packageInfo.requestedPermissions) {
+ if (permission.equals(INTERACT_ACROSS_PROFILES)) {
+ requestsInteractAcrossProfiles = true;
+ } else if (permission.equals(INTERACT_ACROSS_USERS_FULL)) {
+ requestsInteractAcrossUsersFull = true;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "Could not find package.", e);
+ requestsInteractAcrossProfiles = false;
+ requestsInteractAcrossUsersFull = false;
+ }
+
+ hasCachedPermissionRequests = true;
+ }
+}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/DpcUserBinder.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/DpcUserBinder.java
new file mode 100644
index 0000000..60f378d
--- /dev/null
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/DpcUserBinder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps;
+
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.UserHandle;
+import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
+
+/** A {@link ConnectionBinder} used by Device Policy Controllers for binding across users. */
+public class DpcUserBinder implements ConnectionBinder {
+
+ private final UserHandle userHandle;
+
+ private final ComponentName deviceAdminReceiver;
+
+ public DpcUserBinder(ComponentName deviceAdminReceiver, UserHandle userHandle) {
+ if (userHandle == null) {
+ throw new NullPointerException();
+ }
+ if (deviceAdminReceiver == null) {
+ throw new NullPointerException();
+ }
+
+ this.userHandle = userHandle;
+ this.deviceAdminReceiver = deviceAdminReceiver;
+ }
+
+ @Override
+ public boolean tryBind(
+ Context context,
+ ComponentName bindToService,
+ ServiceConnection connection,
+ AvailabilityRestrictions availabilityRestrictions) {
+ DevicePolicyManager devicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ Intent bindIntent = new Intent();
+ bindIntent.setComponent(bindToService);
+ boolean hasBound =
+ devicePolicyManager.bindDeviceAdminServiceAsUser(
+ deviceAdminReceiver, bindIntent, connection, Context.BIND_AUTO_CREATE, userHandle);
+ if (!hasBound) {
+ context.unbindService(connection);
+ }
+ return hasBound;
+ }
+
+ @Override
+ public boolean bindingIsPossible(
+ Context context, AvailabilityRestrictions availabilityRestrictions) {
+ return true;
+ }
+
+ @Override
+ public boolean hasPermissionToBind(Context context) {
+ return true;
+ }
+}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/FutureWrapper.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/FutureWrapper.java
index 96d0fd7..b797f0e 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/FutureWrapper.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/FutureWrapper.java
@@ -15,10 +15,10 @@
*/
package com.google.android.enterprise.connectedapps;
-import android.os.Parcel;
+import android.os.Bundle;
+import com.google.android.enterprise.connectedapps.internal.BundleUtilities;
import com.google.android.enterprise.connectedapps.internal.Bundler;
import com.google.android.enterprise.connectedapps.internal.BundlerType;
-import com.google.android.enterprise.connectedapps.internal.ParcelUtilities;
/** Wrapper for adding support for a future type to the Connected Apps SDK. */
public abstract class FutureWrapper<E> implements LocalCallback {
@@ -34,9 +34,9 @@
}
@Override
- public void onResult(int methodIdentifier, Parcel params) {
+ public void onResult(int methodIdentifier, Bundle params) {
@SuppressWarnings("unchecked")
- E result = (E) bundler.readFromParcel(params, bundlerType);
+ E result = (E) bundler.readFromBundle(params, "result", bundlerType);
onResult(result);
}
@@ -44,8 +44,8 @@
public abstract void onResult(E result);
@Override
- public void onException(Parcel exception) {
- Throwable throwable = ParcelUtilities.readThrowableFromParcel(exception);
+ public void onException(Bundle exception) {
+ Throwable throwable = BundleUtilities.readThrowableFromBundle(exception, "throwable");
onException(throwable);
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/LocalCallback.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/LocalCallback.java
index 6c27bb1..6abb0eb 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/LocalCallback.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/LocalCallback.java
@@ -15,11 +15,11 @@
*/
package com.google.android.enterprise.connectedapps;
-import android.os.Parcel;
+import android.os.Bundle;
/**
* Interface used by callbacks used when calling {@link CrossProfileSender#callAsync(long, int,
- * Parcel, LocalCallback, long)}.
+ * Bundle, LocalCallback, Object, long)}.
*/
public interface LocalCallback {
@@ -27,14 +27,14 @@
* Pass a result into the callback.
*
* @param methodIdentifier The method being responded to.
- * @param params The result encoded in a {@link Parcel}. This should not be recycled.
+ * @param params A Bundle containing the result under the key "result".
*/
- void onResult(int methodIdentifier, Parcel params);
+ void onResult(int methodIdentifier, Bundle params);
/**
* Pass an exception into the callback.
*
- * @param exception The exception encoded in a {@link Parcel}. This should not be recycled.
+ * @param exception A Bundle containing the exception under the key "throwable"
*/
- void onException(Parcel exception);
+ void onException(Bundle exception);
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnectionHolder.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnectionHolder.java
new file mode 100644
index 0000000..df2b87e
--- /dev/null
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnectionHolder.java
@@ -0,0 +1,37 @@
+package com.google.android.enterprise.connectedapps;
+
+
+/**
+ * {@link AutoCloseable} wrapper around a connection holder.
+ *
+ * <p>This will automatically call {@link ProfileConnector#removeConnectionHolder(Object)} when
+ * closed.
+ */
+public final class ProfileConnectionHolder implements AutoCloseable {
+ private final ProfileConnector profileConnector;
+ private final Object connectionHolder;
+
+ public static ProfileConnectionHolder create(
+ ProfileConnector profileConnector, Object connectionHolder) {
+
+ ProfileConnectionHolder profileConnectionHolder =
+ new ProfileConnectionHolder(profileConnector, connectionHolder);
+
+ profileConnector.addConnectionHolderAlias(profileConnectionHolder, connectionHolder);
+
+ return profileConnectionHolder;
+ }
+
+ private ProfileConnectionHolder(ProfileConnector profileConnector, Object connectionHolder) {
+ if (profileConnector == null || connectionHolder == null) {
+ throw new NullPointerException();
+ }
+ this.profileConnector = profileConnector;
+ this.connectionHolder = connectionHolder;
+ }
+
+ @Override
+ public void close() {
+ profileConnector.removeConnectionHolder(connectionHolder);
+ }
+}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnector.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnector.java
index f5335b3..58d036c 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnector.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/ProfileConnector.java
@@ -21,44 +21,31 @@
/** A {@link ProfileConnector} is used to manage the connection between profiles. */
public interface ProfileConnector {
/**
- * Start trying to connect to the other profile and start manually managing the connection.
+ * Execute {@link #connect(Object)} with a new connection holder.
*
- * <p>This will mean that the connection will not be dropped automatically to save resources.
- *
- * <p>Must be called before interacting with synchronous cross-profile methods.
- *
- * <p>If the connection can not be made, then no errors will be thrown and connections will
- * re-attempted indefinitely.
- *
- * @see #connect()
- * @see #stopManualConnectionManagement()
+ * <p>You must use {@link #removeConnectionHolder(Object)} with the returned {@link
+ * ProfileConnectionHolder} or call {@link ProfileConnectionHolder#close()} when you are finished
+ * with the connection.
*/
- void startConnecting();
+ ProfileConnectionHolder connect() throws UnavailableProfileException;
/**
- * Attempt to connect to the other profile and start manually managing the connection.
+ * Attempt to connect to the other profile and add a connection holder.
*
* <p>This will mean that the connection will not be dropped automatically to save resources.
*
- * <p>Must be called before interacting with synchronous cross-profile methods.
- *
* <p>This must not be called from the main thread.
*
- * @see #startConnecting()
- * @see #stopManualConnectionManagement()
+ * <p>You must remove the connection holder once you have finished with it. See {@link
+ * #removeConnectionHolder(Object)}.
+ *
+ * <p>Returns a {@link ProfileConnectionHolder} which can be used to automatically remove this
+ * connection holder using try-with-resources. Either the {@link ProfileConnectionHolder} or the
+ * passed in {@code connectionHolder} can be used with {@link #removeConnectionHolder(Object)}.
+ *
* @throws UnavailableProfileException If the connection cannot be made.
*/
- void connect() throws UnavailableProfileException;
-
- /**
- * Stop manual connection management.
- *
- * <p>This can be called after {@link #startConnecting()} to return connection management
- * responsibilities to the SDK.
- *
- * <p>You should not make any synchronous cross-profile calls after calling this method.
- */
- void stopManualConnectionManagement();
+ ProfileConnectionHolder connect(Object connectionHolder) throws UnavailableProfileException;
/**
* Return the {@link CrossProfileSender} being used for this connection.
@@ -68,34 +55,28 @@
CrossProfileSender crossProfileSender();
/**
- * Register a listener to be called when a profile is connected or disconnected.
+ * Add a listener to be called when a profile is connected or disconnected.
*
* <p>{@link #isConnected()} can be called to check if a connection is established.
*
- * @see #unregisterConnectionListener(ConnectionListener)
+ * @see #removeConnectionListener(ConnectionListener)
*/
- void registerConnectionListener(ConnectionListener listener);
+ void addConnectionListener(ConnectionListener listener);
+
+ /** Remove a listener added using {@link #addConnectionListener(ConnectionListener)}. */
+ void removeConnectionListener(ConnectionListener listener);
/**
- * Unregister a listener registered using {@link #registerConnectionListener(
- * ConnectionListener)}.
- */
- void unregisterConnectionListener(ConnectionListener listener);
-
- /**
- * Register a listener to be called when a profile becomes available or unavailable.
+ * Add a listener to be called when a profile becomes available or unavailable.
*
* <p>{@link #isAvailable()} can be called to check if a profile is available.
*
- * @see #unregisterAvailabilityListener(AvailabilityListener)
+ * @see #removeAvailabilityListener(AvailabilityListener)
*/
- void registerAvailabilityListener(AvailabilityListener listener);
+ void addAvailabilityListener(AvailabilityListener listener);
- /**
- * Unregister a listener registered using {@link #registerAvailabilityListener(
- * AvailabilityListener)}.
- */
- void unregisterAvailabilityListener(AvailabilityListener listener);
+ /** Remove a listener registered using {@link #addAvailabilityListener( AvailabilityListener)}. */
+ void removeAvailabilityListener(AvailabilityListener listener);
/**
* Return true if there is another profile which could be connected to.
@@ -122,10 +103,36 @@
Context applicationContext();
/**
- * Returns true if this connection is being managed manually.
+ * Register an object as holding the connection open.
*
- * <p>Use {@link #startConnecting()} to begin manual connection management, and {@link
- * #stopManualConnectionManagement()} to end it.
+ * <p>While there is at least one connection holder, the connected apps SDK will attempt to stay
+ * connected.
+ *
+ * <p>You must remove the connection holder once you have finished with it. See {@link
+ * #removeConnectionHolder(Object)}.
+ *
+ * <p>Returns a {@link ProfileConnectionHolder} which can be used to automatically remove this
+ * connection holder using try-with-resources. Either the {@link ProfileConnectionHolder} or the
+ * passed in {@code connectionHolder} can be used with {@link #removeConnectionHolder(Object)}.
*/
- boolean isManuallyManagingConnection();
+ ProfileConnectionHolder addConnectionHolder(Object connectionHolder);
+
+ /**
+ * Registers a connection holder alias.
+ *
+ * <p>This means that if the key is removed, then the value will also be removed. If the value is
+ * removed, the key will not be removed.
+ */
+ void addConnectionHolderAlias(Object key, Object value);
+
+ /**
+ * Remove a connection holder.
+ *
+ * <p>Once there are no remaining connection holders, the connection will be able to be closed.
+ *
+ * <p>See {@link #addConnectionHolder(Object)}.
+ */
+ void removeConnectionHolder(Object connectionHolder);
+
+ void clearConnectionHolders();
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserBinderFactory.java
similarity index 65%
copy from tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
copy to sdk/src/main/java/com/google/android/enterprise/connectedapps/UserBinderFactory.java
index 06c01ba..ea5866d 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserBinderFactory.java
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.enterprise.connectedapps.testapp.types;
+package com.google.android.enterprise.connectedapps;
-import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
+import android.os.UserHandle;
-public class TestInterfaceProvider {
+/**
+ * Used to provide custom {@link ConnectionBinder} implementations to {@link AbstractUserConnector}.
+ */
+public interface UserBinderFactory {
- @CrossProfileProvider
- public TestCrossProfileInterface provideCrossProfileInterface() {
- return s -> s;
- }
+ ConnectionBinder createBinder(UserHandle handle);
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnectionHolder.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnectionHolder.java
new file mode 100644
index 0000000..fbec5a0
--- /dev/null
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnectionHolder.java
@@ -0,0 +1,41 @@
+package com.google.android.enterprise.connectedapps;
+
+import android.os.UserHandle;
+
+/**
+ * {@link AutoCloseable} wrapper around a connection holder.
+ *
+ * <p>This will automatically call {@link UserConnector#removeConnectionHolder(UserHandle, Object)}
+ * when closed.
+ */
+public final class UserConnectionHolder implements AutoCloseable {
+ private final UserConnector userConnector;
+ private final UserHandle userHandle;
+ private final Object connectionHolder;
+
+ public static UserConnectionHolder create(
+ UserConnector userConnector, UserHandle userHandle, Object connectionHolder) {
+
+ UserConnectionHolder userConnectionHolder =
+ new UserConnectionHolder(userConnector, userHandle, connectionHolder);
+
+ userConnector.addConnectionHolderAlias(userHandle, userConnectionHolder, connectionHolder);
+
+ return userConnectionHolder;
+ }
+
+ private UserConnectionHolder(
+ UserConnector userConnector, UserHandle userHandle, Object connectionHolder) {
+ if (userConnector == null || userHandle == null || connectionHolder == null) {
+ throw new NullPointerException();
+ }
+ this.userConnector = userConnector;
+ this.userHandle = userHandle;
+ this.connectionHolder = connectionHolder;
+ }
+
+ @Override
+ public void close() {
+ userConnector.removeConnectionHolder(userHandle, connectionHolder);
+ }
+}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnector.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnector.java
index 29f888b..199a4f1 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnector.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnector.java
@@ -21,45 +21,35 @@
/** A {@link UserConnector} is used to manage the connection between users. */
public interface UserConnector {
- /**
- * Start trying to connect to another user and start manually managing the connection.
- *
- * <p>This will mean that the connection will not be dropped automatically to save resources.
- *
- * <p>Must be called before interacting with synchronous cross-user methods.
- *
- * <p>If the connection can not be made, then no errors will be thrown and connections will
- * re-attempted indefinitely.
- *
- * @see #connect(UserHandle)
- * @see #stopManualConnectionManagement(UserHandle)
- */
- void startConnecting(UserHandle userHandle);
/**
- * Attempt to connect to the user and start manually managing the connection.
+ * Execute {@link #connect(UserHandle, Object)} with a new connection holder.
+ *
+ * <p>You must use {@link #removeConnectionHolder(UserHandle, Object)} with the returned {@link
+ * UserConnectionHolder} or call {@link UserConnectionHolder#close()} when you are finished with
+ * the connection.
+ */
+ UserConnectionHolder connect(UserHandle userHandle) throws UnavailableProfileException;
+
+ /**
+ * Attempt to connect to the other user and add a connection holder.
*
* <p>This will mean that the connection will not be dropped automatically to save resources.
*
- * <p>Must be called before interacting with synchronous cross-profile methods.
- *
* <p>This must not be called from the main thread.
*
- * @see #startConnecting(UserHandle)
- * @see #stopManualConnectionManagement(UserHandle)
+ * <p>You must remove the connection holder once you have finished with it. See {@link
+ * #removeConnectionHolder(UserHandle, Object)}.
+ *
+ * <p>Returns a {@link UserConnectionHolder} which can be used to automatically remove this
+ * connection holder using try-with-resources. Either the {@link UserConnectionHolder} or the
+ * passed in {@code connectionHolder} can be used with {@link #removeConnectionHolder(UserHandle,
+ * Object)}.
+ *
* @throws UnavailableProfileException If the connection cannot be made.
*/
- void connect(UserHandle userHandle) throws UnavailableProfileException;
-
- /**
- * Stop manual connection management.
- *
- * <p>This can be called after {@link #startConnecting(UserHandle)} to return connection
- * management responsibilities to the SDK.
- *
- * <p>You should not make any synchronous cross-profile calls after calling this method.
- */
- void stopManualConnectionManagement(UserHandle userHandle);
+ UserConnectionHolder connect(UserHandle userHandle, Object connectionHolder)
+ throws UnavailableProfileException;
/**
* Return the {@link CrossProfileSender} being used for the connection to the user.
@@ -69,34 +59,34 @@
CrossProfileSender crossProfileSender(UserHandle userHandle);
/**
- * Register a listener to be called when the user is connected or disconnected.
+ * Add a listener to be called when the user is connected or disconnected.
*
* <p>{@link #isConnected(UserHandle)} can be called to check if a connection is established.
*
- * @see #unregisterConnectionListener(UserHandle, ConnectionListener)
+ * @see #removeConnectionListener(UserHandle, ConnectionListener)
*/
- void registerConnectionListener(UserHandle userHandle, ConnectionListener listener);
+ void addConnectionListener(UserHandle userHandle, ConnectionListener listener);
/**
- * Unregister a listener registered using {@link #registerConnectionListener(UserHandle,
+ * Remove a listener registered using {@link #addConnectionListener(UserHandle,
* ConnectionListener)}.
*/
- void unregisterConnectionListener(UserHandle userHandle, ConnectionListener listener);
+ void removeConnectionListener(UserHandle userHandle, ConnectionListener listener);
/**
- * Register a listener to be called when a user becomes available or unavailable.
+ * Add a listener to be called when a user becomes available or unavailable.
*
* <p>{@link #isAvailable(UserHandle)} can be called to check if a user is available.
*
- * @see #unregisterAvailabilityListener(UserHandle, AvailabilityListener)
+ * @see #removeAvailabilityListener(UserHandle, AvailabilityListener)
*/
- void registerAvailabilityListener(UserHandle userHandle, AvailabilityListener listener);
+ void addAvailabilityListener(UserHandle userHandle, AvailabilityListener listener);
/**
- * Unregister a listener registered using {@link #registerAvailabilityListener(UserHandle,
+ * Remove a listener registered using {@link #addAvailabilityListener(UserHandle,
* AvailabilityListener)}.
*/
- void unregisterAvailabilityListener(UserHandle userHandle, AvailabilityListener listener);
+ void removeAvailabilityListener(UserHandle userHandle, AvailabilityListener listener);
/**
* Return true if the user can be connected to.
@@ -114,21 +104,43 @@
*/
boolean isConnected(UserHandle userHandle);
- /**
- * Return an instance of {@link ConnectedAppsUtils} for dealing with the connection to the user.
- */
- ConnectedAppsUtils utils(UserHandle userHandle);
-
Permissions permissions(UserHandle userHandle);
/** Return the application context used by the user. */
Context applicationContext(UserHandle userHandle);
/**
- * Returns true if the connection to the user is being managed manually.
+ * Register an object as holding the connection open.
*
- * <p>Use {@link #startConnecting(UserHandle)} to begin manual connection management, and {@link
- * #stopManualConnectionManagement(UserHandle)} to end it.
+ * <p>While there is at least one connection holder, the Connected Apps SDK will attempt to stay
+ * connected.
+ *
+ * <p>You must remove the connection holder once you have finished with it. See {@link
+ * #removeConnectionHolder(UserHandle, Object)}.
+ *
+ * <p>Returns a {@link UserConnectionHolder} which can be used to automatically remove this
+ * connection holder using try-with-resources. Either the {@link UserConnectionHolder} or the
+ * passed in {@code connectionHolder} can be used with {@link #removeConnectionHolder(UserHandle,
+ * Object)}.
*/
- boolean isManuallyManagingConnection(UserHandle userHandle);
+ UserConnectionHolder addConnectionHolder(UserHandle userHandle, Object connectionHolder);
+
+ /**
+ * Registers a connection holder alias.
+ *
+ * <p>This means that if the key is removed, then the value will also be removed. If the value is
+ * removed, the key will not be removed.
+ */
+ void addConnectionHolderAlias(UserHandle userHandle, Object key, Object value);
+
+ /**
+ * Remove a connection holder.
+ *
+ * <p>Once there are no remaining connection holders, the connection will be able to be closed.
+ *
+ * <p>See {@link #addConnectionHolder(UserHandle, Object)}.
+ */
+ void removeConnectionHolder(UserHandle userHandle, Object connectionHolder);
+
+ void clearConnectionHolders(UserHandle userHandle);
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnectorWrapper.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnectorWrapper.java
new file mode 100644
index 0000000..0f49a15
--- /dev/null
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/UserConnectorWrapper.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps;
+
+import android.content.Context;
+import android.os.UserHandle;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+
+/**
+ * A compatibility wrapper which allows a {@link UserConnector} to be used in generated code where a
+ * {@link ProfileConnector} is expected.
+ */
+public class UserConnectorWrapper implements ProfileConnector {
+
+ private final UserConnector userConnector;
+
+ private final UserHandle userHandle;
+
+ public UserConnectorWrapper(UserConnector userConnector, UserHandle userHandle) {
+ this.userConnector = userConnector;
+ this.userHandle = userHandle;
+ }
+
+ @Override
+ public ProfileConnectionHolder connect() throws UnavailableProfileException {
+ return connect(CrossProfileSender.MANUAL_MANAGEMENT_CONNECTION_HOLDER);
+ }
+
+ @Override
+
+ public ProfileConnectionHolder connect(Object connectionHolder)
+ throws UnavailableProfileException {
+
+ userConnector.connect(userHandle, connectionHolder);
+
+
+ return ProfileConnectionHolder.create(this, connectionHolder);
+
+ }
+
+ @Override
+ public CrossProfileSender crossProfileSender() {
+ return userConnector.crossProfileSender(userHandle);
+ }
+
+ @Override
+ public void addConnectionListener(ConnectionListener listener) {
+ userConnector.addConnectionListener(userHandle, listener);
+ }
+
+ @Override
+ public void removeConnectionListener(ConnectionListener listener) {
+ userConnector.removeConnectionListener(userHandle, listener);
+ }
+
+ @Override
+ public void addAvailabilityListener(AvailabilityListener listener) {
+ userConnector.addAvailabilityListener(userHandle, listener);
+ }
+
+ @Override
+ public void removeAvailabilityListener(AvailabilityListener listener) {
+ userConnector.removeAvailabilityListener(userHandle, listener);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return userConnector.isAvailable(userHandle);
+ }
+
+ @Override
+ public boolean isConnected() {
+ return userConnector.isConnected(userHandle);
+ }
+
+ @Override
+ public ConnectedAppsUtils utils() {
+ throw new UnsupportedOperationException(
+ "Cannot get ConnectedAppsUtils for a cross-user connector.");
+ }
+
+ @Override
+ public Permissions permissions() {
+ return userConnector.permissions(userHandle);
+ }
+
+ @Override
+ public Context applicationContext() {
+ return userConnector.applicationContext(userHandle);
+ }
+
+ @Override
+ public ProfileConnectionHolder addConnectionHolder(Object connectionHolder) {
+ userConnector.addConnectionHolder(userHandle, connectionHolder);
+
+ return ProfileConnectionHolder.create(this, connectionHolder);
+ }
+
+ @Override
+ public void addConnectionHolderAlias(Object key, Object value) {
+ userConnector.addConnectionHolderAlias(userHandle, key, value);
+ }
+
+ @Override
+ public void removeConnectionHolder(Object connectionHolder) {
+ userConnector.removeConnectionHolder(userHandle, connectionHolder);
+ }
+
+ @Override
+ public void clearConnectionHolders() {
+ userConnector.clearConnectionHolders(userHandle);
+ }
+}
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 511a77a..17fc457 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,7 +16,7 @@
package com.google.android.enterprise.connectedapps.exceptions;
/**
- * Thrown when a {@link Throwable} is thrown during a cross-profile call.
+ * Thrown when a {@link RuntimeException} is thrown during a cross-profile call.
*
* <p>To get the original exception, call {@link #getCause()}.
*/
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ParcelCallReceiver.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleCallReceiver.java
similarity index 66%
rename from sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ParcelCallReceiver.java
rename to sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleCallReceiver.java
index e52386f..38ff3bc 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ParcelCallReceiver.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleCallReceiver.java
@@ -15,6 +15,7 @@
*/
package com.google.android.enterprise.connectedapps.internal;
+import android.os.Bundle;
import android.os.Parcel;
import com.google.android.enterprise.connectedapps.CrossProfileSender;
import java.nio.ByteBuffer;
@@ -25,13 +26,20 @@
/**
* Build up parcels over multiple calls and prepare responses.
*
- * <p>This is the counterpart to {@link ParcelCallSender}. Calls by the {@link ParcelCallSender}
+ * <p>This is the counterpart to {@link BundleCallSender}. Calls by the {@link BundleCallSender}
* should be relayed to an instance of this class.
*/
-public final class ParcelCallReceiver {
+public final class BundleCallReceiver {
+
+ static final byte STATUS_COMPLETE = 0;
+ static final byte STATUS_INCOMPLETE = 1;
+ static final byte STATUS_INCLUDES_BUNDLES = 2;
+
private final Map<Long, byte[]> preparedCalls = new HashMap<>();
private final Map<Long, Integer> preparedCallParts = new HashMap<>();
private final Map<Long, byte[]> preparedResponses = new HashMap<>();
+ private final Map<Long, Bundle> preparedCallBundles = new HashMap<>();
+ private final Map<Long, Bundle> preparedResponseBundles = new HashMap<>();
/**
* Prepare a response to be returned by calls to {@link #getPreparedResponse(long, int)}.
@@ -41,20 +49,30 @@
* is a 1, then the following 4 bytes will be an {@link Integer} representing the total number of
* bytes in the response.
*
- * <p>The @{link Parcel} will not be recycled.</p>
+ * <p>The {@code byte[]} returned will consist only of a 2 if a bundle needs to be fetched using
+ * {@link #getPreparedResponseBundle(long, int)}.
*/
- public byte[] prepareResponse(long callId, Parcel responseParcel) {
- byte[] responseBytes = responseParcel.marshall();
+ public byte[] prepareResponse(long callId, Bundle responseBundle) {
+ Parcel responseParcel = Parcel.obtain();
+ responseBundle.writeToParcel(responseParcel, /* flags= */ 0);
+
+ byte[] responseBytes;
+ try {
+ responseBytes = responseParcel.marshall();
+ } catch (Exception | AssertionError e) {
+ prepareResponseBundle(callId, /* bundleId= */ 0, responseBundle);
+ return new byte[] {STATUS_INCLUDES_BUNDLES};
+ } finally {
+ responseParcel.recycle();
+ }
if (responseBytes.length <= CrossProfileSender.MAX_BYTES_PER_BLOCK) {
- // Prepend with 0 to indicate the bytes are complete
- return ByteUtilities.joinByteArrays(new byte[] {0}, responseBytes);
+ return ByteUtilities.joinByteArrays(new byte[] {STATUS_COMPLETE}, responseBytes);
}
// Record the bytes to be sent and send the first block
preparedResponses.put(callId, responseBytes);
byte[] response = new byte[CrossProfileSender.MAX_BYTES_PER_BLOCK + 5];
- // 1 = has additional content
- response[0] = 1;
+ response[0] = STATUS_INCOMPLETE;
byte[] sizeBytes = ByteBuffer.allocate(4).putInt(responseBytes.length).array();
System.arraycopy(sizeBytes, /* srcPos= */ 0, response, /* destPos= */ 1, /* length= */ 4);
System.arraycopy(
@@ -66,6 +84,26 @@
return response;
}
+ public void prepareBundle(long callId, int bundleId, Bundle bundle) {
+ if (preparedCallBundles.containsKey(callId)) {
+ throw new IllegalStateException("Already prepared bundle for call " + callId);
+ }
+
+ preparedCallBundles.put(callId, bundle);
+ }
+
+ private void prepareResponseBundle(long callId, int bundleId, Bundle bundle) {
+ if (preparedResponseBundles.containsKey(callId)) {
+ throw new IllegalStateException("Already prepared bundle for response " + callId);
+ }
+
+ preparedResponseBundles.put(callId, bundle);
+ }
+
+ public Bundle getPreparedResponseBundle(long callId, int bundleId) {
+ return preparedResponseBundles.remove(callId);
+ }
+
/**
* Prepare a call, storing one block of bytes for a call which will be completed with a call to
* {@link #getPreparedCall(long, int, byte[])}.
@@ -89,18 +127,20 @@
}
/**
- * Fetch the full {@link Parcel using bytes previously stored by calls to
- * {@link #prepareCall(long, int, int, byte[])}.
+ * Fetch the full {@link Bundle} using bytes previously stored by calls to {@link
+ * #prepareCall(long, int, int, byte[])}.
*
* <p>If this is the only block, then the {@code paramBytes} will be unmarshalled directly into a
- * {@link Parcel}.
- *
- * <p>The returned {@link Parcel} must be recycled after use.
+ * {@link Bundle}.
*
* @throws IllegalStateException If this is not the only block, and any previous blocks are
* missing.
*/
- public Parcel getPreparedCall(long callId, int blockId, byte[] paramBytes) {
+ public Bundle getPreparedCall(long callId, int blockId, byte[] paramBytes) {
+ if (bytesRefersToBundle(paramBytes)) {
+ return preparedCallBundles.remove(callId);
+ }
+
if (blockId > 0) {
int expectedBlocks = 0;
for (int i = 0; i < blockId; i++) {
@@ -122,14 +162,21 @@
preparedCallParts.remove(callId);
}
- Parcel parcel = Parcel.obtain(); // Recycled by caller
+ Parcel parcel = Parcel.obtain();
parcel.unmarshall(paramBytes, 0, paramBytes.length);
parcel.setDataPosition(0);
- return parcel;
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ bundle.readFromParcel(parcel);
+ parcel.recycle();
+ return bundle;
+ }
+
+ static boolean bytesRefersToBundle(byte[] bytes) {
+ return bytes[0] == STATUS_INCLUDES_BUNDLES;
}
/**
- * Get a block from a response previously prepared with {@link #prepareResponse(long, Parcel)}.
+ * Get a block from a response previously prepared with {@link #prepareResponse(long, Bundle)}.
*
* <p>If this is the final block, then the prepared blocks will be dropped, and future calls to
* this method will fail.
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ParcelCallSender.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleCallSender.java
similarity index 60%
rename from sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ParcelCallSender.java
rename to sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleCallSender.java
index a5b729a..e7fa9fd 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ParcelCallSender.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleCallSender.java
@@ -16,7 +16,11 @@
package com.google.android.enterprise.connectedapps.internal;
import static com.google.android.enterprise.connectedapps.CrossProfileSender.MAX_BYTES_PER_BLOCK;
+import static com.google.android.enterprise.connectedapps.internal.BundleCallReceiver.STATUS_INCLUDES_BUNDLES;
+import static com.google.android.enterprise.connectedapps.internal.BundleCallReceiver.STATUS_INCOMPLETE;
+import static com.google.android.enterprise.connectedapps.internal.BundleCallReceiver.bytesRefersToBundle;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;
@@ -30,20 +34,28 @@
* This represents a single action of (sending a {@link Parcel} and possibly fetching a response,
* which may be split up over many calls (if the payload is large).
*
- * <p>The receiver should relay calls to a {@link ParcelCallReceiver}.
+ * <p>The receiver should relay calls to a {@link BundleCallReceiver}.
*/
-abstract class ParcelCallSender {
+abstract class BundleCallSender {
+
+ private static final String LOG_TAG = "BundleCallSender";
private static final long RETRY_DELAY_MILLIS = 10;
private static final int MAX_RETRIES = 10;
/**
- * The arguments passed to this should be passed to {@link ParcelCallReceiver#prepareCall(long,
+ * The arguments passed to this should be passed to {@link BundleCallReceiver#prepareCall(long,
* int, int, byte[])}.
*/
abstract void prepareCall(long callId, int blockId, int totalBytes, byte[] bytes)
throws RemoteException;
+ /**
+ * The arguments passed to this should be passed to {@link BundleCallReceiver#prepareBundle(long,
+ * int, Bundle)}.
+ */
+ abstract void prepareBundle(long callId, int bundleId, Bundle bundle) throws RemoteException;
+
private void prepareCallAndRetry(
long callId, int blockId, int totalBytes, byte[] bytes, int retries) throws RemoteException {
while (true) {
@@ -58,7 +70,28 @@
try {
Thread.sleep(RETRY_DELAY_MILLIS);
} catch (InterruptedException ex) {
- Log.w("ParcelCallSender", "Interrupted on prepare retry", ex);
+ Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
+ // If we can't sleep we'll just try again immediately
+ }
+ }
+ }
+ }
+
+ private void prepareBundleAndRetry(long callId, int bundleId, Bundle bundle, int retries)
+ throws RemoteException {
+ while (true) {
+ try {
+ prepareBundle(callId, bundleId, bundle);
+ break;
+ } catch (TransactionTooLargeException e) {
+ if (retries-- <= 0) {
+ throw e;
+ }
+
+ try {
+ Thread.sleep(RETRY_DELAY_MILLIS);
+ } catch (InterruptedException ex) {
+ Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
// If we can't sleep we'll just try again immediately
}
}
@@ -67,7 +100,7 @@
/**
* The arguments passed to this should be passed to {@link
- * ParcelCallReceiver#getPreparedCall(long, int, byte[])} and used to complete the call.
+ * BundleCallReceiver#getPreparedCall(long, int, byte[])} and used to complete the call.
*/
abstract byte[] call(long callId, int blockId, byte[] bytes) throws RemoteException;
@@ -84,7 +117,7 @@
try {
Thread.sleep(RETRY_DELAY_MILLIS);
} catch (InterruptedException ex) {
- Log.w("ParcelCallSender", "Interrupted on prepare retry", ex);
+ Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
// If we can't sleep we'll just try again immediately
}
}
@@ -93,10 +126,16 @@
/**
* The arguments passed to this should be passed to {@link
- * ParcelCallReceiver#getPreparedResponse(long, int)}.
+ * BundleCallReceiver#getPreparedResponse(long, int)}.
*/
abstract byte[] fetchResponse(long callId, int blockId) throws RemoteException;
+ /**
+ * The arguments passed to this should be passed to {@link
+ * BundleCallReceiver#getPreparedResponseBundle(long, int)}.
+ */
+ abstract Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException;
+
private byte[] fetchResponseAndRetry(long callId, int blockId, int retries)
throws RemoteException {
while (true) {
@@ -110,7 +149,29 @@
try {
Thread.sleep(RETRY_DELAY_MILLIS);
} catch (InterruptedException ex) {
- Log.w("ParcelCallSender", "Interrupted on prepare retry", ex);
+ Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
+ // If we can't sleep we'll just try again immediately
+ }
+ }
+ }
+ }
+
+ private Bundle fetchResponseBundleAndRetry(long callId, int bundleId, int retries)
+ throws RemoteException {
+ while (true) {
+ try {
+ Bundle b = fetchResponseBundle(callId, bundleId);
+ b.setClassLoader(Bundler.class.getClassLoader());
+ return b;
+ } catch (TransactionTooLargeException e) {
+ if (retries-- <= 0) {
+ throw e;
+ }
+
+ try {
+ Thread.sleep(RETRY_DELAY_MILLIS);
+ } catch (InterruptedException ex) {
+ Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
// If we can't sleep we'll just try again immediately
}
}
@@ -121,15 +182,59 @@
* Use the prepareCall(long, int, int, byte[])} and {@link #call(long, int, byte[])} methods to
* make a call.
*
- * <p>The returned {@link Parcel} must be recycled after use.
- *
- * <p>Returns {@code null} if the call does not return anything
- *
* @throws UnavailableProfileException if any call fails
*/
- public Parcel makeParcelCall(Parcel parcel) throws UnavailableProfileException {
+ public Bundle makeBundleCall(Bundle bundle) throws UnavailableProfileException {
long callIdentifier = UUID.randomUUID().getMostSignificantBits();
- byte[] bytes = parcel.marshall();
+
+ Parcel parcel = Parcel.obtain();
+ bundle.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ byte[] bytes;
+
+ try {
+ bytes = parcel.marshall();
+ } catch (RuntimeException | AssertionError e) {
+ // We can't marshall the parcel so we send the bundle directly
+ try {
+ prepareBundleAndRetry(callIdentifier, /* bundleId= */ 0, bundle, MAX_RETRIES);
+ } catch (RemoteException e1) {
+ throw new UnavailableProfileException("Error passing bundle for call", e1);
+ }
+ bytes = new byte[] {STATUS_INCLUDES_BUNDLES};
+ } finally {
+ parcel.recycle();
+ }
+
+ byte[] returnBytes = makeParcelCall(callIdentifier, bytes);
+
+ if (returnBytes.length == 0) {
+ return null;
+ }
+
+ if (bytesRefersToBundle(returnBytes)) {
+ try {
+ return fetchResponseBundleAndRetry(callIdentifier, /* bundleId= */ 0, MAX_RETRIES);
+ } catch (RemoteException e) {
+ throw new UnavailableProfileException("Error fetching bundle for response", e);
+ }
+ }
+
+ Parcel returnParcel = fetchResponseParcel(callIdentifier, returnBytes);
+ Bundle returnBundle = new Bundle(Bundler.class.getClassLoader());
+ returnBundle.readFromParcel(returnParcel);
+ returnParcel.recycle();
+
+ return returnBundle;
+ }
+
+ private boolean bytesAreIncomplete(byte[] bytes) {
+ return bytes[0] == STATUS_INCOMPLETE;
+ }
+
+ private byte[] makeParcelCall(long callIdentifier, byte[] bytes)
+ throws UnavailableProfileException {
try {
int numberOfBlocks = (int) Math.ceil(bytes.length * 1.0 / MAX_BYTES_PER_BLOCK);
int blockIdentifier = 0;
@@ -152,32 +257,18 @@
}
// Since we know block size is below the limit any errors will be temporary so we should retry
- byte[] returnBytes = callAndRetry(callIdentifier, blockIdentifier, bytes, MAX_RETRIES);
-
- if (returnBytes.length == 0) {
- return null;
- }
-
- return fetchResponseParcel(callIdentifier, returnBytes);
+ return callAndRetry(callIdentifier, blockIdentifier, bytes, MAX_RETRIES);
} catch (RemoteException e) {
throw new UnavailableProfileException("Could not access other profile", e);
}
}
- /**
- * Use the {@link ParcelCallSender#prepareCall(long, int, int, byte[])} and {@link
- * ParcelCallSender#fetchResponse(long, int)} methods to fetch a prepared response.
- *
- * <p>The returned {@link Parcel} must be recycled after use.
- *
- * @throws UnavailableProfileException if any call fails
- */
private Parcel fetchResponseParcel(long callIdentifier, byte[] returnBytes)
throws UnavailableProfileException {
// returnBytes[0] is 0 if the bytes are complete, or 1 if we need to fetch more
int byteOffset = 1;
- if (returnBytes[0] == 1) {
+ if (bytesAreIncomplete(returnBytes)) {
// returnBytes[1] - returnBytes[4] are an int representing the total size of the return
// value
int totalBytes = ByteBuffer.wrap(returnBytes).getInt(/* index= */ 1);
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleUtilities.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleUtilities.java
new file mode 100644
index 0000000..5e5d946
--- /dev/null
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BundleUtilities.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.internal;
+
+import android.os.Bundle;
+
+/** This class is only for internal use by the SDK. */
+public final class BundleUtilities {
+ private BundleUtilities() {}
+
+ public static void writeThrowableToBundle(Bundle bundle, String key, Throwable throwable) {
+ bundle.putSerializable(key, throwable);
+ }
+
+ public static Throwable readThrowableFromBundle(Bundle bundle, String key) {
+ bundle.setClassLoader(Bundler.class.getClassLoader());
+ return (Throwable) bundle.getSerializable(key);
+ }
+}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/Bundler.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/Bundler.java
index 830fc67..4837f4c 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/Bundler.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/Bundler.java
@@ -15,18 +15,18 @@
*/
package com.google.android.enterprise.connectedapps.internal;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
/**
- * A {@link Bundler} is used to read and write {@link Parcel} instances without needing to use the
- * specific methods for the type of object being read/written.
+ * A {@link Bundler} is used to read and write {@link Bundler} and {@link Parcel} instances without
+ * needing to use the specific methods for the type of object being read/written.
*
* <p>Each {@link CrossProfileConfiguration} will have a {@link Bundler} which can deal with all of
* the types used by that {@link CrossProfileConfiguration}.
*/
-// TODO(158552516): Rename now this no longer concerns Bundles
public interface Bundler extends Parcelable {
/*
* We make {@link Bundler} instances implement {@link Parcelable} so that they can be passed
@@ -35,6 +35,52 @@
*/
/**
+ * Write a value to a {@link Bundle}.
+ *
+ * @throws IllegalArgumentException if the {@code value} type is unsupported.
+ */
+ void writeToBundle(Bundle bundle, String key, Object value, BundlerType valueType);
+
+ default void writeToBundle(Bundle bundle, String key, byte value, BundlerType valueType) {
+ bundle.putByte(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, short value, BundlerType valueType) {
+ bundle.putShort(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, int value, BundlerType valueType) {
+ bundle.putInt(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, long value, BundlerType valueType) {
+ bundle.putLong(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, char value, BundlerType valueType) {
+ bundle.putChar(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, float value, BundlerType valueType) {
+ bundle.putFloat(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, double value, BundlerType valueType) {
+ bundle.putDouble(key, value);
+ }
+
+ default void writeToBundle(Bundle bundle, String key, boolean value, BundlerType valueType) {
+ bundle.putBoolean(key, value);
+ }
+
+ /**
+ * Read a value from a {@link Bundle}.
+ *
+ * @throws IllegalArgumentException if the {@code valueClass} type is unsupported.
+ */
+ Object readFromBundle(Bundle bundle, String key, BundlerType valueClass);
+
+ /**
* Write a value to a {@link Parcel}.
*
* @throws IllegalArgumentException if the {@code value} type is unsupported.
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileParcelCallSender.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileBundleCallSender.java
similarity index 80%
rename from sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileParcelCallSender.java
rename to sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileBundleCallSender.java
index 3d66792..6d327be 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileParcelCallSender.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileBundleCallSender.java
@@ -15,23 +15,24 @@
*/
package com.google.android.enterprise.connectedapps.internal;
+import android.os.Bundle;
import android.os.RemoteException;
import com.google.android.enterprise.connectedapps.ICrossProfileCallback;
import com.google.android.enterprise.connectedapps.ICrossProfileService;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
- * Implementation of {@link ParcelCallSender} used when making synchronous or asynchronous
+ * Implementation of {@link BundleCallSender} used when making synchronous or asynchronous
* cross-profile calls.
*/
-public final class CrossProfileParcelCallSender extends ParcelCallSender {
+public final class CrossProfileBundleCallSender extends BundleCallSender {
private final ICrossProfileService wrappedService;
private final long crossProfileTypeIdentifier;
private final int methodIdentifier;
private final @Nullable ICrossProfileCallback callback;
- public CrossProfileParcelCallSender(
+ public CrossProfileBundleCallSender(
ICrossProfileService service,
long crossProfileTypeIdentifier,
int methodIdentifier,
@@ -52,6 +53,11 @@
}
@Override
+ void prepareBundle(long callId, int bundleId, Bundle bundle) throws RemoteException {
+ wrappedService.prepareBundle(callId, bundleId, bundle);
+ }
+
+ @Override
byte[] call(long callId, int blockId, byte[] params) throws RemoteException {
return wrappedService.call(
callId, blockId, crossProfileTypeIdentifier, methodIdentifier, params, callback);
@@ -61,4 +67,9 @@
byte[] fetchResponse(long callId, int blockId) throws RemoteException {
return wrappedService.fetchResponse(callId, blockId);
}
+
+ @Override
+ Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException {
+ return wrappedService.fetchResponseBundle(callId, bundleId);
+ }
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackParcelCallSender.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackBundleCallSender.java
similarity index 72%
rename from sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackParcelCallSender.java
rename to sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackBundleCallSender.java
index 5bd4b65..0c6dbb7 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackParcelCallSender.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackBundleCallSender.java
@@ -15,16 +15,17 @@
*/
package com.google.android.enterprise.connectedapps.internal;
+import android.os.Bundle;
import android.os.RemoteException;
import com.google.android.enterprise.connectedapps.ICrossProfileCallback;
-/** Implementation of {@link ParcelCallSender} used when passing a callback return value. */
-public class CrossProfileCallbackParcelCallSender extends ParcelCallSender {
+/** Implementation of {@link BundleCallSender} used when passing a callback return value. */
+public class CrossProfileCallbackBundleCallSender extends BundleCallSender {
private final ICrossProfileCallback callback;
private final int methodIdentifier;
- public CrossProfileCallbackParcelCallSender(
+ public CrossProfileCallbackBundleCallSender(
ICrossProfileCallback callback, int methodIdentifier) {
if (callback == null) {
throw new NullPointerException("callback must not be null");
@@ -39,6 +40,11 @@
callback.prepareResult(callId, blockId, totalBytes, bytes);
}
+ @Override
+ void prepareBundle(long callId, int bundleId, Bundle bundle) throws RemoteException {
+ callback.prepareBundle(callId, bundleId, bundle);
+ }
+
/**
* Relays to {@link ICrossProfileCallback#onResult(long, int, int, byte[])}.
*
@@ -51,11 +57,18 @@
}
/**
- * Callbacks cannot themselves return values, so this method will always throw an {@link
- * IllegalStateException}.
+ * Always throw an {@link IllegalStateException} as callbacks cannot themselves return values.
*/
@Override
byte[] fetchResponse(long callId, int blockId) throws RemoteException {
throw new IllegalStateException();
}
+
+ /**
+ * Always throw an {@link IllegalStateException} as callbacks cannot themselves return values.
+ */
+ @Override
+ Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException {
+ throw new IllegalStateException();
+ }
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackExceptionParcelCallSender.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackExceptionBundleCallSender.java
similarity index 69%
rename from sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackExceptionParcelCallSender.java
rename to sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackExceptionBundleCallSender.java
index cf63fd7..d39e80c 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackExceptionParcelCallSender.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackExceptionBundleCallSender.java
@@ -15,15 +15,16 @@
*/
package com.google.android.enterprise.connectedapps.internal;
+import android.os.Bundle;
import android.os.RemoteException;
import com.google.android.enterprise.connectedapps.ICrossProfileCallback;
-/** Implementation of {@link ParcelCallSender} used when passing a callback exception. */
-public class CrossProfileCallbackExceptionParcelCallSender extends ParcelCallSender {
+/** Implementation of {@link BundleCallSender} used when passing a callback exception. */
+public class CrossProfileCallbackExceptionBundleCallSender extends BundleCallSender {
private final ICrossProfileCallback callback;
- public CrossProfileCallbackExceptionParcelCallSender(ICrossProfileCallback callback) {
+ public CrossProfileCallbackExceptionBundleCallSender(ICrossProfileCallback callback) {
if (callback == null) {
throw new NullPointerException("callback must not be null");
}
@@ -36,6 +37,11 @@
callback.prepareResult(callId, blockId, totalBytes, bytes);
}
+ @Override
+ void prepareBundle(long callId, int bundleId, Bundle bundle) throws RemoteException {
+ callback.prepareBundle(callId, bundleId, bundle);
+ }
+
/**
* Relays to {@link ICrossProfileCallback#onException(long, int, byte[])}}.
*
@@ -48,11 +54,18 @@
}
/**
- * Callbacks cannot themselves return values, so this method will always throw an {@link
- * IllegalStateException}.
+ * Always throw an {@link IllegalStateException} as callbacks cannot themselves return values.
*/
@Override
byte[] fetchResponse(long callId, int blockId) throws RemoteException {
throw new IllegalStateException();
}
+
+ /**
+ * Always throw an {@link IllegalStateException} as callbacks cannot themselves return values.
+ */
+ @Override
+ Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException {
+ throw new IllegalStateException();
+ }
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMerger.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMerger.java
index cb7fada..eff4e8e 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMerger.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMerger.java
@@ -16,6 +16,7 @@
package com.google.android.enterprise.connectedapps.internal;
import com.google.android.enterprise.connectedapps.Profile;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -35,10 +36,18 @@
void onResult(Map<Profile, R> results);
}
- private boolean hasCompleted = false;
+ private final Object lock = new Object();
private final int expectedResults;
+
+ @GuardedBy("lock")
+ private boolean hasCompleted = false;
+
+ @GuardedBy("lock")
private final Map<Profile, R> results = new HashMap<>();
+
+ @GuardedBy("lock")
private final Set<Profile> missedResults = new HashSet<>();
+
private final CrossProfileCallbackMultiMergerCompleteListener<R> listener;
public CrossProfileCallbackMultiMerger(
@@ -59,39 +68,52 @@
* <p>This should be called for every missing result. For example, if a remote call fails.
*/
public void missingResult(Profile profileId) {
- if (hasCompleted) {
- // Once a result has been posted we don't check any more
- return;
- }
+ synchronized (lock) {
+ if (hasCompleted) {
+ // Once a result has been posted we don't check any more
+ return;
+ }
- if (results.containsKey(profileId) || missedResults.contains(profileId)) {
- // Only one result per profile is accepted
- return;
+ if (results.containsKey(profileId) || missedResults.contains(profileId)) {
+ // Only one result per profile is accepted
+ return;
+ }
+ missedResults.add(profileId);
}
- missedResults.add(profileId);
checkIfCompleted();
}
public void onResult(Profile profileId, R value) {
- if (hasCompleted) {
- // Once a result has been posted we don't check any more
- return;
- }
- if (results.containsKey(profileId) || missedResults.contains(profileId)) {
- // Only one result per profile is accepted
- return;
- }
+ synchronized (lock) {
+ if (hasCompleted) {
+ // Once a result has been posted we don't check any more
+ return;
+ }
+ if (results.containsKey(profileId) || missedResults.contains(profileId)) {
+ // Only one result per profile is accepted
+ return;
+ }
- results.put(profileId, value);
+ results.put(profileId, value);
+ }
checkIfCompleted();
}
private void checkIfCompleted() {
- if (results.size() + missedResults.size() >= expectedResults) {
- hasCompleted = true;
- listener.onResult(results);
+ Map<Profile, R> resultsCopy = null;
+ synchronized (lock) {
+ if (results.size() + missedResults.size() >= expectedResults) {
+ hasCompleted = true;
+ // Some tests rely on values in the map potentially being null, so using HashMap instead of
+ // ImmutableMap here as some production code may depend on this behavior.
+ resultsCopy = new HashMap<>(results);
+ }
+ }
+ // Listener notified outside the lock to avoid potential deadlocks.
+ if (resultsCopy != null) {
+ listener.onResult(resultsCopy);
}
}
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileFutureResultWriter.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileFutureResultWriter.java
index 7b58047..c884e36 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileFutureResultWriter.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/CrossProfileFutureResultWriter.java
@@ -15,7 +15,7 @@
*/
package com.google.android.enterprise.connectedapps.internal;
-import android.os.Parcel;
+import android.os.Bundle;
import android.util.Log;
import com.google.android.enterprise.connectedapps.ICrossProfileCallback;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
@@ -42,35 +42,31 @@
@Override
public void onSuccess(E result) {
- Parcel parcel = Parcel.obtain(); // Recycled in this method
- bundler.writeToParcel(parcel, result, bundlerType, /* flags= */ 0);
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ bundler.writeToBundle(bundle, "result", result, bundlerType);
try {
- CrossProfileCallbackParcelCallSender parcelCallSender =
- new CrossProfileCallbackParcelCallSender(callback, /* methodIdentifier= */ 0);
- parcelCallSender.makeParcelCall(parcel);
+ CrossProfileCallbackBundleCallSender bundleCallSender =
+ new CrossProfileCallbackBundleCallSender(callback, /* methodIdentifier= */ 0);
+ bundleCallSender.makeBundleCall(bundle);
} catch (UnavailableProfileException e) {
Log.e("FutureResult", "Connection was dropped before response");
} catch (RuntimeException e) {
onFailure(new UnavailableProfileException("Error when writing result of future", e));
- } finally {
- parcel.recycle();
}
}
@Override
public void onFailure(Throwable throwable) {
- Parcel parcel = Parcel.obtain(); // Recycled in this method
- ParcelUtilities.writeThrowableToParcel(parcel, throwable);
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ BundleUtilities.writeThrowableToBundle(bundle, "throwable", throwable);
try {
- CrossProfileCallbackExceptionParcelCallSender parcelCallSender =
- new CrossProfileCallbackExceptionParcelCallSender(callback);
- parcelCallSender.makeParcelCall(parcel);
+ CrossProfileCallbackExceptionBundleCallSender bundleCallSender =
+ new CrossProfileCallbackExceptionBundleCallSender(callback);
+ bundleCallSender.makeBundleCall(bundle);
} catch (UnavailableProfileException e) {
Log.e("FutureResult", "Connection was dropped before response");
- } finally {
- parcel.recycle();
}
}
}
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/ExceptionThrower.java
similarity index 75%
rename from sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/BackgroundExceptionThrower.java
rename to sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/ExceptionThrower.java
index 9511e19..64338c6 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/ExceptionThrower.java
@@ -18,10 +18,10 @@
import android.os.Handler;
import android.os.Looper;
-/** Utility class for throwing an exception in the background after a delay. */
-public final class BackgroundExceptionThrower {
+/** Utility class for throwing an exception on the main thread after a delay. */
+public final class ExceptionThrower {
- private BackgroundExceptionThrower() {}
+ private ExceptionThrower() {}
private static class ThrowingRunnable implements Runnable {
RuntimeException runtimeException;
@@ -35,24 +35,22 @@
this.error = error;
}
+
@Override
public void run() {
- if (error != null) {
- throw error;
- }
throw runtimeException;
}
}
- /** Throw the given {@link Throwable} after a delay on the main looper. */
- public static void throwInBackground(RuntimeException throwable) {
+ /** Throw the given {@link RuntimeException} after a delay on the main looper. */
+ public static void delayThrow(RuntimeException runtimeException) {
// We add a small delay to ensure that the return can be completed before crashing
- new Handler(Looper.getMainLooper()).postDelayed(new ThrowingRunnable(throwable), 1000);
+ new Handler(Looper.getMainLooper()).postDelayed(new ThrowingRunnable(runtimeException), 1000);
}
/** Throw the given {@link Error} after a delay on the main looper. */
- public static void throwInBackground(Error throwable) {
+ public static void delayThrow(Error error) {
// We add a small delay to ensure that the return can be completed before crashing
- new Handler(Looper.getMainLooper()).postDelayed(new ThrowingRunnable(throwable), 1000);
+ new Handler(Looper.getMainLooper()).postDelayed(new ThrowingRunnable(error), 1000);
}
}
diff --git a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/MethodRunner.java b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/MethodRunner.java
index c9ee3dd..d6c54de 100644
--- a/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/MethodRunner.java
+++ b/sdk/src/main/java/com/google/android/enterprise/connectedapps/internal/MethodRunner.java
@@ -16,10 +16,10 @@
package com.google.android.enterprise.connectedapps.internal;
import android.content.Context;
-import android.os.Parcel;
+import android.os.Bundle;
import com.google.android.enterprise.connectedapps.ICrossProfileCallback;
/** Interface used internally by the SDK */
public interface MethodRunner {
- Parcel call(Context context, Parcel params, ICrossProfileCallback callback);
+ Bundle call(Context context, Bundle params, ICrossProfileCallback callback);
}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnector.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnector.java
index 1fdb5f3..deb4c8d 100644
--- a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnector.java
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnector.java
@@ -21,11 +21,16 @@
import com.google.android.enterprise.connectedapps.ConnectionListener;
import com.google.android.enterprise.connectedapps.CrossProfileSender;
import com.google.android.enterprise.connectedapps.Permissions;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
import com.google.android.enterprise.connectedapps.ProfileConnector;
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
/**
* A fake {@link ProfileConnector} for use in tests.
@@ -33,7 +38,7 @@
* <p>This should be extended to make it compatible with a specific {@link ProfileConnector}
* interface.
*/
-public abstract class AbstractFakeProfileConnector implements ProfileConnector {
+public abstract class AbstractFakeProfileConnector implements FakeProfileConnector {
enum WorkProfileState {
DOES_NOT_EXIST,
@@ -47,8 +52,11 @@
private WorkProfileState workProfileState = WorkProfileState.DOES_NOT_EXIST;
private boolean isConnected = false;
private boolean hasPermissionToMakeCrossProfileCalls = true;
- private boolean isManuallyManagingConnection = false;
+ private ConnectionHandler connectionHandler = () -> true;
+ private Executor executor = Runnable::run;
+ private final Set<Object> connectionHolders = Collections.newSetFromMap(new WeakHashMap<>());
+ private final Map<Object, Set<Object>> connectionHolderAliases = new WeakHashMap<>();
private final Set<ConnectionListener> connectionListeners = new HashSet<>();
private final Set<AvailabilityListener> availabilityListeners = new HashSet<>();
@@ -151,32 +159,30 @@
notifyAvailabilityChanged();
}
- /**
- * Force the connector to be "automatically" connected.
- *
- * <p>This call should only be used by the SDK and should not be called in tests. If you want to
- * connect manually, use {@link #startConnecting()}, or for automatic management just make the
- * asynchronous call directly.
- *
- * @hide
- */
+ @Override
+ public void setConnectionHandler(ConnectionHandler connectionHandler) {
+ this.connectionHandler = connectionHandler;
+ }
+
+ @Override
+ public void setExecutor(Executor executor) {
+ this.executor = executor;
+ }
+
+ @Override
public void automaticallyConnect() {
- if (isAvailable() && !isConnected) {
+ if (hasPermissionToMakeCrossProfileCalls
+ && isAvailable()
+ && !isConnected
+ && connectionHandler.tryConnect()) {
isConnected = true;
notifyConnectionChanged();
}
}
- /**
- * Disconnect after an automatic connection.
- *
- * <p>In reality, this timeout happens some arbitrary time of no interaction with the other
- * profile.
- *
- * <p>If {@link #isManuallyManagingConnection()} is true, then this will do nothing.
- */
+ @Override
public void timeoutConnection() {
- if (isManuallyManagingConnection) {
+ if (!connectionHolders.isEmpty()) {
return;
}
@@ -186,37 +192,32 @@
}
}
- @Override
- public void startConnecting() {
- isManuallyManagingConnection = true;
- automaticallyConnect();
- }
-
/**
* This fake does not enforce the requirement that calls to {@link #connect()} do not occur on the
* UI Thread.
*/
@Override
- public void connect() throws UnavailableProfileException {
- if (!isAvailable()) {
- throw new UnavailableProfileException("No profile available");
- }
-
- isManuallyManagingConnection = true;
- automaticallyConnect();
- }
-
- /**
- * Stop manually managing the connection and ensure that the connector is disconnected.
- */
- public void disconnect() {
- stopManualConnectionManagement();
- timeoutConnection();
+ public ProfileConnectionHolder connect() throws UnavailableProfileException {
+ return connect(CrossProfileSender.MANUAL_MANAGEMENT_CONNECTION_HOLDER);
}
@Override
- public void stopManualConnectionManagement() {
- isManuallyManagingConnection = false;
+ public ProfileConnectionHolder connect(Object connectionHolder)
+ throws UnavailableProfileException {
+ if (!hasPermissionToMakeCrossProfileCalls || !isAvailable()) {
+ throw new UnavailableProfileException("No profile available");
+ }
+
+ connectionHolders.add(connectionHolder);
+ automaticallyConnect();
+
+ return ProfileConnectionHolder.create(this, connectionHolder);
+ }
+
+ /** Stop manually managing the connection and ensure that the connector is disconnected. */
+ public void disconnect() {
+ connectionHolders.clear();
+ timeoutConnection();
}
/** Unsupported by the fake so always returns {@code null}. */
@@ -226,12 +227,12 @@
}
@Override
- public void registerConnectionListener(ConnectionListener listener) {
+ public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
@Override
- public void unregisterConnectionListener(ConnectionListener listener) {
+ public void removeConnectionListener(ConnectionListener listener) {
connectionListeners.remove(listener);
}
@@ -242,12 +243,12 @@
}
@Override
- public void registerAvailabilityListener(AvailabilityListener listener) {
+ public void addAvailabilityListener(AvailabilityListener listener) {
availabilityListeners.add(listener);
}
@Override
- public void unregisterAvailabilityListener(AvailabilityListener listener) {
+ public void removeAvailabilityListener(AvailabilityListener listener) {
availabilityListeners.remove(listener);
}
@@ -274,20 +275,14 @@
@Override
public Permissions permissions() {
- return new FakePermissions(this);
+ return new FakeCrossProfilePermissions(this);
}
- /** Not supported by the fake so returns null. */
@Override
public Context applicationContext() {
return applicationContext;
}
- @Override
- public boolean isManuallyManagingConnection() {
- return isManuallyManagingConnection;
- }
-
/**
* Set whether or not the app has been given the appropriate permission to make cross-profile
* calls.
@@ -300,4 +295,45 @@
boolean hasPermissionToMakeCrossProfileCalls() {
return hasPermissionToMakeCrossProfileCalls;
}
+
+ @Override
+ public ProfileConnectionHolder addConnectionHolder(Object connectionHolder) {
+ connectionHolders.add(connectionHolder);
+ executor.execute(this::automaticallyConnect);
+
+ return ProfileConnectionHolder.create(this, connectionHolder);
+ }
+
+ @Override
+ public void addConnectionHolderAlias(Object key, Object value) {
+ if (!connectionHolderAliases.containsKey(key)) {
+ connectionHolderAliases.put(key, Collections.newSetFromMap(new WeakHashMap<>()));
+ }
+
+ connectionHolderAliases.get(key).add(value);
+ }
+
+ @Override
+ public void removeConnectionHolder(Object connectionHolder) {
+ if (connectionHolderAliases.containsKey(connectionHolder)) {
+ Set<Object> aliases = connectionHolderAliases.get(connectionHolder);
+ connectionHolderAliases.remove(connectionHolder);
+ for (Object alias : aliases) {
+ removeConnectionHolder(alias);
+ }
+ }
+
+ connectionHolders.remove(connectionHolder);
+ }
+
+ @Override
+ public void clearConnectionHolders() {
+ connectionHolderAliases.clear();
+ connectionHolders.clear();
+ }
+
+ @Override
+ public boolean hasExplicitConnectionHolders() {
+ return !connectionHolders.isEmpty();
+ }
}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeUserConnector.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeUserConnector.java
new file mode 100644
index 0000000..530baa3
--- /dev/null
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeUserConnector.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testing;
+
+import android.content.Context;
+import android.os.UserHandle;
+import com.google.android.enterprise.connectedapps.AvailabilityListener;
+import com.google.android.enterprise.connectedapps.ConnectionListener;
+import com.google.android.enterprise.connectedapps.CrossProfileSender;
+import com.google.android.enterprise.connectedapps.Permissions;
+import com.google.android.enterprise.connectedapps.UserConnectionHolder;
+import com.google.android.enterprise.connectedapps.UserConnector;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A fake {@link UserConnector} for use in tests.
+ *
+ * <p>This should be extended to make it compatible with a specific {@link UserConnector} interface.
+ */
+public abstract class AbstractFakeUserConnector implements FakeUserConnector {
+
+ private static final class SpecificUserConnector {
+
+ enum UserState {
+ DOES_NOT_EXIST,
+ TURNED_OFF,
+ TURNED_ON
+ }
+
+ private UserState userState = UserState.DOES_NOT_EXIST;
+ private boolean isConnected = false;
+ private boolean hasPermissionToMakeCrossUserCalls = true;
+
+ private final Set<Object> connectionHolders = Collections.newSetFromMap(new WeakHashMap<>());
+ private final Map<Object, Set<Object>> connectionHolderAliases = new WeakHashMap<>();
+ private final Set<ConnectionListener> connectionListeners = new HashSet<>();
+ private final Set<AvailabilityListener> availabilityListeners = new HashSet<>();
+
+ private void createUser() {
+ if (userState == UserState.DOES_NOT_EXIST) {
+ userState = UserState.TURNED_OFF;
+ }
+ }
+
+ private void removeUser() {
+ turnOffUser();
+ userState = UserState.DOES_NOT_EXIST;
+ }
+
+ private void turnOnUser() {
+ if (userState == UserState.TURNED_ON) {
+ return;
+ }
+
+ if (userState == UserState.DOES_NOT_EXIST) {
+ createUser();
+ }
+
+ userState = UserState.TURNED_ON;
+ notifyAvailabilityChanged();
+ }
+
+ private void turnOffUser() {
+ if (userState == UserState.TURNED_OFF) {
+ return;
+ }
+
+ if (userState == UserState.DOES_NOT_EXIST) {
+ createUser();
+ }
+
+ if (isConnected) {
+ isConnected = false;
+ notifyConnectionChanged();
+ }
+
+ userState = UserState.TURNED_OFF;
+ notifyAvailabilityChanged();
+ }
+
+ private boolean isConnected() {
+ return isConnected;
+ }
+
+ private void connect(Object connectionHolder) {
+ addConnectionHolder(connectionHolder);
+ }
+
+ private void timeoutConnection() {
+ if (!connectionHolders.isEmpty()) {
+ return;
+ }
+
+ if (isConnected) {
+ isConnected = false;
+ notifyConnectionChanged();
+ }
+ }
+
+ private void automaticallyConnect() {
+ if (isAvailable() && !isConnected) {
+ isConnected = true;
+ notifyConnectionChanged();
+ }
+ }
+
+ private void disconnect() {
+ connectionHolders.clear();
+ timeoutConnection();
+ }
+
+ private boolean isAvailable() {
+ return userState == UserState.TURNED_ON;
+ }
+
+ private void addConnectionListener(ConnectionListener listener) {
+ connectionListeners.add(listener);
+ }
+
+ private void removeConnectionListener(ConnectionListener listener) {
+ connectionListeners.remove(listener);
+ }
+
+ private void notifyConnectionChanged() {
+ for (ConnectionListener listener : connectionListeners) {
+ listener.connectionChanged();
+ }
+ }
+
+ private void addAvailabilityListener(AvailabilityListener listener) {
+ availabilityListeners.add(listener);
+ }
+
+ private void removeAvailabilityListener(AvailabilityListener listener) {
+ availabilityListeners.remove(listener);
+ }
+
+ private void notifyAvailabilityChanged() {
+ for (AvailabilityListener listener : availabilityListeners) {
+ listener.availabilityChanged();
+ }
+ }
+
+ private void addConnectionHolder(Object connectionHolder) {
+ connectionHolders.add(connectionHolder);
+ automaticallyConnect();
+ }
+
+ private void addConnectionHolderAlias(Object key, Object value) {
+ if (!connectionHolderAliases.containsKey(key)) {
+ connectionHolderAliases.put(key, Collections.newSetFromMap(new WeakHashMap<>()));
+ }
+
+ connectionHolderAliases.get(key).add(value);
+ }
+
+ private void removeConnectionHolder(Object connectionHolder) {
+ if (connectionHolderAliases.containsKey(connectionHolder)) {
+ Set<Object> aliases = connectionHolderAliases.get(connectionHolder);
+ connectionHolderAliases.remove(connectionHolder);
+ for (Object alias : aliases) {
+ removeConnectionHolder(alias);
+ }
+ }
+
+ connectionHolders.remove(connectionHolder);
+ }
+
+ private void clearConnectionHolders() {
+ connectionHolderAliases.clear();
+ ;
+ connectionHolders.clear();
+ }
+
+ private boolean hasExplicitConnectionHolders() {
+ return !connectionHolders.isEmpty();
+ }
+ }
+
+ private final Context applicationContext;
+
+ private final Map<UserHandle, SpecificUserConnector> specificUsers = new HashMap<>();
+
+ private UserHandle currentUser;
+
+ public AbstractFakeUserConnector(Context context) {
+ if (context == null) {
+ throw new NullPointerException();
+ }
+
+ this.applicationContext = context.getApplicationContext();
+ }
+
+ /**
+ * Simulate running on a particular user.
+ *
+ * <p>If {@code currentUser} does not exist or is not turned on, then it will be created and
+ * turned on.
+ *
+ * @see #runningOnUser
+ */
+ public void setRunningOnUser(UserHandle currentUser) {
+ this.currentUser = currentUser;
+ connectorFor(currentUser).turnOnUser();
+ }
+
+ /**
+ * Get the current user being simulated.
+ *
+ * @see #setRunningOnUser(UserHandle)
+ */
+ public UserHandle runningOnUser() {
+ return currentUser;
+ }
+
+ /**
+ * Simulate the creation of a user.
+ *
+ * <p>The new user will be turned off by default.
+ */
+ public void createUser(UserHandle userHandle) {
+ connectorFor(userHandle).createUser();
+ }
+
+ /**
+ * Remove a simulated user.
+ *
+ * <p>The simulated user will be turned off first.
+ */
+ public void removeUser(UserHandle userHandle) {
+ connectorFor(userHandle).removeUser();
+ }
+
+ /**
+ * Simulate a user being turned on.
+ *
+ * <p>If no such user exists, then it will be created.
+ */
+ public void turnOnUser(UserHandle userHandle) {
+ connectorFor(userHandle).turnOnUser();
+ }
+
+ /**
+ * Simulate a user being turned off.
+ *
+ * <p>If no such user exists, then it will be created.
+ */
+ public void turnOffUser(UserHandle userHandle) {
+ connectorFor(userHandle).turnOffUser();
+ }
+
+ /**
+ * Force the connector to be "automatically" connected.
+ *
+ * <p>This call should only be used by the SDK and should not be called in tests. If you want to
+ * connect manually, use {@link #addConnectionHolder(UserHandle, Object)}, or for automatic
+ * management just make the asynchronous call directly.
+ *
+ * @hide
+ */
+ public void automaticallyConnect(UserHandle userHandle) {
+ connectorFor(userHandle).automaticallyConnect();
+ }
+
+ /**
+ * Disconnect after an automatic connection.
+ *
+ * <p>In reality, this timeout happens some arbitrary time of no interaction with the other
+ * profile.
+ *
+ * <p>If there are connection holders, then this will do nothing.
+ */
+ public void timeoutConnection(UserHandle userHandle) {
+ connectorFor(userHandle).timeoutConnection();
+ }
+
+ /**
+ * This fake does not enforce the requirement that calls to {@link #connect(UserHandle)} do not
+ * occur on the UI Thread.
+ */
+ @Override
+ public UserConnectionHolder connect(UserHandle userHandle) throws UnavailableProfileException {
+ return connect(userHandle, new Object());
+ }
+
+ @Override
+ public UserConnectionHolder connect(UserHandle userHandle, Object connectionHolder)
+
+ throws UnavailableProfileException {
+ if (!isAvailable(userHandle)) {
+ throw new UnavailableProfileException("User not available");
+ }
+
+ connectorFor(userHandle).connect(connectionHolder);
+
+ return UserConnectionHolder.create(this, userHandle, connectionHolder);
+ }
+
+ /** Stop manually managing the connection and ensure that the connector is disconnected. */
+ public void disconnect(UserHandle userHandle) {
+ connectorFor(userHandle).disconnect();
+ }
+
+ /** Unsupported by the fake so always returns {@code null}. */
+ @Override
+ public CrossProfileSender crossProfileSender(UserHandle userHandle) {
+ return null;
+ }
+
+ @Override
+ public void addConnectionListener(UserHandle userHandle, ConnectionListener listener) {
+ connectorFor(userHandle).addConnectionListener(listener);
+ }
+
+ @Override
+ public void removeConnectionListener(UserHandle userHandle, ConnectionListener listener) {
+ connectorFor(userHandle).removeConnectionListener(listener);
+ }
+
+ @Override
+ public void addAvailabilityListener(UserHandle userHandle, AvailabilityListener listener) {
+ connectorFor(userHandle).addAvailabilityListener(listener);
+ }
+
+ @Override
+ public void removeAvailabilityListener(UserHandle userHandle, AvailabilityListener listener) {
+ connectorFor(userHandle).removeAvailabilityListener(listener);
+ }
+
+ @Override
+ public boolean isAvailable(UserHandle userHandle) {
+ return connectorFor(userHandle).isAvailable();
+ }
+
+ @Override
+ public boolean isConnected(UserHandle userHandle) {
+ return connectorFor(userHandle).isConnected();
+ }
+
+ @Override
+ public Permissions permissions(UserHandle userHandle) {
+ return new FakeCrossUserPermissions(this, userHandle);
+ }
+
+ @Override
+ public Context applicationContext(UserHandle userHandle) {
+ return applicationContext;
+ }
+
+ /**
+ * Set whether or not the app has been given the appropriate permission to make cross-user calls.
+ */
+ public void setHasPermissionToMakeCrossProfileCalls(
+ UserHandle userHandle, boolean hasPermissionToMakeCrossUserCalls) {
+ connectorFor(userHandle).hasPermissionToMakeCrossUserCalls = hasPermissionToMakeCrossUserCalls;
+ }
+
+ boolean hasPermissionToMakeCrossProfileCalls(UserHandle userHandle) {
+ return connectorFor(userHandle).hasPermissionToMakeCrossUserCalls;
+ }
+
+ private SpecificUserConnector connectorFor(UserHandle userHandle) {
+ if (!specificUsers.containsKey(userHandle)) {
+ specificUsers.put(userHandle, new SpecificUserConnector());
+ }
+
+ return specificUsers.get(userHandle);
+ }
+
+ @Override
+ public UserConnectionHolder addConnectionHolder(UserHandle userHandle, Object connectionHolder) {
+ connectorFor(userHandle).addConnectionHolder(connectionHolder);
+
+ return UserConnectionHolder.create(this, userHandle, connectionHolder);
+ }
+
+ @Override
+ public void addConnectionHolderAlias(UserHandle userHandle, Object key, Object value) {
+ connectorFor(userHandle).addConnectionHolderAlias(key, value);
+ }
+
+ @Override
+ public void removeConnectionHolder(UserHandle userHandle, Object connectionHolder) {
+ connectorFor(userHandle).removeConnectionHolder(connectionHolder);
+ }
+
+ @Override
+ public void clearConnectionHolders(UserHandle userHandle) {
+ connectorFor(userHandle).clearConnectionHolders();
+ }
+
+ @Override
+ public boolean hasExplicitConnectionHolders(UserHandle userHandle) {
+ return connectorFor(userHandle).hasExplicitConnectionHolders();
+ }
+}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakePermissions.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfilePermissions.java
similarity index 87%
rename from testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakePermissions.java
rename to testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfilePermissions.java
index 763b67c..4da4e09 100644
--- a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakePermissions.java
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfilePermissions.java
@@ -17,11 +17,11 @@
import com.google.android.enterprise.connectedapps.Permissions;
-class FakePermissions implements Permissions {
+class FakeCrossProfilePermissions implements Permissions {
private final AbstractFakeProfileConnector fakeProfileConnector;
- FakePermissions(AbstractFakeProfileConnector fakeProfileConnector) {
+ FakeCrossProfilePermissions(AbstractFakeProfileConnector fakeProfileConnector) {
this.fakeProfileConnector = fakeProfileConnector;
}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakePermissions.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeCrossUserPermissions.java
similarity index 64%
copy from testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakePermissions.java
copy to testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeCrossUserPermissions.java
index 763b67c..83597a1 100644
--- a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakePermissions.java
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeCrossUserPermissions.java
@@ -15,18 +15,22 @@
*/
package com.google.android.enterprise.connectedapps.testing;
+import android.os.UserHandle;
import com.google.android.enterprise.connectedapps.Permissions;
-class FakePermissions implements Permissions {
+class FakeCrossUserPermissions implements Permissions {
- private final AbstractFakeProfileConnector fakeProfileConnector;
+ private final AbstractFakeUserConnector fakeUserConnector;
- FakePermissions(AbstractFakeProfileConnector fakeProfileConnector) {
- this.fakeProfileConnector = fakeProfileConnector;
+ private final UserHandle userHandle;
+
+ FakeCrossUserPermissions(AbstractFakeUserConnector fakeUserConnector, UserHandle userHandle) {
+ this.fakeUserConnector = fakeUserConnector;
+ this.userHandle = userHandle;
}
@Override
public boolean canMakeCrossProfileCalls() {
- return fakeProfileConnector.hasPermissionToMakeCrossProfileCalls();
+ return fakeUserConnector.hasPermissionToMakeCrossProfileCalls(userHandle);
}
}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeProfileConnector.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeProfileConnector.java
new file mode 100644
index 0000000..98dd36a
--- /dev/null
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeProfileConnector.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testing;
+
+import com.google.android.enterprise.connectedapps.ProfileConnector;
+import java.util.concurrent.Executor;
+
+/** Implemented by generated {@link ProfileConnector} fakes. */
+public interface FakeProfileConnector extends ProfileConnector {
+
+ /**
+ * Disconnect after an automatic connection.
+ *
+ * <p>In reality, this timeout happens some arbitrary time of no interaction with the other
+ * profile.
+ *
+ * <p>If there are any connection holders, then this will do nothing.
+ */
+ void timeoutConnection();
+
+ /**
+ * Set the connection handler.
+ *
+ * <p>Upon attempting to connect, the connectionHandler is invoked. A return value of true
+ * simulates that the connection was successful, whereas a return value of false simulates that
+ * the connection failed.
+ *
+ * <p>If not set, the default connection handler always returns true (success).
+ */
+ void setConnectionHandler(ConnectionHandler connectionHandler);
+
+ /**
+ * Set the executor used for asynchronous operations.
+ *
+ * <p>If not set, all asynchronous operations will run synchronously.
+ *
+ * <p>Currently this only applies to calls to {@link #addConnectionHolder}.
+ */
+ void setExecutor(Executor executor);
+
+ /**
+ * Force the connector to be "automatically" connected.
+ *
+ * <p>This call should only be used by the SDK and should not be called in tests. If you want to
+ * connect manually, use {@link #addConnectionHolder(Object)}, or for automatic management just
+ * make the asynchronous call directly.
+ *
+ * @hide
+ */
+ void automaticallyConnect();
+
+ /**
+ * Returns true if explicit connection holders have been added.
+ *
+ * <p>This call should only be used by the SDK and should not be called in tests.
+ *
+ * @hide
+ */
+ boolean hasExplicitConnectionHolders();
+
+ /** A connection handler which gets invoked when attempting to connect to the other profile. */
+ public interface ConnectionHandler {
+
+ /** Return whether the connection was successful. */
+ boolean tryConnect();
+ }
+}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeUserConnector.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeUserConnector.java
new file mode 100644
index 0000000..ccf5776
--- /dev/null
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeUserConnector.java
@@ -0,0 +1,8 @@
+package com.google.android.enterprise.connectedapps.testing;
+
+import android.os.UserHandle;
+import com.google.android.enterprise.connectedapps.UserConnector;
+
+public interface FakeUserConnector extends UserConnector {
+ boolean hasExplicitConnectionHolders(UserHandle userHandle);
+}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeUserConnectorWrapper.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeUserConnectorWrapper.java
new file mode 100644
index 0000000..faa826b
--- /dev/null
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/FakeUserConnectorWrapper.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testing;
+
+import android.content.Context;
+import android.os.UserHandle;
+import com.google.android.enterprise.connectedapps.AvailabilityListener;
+import com.google.android.enterprise.connectedapps.ConnectedAppsUtils;
+import com.google.android.enterprise.connectedapps.ConnectionListener;
+import com.google.android.enterprise.connectedapps.CrossProfileSender;
+import com.google.android.enterprise.connectedapps.CrossUserConnector;
+import com.google.android.enterprise.connectedapps.Permissions;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import java.util.concurrent.Executor;
+
+/**
+ * A compatibility wrapper which allows an {@link AbstractFakeUserConnector} to be used in generated
+ * code where a {@link FakeProfileConnector} is expected.
+ */
+public class FakeUserConnectorWrapper implements FakeProfileConnector {
+
+ private final AbstractFakeUserConnector connector;
+
+ private final UserHandle handle;
+
+ public FakeUserConnectorWrapper(AbstractFakeUserConnector connector, UserHandle handle) {
+ this.connector = connector;
+ this.handle = handle;
+ }
+
+ @Override
+ public void timeoutConnection() {
+ connector.timeoutConnection(handle);
+ }
+
+ @Override
+ public void setConnectionHandler(ConnectionHandler connectionHandler) {
+ // TODO(b/214511168): Implement it
+ throw new UnsupportedOperationException("Cannot set ConnectionHandler for UserConnector");
+ }
+
+ @Override
+ public void setExecutor(Executor executor) {
+ // TODO(b/214511168): Implement it
+ throw new UnsupportedOperationException("Cannot set Executor for UserConnector");
+ }
+
+ @Override
+ public void automaticallyConnect() {
+ connector.automaticallyConnect(handle);
+ }
+
+ @Override
+ public ProfileConnectionHolder connect(Object connectionHolder)
+ throws UnavailableProfileException {
+ return addConnectionHolder(connectionHolder);
+ }
+
+ @Override
+ public ProfileConnectionHolder connect() throws UnavailableProfileException {
+ return connect(CrossProfileSender.MANUAL_MANAGEMENT_CONNECTION_HOLDER);
+ }
+
+ @Override
+ public CrossProfileSender crossProfileSender() {
+ return connector.crossProfileSender(handle);
+ }
+
+ @Override
+ public void addConnectionListener(ConnectionListener listener) {
+ connector.addConnectionListener(handle, listener);
+ }
+
+ @Override
+ public void removeConnectionListener(ConnectionListener listener) {
+ connector.removeConnectionHolder(handle, listener);
+ }
+
+ @Override
+ public void addAvailabilityListener(AvailabilityListener listener) {
+ connector.addAvailabilityListener(handle, listener);
+ }
+
+ @Override
+ public void removeAvailabilityListener(AvailabilityListener listener) {
+ connector.removeAvailabilityListener(handle, listener);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return connector.isAvailable(handle);
+ }
+
+ @Override
+ public boolean isConnected() {
+ return connector.isConnected(handle);
+ }
+
+ /**
+ * Unsupported for {@link FakeUserConnectorWrapper} as unsupported by all {@link
+ * CrossUserConnector}s.
+ */
+ @Override
+ public ConnectedAppsUtils utils() {
+ throw new UnsupportedOperationException("Cannot get ConnectedAppsUtils for UserConnector");
+ }
+
+ @Override
+ public Permissions permissions() {
+ return connector.permissions(handle);
+ }
+
+ @Override
+ public Context applicationContext() {
+ return connector.applicationContext(handle);
+ }
+
+ @Override
+ public ProfileConnectionHolder addConnectionHolder(Object connectionHolder) {
+ connector.addConnectionHolder(handle, connectionHolder);
+
+ return ProfileConnectionHolder.create(this, connectionHolder);
+ }
+
+ @Override
+ public void addConnectionHolderAlias(Object key, Object value) {
+ connector.addConnectionHolderAlias(handle, key, value);
+ }
+
+ @Override
+ public void removeConnectionHolder(Object connectionHolder) {
+ connector.removeConnectionHolder(handle, connectionHolder);
+ }
+
+ @Override
+ public void clearConnectionHolders() {
+ connector.clearConnectionHolders(handle);
+ }
+
+ @Override
+ public boolean hasExplicitConnectionHolders() {
+ return connector.hasExplicitConnectionHolders(handle);
+ }
+}
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/InstrumentedTestUtilities.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/InstrumentedTestUtilities.java
index 2d2ee60..2951176 100644
--- a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/InstrumentedTestUtilities.java
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/InstrumentedTestUtilities.java
@@ -76,8 +76,7 @@
grantInteractAcrossUsers(packageName);
- ProfileAvailabilityPoll.blockUntilProfileRunningAndUnlocked(
- context, getWorkProfileUserHandle());
+ ProfileAvailabilityPoll.blockUntilUserRunningAndUnlocked(context, getWorkProfileUserHandle());
}
private UserHandle getWorkProfileUserHandle() {
@@ -222,7 +221,7 @@
}
};
- connector.registerConnectionListener(connectionListener);
+ connector.addConnectionListener(connectionListener);
connectionListener.connectionChanged();
try {
@@ -231,7 +230,7 @@
throw new AssertionError("Error waiting to disconnect", e);
}
- connector.unregisterConnectionListener(connectionListener);
+ connector.removeConnectionListener(connectionListener);
}
/**
@@ -249,7 +248,7 @@
}
};
- connector.registerConnectionListener(connectionListener);
+ connector.addConnectionListener(connectionListener);
connectionListener.connectionChanged();
try {
@@ -258,7 +257,7 @@
throw new AssertionError("Error waiting to connect", e);
}
- connector.unregisterConnectionListener(connectionListener);
+ connector.removeConnectionListener(connectionListener);
}
private static String runCommandWithOutput(String command) {
diff --git a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/ProfileAvailabilityPoll.java b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/ProfileAvailabilityPoll.java
index 42ff10c..5c98d60 100644
--- a/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/ProfileAvailabilityPoll.java
+++ b/testing/sdk/src/main/java/com/google/android/enterprise/connectedapps/testing/ProfileAvailabilityPoll.java
@@ -25,7 +25,7 @@
private static final int POLL_FREQUENCY_MS = 1000;
private static final int POLL_TIMEOUT_MS = 30000;
- public static void blockUntilProfileRunningAndUnlocked(Context context, UserHandle userHandle) {
+ public static void blockUntilUserRunningAndUnlocked(Context context, UserHandle userHandle) {
UserManager userManager = context.getSystemService(UserManager.class);
BlockingPoll.poll(
() -> userManager.isUserRunning(userHandle) && userManager.isUserUnlocked(userHandle),
@@ -33,7 +33,7 @@
POLL_TIMEOUT_MS);
}
- public static void blockUntilProfileNotAvailable(Context context, UserHandle userHandle) {
+ public static void blockUntilUserNotAvailable(Context context, UserHandle userHandle) {
UserManager userManager = context.getSystemService(UserManager.class);
BlockingPoll.poll(
() -> !userManager.isUserRunning(userHandle) || userManager.isQuietModeEnabled(userHandle),
diff --git a/tests/instrumented/src/AndroidManifest.xml b/tests/instrumented/src/AndroidManifest.xml
index 381a313..50515ff 100644
--- a/tests/instrumented/src/AndroidManifest.xml
+++ b/tests/instrumented/src/AndroidManifest.xml
@@ -24,14 +24,16 @@
android:targetSdkVersion="28"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<application>
<uses-library android:name="android.test.runner" />
+ <service android:name="com.google.android.enterprise.connectedapps.testapp.connector.ExceptionsSuppressingConnector_Service" android:exported="false" />
<service android:name="com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector_Service" android:exported="false" />
+ <service android:name="com.google.android.enterprise.connectedapps.testapp.crossuser.AppCrossUserConnector_Service" android:exported="false" />
</application>
- <!-- TODO(179354604): this needs to be changed upstream -->
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="com.google.android.apps.common.testing.testrunner.Google3InstrumentationTestRunner"
android:targetPackage="com.google.android.enterprise.connectedapps"
android:label="Connected Apps SDK test"/>
</manifest>
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/AvailabilityListenerTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/AvailabilityListenerTest.java
index f35f3b5..8f9de63 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/AvailabilityListenerTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/AvailabilityListenerTest.java
@@ -63,7 +63,7 @@
utilities.ensureWorkProfileTurnedOn();
TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
- connector.registerAvailabilityListener(availabilityListener);
+ connector.addAvailabilityListener(availabilityListener);
utilities.turnOffWorkProfileAndWait();
@@ -78,7 +78,7 @@
utilities.ensureWorkProfileTurnedOff();
TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
- connector.registerAvailabilityListener(availabilityListener);
+ connector.addAvailabilityListener(availabilityListener);
utilities.turnOnWorkProfileAndWait();
@@ -91,7 +91,7 @@
throws InterruptedException {
utilities.ensureWorkProfileTurnedOn();
TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
- connector.registerAvailabilityListener(availabilityListener);
+ connector.addAvailabilityListener(availabilityListener);
ListenableFuture<Void> unusedFuture = type.other().killApp();
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/BinderParcelableTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/BinderParcelableTest.java
new file mode 100644
index 0000000..0a2ad44
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/BinderParcelableTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.os.Parcelable;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingExceptionCallbackListener;
+import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingParcelableCallbackListener;
+import com.google.android.enterprise.connectedapps.instrumented.utils.InstrumentedTestUtilities;
+import com.google.android.enterprise.connectedapps.testapp.ParcelableContainingBinder;
+import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
+import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests regarding parcelable types which contain a binder. */
+@RunWith(JUnit4.class)
+public class BinderParcelableTest {
+ private static final Application context = ApplicationProvider.getApplicationContext();
+
+ private static final TestProfileConnector connector = TestProfileConnector.create(context);
+ private static final InstrumentedTestUtilities utilities =
+ new InstrumentedTestUtilities(context, connector);
+
+ private final ProfileTestCrossProfileType type = ProfileTestCrossProfileType.create(connector);
+ private final BlockingExceptionCallbackListener exceptionCallbackListener =
+ new BlockingExceptionCallbackListener();
+
+ private final ParcelableContainingBinder parcelableContainingBinder =
+ new ParcelableContainingBinder();
+
+ @Before
+ public void setup() {
+ utilities.ensureReadyForCrossProfileCalls();
+ }
+
+ @AfterClass
+ public static void teardownClass() {
+ utilities.ensureNoWorkProfile();
+ }
+
+ @Test
+ public void parcelableContainingBinderArgumentAndReturnType_bothWork()
+ throws UnavailableProfileException {
+ utilities.addConnectionHolderAndWait(this);
+
+ // Binders won't be identical
+ assertThat(type.other().identityParcelableMethod(parcelableContainingBinder)).isNotNull();
+ }
+
+ @Test
+ public void parcelableContainingBinderAsyncMethod_works() throws Exception {
+ BlockingParcelableCallbackListener callbackListener = new BlockingParcelableCallbackListener();
+
+ type.other()
+ .asyncIdentityParcelableMethod(
+ parcelableContainingBinder, callbackListener, exceptionCallbackListener);
+
+ // Binders won't be identical
+ assertThat(callbackListener.await()).isNotNull();
+ }
+
+ @Test
+ public void futureParcelableContainingBinder_works() throws Exception {
+ ListenableFuture<Parcelable> future =
+ type.other().futureIdentityParcelableMethod(parcelableContainingBinder);
+
+ // Binders won't be identical
+ assertThat(future.get()).isNotNull();
+ }
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/BothProfilesTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/BothProfilesTest.java
deleted file mode 100644
index 8b15d35..0000000
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/BothProfilesTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.enterprise.connectedapps.instrumented.tests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.Application;
-import androidx.test.core.app.ApplicationProvider;
-import com.google.android.enterprise.connectedapps.Profile;
-import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingStringCallbackListenerMulti;
-import com.google.android.enterprise.connectedapps.instrumented.utils.InstrumentedTestUtilities;
-import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
-import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileTypeWhichNeedsContext;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests regarding calling a method on both profiles. */
-@RunWith(JUnit4.class)
-public class BothProfilesTest {
- private static final int FIVE_SECONDS = 5000;
- private static final String STRING = "String";
-
- private static final Application context = ApplicationProvider.getApplicationContext();
-
- private static final TestProfileConnector connector = TestProfileConnector.create(context);
- private static final InstrumentedTestUtilities utilities =
- new InstrumentedTestUtilities(context, connector);
-
- private final ProfileTestCrossProfileTypeWhichNeedsContext type =
- ProfileTestCrossProfileTypeWhichNeedsContext.create(connector);
-
- @Before
- public void setup() {
- utilities.ensureReadyForCrossProfileCalls();
- }
-
- @AfterClass
- public static void teardown() {
- utilities.ensureNoWorkProfile();
- }
-
- /** This test could not be covered by Robolectric. */
- @Test
- public void both_synchronous_timesOutOnWorkProfile_timeoutNotEnforcedOnSynchronousCalls() {
- utilities.manuallyConnectAndWait();
-
- Map<Profile, String> result =
- type.both()
- .timeout(FIVE_SECONDS)
- .identityStringMethodWhichDelays10SecondsOnWorkProfile(STRING);
-
- assertThat(result).containsKey(connector.utils().getPersonalProfile());
- assertThat(result).containsKey(connector.utils().getWorkProfile());
- }
-
- /** This test could not be covered by Robolectric. */
- @Test
- public void both_async_timesOutOnWorkProfile_onlyIncludesPersonalProfile()
- throws InterruptedException {
-
- BlockingStringCallbackListenerMulti callbackListener =
- new BlockingStringCallbackListenerMulti();
-
- type.both()
- .timeout(FIVE_SECONDS)
- .asyncIdentityStringMethodWhichDelays10SecondsOnWorkProfile(STRING, callbackListener);
- Map<Profile, String> result = callbackListener.await();
-
- assertThat(result).containsKey(connector.utils().getPersonalProfile());
- assertThat(result).doesNotContainKey(connector.utils().getWorkProfile());
- }
-
- /** This test could not be covered by Robolectric. */
- @Test
- public void both_future_timesOutOnWorkProfile_onlyIncludesPersonalProfile()
- throws InterruptedException, ExecutionException {
- Map<Profile, String> result =
- type.both()
- .timeout(FIVE_SECONDS)
- .futureIdentityStringMethodWhichDelays10SecondsOnWorkProfile(STRING)
- .get();
-
- assertThat(result).containsKey(connector.utils().getPersonalProfile());
- assertThat(result).doesNotContainKey(connector.utils().getWorkProfile());
- }
-}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ConnectTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ConnectTest.java
index 84069a7..9d3d5da 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ConnectTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ConnectTest.java
@@ -20,10 +20,11 @@
import android.app.Application;
import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
import com.google.android.enterprise.connectedapps.instrumented.utils.InstrumentedTestUtilities;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
-
+import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -43,7 +44,10 @@
public class ConnectTest {
private static final Application context = ApplicationProvider.getApplicationContext();
+ private static final String STRING = "String";
+
private static final TestProfileConnector connector = TestProfileConnector.create(context);
+ private final ProfileTestCrossProfileType type = ProfileTestCrossProfileType.create(connector);
private static final InstrumentedTestUtilities utilities =
new InstrumentedTestUtilities(context, connector);
@@ -54,7 +58,7 @@
@After
public void teardown() {
- connector.stopManualConnectionManagement();
+ connector.clearConnectionHolders();
utilities.waitForDisconnected();
}
@@ -73,15 +77,6 @@
}
@Test
- public void connect_startsManuallyManagingConnection() throws Exception {
- utilities.ensureReadyForCrossProfileCalls();
-
- connector.connect();
-
- assertThat(connector.isManuallyManagingConnection()).isTrue();
- }
-
- @Test
public void connect_otherProfileNotAvailable_throwsUnavailableProfileException() {
utilities.ensureNoWorkProfile();
@@ -98,15 +93,6 @@
}
@Test
- public void connect_otherProfileNotAvailable_doesNotStartManuallyManagingConnection() {
- utilities.ensureNoWorkProfile();
-
- connectIgnoreExceptions();
-
- assertThat(connector.isManuallyManagingConnection()).isFalse();
- }
-
- @Test
public void connect_alreadyConnected_returns() throws UnavailableProfileException {
utilities.ensureReadyForCrossProfileCalls();
connector.connect();
@@ -116,6 +102,17 @@
assertThat(connector.isConnected()).isTrue();
}
+ // This is testing a race condition so it tries the same thing repeatedly. If this test becomes
+ // flaky it indicates the race condition is back
+ @Test
+ public void connect_immediatelyUsesConnection_connectionHolderIsSet() throws Exception {
+ for (int i = 0; i < 1000; i++) {
+ try (ProfileConnectionHolder ignored = connector.connect()) {
+ assertThat(type.other().identityStringMethod(STRING)).isEqualTo(STRING);
+ }
+ }
+ }
+
private void connectIgnoreExceptions() {
try {
connector.connect();
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/CrossUserEndToEndTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/CrossUserEndToEndTest.java
new file mode 100644
index 0000000..6118ad9
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/CrossUserEndToEndTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.tests;
+
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.getUserHandleForUserId;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.os.UserHandle;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingExceptionCallbackListener;
+import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingStringCallbackListener;
+import com.google.android.enterprise.connectedapps.instrumented.utils.UserConnectorTestUtilities;
+import com.google.android.enterprise.connectedapps.instrumented.utils.UserManagementTestUtilities;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.AppCrossUserConnector;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.UserTestCrossUserType;
+import java.util.concurrent.ExecutionException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for high level behaviour running on a correctly configured device (with a managed profile
+ * with the app installed in both sides, granted INTERACT_ACROSS_USERS).
+ *
+ * <p>This tests that each type of call works in both directions.
+ */
+@RunWith(JUnit4.class)
+public class CrossUserEndToEndTest {
+
+ private static final Application context = ApplicationProvider.getApplicationContext();
+
+ private static final String STRING = "String";
+
+ private final UserManagementTestUtilities userUtilities =
+ new UserManagementTestUtilities(context);
+
+ private int otherUserId;
+ private UserHandle other;
+ private UserHandle current;
+ private AppCrossUserConnector connector;
+ private UserConnectorTestUtilities connectorUtilities;
+ private UserTestCrossUserType testCrossUserType;
+
+ @Before
+ public void setup() {
+ otherUserId = userUtilities.ensureUserReadyForCrossUserCalls();
+ other = getUserHandleForUserId(otherUserId);
+ current = getUserHandleForUserId(0);
+ connector = AppCrossUserConnector.create(context);
+ connectorUtilities = new UserConnectorTestUtilities(connector);
+ testCrossUserType = UserTestCrossUserType.create(connector);
+ }
+
+ @After
+ public void teardown() {
+ connector.clearConnectionHolders(other);
+ connectorUtilities.waitForDisconnected(other);
+ }
+
+ @Test
+ public void isAvailable_isTrue() {
+ assertThat(connector.isAvailable(other)).isTrue();
+ }
+
+ @Test
+ public void isConnected_isFalse() {
+ connector.removeConnectionHolder(other, this);
+ connectorUtilities.waitForDisconnected(other);
+
+ assertThat(connector.isConnected(other)).isFalse();
+ }
+
+ @Test
+ public void hasConnected_isConnectedIsTrue() {
+ connectorUtilities.addConnectionHolderAndWait(other, this);
+
+ assertThat(connector.isConnected(other)).isTrue();
+ }
+
+ @Test
+ public void hasConnected_userStopped_isConnectedIsFalse() throws UnavailableProfileException {
+ connector.connect(other);
+
+ userUtilities.stopUser(otherUserId);
+
+ assertThat(connector.isConnected(other)).isFalse();
+ }
+
+ @Test
+ public void hasConnected_synchronousConnection_isConnectedIsTrue()
+ throws UnavailableProfileException {
+ connector.connect(other);
+
+ assertThat(connector.isConnected(other)).isTrue();
+ }
+
+ @Test
+ public void callOnCurrent_resultIsCorrect() {
+ assertThat(testCrossUserType.current().identityStringMethod(STRING)).isEqualTo(STRING);
+ }
+
+ @Test
+ public void callsUser_givesCurrentUserHandle_resultIsCorrect()
+ throws UnavailableProfileException {
+ assertThat(testCrossUserType.user(current).identityStringMethod(STRING)).isEqualTo(STRING);
+ }
+
+ @Test
+ public void synchronousMethod_resultIsCorrect() throws UnavailableProfileException {
+ connector.connect(other);
+
+ assertThat(testCrossUserType.user(other).identityStringMethod(STRING)).isEqualTo(STRING);
+ }
+
+ @Test
+ public void futureMethod_resultIsCorrect() throws InterruptedException, ExecutionException {
+ assertThat(testCrossUserType.user(other).listenableFutureIdentityStringMethod(STRING).get())
+ .isEqualTo(STRING);
+ }
+
+ @Test
+ public void asyncMethod_resultIsCorrect() throws InterruptedException {
+ BlockingStringCallbackListener stringCallbackListener = new BlockingStringCallbackListener();
+
+ testCrossUserType
+ .user(other)
+ .asyncIdentityStringMethod(
+ STRING, stringCallbackListener, new BlockingExceptionCallbackListener());
+
+ assertThat(stringCallbackListener.await()).isEqualTo(STRING);
+ }
+
+ // No round-trip tests which call into the other user and the other user calls back into the
+ // current user, as there is currently no way to grant INTERACT_ACROSS_USERS_FULL on a
+ // non-instrumented user in Android (and it is unlikely that this will never be added).
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ExceptionsTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ExceptionsTest.java
new file mode 100644
index 0000000..289b4fb
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/ExceptionsTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.app.Application;
+import android.os.ParcelFileDescriptor;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
+import com.google.android.enterprise.connectedapps.instrumented.utils.InstrumentedTestUtilities;
+import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
+import com.google.android.enterprise.connectedapps.testapp.connector.ExceptionsSuppressingConnector;
+import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
+import com.google.android.enterprise.connectedapps.testapp.types.ProfileExceptionsSuppressingCrossProfileType;
+import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for exception handling in cross-profile methods. */
+@RunWith(JUnit4.class)
+public class ExceptionsTest {
+ private static final Application context = ApplicationProvider.getApplicationContext();
+
+ @Test
+ public void remoteException_throwsLocallyAndCrashes() throws IOException, InterruptedException {
+ TestProfileConnector connector = TestProfileConnector.create(context);
+ InstrumentedTestUtilities utilities = new InstrumentedTestUtilities(context, connector);
+ ProfileTestCrossProfileType type = ProfileTestCrossProfileType.create(connector);
+ utilities.ensureReadyForCrossProfileCalls();
+ utilities.addConnectionHolderAndWait(this);
+ String pidsBefore = getProcessIdsOfThisPackage();
+
+ ProfileRuntimeException exception = assertThrows(
+ ProfileRuntimeException.class,
+ () -> type.other().methodWhichThrowsRuntimeException());
+ assertThat(exception).hasCauseThat().isInstanceOf(CustomRuntimeException.class);
+
+ TimeUnit.SECONDS.sleep(5);
+
+ assertThat(getProcessIdsOfThisPackage()).isNotEqualTo(pidsBefore);
+ }
+
+ @Test
+ public void remoteException_throwsLocallyAndSuppresses() throws IOException, InterruptedException {
+ ExceptionsSuppressingConnector connector = ExceptionsSuppressingConnector.create(context);
+ InstrumentedTestUtilities utilities = new InstrumentedTestUtilities(context, connector);
+ ProfileExceptionsSuppressingCrossProfileType type =
+ ProfileExceptionsSuppressingCrossProfileType.create(connector);
+ utilities.ensureReadyForCrossProfileCalls();
+ utilities.addConnectionHolderAndWait(this);
+ String pidsBefore = getProcessIdsOfThisPackage();
+
+ ProfileRuntimeException exception = assertThrows(
+ ProfileRuntimeException.class,
+ () -> type.other().methodWhichThrowsRuntimeException());
+ assertThat(exception).hasCauseThat().isInstanceOf(CustomRuntimeException.class);
+
+ TimeUnit.SECONDS.sleep(5);
+
+ assertThat(getProcessIdsOfThisPackage()).isEqualTo(pidsBefore);
+ }
+
+ private static String getProcessIdsOfThisPackage() throws IOException {
+ ParcelFileDescriptor fd = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .executeShellCommand("pidof " + context.getApplicationInfo().processName);
+ InputStream inputStream = new FileInputStream(fd.getFileDescriptor());
+ StringBuilder sb = new StringBuilder();
+ for (int ch; (ch = inputStream.read()) != -1; ) {
+ sb.append((char) ch);
+ }
+ return sb.toString();
+ }
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/HappyPathEndToEndTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/HappyPathEndToEndTest.java
index 3b9ea57..719ca9a 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/HappyPathEndToEndTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/HappyPathEndToEndTest.java
@@ -19,7 +19,6 @@
import android.app.Application;
import androidx.test.core.app.ApplicationProvider;
-
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingExceptionCallbackListener;
import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingStringCallbackListener;
@@ -32,11 +31,9 @@
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import android.util.Log;
/**
* Tests for high level behaviour running on a correctly configured device (with a managed profile
@@ -64,7 +61,7 @@
@After
public void teardown() {
- connector.stopManualConnectionManagement();
+ connector.clearConnectionHolders();
utilities.waitForDisconnected();
}
@@ -80,7 +77,7 @@
@Test
public void isConnected_isFalse() {
- connector.stopManualConnectionManagement();
+ connector.clearConnectionHolders();
utilities.waitForDisconnected();
assertThat(connector.isConnected()).isFalse();
@@ -88,14 +85,14 @@
@Test
public void isConnected_hasConnected_isTrue() {
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
assertThat(connector.isConnected()).isTrue();
}
@Test
public void synchronousMethod_resultIsCorrect() throws UnavailableProfileException {
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
assertThat(type.other().identityStringMethod(STRING)).isEqualTo(STRING);
}
@@ -119,7 +116,7 @@
@Test
public void synchronousMethod_fromOtherProfile_resultIsCorrect()
throws UnavailableProfileException {
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
typeWithContext.other().connectToOtherProfile();
BlockingPoll.poll(
() -> {
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/InstrumentedTestUtilitiesTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/InstrumentedTestUtilitiesTest.java
index 81c9ce8..cc3d325 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/InstrumentedTestUtilitiesTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/InstrumentedTestUtilitiesTest.java
@@ -22,7 +22,7 @@
import com.google.android.enterprise.connectedapps.AvailabilityListener;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testing.InstrumentedTestUtilities;
-
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,15 +34,20 @@
private static final Application context = ApplicationProvider.getApplicationContext();
- private static final TestProfileConnector connector = TestProfileConnector.create(context);
- private static final InstrumentedTestUtilities utilities =
+ private final static TestProfileConnector connector = TestProfileConnector.create(context);
+ private final static InstrumentedTestUtilities utilities =
new InstrumentedTestUtilities(context, connector);
@AfterClass
- public static void teardown() {
+ public static void teardownClass() {
utilities.ensureNoWorkProfile();
}
+ @After
+ public void teardown() {
+ connector.clearConnectionHolders();
+ }
+
@Test
public void isAvailable_ensureReadyForCrossProfileCalls_isTrue() {
utilities.ensureReadyForCrossProfileCalls();
@@ -84,7 +89,7 @@
public void isConnected_waitForConnected_isTrue() {
utilities.ensureReadyForCrossProfileCalls();
- connector.startConnecting();
+ connector.addConnectionHolder(this);
utilities.waitForConnected();
assertThat(connector.isConnected()).isTrue();
@@ -93,10 +98,10 @@
@Test
public void isConnected_waitForDisconnected_isFalse() {
utilities.ensureReadyForCrossProfileCalls();
- connector.startConnecting();
+ connector.addConnectionHolder(this);
utilities.waitForConnected();
- connector.stopManualConnectionManagement();
+ connector.clearConnectionHolders();
utilities.waitForDisconnected();
assertThat(connector.isConnected()).isFalse();
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/MessageSizeTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/MessageSizeTest.java
index 363b271..e0cc1e9 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/MessageSizeTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/MessageSizeTest.java
@@ -27,7 +27,6 @@
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
import java.util.concurrent.ExecutionException;
-
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
@@ -58,23 +57,22 @@
}
@AfterClass
- public static void teardown() {
+ public static void teardownClass() {
utilities.ensureNoWorkProfile();
}
@Test
public void synchronous_smallMessage_sends() throws UnavailableProfileException {
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
assertThat(type.other().identityStringMethod(SMALL_STRING)).isEqualTo(SMALL_STRING);
}
@Test
public void synchronous_largeMessage_sends() throws UnavailableProfileException {
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
- // We can't use the asserts which compare Strings because of b/158998985
- assertThat(type.other().identityStringMethod(LARGE_STRING).equals(LARGE_STRING)).isTrue();
+ assertThat(type.other().identityStringMethod(LARGE_STRING)).isEqualTo(LARGE_STRING);
}
@Test
@@ -90,8 +88,7 @@
type.other()
.asyncIdentityStringMethod(LARGE_STRING, stringCallbackListener, exceptionCallbackListener);
- // We can't use the asserts which compare Strings because of b/158998985
- assertThat(stringCallbackListener.await().equals(LARGE_STRING)).isTrue();
+ assertThat(stringCallbackListener.await()).isEqualTo(LARGE_STRING);
}
@Test
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotInstalledInOtherUserTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotInstalledInOtherUserTest.java
index 9b6de41..24ccd87 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotInstalledInOtherUserTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotInstalledInOtherUserTest.java
@@ -25,7 +25,6 @@
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
import com.google.common.util.concurrent.ListenableFuture;
-
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,7 +47,7 @@
new InstrumentedTestUtilities(context, connector);
@AfterClass
- public static void teardown() {
+ public static void teardownClass() {
utilities.ensureNoWorkProfile();
}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotReallySerializableTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotReallySerializableTest.java
index 912138b..589d3eb 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotReallySerializableTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/NotReallySerializableTest.java
@@ -29,7 +29,6 @@
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
import com.google.common.util.concurrent.ListenableFuture;
-
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
@@ -60,14 +59,14 @@
}
@AfterClass
- public static void teardown() {
+ public static void teardownClass() {
utilities.ensureNoWorkProfile();
}
@Test
public void
synchronous_serializableObjectIsNotReallySerializable_throwsProfileRuntimeException() {
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
assertThrows(
ProfileRuntimeException.class,
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/SecondUserTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/SecondUserTest.java
index b882aa6..15a5026 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/SecondUserTest.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/SecondUserTest.java
@@ -65,7 +65,7 @@
public void call_hasWorkProfile_hasSecondUser_executesOnWorkProfile()
throws UnavailableProfileException {
utilities.ensureReadyForCrossProfileCalls();
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
int secondUserId = utilities.createUser("SecondUser");
try {
@@ -83,7 +83,7 @@
public void call_hasWorkProfile_hasSecondUser_fromWorkProfile_executesOnThisUser()
throws UnavailableProfileException {
utilities.ensureReadyForCrossProfileCalls();
- utilities.manuallyConnectAndWait();
+ utilities.addConnectionHolderAndWait(this);
int secondUserId = utilities.createUser("SecondUser");
try {
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/TimeoutTest.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/TimeoutTest.java
new file mode 100644
index 0000000..c1d36d9
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/tests/TimeoutTest.java
@@ -0,0 +1,66 @@
+package com.google.android.enterprise.connectedapps.instrumented.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
+import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingExceptionCallbackListener;
+import com.google.android.enterprise.connectedapps.instrumented.utils.BlockingStringCallbackListener;
+import com.google.android.enterprise.connectedapps.instrumented.utils.InstrumentedTestUtilities;
+import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
+import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for timeout behaviour.
+ *
+ * <p>This is required as robolectric does not have callbacks behave correctly after the binding is
+ * closed.
+ */
+@RunWith(JUnit4.class)
+public class TimeoutTest {
+ private static final Application context = ApplicationProvider.getApplicationContext();
+
+ private static final String STRING = "String";
+
+ private final TestProfileConnector connector = TestProfileConnector.create(context);
+ private final InstrumentedTestUtilities utilities =
+ new InstrumentedTestUtilities(context, connector);
+ private final ProfileTestCrossProfileType type = ProfileTestCrossProfileType.create(connector);
+
+ @Before
+ public void setup() {
+ utilities.ensureReadyForCrossProfileCalls();
+ }
+
+ @After
+ public void teardown() {
+ utilities.ensureNoWorkProfile();
+ }
+
+ @Test
+ public void
+ other_async_callbackTriggeredMultipleTimes_connectionHeldOpen_isReceivedMultipleTimes()
+ throws Exception {
+ BlockingStringCallbackListener stringCallbackListener = new BlockingStringCallbackListener();
+
+ try (ProfileConnectionHolder connectionHolder =
+ connector.addConnectionHolder(stringCallbackListener)) {
+ type.other()
+ .asyncIdentityStringMethodWhichCallsBackTwiceWithNonBlockingDelay(
+ STRING,
+ stringCallbackListener,
+ /* secondsDelay= */ 60,
+ new BlockingExceptionCallbackListener());
+ stringCallbackListener.await();
+
+ assertThat(stringCallbackListener.await(200, TimeUnit.SECONDS)).isEqualTo(STRING);
+ }
+ }
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingCallbackListener.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingCallbackListener.java
index 6963fdd..d4787f0 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingCallbackListener.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingCallbackListener.java
@@ -15,7 +15,9 @@
*/
package com.google.android.enterprise.connectedapps.instrumented.utils;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
/**
* Base class for callback listeners which can block until a result is received.
@@ -24,16 +26,17 @@
* {@link #receive(Object)} when the callback completes.
*/
public abstract class BlockingCallbackListener<E> {
- private E callbackValue;
- private final CountDownLatch latch = new CountDownLatch(1);
+ private BlockingQueue<E> results = new LinkedBlockingQueue<>();
+
+ public E await(long timeout, TimeUnit unit) throws InterruptedException {
+ return results.poll(timeout, unit);
+ }
public E await() throws InterruptedException {
- latch.await();
- return callbackValue;
+ return await(10, TimeUnit.MINUTES);
}
protected void receive(E value) {
- callbackValue = value;
- latch.countDown();
+ results.offer(value);
}
}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingParcelableCallbackListener.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingParcelableCallbackListener.java
new file mode 100644
index 0000000..1c42229
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/BlockingParcelableCallbackListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.utils;
+
+import android.os.Parcelable;
+import com.google.android.enterprise.connectedapps.testapp.TestParcelableCallbackListener;
+
+/** A {@link TestParcelableCallbackListener} which can block for a result. */
+public class BlockingParcelableCallbackListener extends BlockingCallbackListener<Parcelable>
+ implements TestParcelableCallbackListener {
+ @Override
+ public void parcelableCallback(Parcelable s) {
+ receive(s);
+ }
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/InstrumentedTestUtilities.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/InstrumentedTestUtilities.java
index be1db8e..3c05431 100644
--- a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/InstrumentedTestUtilities.java
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/InstrumentedTestUtilities.java
@@ -15,24 +15,14 @@
*/
package com.google.android.enterprise.connectedapps.instrumented.utils;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.getUserHandleForUserId;
+import static com.google.android.enterprise.connectedapps.instrumented.utils.UserAndProfileTestUtilities.runCommandWithOutput;
import android.content.Context;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
-import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
import com.google.android.enterprise.connectedapps.ProfileConnector;
-import com.google.android.enterprise.connectedapps.SharedTestUtilities;
-import com.google.android.enterprise.connectedapps.instrumented.utils.ServiceCall.Parameter;
import com.google.android.enterprise.connectedapps.testing.ProfileAvailabilityPoll;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.NoSuchElementException;
-import java.util.Scanner;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Wrapper around {@link
@@ -44,73 +34,14 @@
private final ProfileConnector connector;
private final Context context;
private final com.google.android.enterprise.connectedapps.testing.InstrumentedTestUtilities
- instrumentedTestUtilities;
-
- private static final int S_REQUEST_QUIET_MODE_ENABLED_ID = 73;
- private static final int R_REQUEST_QUIET_MODE_ENABLED_ID = 72;
- private static final int REQUEST_QUIET_MODE_ENABLED_ID = 58;
-
- private static final String USER_ID_KEY = "USER_ID";
- private static final Parameter USER_ID_PARAMETER = new Parameter(USER_ID_KEY);
-
- private static final ServiceCall S_TURN_OFF_WORK_PROFILE_COMMAND =
- new ServiceCall("user", S_REQUEST_QUIET_MODE_ENABLED_ID)
- .setUser(1000) // user 1000 has packageName "android"
- .addStringParam("android") // callingPackage
- .addBooleanParam(true) // enableQuietMode
- .addIntParam(USER_ID_PARAMETER) // userId
- .addIntParam(0) // target
- .addIntParam(0); // flags
-
- private static final ServiceCall R_TURN_OFF_WORK_PROFILE_COMMAND =
- new ServiceCall("user", R_REQUEST_QUIET_MODE_ENABLED_ID)
- .setUser(1000) // user 1000 has packageName "android"
- .addStringParam("android") // callingPackage
- .addBooleanParam(true) // enableQuietMode
- .addIntParam(USER_ID_PARAMETER) // userId
- .addIntParam(0) // target
- .addIntParam(0); // flags
-
- private static final ServiceCall TURN_OFF_WORK_PROFILE_COMMAND =
- new ServiceCall("user", REQUEST_QUIET_MODE_ENABLED_ID)
- .setUser(1000) // user 1000 has packageName "android"
- .addStringParam("android") // callingPackage
- .addBooleanParam(true) // enableQuietMode
- .addIntParam(USER_ID_PARAMETER) // userId
- .addIntParam(0); // target
-
- private static final ServiceCall S_TURN_ON_WORK_PROFILE_COMMAND =
- new ServiceCall("user", S_REQUEST_QUIET_MODE_ENABLED_ID)
- .setUser(1000) // user 1000 has packageName "android"
- .addStringParam("android") // callingPackage
- .addBooleanParam(false) // enableQuietMode
- .addIntParam(USER_ID_PARAMETER) // userId
- .addIntParam(0) // target
- .addIntParam(0); // flags
-
- private static final ServiceCall R_TURN_ON_WORK_PROFILE_COMMAND =
- new ServiceCall("user", R_REQUEST_QUIET_MODE_ENABLED_ID)
- .setUser(1000) // user 1000 has packageName "android"
- .addStringParam("android") // callingPackage
- .addBooleanParam(false) // enableQuietMode
- .addIntParam(USER_ID_PARAMETER) // userId
- .addIntParam(0) // target
- .addIntParam(0); // flags
-
- private static final ServiceCall TURN_ON_WORK_PROFILE_COMMAND =
- new ServiceCall("user", REQUEST_QUIET_MODE_ENABLED_ID)
- .setUser(1000) // user 1000 has packageName "android"
- .addStringParam("android") // callingPackage
- .addBooleanParam(false) // enableQuietMode
- .addIntParam(USER_ID_PARAMETER) // userId
- .addIntParam(0); // target
+ instrumentedTestUtilities;
public InstrumentedTestUtilities(Context context, ProfileConnector connector) {
this.context = context;
this.connector = connector;
this.instrumentedTestUtilities =
- new com.google.android.enterprise.connectedapps.testing.InstrumentedTestUtilities(
- context, connector);
+ new com.google.android.enterprise.connectedapps.testing.InstrumentedTestUtilities(
+ context, connector);
}
/**
@@ -130,7 +61,7 @@
}
private UserHandle getWorkProfileUserHandle() {
- return SharedTestUtilities.getUserHandleForUserId(getWorkProfileUserId());
+ return getUserHandleForUserId(getWorkProfileUserId());
}
/**
@@ -156,7 +87,7 @@
public void installInUser(int userId) {
runCommandWithOutput(
- "cmd package install-existing --user " + userId + " " + context.getPackageName());
+ "cmd package install-existing --user " + userId + " " + context.getPackageName());
}
/**
@@ -167,7 +98,7 @@
public void grantInteractAcrossUsers() {
// TODO(scottjonathan): Support INTERACT_ACROSS_PROFILES in these tests.
runCommandWithOutput(
- "pm grant " + context.getPackageName() + " android.permission.INTERACT_ACROSS_USERS");
+ "pm grant " + context.getPackageName() + " android.permission.INTERACT_ACROSS_USERS");
}
/**
@@ -190,15 +121,12 @@
return;
}
-
- runCommandWithOutput("pm remove-user " + getWorkProfileUserId());
-
// TODO(162219825): Try to remove the package
-// throw new IllegalStateException(
-// "There is already a work profile on the device with user id "
-// + getWorkProfileUserId()
-// + ".");
+ throw new IllegalStateException(
+ "There is already a work profile on the device with user id "
+ + getWorkProfileUserId()
+ + ".");
}
runCommandWithOutput("pm create-user --profileOf 0 --managed TestProfile123");
int workProfileUserId = getWorkProfileUserId();
@@ -208,7 +136,7 @@
private static boolean userHasPackageInstalled(int userId, String packageName) {
String expectedPackageLine = "package:" + packageName;
String[] installedPackages =
- runCommandWithOutput("pm list packages --user " + userId).split("\n");
+ runCommandWithOutput("pm list packages --user " + userId).split("\n");
for (String installedPackage : installedPackages) {
if (installedPackage.equals(expectedPackageLine)) {
return true;
@@ -228,51 +156,6 @@
}
/**
- * Turn off the work profile and block until it has been turned off.
- *
- * <p>This uses {@link ServiceCall} and so is only guaranteed to work correctly on AOSP.
- *
- * @see #turnOffWorkProfile()
- */
- public void turnOffWorkProfileAndWait() {
- turnOffWorkProfile();
-
- ProfileAvailabilityPoll.blockUntilProfileNotAvailable(context, getWorkProfileUserHandle());
- }
-
- // TODO(160147511): Remove use of service calls for versions after R
- /**
- * Turn off the work profile
- *
- * <p>This uses {@link ServiceCall} and so is only guaranteed to work correctly on AOSP.
- *
- * @see #turnOffWorkProfileAndWait()
- */
- public void turnOffWorkProfile() {
- if (VERSION.CODENAME.equals("S")) {
- runCommandWithOutput(
- S_TURN_OFF_WORK_PROFILE_COMMAND
- .prepare()
- .setInt(USER_ID_KEY, getWorkProfileUserId())
- .getCommand());
- } else if (VERSION.SDK_INT == VERSION_CODES.R) {
- runCommandWithOutput(
- R_TURN_OFF_WORK_PROFILE_COMMAND
- .prepare()
- .setInt(USER_ID_KEY, getWorkProfileUserId())
- .getCommand());
- } else if (VERSION.SDK_INT == VERSION_CODES.Q || VERSION.SDK_INT == VERSION_CODES.P) {
- runCommandWithOutput(
- TURN_OFF_WORK_PROFILE_COMMAND
- .prepare()
- .setInt(USER_ID_KEY, getWorkProfileUserId())
- .getCommand());
- } else {
- throw new IllegalStateException("Cannot turn off work on this version of android");
- }
- }
-
- /**
* Turn on the work profile and block until it has been turned on.
*
* <p>This uses {@link ServiceCall} and so is only guaranteed to work correctly on AOSP.
@@ -286,40 +169,42 @@
turnOnWorkProfile();
- ProfileAvailabilityPoll.blockUntilProfileRunningAndUnlocked(
- context, getWorkProfileUserHandle());
+ ProfileAvailabilityPoll.blockUntilUserRunningAndUnlocked(context, getWorkProfileUserHandle());
}
- // TODO(160147511): Remove use of service calls for versions after R
/**
- * Turn on the work profile and block until it has been turned on.
+ * Turn on the work profile.
*
* <p>This uses {@link ServiceCall} and so is only guaranteed to work correctly on AOSP.
*
* @see #turnOnWorkProfileAndWait()
*/
public void turnOnWorkProfile() {
- if (VERSION.CODENAME.equals("S")) {
- runCommandWithOutput(
- S_TURN_ON_WORK_PROFILE_COMMAND
- .prepare()
- .setInt(USER_ID_KEY, getWorkProfileUserId())
- .getCommand());
- } else if (VERSION.SDK_INT == VERSION_CODES.R) {
- runCommandWithOutput(
- R_TURN_ON_WORK_PROFILE_COMMAND
- .prepare()
- .setInt(USER_ID_KEY, getWorkProfileUserId())
- .getCommand());
- } else if (VERSION.SDK_INT == VERSION_CODES.Q || VERSION.SDK_INT == VERSION_CODES.P) {
- runCommandWithOutput(
- TURN_ON_WORK_PROFILE_COMMAND
- .prepare()
- .setInt(USER_ID_KEY, getWorkProfileUserId())
- .getCommand());
- } else {
- throw new IllegalStateException("Cannot turn on work on this version of android");
- }
+ UserAndProfileTestUtilities.turnOnUser(getWorkProfileUserId());
+ }
+
+ /**
+ * Turn off the work profile and block until it has been turned off.
+ *
+ * <p>This uses {@link ServiceCall} and so is only guaranteed to work correctly on AOSP.
+ *
+ * @see #turnOffWorkProfile()
+ */
+ public void turnOffWorkProfileAndWait() {
+ turnOffWorkProfile();
+
+ ProfileAvailabilityPoll.blockUntilUserNotAvailable(context, getWorkProfileUserHandle());
+ }
+
+ /**
+ * Turn off the work profile.
+ *
+ * <p>This uses {@link ServiceCall} and so is only guaranteed to work correctly on AOSP.
+ *
+ * @see #turnOffWorkProfileAndWait()
+ */
+ public void turnOffWorkProfile() {
+ UserAndProfileTestUtilities.turnOffUser(getWorkProfileUserId());
}
/**
@@ -339,50 +224,20 @@
}
/**
- * Manually call {@link ProfileConnector#startConnecting()} and wait for connection to be
+ * Call {@link ProfileConnector#addConnectionHolder(Object)} ()} and wait for connection to be
* complete.
*/
- public void manuallyConnectAndWait() {
- connector.startConnecting();
+ public ProfileConnectionHolder addConnectionHolderAndWait(Object connectionHolder) {
+ ProfileConnectionHolder p = connector.addConnectionHolder(connectionHolder);
waitForConnected();
+ return p;
}
- private static final Pattern CREATE_USER_PATTERN =
- Pattern.compile("Success: created user id (\\d+)");
-
public int createUser(String username) {
- String output = runCommandWithOutput("pm create-user " + username);
-
- Matcher userMatcher = CREATE_USER_PATTERN.matcher(output);
- if (userMatcher.find()) {
- return Integer.parseInt(userMatcher.group(1));
- }
-
- throw new IllegalStateException("Could not create user. Output: " + output);
+ return UserAndProfileTestUtilities.createUser(username);
}
public void startUser(int userId) {
- UserHandle userHandle = SharedTestUtilities.getUserHandleForUserId(userId);
- InstrumentedTestUtilities.runCommandWithOutput("am start-user " + userId);
- ProfileAvailabilityPoll.blockUntilProfileRunningAndUnlocked(context, userHandle);
- }
-
- private static String runCommandWithOutput(String command) {
- // TODO: Log output so we can see why it's failing
- ParcelFileDescriptor p = runCommand(command);
-
- InputStream inputStream = new FileInputStream(p.getFileDescriptor());
-
- try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
- return scanner.useDelimiter("\\A").next();
- } catch (NoSuchElementException e) {
- return "";
- }
- }
-
- private static ParcelFileDescriptor runCommand(String command) {
- return InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .executeShellCommand(command);
+ UserAndProfileTestUtilities.startUserAndBlock(context, userId);
}
}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserAndProfileTestUtilities.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserAndProfileTestUtilities.java
new file mode 100644
index 0000000..3bf283f
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserAndProfileTestUtilities.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.utils;
+
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.getUserHandleForUserId;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.ParcelFileDescriptor;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.enterprise.connectedapps.instrumented.utils.ServiceCall.Parameter;
+import com.google.android.enterprise.connectedapps.testing.ProfileAvailabilityPoll;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+// TODO(b/160147511): Remove use of service calls for versions after R
+final class UserAndProfileTestUtilities {
+
+ private static final int R_REQUEST_QUIET_MODE_ENABLED_ID = 72;
+ private static final int REQUEST_QUIET_MODE_ENABLED_ID = 58;
+
+ private static final String USER_ID_KEY = "USER_ID";
+ private static final Parameter USER_ID_PARAMETER = new Parameter(USER_ID_KEY);
+
+ private static final ServiceCall R_TURN_OFF_USER_COMMAND =
+ new ServiceCall("user", R_REQUEST_QUIET_MODE_ENABLED_ID)
+ .setUser(1000) // user 1000 has packageName "android"
+ .addStringParam("android") // callingPackage
+ .addBooleanParam(true) // enableQuietMode
+ .addIntParam(USER_ID_PARAMETER) // userId
+ .addIntParam(0) // target
+ .addIntParam(0); // flags
+
+ private static final ServiceCall TURN_OFF_USER_COMMAND =
+ new ServiceCall("user", REQUEST_QUIET_MODE_ENABLED_ID)
+ .setUser(1000) // user 1000 has packageName "android"
+ .addStringParam("android") // callingPackage
+ .addBooleanParam(true) // enableQuietMode
+ .addIntParam(USER_ID_PARAMETER) // userId
+ .addIntParam(0); // target
+
+ private static final ServiceCall R_TURN_ON_USER_COMMAND =
+ new ServiceCall("user", R_REQUEST_QUIET_MODE_ENABLED_ID)
+ .setUser(1000) // user 1000 has packageName "android"
+ .addStringParam("android") // callingPackage
+ .addBooleanParam(false) // enableQuietMode
+ .addIntParam(USER_ID_PARAMETER) // userId
+ .addIntParam(0) // target
+ .addIntParam(0); // flags
+
+ private static final ServiceCall TURN_ON_USER_COMMAND =
+ new ServiceCall("user", REQUEST_QUIET_MODE_ENABLED_ID)
+ .setUser(1000) // user 1000 has packageName "android"
+ .addStringParam("android") // callingPackage
+ .addBooleanParam(false) // enableQuietMode
+ .addIntParam(USER_ID_PARAMETER) // userId
+ .addIntParam(0); // target
+
+ static void turnOnUser(int userId) {
+ if (VERSION.SDK_INT == VERSION_CODES.R) {
+ runServiceCall(R_TURN_ON_USER_COMMAND, userId);
+ } else if (VERSION.SDK_INT == VERSION_CODES.Q || VERSION.SDK_INT == VERSION_CODES.P) {
+ runServiceCall(TURN_ON_USER_COMMAND, userId);
+ } else {
+ throw new IllegalStateException("Cannot turn on user on this version of android");
+ }
+ }
+
+ static void turnOffUser(int userId) {
+ if (VERSION.SDK_INT == VERSION_CODES.R) {
+ runServiceCall(R_TURN_OFF_USER_COMMAND, userId);
+ } else if (VERSION.SDK_INT == VERSION_CODES.Q || VERSION.SDK_INT == VERSION_CODES.P) {
+ runServiceCall(TURN_OFF_USER_COMMAND, userId);
+ } else {
+ throw new IllegalStateException("Cannot turn off user on this version of android");
+ }
+ }
+
+ private static void runServiceCall(ServiceCall serviceCall, int userId) {
+ runCommandWithOutput(serviceCall.prepare().setInt(USER_ID_KEY, userId).getCommand());
+ }
+
+ private static final Pattern CREATE_USER_PATTERN =
+ Pattern.compile("Success: created user id (\\d+)");
+
+ static int createUser(String username) {
+ String output = runCommandWithOutput("pm create-user " + username);
+
+ Matcher userMatcher = CREATE_USER_PATTERN.matcher(output);
+ if (userMatcher.find()) {
+ return Integer.parseInt(userMatcher.group(1));
+ }
+
+ throw new IllegalStateException("Could not create user. Output: " + output);
+ }
+
+ static void startUserAndBlock(Context context, int userId) {
+ runCommandWithOutput("am start-user " + userId);
+ ProfileAvailabilityPoll.blockUntilUserRunningAndUnlocked(
+ context, getUserHandleForUserId(userId));
+ }
+
+ static String runCommandWithOutput(String command) {
+ ParcelFileDescriptor p = runCommand(command);
+ InputStream inputStream = new FileInputStream(p.getFileDescriptor());
+
+ try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
+ return scanner.useDelimiter("\\A").next();
+ } catch (NoSuchElementException ignored) {
+ return "";
+ }
+ }
+
+ private static ParcelFileDescriptor runCommand(String command) {
+ return InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .executeShellCommand(command);
+ }
+
+ private UserAndProfileTestUtilities() {}
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserConnectorTestUtilities.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserConnectorTestUtilities.java
new file mode 100644
index 0000000..71f4af6
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserConnectorTestUtilities.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.utils;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.UserHandle;
+import com.google.android.enterprise.connectedapps.ConnectionListener;
+import com.google.android.enterprise.connectedapps.UserConnector;
+import java.util.concurrent.CountDownLatch;
+
+public class UserConnectorTestUtilities {
+
+ private static final int TIMEOUT_MS = 10_000;
+
+ private final UserConnector connector;
+
+ public UserConnectorTestUtilities(UserConnector connector) {
+ this.connector = connector;
+ }
+
+ public void waitForConnected(UserHandle userHandle) {
+ CountDownLatch connectionLatch = new CountDownLatch(1);
+
+ ConnectionListener connectionListener =
+ () -> {
+ if (connector.isConnected(userHandle)) {
+ connectionLatch.countDown();
+ }
+ };
+
+ connector.addConnectionListener(userHandle, connectionListener);
+ connectionListener.connectionChanged();
+
+ try {
+ connectionLatch.await(TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("Error waiting to connect", e);
+ } finally {
+ connector.removeConnectionListener(userHandle, connectionListener);
+ }
+ }
+
+ public void waitForDisconnected(UserHandle userHandle) {
+ CountDownLatch connectionLatch = new CountDownLatch(1);
+
+ ConnectionListener connectionListener =
+ () -> {
+ if (!connector.isConnected(userHandle)) {
+ connectionLatch.countDown();
+ }
+ };
+
+ connector.addConnectionListener(userHandle, connectionListener);
+ connectionListener.connectionChanged();
+
+ try {
+ connectionLatch.await(TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("Error waiting to disconnect", e);
+ } finally {
+ connector.removeConnectionListener(userHandle, connectionListener);
+ }
+ }
+
+ public void addConnectionHolderAndWait(UserHandle userHandle, Object connectionHolder) {
+ connector.addConnectionHolder(userHandle, connectionHolder);
+ waitForConnected(userHandle);
+ }
+}
diff --git a/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserManagementTestUtilities.java b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserManagementTestUtilities.java
new file mode 100644
index 0000000..4188395
--- /dev/null
+++ b/tests/instrumented/src/main/java/com/google/android/enterprise/connectedapps/instrumented/utils/UserManagementTestUtilities.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.instrumented.utils;
+
+import static android.os.UserHandle.getUserHandleForUid;
+import static com.google.android.enterprise.connectedapps.instrumented.utils.UserAndProfileTestUtilities.runCommandWithOutput;
+
+import android.content.Context;
+import android.os.UserHandle;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.enterprise.connectedapps.testing.BlockingPoll;
+import com.google.android.enterprise.connectedapps.testing.ProfileAvailabilityPoll;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class UserManagementTestUtilities {
+
+ private final Context context;
+
+ public UserManagementTestUtilities(Context context) {
+ this.context = context;
+ }
+
+ public void stopUser(int userId) {
+ runCommandWithOutput("am stop-user -w -f " + userId);
+ }
+
+ /** Create a user, perform all necessary user setup, and return their ID. */
+ public int ensureUserReadyForCrossUserCalls() {
+ return ensureUserReadyForCrossUserCalls(context.getPackageName());
+ }
+
+ public int ensureUserReadyForCrossUserCalls(String packageName) {
+ int userId = createOrFindOtherUser();
+ UserAndProfileTestUtilities.startUserAndBlock(context, userId);
+
+ ensurePackageInstalled(userId, packageName);
+ grantInteractAcrossUsersFull();
+
+ waitForChangesToTakeEffect(getUserHandleForUid(userId));
+
+ return userId;
+ }
+
+ private int createOrFindOtherUser() {
+ if (hasOtherUser()) {
+ return getOtherUserId();
+ }
+
+ return createOtherUser();
+ }
+
+ private int createOtherUser() {
+ int userId = UserAndProfileTestUtilities.createUser("TestOtherUser");
+ BlockingPoll.poll(this::hasOtherUser, 100, 10000);
+ installPackage(userId, context.getPackageName());
+ return userId;
+ }
+
+ private boolean hasOtherUser() {
+ try {
+ getOtherUserId();
+ return true;
+ } catch (IllegalStateException e) {
+ return false;
+ }
+ }
+
+ private int getOtherUserId() {
+ String userList = runCommandWithOutput("pm list users");
+
+ Matcher matcher = Pattern.compile("UserInfo\\{(.*):.*:.*\\}").matcher(userList);
+
+ while (matcher.find()) {
+ int userId = Integer.parseInt(matcher.group(1));
+ if (userId != 0) {
+ // Skip system user
+ return userId;
+ }
+ }
+
+ throw new IllegalStateException("No non-system user found: " + userList);
+ }
+
+ private void ensurePackageInstalled(int userId, String packageName) {
+ if (!packageName.equals(context.getPackageName())) {
+ installPackage(userId, packageName);
+ }
+ }
+
+ private static void installPackage(int userId, String packageName) {
+ runCommandWithOutput("cmd package install-existing --user " + userId + " " + packageName);
+ }
+
+ private void grantInteractAcrossUsersFull() {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.INTERACT_ACROSS_USERS_FULL");
+ }
+
+ private void waitForChangesToTakeEffect(UserHandle userHandle) {
+ ProfileAvailabilityPoll.blockUntilUserRunningAndUnlocked(context, userHandle);
+ }
+}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsTest.java
index 08e4e2a..b7ff2dd 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/AlwaysThrowsTest.java
@@ -53,7 +53,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_AlwaysThrows");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_AlwaysThrows");
}
@Test
@@ -66,10 +66,9 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_AlwaysThrows")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_AlwaysThrows")
.contentsAsUtf8String()
- .contains(
- "class ProfileNotesType_AlwaysThrows implements" + " ProfileNotesType_SingleSender");
+ .contains("class NotesType_AlwaysThrows implements NotesType_SingleSender");
}
@Test
@@ -82,8 +81,8 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_AlwaysThrows")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_AlwaysThrows")
.contentsAsUtf8String()
- .contains("public ProfileNotesType_AlwaysThrows(String errorMessage)");
+ .contains("public NotesType_AlwaysThrows(String errorMessage)");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerTest.java
index ca62a9d..88d3d96 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/BundlerTest.java
@@ -53,7 +53,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_Bundler");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_Bundler");
}
@Test
@@ -66,8 +66,8 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_Bundler")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_Bundler")
.contentsAsUtf8String()
- .contains("ProfileNotesType_Bundler implements Bundler");
+ .contains("NotesType_Bundler implements Bundler");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CacheableTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CacheableTest.java
new file mode 100644
index 0000000..f0c1bef
--- /dev/null
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CacheableTest.java
@@ -0,0 +1,319 @@
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.INSTALLATION_LISTENER_NAME;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.NOTES_PACKAGE;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.SERIALIZABLE_OBJECT;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.installationListener;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.installationListenerSimple;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.installationListenerSimpleWithIntentParam;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.installationListenerSimpleWithStringParam;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationStrings;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CacheableTest {
+ private static final String CACHEABLE_METHOD_RETURNS_VOID_ERROR =
+ "Methods annotated with @Cacheable must return a non-void type";
+ private static final String CACHEABLE_ANNOTATION_ON_NON_METHOD_ERROR =
+ "annotation type not applicable to this kind of declaration";
+ private static final String CACHEABLE_METHOD_RETURNS_NON_SERIALIZABLE_ERROR =
+ "Methods annotated with @Cacheable must return a type which implements Serializable, return"
+ + " a future with a Serializable result or return void with a simple callback parameter.";
+ private static final String CACHEABLE_METHOD_NON_SIMPLE_CALLBACK_ERROR =
+ "Methods annotated with @Cacheable may only have a callback parameter which is simple.";
+ private static final String CACHEABLE_METHOD_USES_INVALID_PARAMETERS_ERROR =
+ "Methods annotated with @Cacheable may only use callbacks that take a single Serializable"
+ + " parameter.";
+
+ private final AnnotationStrings annotationStrings;
+
+ public CacheableTest(AnnotationStrings annotationStrings) {
+ this.annotationStrings = annotationStrings;
+ }
+
+ @Parameters(name = "{0}")
+ public static Iterable<AnnotationStrings> getAnnotationPrinters() {
+ return AnnotationFinder.annotationStrings();
+ }
+
+ @Test
+ public void validCacheableAnnotation_compiles() {
+ JavaFileObject validCacheableMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public int countNotes() {",
+ "return 1;",
+ " }",
+ "}");
+
+ Compilation compilation = javac().withProcessors(new Processor()).compile(validCacheableMethod);
+
+ assertThat(compilation).succeededWithoutWarnings();
+ }
+
+ @Test
+ public void cacheableAnnotationOnClass_hasError() {
+ JavaFileObject validCacheableMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public final class NotesType {",
+ "public void refreshNotes() {",
+ " }",
+ "}");
+
+ Compilation compilation = javac().withProcessors(new Processor()).compile(validCacheableMethod);
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_ANNOTATION_ON_NON_METHOD_ERROR)
+ .inFile(validCacheableMethod);
+ }
+
+ @Test
+ public void cacheableMethodReturnsVoid_hasError() {
+ JavaFileObject voidMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public void refreshNotes() {",
+ " }",
+ "}");
+
+ Compilation compilation = javac().withProcessors(new Processor()).compile(voidMethod);
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_METHOD_RETURNS_VOID_ERROR)
+ .inFile(voidMethod);
+ }
+
+ @Test
+ public void cacheableMethodReturnsNonSerializable_hasError() {
+ JavaFileObject nonSerializableMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import android.content.Intent;",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public Intent refreshNotes() {",
+ "return new Intent();",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac().withProcessors(new Processor()).compile(nonSerializableMethod);
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_METHOD_RETURNS_NON_SERIALIZABLE_ERROR)
+ .inFile(nonSerializableMethod);
+ }
+
+ @Test
+ public void cacheableMethodReturnSerializable_compiles() {
+ JavaFileObject serializableReturnMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import android.content.Intent;",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public SerializableObject refreshNotes() {",
+ "return null;",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(serializableReturnMethod, SERIALIZABLE_OBJECT);
+
+ assertThat(compilation).succeededWithoutWarnings();
+ }
+
+ @Test
+ public void cacheableMethodReturnsFutureWithSerializableResult_compiles() {
+ JavaFileObject futureWithSerializableReturnMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import com.google.common.util.concurrent.ListenableFuture;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public ListenableFuture<SerializableObject> refreshNotes() {",
+ "return null;",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(futureWithSerializableReturnMethod, SERIALIZABLE_OBJECT);
+
+ assertThat(compilation).succeededWithoutWarnings();
+ }
+
+ @Test
+ public void cacheableMethodReturnsFutureWithNonSerializableResult_hasError() {
+ JavaFileObject futureWithNonSerializableReturnMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import android.content.Intent;",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import com.google.common.util.concurrent.ListenableFuture;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public ListenableFuture<Intent> refreshNotes() {",
+ "return null;",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(futureWithNonSerializableReturnMethod, SERIALIZABLE_OBJECT);
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_METHOD_RETURNS_NON_SERIALIZABLE_ERROR)
+ .inFile(futureWithNonSerializableReturnMethod);
+ }
+
+ @Test
+ public void cacheableMethodHasSimpleCallbackParameter_compiles() {
+ JavaFileObject simpleCallbackParameterMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import android.content.Intent;",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public void refreshNotes(" + INSTALLATION_LISTENER_NAME + " a) {",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ simpleCallbackParameterMethod,
+ installationListenerSimpleWithStringParam(annotationStrings));
+
+ assertThat(compilation).succeededWithoutWarnings();
+ }
+
+ @Test
+ public void cacheableMethodHasNonSimpleCallbackParameter_hasError() {
+ JavaFileObject nonSimpleCallbackParameterMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public void refreshNotes(" + INSTALLATION_LISTENER_NAME + " a) {",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(nonSimpleCallbackParameterMethod, installationListener(annotationStrings));
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_METHOD_NON_SIMPLE_CALLBACK_ERROR)
+ .inFile(nonSimpleCallbackParameterMethod);
+ }
+
+ @Test
+ public void cacheableMethodHasCallbackWithNonSerializableParameter_hasError() {
+ JavaFileObject simpleCallbackParameterMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public void refreshNotes(" + INSTALLATION_LISTENER_NAME + " a) {",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ simpleCallbackParameterMethod,
+ installationListenerSimpleWithIntentParam(annotationStrings));
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_METHOD_USES_INVALID_PARAMETERS_ERROR)
+ .inFile(simpleCallbackParameterMethod);
+ }
+
+ @Test
+ public void cacheableMethodHasCallbackWithNoParameters_hasError() {
+ JavaFileObject simpleCallbackParameterMethod =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import com.google.android.enterprise.connectedapps.annotations.Cacheable;",
+ "import " + annotationStrings.crossProfileQualifiedName() + ";",
+ "public final class NotesType {",
+ annotationStrings.crossProfileAsAnnotation(),
+ "@Cacheable",
+ "public void refreshNotes(" + INSTALLATION_LISTENER_NAME + " a) {",
+ " }",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(simpleCallbackParameterMethod, installationListenerSimple(annotationStrings));
+
+ assertThat(compilation)
+ .hadErrorContaining(CACHEABLE_METHOD_USES_INVALID_PARAMETERS_ERROR)
+ .inFile(simpleCallbackParameterMethod);
+ }
+}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackSupportedParameterTypeTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackSupportedParameterTypeTest.java
index 136fa36..b5aa559 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackSupportedParameterTypeTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackSupportedParameterTypeTest.java
@@ -83,7 +83,8 @@
"java.util.List<com.google.protos.connectedappssdk.TestProtoOuterClass.TestProto>",
"com.google.common.collect.ImmutableMap<String, String>",
"android.util.Pair<String, Integer>",
- "com.google.common.base.Optional<ParcelableObject>"
+ "com.google.common.base.Optional<ParcelableObject>",
+ "android.graphics.drawable.Drawable"
};
return combineParameters(AnnotationFinder.annotationStrings(), Arrays.asList(types));
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackTest.java
index adc1c3d..c733045 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileCallbackTest.java
@@ -693,7 +693,7 @@
installationListenerWithStringParam(annotationStrings));
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileNotesType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.NotesType_Bundler")
.contentsAsUtf8String()
.contains(".writeString");
}
@@ -729,22 +729,22 @@
.compile(notesType, annotatedNotesProvider(annotationStrings), installationListener);
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileNotesType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.NotesType_Bundler")
.contentsAsUtf8String()
.contains(".writeString");
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileNotesType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.NotesType_Bundler")
.contentsAsUtf8String()
.contains(".writeFloat");
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileNotesType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.NotesType_Bundler")
.contentsAsUtf8String()
.contains(".writeInt"); // used for Boolean
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileNotesType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.NotesType_Bundler")
.contentsAsUtf8String()
.contains(".writeByte");
}
@@ -771,7 +771,7 @@
installationListenerWithListStringParam(annotationStrings));
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileNotesType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.NotesType_Bundler")
.contentsAsUtf8String()
.contains("ParcelableList");
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileProviderClassTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileProviderClassTest.java
index 08c19a2..fc6a106 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileProviderClassTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileProviderClassTest.java
@@ -314,7 +314,7 @@
javac().withProcessors(new Processor()).compile(notesProvider, staticType);
assertThat(compilation)
- .generatedSourceFile("com.google.android.enterprise.notes.ProfileStaticType_Bundler")
+ .generatedSourceFile("com.google.android.enterprise.notes.StaticType_Bundler")
.contentsAsUtf8String()
.contains("parcel.writeString(");
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedParameterTypeTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedParameterTypeTest.java
index 0b4f7f1..325165f 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedParameterTypeTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedParameterTypeTest.java
@@ -49,22 +49,39 @@
public static Iterable<Object[]> data() {
String[] types = {
"String",
+ "CharSequence",
"String[]",
"byte",
+ "byte[]",
+ "byte[][][]",
"Byte",
"short",
+ "short[]",
+ "short[][][]",
"Short",
"int",
+ "int[]",
+ "int[][][]",
"Integer",
"long",
+ "long[]",
+ "long[][][]",
"Long",
"float",
+ "float[]",
+ "float[][][]",
"Float",
"double",
+ "double[]",
+ "double[][][]",
"Double",
"char",
+ "char[]",
+ "char[][][]",
"Character",
"boolean",
+ "boolean[]",
+ "boolean[][][]",
"Boolean",
"ParcelableObject",
"ParcelableObject[]",
@@ -89,7 +106,9 @@
"com.google.common.collect.ImmutableMap<String, String>",
"android.util.Pair<String, Integer>",
"android.graphics.Bitmap",
- "android.content.Context"
+ "android.content.Context",
+ "android.os.Parcelable",
+ "android.graphics.drawable.Drawable"
};
return combineParameters(AnnotationFinder.annotationStrings(), Arrays.asList(types));
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedReturnTypeTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedReturnTypeTest.java
index 9335557..7c5cebb 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedReturnTypeTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileSupportedReturnTypeTest.java
@@ -50,22 +50,39 @@
new TypeWithReturnValue[] {
TypeWithReturnValue.referenceType("Void"),
TypeWithReturnValue.referenceType("String"),
+ TypeWithReturnValue.referenceType("CharSequence"),
TypeWithReturnValue.referenceType("String[]"),
TypeWithReturnValue.primitiveType("byte", "0"),
+ TypeWithReturnValue.referenceType("byte[]"),
+ TypeWithReturnValue.referenceType("byte[][][]"),
TypeWithReturnValue.referenceType("Byte"),
TypeWithReturnValue.primitiveType("short", "0"),
+ TypeWithReturnValue.referenceType("short[]"),
+ TypeWithReturnValue.referenceType("short[][][]"),
TypeWithReturnValue.referenceType("Short"),
TypeWithReturnValue.primitiveType("int", "0"),
+ TypeWithReturnValue.referenceType("int[]"),
+ TypeWithReturnValue.referenceType("int[][][]"),
TypeWithReturnValue.referenceType("Integer"),
TypeWithReturnValue.primitiveType("long", "0"),
+ TypeWithReturnValue.referenceType("long[]"),
+ TypeWithReturnValue.referenceType("long[][][]"),
TypeWithReturnValue.referenceType("Long"),
TypeWithReturnValue.primitiveType("float", "0"),
+ TypeWithReturnValue.referenceType("float[]"),
+ TypeWithReturnValue.referenceType("float[][][]"),
TypeWithReturnValue.referenceType("Float"),
TypeWithReturnValue.primitiveType("double", "0"),
+ TypeWithReturnValue.referenceType("double[]"),
+ TypeWithReturnValue.referenceType("double[][][]"),
TypeWithReturnValue.referenceType("Double"),
TypeWithReturnValue.primitiveType("char", "'a'"),
+ TypeWithReturnValue.referenceType("char[]"),
+ TypeWithReturnValue.referenceType("char[][][]"),
TypeWithReturnValue.referenceType("Character"),
TypeWithReturnValue.primitiveType("boolean", "false"),
+ TypeWithReturnValue.referenceType("boolean[]"),
+ TypeWithReturnValue.referenceType("boolean[][][]"),
TypeWithReturnValue.referenceType("Boolean"),
TypeWithReturnValue.referenceType("ParcelableObject"),
TypeWithReturnValue.referenceType("ParcelableObject[]"),
@@ -102,6 +119,8 @@
TypeWithReturnValue.referenceType("android.util.Pair<String, Integer>"),
TypeWithReturnValue.referenceType("com.google.common.base.Optional<ParcelableObject>"),
TypeWithReturnValue.referenceType("android.graphics.Bitmap"),
+ TypeWithReturnValue.referenceType("android.os.Parcelable"),
+ TypeWithReturnValue.referenceType("android.graphics.drawable.Drawable")
};
return combineParameters(
AnnotationFinder.annotationStrings(), Arrays.asList(typesWithReturnValues));
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTest.java
index eecccac..ef36682 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTest.java
@@ -64,9 +64,6 @@
"@CROSS_PROFILE_ANNOTATION annotations on methods can not specify a connector";
private static final String METHOD_PARCELABLE_WRAPPERS_ERROR =
"@CROSS_PROFILE_ANNOTATION annotations on methods can not specify parcelable wrappers";
- private static final String METHOD_CLASSNAME_ERROR =
- "@CROSS_PROFILE_ANNOTATION annotations on methods can not specify a profile class name";
- private static final String INVALID_TIMEOUT_MILLIS = "timeoutMillis must be positive";
private static final String ASYNC_DECLARED_EXCEPTION_ERROR =
"Asynchronous methods annotated @CROSS_PROFILE_ANNOTATION cannot declare exceptions";
private static final String PARCELABLE_WRAPPER_ANNOTATION_ERROR =
@@ -894,53 +891,6 @@
}
@Test
- public void crossProfileMethodWithPrimitiveArrayParameterType_hasError() {
- JavaFileObject notesType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(),
- " public void refreshNotes(int[] i) {",
- " }",
- "}");
-
- Compilation compilation =
- javac()
- .withProcessors(new Processor())
- .compile(notesType, annotatedNotesProvider(annotationStrings));
-
- assertThat(compilation)
- .hadErrorContaining(formatErrorMessage(UNSUPPORTED_PARAMETER_TYPE_ERROR, annotationStrings))
- .inFile(notesType);
- }
-
- @Test
- public void crossProfileMethodWithPrimitiveArrayReturnType_hasError() {
- JavaFileObject notesType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(),
- " public int[] refreshNotes() {",
- " return null;",
- " }",
- "}");
-
- Compilation compilation =
- javac()
- .withProcessors(new Processor())
- .compile(notesType, annotatedNotesProvider(annotationStrings));
-
- assertThat(compilation)
- .hadErrorContaining(formatErrorMessage(UNSUPPORTED_RETURN_TYPE_ERROR, annotationStrings))
- .inFile(notesType);
- }
-
- @Test
public void crossProfileMethodWithMultiDimensionalArrayParameterType_hasError() {
JavaFileObject notesType =
JavaFileObjects.forSourceLines(
@@ -1040,30 +990,6 @@
}
@Test
- public void specifyProfileClassNameOnMethodAnnotation_hasError() {
- JavaFileObject notesType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(
- "profileClassName=\"" + NOTES_PACKAGE + ".ProfileNotes\""),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation =
- javac()
- .withProcessors(new Processor())
- .compile(notesType, annotatedNotesProvider(annotationStrings));
-
- assertThat(compilation)
- .hadErrorContaining(formatErrorMessage(METHOD_CLASSNAME_ERROR, annotationStrings))
- .inFile(notesType);
- }
-
- @Test
public void crossProfileInterface_works() {
JavaFileObject notesType =
JavaFileObjects.forSourceLines(
@@ -1093,83 +1019,6 @@
}
@Test
- public void crossProfile_specifiesValidTimeoutMillisAndAlsoOnType_compiles() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=30"),
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=10"),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation).succeededWithoutWarnings();
- }
-
- @Test
- public void crossProfile_specifiesValidTimeoutMillis_compiles() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=10"),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation).succeededWithoutWarnings();
- }
-
- @Test
- public void crossProfile_specifiesNegativeTimeoutMillis_hasError() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=-10"),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation)
- .hadErrorContaining(formatErrorMessage(INVALID_TIMEOUT_MILLIS, annotationStrings))
- .inFile(crossProfileType);
- }
-
- @Test
- public void crossProfileType_specifiesZeroTimeoutMillis_hasError() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=0"),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation)
- .hadErrorContaining(formatErrorMessage(INVALID_TIMEOUT_MILLIS, annotationStrings))
- .inFile(crossProfileType);
- }
-
- @Test
public void crossProfileMethod_synchronous_declaresException_compiles() {
JavaFileObject crossProfileType =
JavaFileObjects.forSourceLines(
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeTest.java
index cb692ff..4b30a11 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossProfileTypeTest.java
@@ -57,8 +57,7 @@
private static final String NON_PUBLIC_CLASS_ERROR =
"@CROSS_PROFILE_ANNOTATION types must be public";
private static final String CONNECTOR_MUST_EXTEND_CONNECTOR =
- "Interfaces specified as a connector must extend ProfileConnector";
- private static final String INVALID_TIMEOUT_MILLIS = "timeoutMillis must be positive";
+ "Interfaces specified as a connector must extend ProfileConnector or UserConnector";
private static final String CONNECTOR_MUST_BE_INTERFACE = "Connectors must be interfaces";
private static final String NOT_STATIC_ERROR =
"Types annotated @CROSS_PROFILE_ANNOTATION(isStatic=true) must not contain any non-static"
@@ -159,63 +158,6 @@
}
@Test
- public void crossProfileType_specifiesValidTimeoutMillis_compiles() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=10"),
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation).succeededWithoutWarnings();
- }
-
- @Test
- public void crossProfileType_specifiesNegativeTimeoutMillis_hasError() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=-10"),
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation).hadErrorContaining(INVALID_TIMEOUT_MILLIS).inFile(crossProfileType);
- }
-
- @Test
- public void crossProfileType_specifiesZeroTimeoutMillis_hasError() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- annotationStrings.crossProfileAsAnnotation("timeoutMillis=0"),
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation = javac().withProcessors(new Processor()).compile(crossProfileType);
-
- assertThat(compilation).hadErrorContaining(INVALID_TIMEOUT_MILLIS).inFile(crossProfileType);
- }
-
- @Test
public void crossProfileType_specifiesNotInterfaceConnector_hasError() {
JavaFileObject crossProfileType =
JavaFileObjects.forSourceLines(
@@ -244,7 +186,7 @@
}
@Test
- public void crossProfileType_specifiesConnectorNotExtendingProfileConnector_hasError() {
+ public void crossProfileType_specifiesConnectorNotExtendingConnectorInterface_hasError() {
JavaFileObject crossProfileType =
JavaFileObjects.forSourceLines(
NOTES_PACKAGE + ".NotesType",
@@ -291,29 +233,6 @@
}
@Test
- public void specifiesAlternativeProfileClassName_generatesCorrectClass() {
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationStrings.crossProfileQualifiedName() + ";",
- annotationStrings.crossProfileAsAnnotation(
- "profileClassName=\"" + NOTES_PACKAGE + ".CrossProfileNotes\""),
- "public final class NotesType {",
- annotationStrings.crossProfileAsAnnotation(),
- " public void refreshNotes() {",
- " }",
- "}");
-
- Compilation compilation =
- javac()
- .withProcessors(new Processor())
- .compile(crossProfileType, annotatedNotesProvider(annotationStrings));
-
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".CrossProfileNotes");
- }
-
- @Test
public void isStaticContainsNoNonStaticMethods_compiles() {
JavaFileObject notesType =
JavaFileObjects.forSourceLines(
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserInterfaceTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserInterfaceTest.java
new file mode 100644
index 0000000..af44fca
--- /dev/null
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserInterfaceTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.NOTES_PACKAGE;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesCrossProfileType;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesProvider;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationPrinter;
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationStrings;
+import com.google.testing.compile.Compilation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CrossUserInterfaceTest {
+
+ private final AnnotationPrinter annotationPrinter;
+
+ public CrossUserInterfaceTest(AnnotationPrinter annotationPrinter) {
+ this.annotationPrinter = annotationPrinter;
+ }
+
+ @Parameters(name = "{0}")
+ public static Iterable<AnnotationStrings> getAnnotationPrinters() {
+ return AnnotationFinder.annotationStrings();
+ }
+
+ @Test
+ public void compile_generatesCurrentMethod() {
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ annotatedNotesProvider(annotationPrinter),
+ annotatedNotesCrossProfileType(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile(NOTES_PACKAGE + ".UserNotesType")
+ .contentsAsUtf8String()
+ .contains("NotesType_SingleSender current()");
+ }
+
+ @Test
+ public void compile_generatesUserMethod() {
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ annotatedNotesProvider(annotationPrinter),
+ annotatedNotesCrossProfileType(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile(NOTES_PACKAGE + ".UserNotesType")
+ .contentsAsUtf8String()
+ .contains("NotesType_SingleSenderCanThrow user(UserHandle userHandle)");
+ }
+
+ @Test
+ public void compile_generatesDefaultImplementation() {
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ annotatedNotesProvider(annotationPrinter),
+ annotatedNotesCrossProfileType(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile(NOTES_PACKAGE + ".DefaultUserNotesType")
+ .contentsAsUtf8String()
+ .contains("class DefaultUserNotesType implements UserNotesType");
+ }
+}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserTestTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserTestTest.java
new file mode 100644
index 0000000..23144bd
--- /dev/null
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/CrossUserTestTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.processor;
+
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.NOTES_PACKAGE;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesConfigurationWithNotesProviderAndCrossUserConnector;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesProvider;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.notesTypeWithDefaultConnector;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder;
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationPrinter;
+import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationStrings;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CrossUserTestTest {
+
+ private final AnnotationPrinter annotationPrinter;
+
+ public CrossUserTestTest(AnnotationPrinter annotationPrinter) {
+ this.annotationPrinter = annotationPrinter;
+ }
+
+ @Parameters(name = "{0}")
+ public static Iterable<AnnotationStrings> getAnnotationPrinters() {
+ return AnnotationFinder.annotationStrings();
+ }
+
+ @Test
+ public void generatesFakeCrossUserConnector() {
+ JavaFileObject crossUserTest =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesTest",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileTestQualifiedName() + ";",
+ annotationPrinter.crossProfileTestAsAnnotation(
+ "configuration=NotesConfiguration.class"),
+ "public final class NotesTest {",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ crossUserTest,
+ annotatedNotesConfigurationWithNotesProviderAndCrossUserConnector(
+ annotationPrinter),
+ annotatedNotesProvider(annotationPrinter),
+ notesTypeWithDefaultConnector(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile("com.google.android.enterprise.connectedapps.FakeCrossUserConnector");
+ }
+
+ @Test
+ public void fakeCrossUserConnector_extendsAbstractFakeUserConnector() {
+ JavaFileObject crossUserTest =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesTest",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileTestQualifiedName() + ";",
+ annotationPrinter.crossProfileTestAsAnnotation(
+ "configuration=NotesConfiguration.class"),
+ "public final class NotesTest {",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ crossUserTest,
+ annotatedNotesConfigurationWithNotesProviderAndCrossUserConnector(
+ annotationPrinter),
+ annotatedNotesProvider(annotationPrinter),
+ notesTypeWithDefaultConnector(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile("com.google.android.enterprise.connectedapps.FakeCrossUserConnector")
+ .contentsAsUtf8String()
+ .contains("extends AbstractFakeUserConnector");
+ }
+
+ @Test
+ public void fakeCrossUserConnector_implementsConnector() {
+ JavaFileObject crossUserTest =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesTest",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileTestQualifiedName() + ";",
+ annotationPrinter.crossProfileTestAsAnnotation(
+ "configuration=NotesConfiguration.class"),
+ "public final class NotesTest {",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ crossUserTest,
+ annotatedNotesConfigurationWithNotesProviderAndCrossUserConnector(
+ annotationPrinter),
+ annotatedNotesProvider(annotationPrinter),
+ notesTypeWithDefaultConnector(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile("com.google.android.enterprise.connectedapps.FakeCrossUserConnector")
+ .contentsAsUtf8String()
+ .contains("implements CrossUserConnector");
+ }
+
+ @Test
+ public void generatesFakeCrossUserClass() {
+ JavaFileObject crossUserTest =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesTest",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileTestQualifiedName() + ";",
+ annotationPrinter.crossProfileTestAsAnnotation(
+ "configuration=NotesConfiguration.class"),
+ "public final class NotesTest {",
+ "}");
+
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ crossUserTest,
+ annotatedNotesConfigurationWithNotesProviderAndCrossUserConnector(
+ annotationPrinter),
+ annotatedNotesProvider(annotationPrinter),
+ notesTypeWithDefaultConnector(annotationPrinter));
+
+ assertThat(compilation)
+ .generatedSourceFile(NOTES_PACKAGE + ".FakeUserNotesType")
+ .contentsAsUtf8String()
+ .contains("class FakeUserNotesType implements UserNotesType");
+ }
+}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherTest.java
index 1ce6023..48dd37e 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/DispatcherTest.java
@@ -18,6 +18,7 @@
import static com.google.android.enterprise.connectedapps.processor.TestUtilities.CUSTOM_PROFILE_CONNECTOR_QUALIFIED_NAME;
import static com.google.android.enterprise.connectedapps.processor.TestUtilities.NOTES_PACKAGE;
import static com.google.android.enterprise.connectedapps.processor.TestUtilities.PROFILE_CONNECTOR_QUALIFIED_NAME;
+import static com.google.android.enterprise.connectedapps.processor.TestUtilities.UNCAUGHT_EXCEPTIONS_POLICY_QUALIFIED_NAME;
import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesConfigurationWithNotesProvider;
import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesCrossProfileType;
import static com.google.android.enterprise.connectedapps.processor.TestUtilities.annotatedNotesProvider;
@@ -39,9 +40,31 @@
public class DispatcherTest {
private final AnnotationPrinter annotationPrinter;
+ private final JavaFileObject notesConfiguration;
+ private final JavaFileObject notesCrossProfileType;
public DispatcherTest(AnnotationPrinter annotationPrinter) {
this.annotationPrinter = annotationPrinter;
+ this.notesConfiguration =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesConfiguration",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileConfigurationQualifiedName() + ";",
+ annotationPrinter.crossProfileConfigurationAsAnnotation(
+ "providers=NotesProvider.class"),
+ "public abstract class NotesConfiguration {",
+ "}");
+ this.notesCrossProfileType =
+ JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesType",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileQualifiedName() + ";",
+ annotationPrinter.crossProfileAsAnnotation("connector=NotesConnector.class"),
+ "public final class NotesType {",
+ annotationPrinter.crossProfileAsAnnotation(),
+ " public void refreshNotes() {",
+ " }",
+ "}");
}
@Parameters(name = "{0}")
@@ -66,45 +89,83 @@
@Test
public void specifiedClassName_generatesSpecifiedClassNameDispatcher() {
- JavaFileObject notesConfiguration =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesConfiguration",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationPrinter.crossProfileConfigurationQualifiedName() + ";",
- annotationPrinter.crossProfileConfigurationAsAnnotation(
- "providers=NotesProvider.class"),
- "public abstract class NotesConfiguration {",
- "}");
- JavaFileObject crossProfileType =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesType",
- "package " + NOTES_PACKAGE + ";",
- "import " + annotationPrinter.crossProfileQualifiedName() + ";",
- annotationPrinter.crossProfileAsAnnotation("connector=NotesConnector.class"),
- "public final class NotesType {",
- annotationPrinter.crossProfileAsAnnotation(),
- " public void refreshNotes() {",
- " }",
- "}");
- JavaFileObject notesConnector =
- JavaFileObjects.forSourceLines(
- NOTES_PACKAGE + ".NotesConnector",
- "package " + NOTES_PACKAGE + ";",
- "import " + CUSTOM_PROFILE_CONNECTOR_QUALIFIED_NAME + ";",
- "import " + PROFILE_CONNECTOR_QUALIFIED_NAME + ";",
- "@CustomProfileConnector(serviceClassName=\"com.google.android.CustomConnector\")",
- "public interface NotesConnector extends ProfileConnector {",
- "}");
-
Compilation compilation =
javac()
.withProcessors(new Processor())
.compile(
notesConfiguration,
annotatedNotesProvider(annotationPrinter),
- notesConnector,
- crossProfileType);
+ buildNotesConnector("serviceClassName=\"com.google.android.CustomConnector\""),
+ notesCrossProfileType);
assertThat(compilation).generatedSourceFile("com.google.android.CustomConnector_Dispatcher");
}
+
+ @Test
+ public void whenExceptionsPolicyIsUnspecified_generatesClassWithRethrowCode() {
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ notesConfiguration,
+ annotatedNotesProvider(annotationPrinter),
+ buildNotesConnector(""),
+ notesCrossProfileType);
+
+ assertThat(compilation)
+ .generatedSourceFile(
+ "com.google.android.enterprise.notes.NotesConnector_Service_Dispatcher")
+ .contentsAsUtf8String()
+ .contains("delayThrow");
+ }
+
+ @Test
+ public void whenExceptionsPolicyIsRethrow_generatesClassWithRethrowCode() {
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ notesConfiguration,
+ annotatedNotesProvider(annotationPrinter),
+ buildNotesConnector(
+ "uncaughtExceptionsPolicy=UncaughtExceptionsPolicy.NOTIFY_RETHROW"),
+ notesCrossProfileType);
+
+ assertThat(compilation)
+ .generatedSourceFile(
+ "com.google.android.enterprise.notes.NotesConnector_Service_Dispatcher")
+ .contentsAsUtf8String()
+ .contains("delayThrow");
+ }
+
+ @Test
+ public void whenExceptionsPolicyIsSuppress_generatesClassWithoutRethrowCode() {
+ Compilation compilation =
+ javac()
+ .withProcessors(new Processor())
+ .compile(
+ notesConfiguration,
+ annotatedNotesProvider(annotationPrinter),
+ buildNotesConnector(
+ "uncaughtExceptionsPolicy=UncaughtExceptionsPolicy.NOTIFY_SUPPRESS"),
+ notesCrossProfileType);
+
+ assertThat(compilation)
+ .generatedSourceFile(
+ "com.google.android.enterprise.notes.NotesConnector_Service_Dispatcher")
+ .contentsAsUtf8String()
+ .doesNotContain("delayThrow");
+ }
+
+ private static JavaFileObject buildNotesConnector(String customProfileConnectorParams) {
+ return JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesConnector",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + CUSTOM_PROFILE_CONNECTOR_QUALIFIED_NAME + ";",
+ "import " + PROFILE_CONNECTOR_QUALIFIED_NAME + ";",
+ "import " + UNCAUGHT_EXCEPTIONS_POLICY_QUALIFIED_NAME + ";",
+ String.format("@CustomProfileConnector(%s)", customProfileConnectorParams),
+ "public interface NotesConnector extends ProfileConnector {",
+ "}");
+ }
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableTest.java
index 59de5e9..a4491c5 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/IfAvailableTest.java
@@ -57,7 +57,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_IfAvailable");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_IfAvailable");
}
@Test
@@ -79,7 +79,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_IfAvailable")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_IfAvailable")
.contentsAsUtf8String()
.contains("void refreshNotes()");
}
@@ -104,7 +104,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_IfAvailable")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_IfAvailable")
.contentsAsUtf8String()
.contains("int refreshNotes(int defaultValue)");
}
@@ -131,7 +131,7 @@
installationListener(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_IfAvailable")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_IfAvailable")
.contentsAsUtf8String()
.contains("void refreshNotes(InstallationListener listener)");
}
@@ -158,7 +158,7 @@
installationListenerWithStringParam(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_IfAvailable")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_IfAvailable")
.contentsAsUtf8String()
.contains(
"void refreshNotes(String s, InstallationListener listener, String defaultValue)");
@@ -185,7 +185,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_IfAvailable")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_IfAvailable")
.contentsAsUtf8String()
.contains("ListenableFuture<String> refreshNotes(String defaultValue)");
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceTest.java
index e59a306..485bb9f 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InterfaceTest.java
@@ -55,7 +55,7 @@
annotatedNotesCrossProfileType(annotationPrinter),
annotatedNotesProvider(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender");
}
@Test
@@ -77,7 +77,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("void refreshNotes()");
}
@@ -105,12 +105,12 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("void refreshNotes()");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("int anotherMethod(String s)");
}
@@ -137,7 +137,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.doesNotContain("anotherMethod");
}
@@ -151,8 +151,7 @@
annotatedNotesCrossProfileType(annotationPrinter),
annotatedNotesProvider(annotationPrinter));
- assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSenderCanThrow");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSenderCanThrow");
}
@Test
@@ -174,7 +173,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSenderCanThrow")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSenderCanThrow")
.contentsAsUtf8String()
.contains("void refreshNotes() throws UnavailableProfileException");
}
@@ -202,12 +201,12 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSenderCanThrow")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSenderCanThrow")
.contentsAsUtf8String()
.contains("void refreshNotes() throws UnavailableProfileException");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSenderCanThrow")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSenderCanThrow")
.contentsAsUtf8String()
.contains("int anotherMethod(String s) throws UnavailableProfileException");
}
@@ -234,7 +233,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSenderCanThrow")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSenderCanThrow")
.contentsAsUtf8String()
.doesNotContain("anotherMethod");
}
@@ -258,9 +257,9 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSenderCanThrow")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSenderCanThrow")
.contentsAsUtf8String()
- .contains("ProfileNotesType_IfAvailable ifAvailable()");
+ .contains("NotesType_IfAvailable ifAvailable()");
}
@Test
@@ -272,7 +271,7 @@
annotatedNotesCrossProfileType(annotationPrinter),
annotatedNotesProvider(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender");
}
@Test
@@ -294,7 +293,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.contains("void refreshNotes()");
}
@@ -319,7 +318,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.contains("Map<Profile, Integer> refreshNotes()");
}
@@ -344,7 +343,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.contains("Map<Profile, String> refreshNotes()");
}
@@ -372,12 +371,12 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.contains("void refreshNotes()");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.contains("Map<Profile, Integer> anotherMethod(String s)");
}
@@ -404,7 +403,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.doesNotContain("anotherMethod");
}
@@ -429,7 +428,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("refreshNotes() throws IOException");
}
@@ -457,15 +456,15 @@
// Order is not predictable
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("refreshNotes() throws ");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("SQLException");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("IOException");
}
@@ -491,7 +490,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("refreshNotes() throws IOException");
}
@@ -519,15 +518,15 @@
// Order is not predictable
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("refreshNotes() throws ");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("SQLException");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_SingleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_SingleSender")
.contentsAsUtf8String()
.contains("IOException");
}
@@ -553,7 +552,7 @@
.compile(notesType, annotatedNotesProvider(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleSender")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleSender")
.contentsAsUtf8String()
.doesNotContain("refreshNotes()");
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileTypeTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileTypeTest.java
index d0af193..a68c948 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileTypeTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalCrossProfileTypeTest.java
@@ -53,7 +53,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_Internal");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_Internal");
}
@Test
@@ -66,9 +66,9 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_Internal")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_Internal")
.contentsAsUtf8String()
- .contains("private ProfileNotesType_Internal() {");
+ .contains("private NotesType_Internal() {");
}
@Test
@@ -81,9 +81,9 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_Internal")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_Internal")
.contentsAsUtf8String()
- .contains("public Parcel call(Context context, int methodIdentifier, Parcel params,");
+ .contains("public Bundle call(Context context, int methodIdentifier, Bundle params,");
}
@Test
@@ -96,8 +96,8 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_Internal")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_Internal")
.contentsAsUtf8String()
- .contains("static ProfileNotesType_Internal instance()");
+ .contains("static NotesType_Internal instance()");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassTest.java
index 2afee1b..9d1ef94 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/InternalProviderClassTest.java
@@ -86,7 +86,7 @@
.generatedSourceFile(NOTES_PACKAGE + ".Profile_NotesProvider_Internal")
.contentsAsUtf8String()
.contains(
- "public Parcel call(Context context, long crossProfileTypeIdentifier, int"
+ "public Bundle call(Context context, long crossProfileTypeIdentifier, int"
+ " methodIdentifier,");
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileTest.java
index 8d38369..e7eae98 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/OtherProfileTest.java
@@ -53,7 +53,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_OtherProfile");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_OtherProfile");
}
@Test
@@ -66,10 +66,9 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_OtherProfile")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_OtherProfile")
.contentsAsUtf8String()
- .contains(
- "class ProfileNotesType_OtherProfile implements" + " ProfileNotesType_SingleSender");
+ .contains("class NotesType_OtherProfile implements NotesType_SingleSender");
}
@Test
@@ -82,9 +81,8 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_OtherProfile")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_OtherProfile")
.contentsAsUtf8String()
- .contains(
- "public ProfileNotesType_OtherProfile(ProfileConnector connector)");
+ .contains("public NotesType_OtherProfile(ProfileConnector connector)");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCrossProfileConfigurationTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCrossProfileConfigurationTest.java
index 49b797e..6ccbef0 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCrossProfileConfigurationTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCrossProfileConfigurationTest.java
@@ -49,7 +49,7 @@
+ " ProfileConnector";
private static final String CONNECTOR_MUST_BE_INTERFACE = "Connectors must be interfaces";
private static final String CONNECTOR_MUST_EXTEND_CONNECTOR =
- "Interfaces specified as a connector must extend ProfileConnector";
+ "Interfaces specified as a connector must extend ProfileConnector or UserConnector";
private final AnnotationStrings annotationStrings;
@@ -435,7 +435,7 @@
}
@Test
- public void specifiesConnectorNotExtendingProfileConnector_hasError() {
+ public void specifiesConnectorNotExtendingConnectorInterface_hasError() {
final JavaFileObject configuration =
JavaFileObjects.forSourceLines(
NOTES_PACKAGE + ".NotesConfiguration",
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCurrentProfileTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCurrentProfileTest.java
index b009f2f..6daab70 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCurrentProfileTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorCurrentProfileTest.java
@@ -53,7 +53,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_CurrentProfile");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_CurrentProfile");
}
@Test
@@ -66,10 +66,9 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_CurrentProfile")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_CurrentProfile")
.contentsAsUtf8String()
- .contains(
- "class ProfileNotesType_CurrentProfile implements" + " ProfileNotesType_SingleSender");
+ .contains("class NotesType_CurrentProfile implements NotesType_SingleSender");
}
@Test
@@ -82,9 +81,8 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_CurrentProfile")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_CurrentProfile")
.contentsAsUtf8String()
- .contains(
- "public ProfileNotesType_CurrentProfile(Context context, NotesType crossProfileType)");
+ .contains("public NotesType_CurrentProfile(Context context, NotesType crossProfileType)");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorMultipleProfilesTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorMultipleProfilesTest.java
index 2f732d1..d56007f 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorMultipleProfilesTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProcessorMultipleProfilesTest.java
@@ -53,8 +53,7 @@
annotatedNotesProvider(annotationPrinter),
annotatedNotesCrossProfileType(annotationPrinter));
- assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleProfiles");
+ assertThat(compilation).generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleProfiles");
}
@Test
@@ -67,11 +66,9 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleProfiles")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleProfiles")
.contentsAsUtf8String()
- .contains(
- "class ProfileNotesType_MultipleProfiles implements"
- + " ProfileNotesType_MultipleSender");
+ .contains("class NotesType_MultipleProfiles implements NotesType_MultipleSender");
}
@Test
@@ -84,13 +81,13 @@
annotatedNotesCrossProfileType(annotationPrinter));
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleProfiles")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleProfiles")
.contentsAsUtf8String()
- .contains("public ProfileNotesType_MultipleProfiles(");
+ .contains("public NotesType_MultipleProfiles(");
assertThat(compilation)
- .generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType_MultipleProfiles")
+ .generatedSourceFile(NOTES_PACKAGE + ".NotesType_MultipleProfiles")
.contentsAsUtf8String()
- .contains("Map<Profile, ProfileNotesType_SingleSenderCanThrow>" + " senders) {");
+ .contains("Map<Profile, NotesType_SingleSenderCanThrow> senders) {");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileInterfaceTest.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileInterfaceTest.java
index 7922750..41ede91 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileInterfaceTest.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/ProfileInterfaceTest.java
@@ -87,24 +87,24 @@
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_SingleSender current()");
+ .contains("NotesType_SingleSender current()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_SingleSenderCanThrow other()");
+ .contains("NotesType_SingleSenderCanThrow other()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
// We ignore the "profile" argument as it gets moved onto another line by the processor
- .contains("ProfileNotesType_SingleSenderCanThrow profile(");
+ .contains("NotesType_SingleSenderCanThrow profile(");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_MultipleSender both()");
+ .contains("NotesType_MultipleSender both()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_MultipleSender profiles(");
+ .contains("NotesType_MultipleSender profiles(");
}
@Test
@@ -179,15 +179,15 @@
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_SingleSenderCanThrow primary()");
+ .contains("NotesType_SingleSenderCanThrow primary()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_SingleSenderCanThrow secondary()");
+ .contains("NotesType_SingleSenderCanThrow secondary()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_MultipleSender suppliers()");
+ .contains("NotesType_MultipleSender suppliers()");
}
@Test
@@ -212,14 +212,14 @@
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_SingleSenderCanThrow primary()");
+ .contains("NotesType_SingleSenderCanThrow primary()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_SingleSenderCanThrow secondary()");
+ .contains("NotesType_SingleSenderCanThrow secondary()");
assertThat(compilation)
.generatedSourceFile(NOTES_PACKAGE + ".ProfileNotesType")
.contentsAsUtf8String()
- .contains("ProfileNotesType_MultipleSender suppliers()");
+ .contains("NotesType_MultipleSender suppliers()");
}
}
diff --git a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestUtilities.java b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestUtilities.java
index b1c05bf..055e22d 100644
--- a/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestUtilities.java
+++ b/tests/processor/src/main/java/com/google/android/enterprise/connectedapps/processor/TestUtilities.java
@@ -32,6 +32,8 @@
"com.google.android.enterprise.connectedapps.annotations.CustomUserConnector";
public static final String GENERATED_PROFILE_CONNECTOR_QUALIFIED_NAME =
"com.google.android.enterprise.connectedapps.annotations.GeneratedProfileConnector";
+ public static final String UNCAUGHT_EXCEPTIONS_POLICY_QUALIFIED_NAME =
+ "com.google.android.enterprise.connectedapps.annotations.UncaughtExceptionsPolicy";
public static final String GENERATED_USER_CONNECTOR_QUALIFIED_NAME =
"com.google.android.enterprise.connectedapps.annotations.GeneratedUserConnector";
public static final String PROFILE_CONNECTOR_QUALIFIED_NAME =
@@ -498,6 +500,42 @@
"}");
}
+ public static JavaFileObject installationListenerSimple(AnnotationPrinter annotationPrinter) {
+ return JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".InstallationListener",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileCallbackQualifiedName() + ";",
+ annotationPrinter.crossProfileCallbackAsAnnotation("simple=true"),
+ "public interface InstallationListener {",
+ " void installationComplete();",
+ "}");
+ }
+
+ public static JavaFileObject installationListenerSimpleWithStringParam(
+ AnnotationPrinter annotationPrinter) {
+ return JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".InstallationListener",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileCallbackQualifiedName() + ";",
+ annotationPrinter.crossProfileCallbackAsAnnotation("simple=true"),
+ "public interface InstallationListener {",
+ " void installationComplete(String s);",
+ "}");
+ }
+
+ public static JavaFileObject installationListenerSimpleWithIntentParam(
+ AnnotationPrinter annotationPrinter) {
+ return JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".InstallationListener",
+ "package " + NOTES_PACKAGE + ";",
+ "import android.content.Intent;",
+ "import " + annotationPrinter.crossProfileCallbackQualifiedName() + ";",
+ annotationPrinter.crossProfileCallbackAsAnnotation("simple=true"),
+ "public interface InstallationListener {",
+ " void installationComplete(Intent i);",
+ "}");
+ }
+
public static JavaFileObject installationListenerWithStringParam(
AnnotationPrinter annotationPrinter) {
return JavaFileObjects.forSourceLines(
@@ -577,6 +615,19 @@
"}");
}
+ public static JavaFileObject annotatedNotesConfigurationWithNotesProviderAndCrossUserConnector(
+ AnnotationPrinter annotationPrinter) {
+ return JavaFileObjects.forSourceLines(
+ NOTES_PACKAGE + ".NotesConfiguration",
+ "package " + NOTES_PACKAGE + ";",
+ "import " + annotationPrinter.crossProfileConfigurationQualifiedName() + ";",
+ "import com.google.android.enterprise.connectedapps.CrossUserConnector;",
+ annotationPrinter.crossProfileConfigurationAsAnnotation(
+ "providers=NotesProvider.class, connector=CrossUserConnector.class"),
+ "public abstract class NotesConfiguration {",
+ "}");
+ }
+
/** Combines two iterables into an iterable of all possible pairs. */
public static Iterable<Object[]> combineParameters(
Iterable<?> parameters1, Iterable<?> parameters2) {
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ConnectedAppsUtilsTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ConnectedAppsUtilsTest.java
index 6692067..b4dec77 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ConnectedAppsUtilsTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ConnectedAppsUtilsTest.java
@@ -53,7 +53,7 @@
testUtilities.createWorkUser();
testUtilities.turnOnWorkProfile();
testUtilities.setRunningOnPersonalProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/CrossProfileSenderTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/CrossProfileSenderTest.java
index a8f14d8..0bea467 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/CrossProfileSenderTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/CrossProfileSenderTest.java
@@ -18,6 +18,7 @@
import static com.google.android.enterprise.connectedapps.RobolectricTestUtilities.TEST_CONNECTOR_CLASS_NAME;
import static com.google.android.enterprise.connectedapps.RobolectricTestUtilities.TEST_SERVICE_CLASS_NAME;
import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS;
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.tryForceRaceCondition;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
@@ -27,11 +28,12 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.os.Build.VERSION_CODES;
-import android.os.Parcel;
+import android.os.Bundle;
import android.os.UserHandle;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +55,7 @@
private CrossProfileSender sender;
private final TestConnectionListener connectionListener = new TestConnectionListener();
private final TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
+ private final TestAvailabilityListener availabilityListener2 = new TestAvailabilityListener();
private final TestScheduledExecutorService scheduledExecutorService =
new TestScheduledExecutorService();
private final RobolectricTestUtilities testUtilities =
@@ -70,7 +73,6 @@
availabilityListener,
scheduledExecutorService,
AvailabilityRestrictions.DEFAULT);
- sender.beginMonitoringAvailabilityChanges();
testUtilities.setBinding(testService, TEST_CONNECTOR_CLASS_NAME);
testUtilities.createWorkUser();
@@ -78,6 +80,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
+ sender.clearConnectionHolders();
}
@Test
@@ -189,47 +192,49 @@
// handle the multiple threads very well
@Test
public void manuallyBind_callingFromUIThread_throwsIllegalStateException() {
- assertThrows(IllegalStateException.class, sender::manuallyBind);
+ assertThrows(
+ IllegalStateException.class,
+ () -> sender.manuallyBind(CrossProfileSender.MANUAL_MANAGEMENT_CONNECTION_HOLDER));
}
@Test
- public void startManuallyBinding_otherProfileIsNotAvailable_doesNotbind() {
+ public void addConnectionHolder_otherProfileIsNotAvailable_doesNotbind() {
testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
assertThat(sender.isBound()).isFalse();
}
@Test
- public void startManuallyBinding_bindingIsNotPossible_doesNotCallConnectionListener() {
+ public void addConnectionHolder_bindingIsNotPossible_doesNotCallConnectionListener() {
testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
}
@Test
- public void startManuallyBinding_otherProfileIsAvailable_binds() {
+ public void addConnectionHolder_otherProfileIsAvailable_binds() {
testUtilities.turnOnWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
assertThat(sender.isBound()).isTrue();
}
@Test
- public void startManuallyBinding_binds_callsConnectionListener() {
+ public void addConnectionHolder_binds_callsConnectionListener() {
testUtilities.turnOnWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(1);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
}
@Test
- public void startManuallyBinding_otherProfileBecomesAvailable_binds() {
+ public void addConnectionHolder_otherProfileBecomesAvailable_binds() {
testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.turnOnWorkProfile();
@@ -237,9 +242,9 @@
}
@Test
- public void startManuallyBinding_otherProfileBecomesAvailable_callsConnectionListener() {
+ public void addConnectionHolder_otherProfileBecomesAvailable_callsConnectionListener() {
testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.turnOnWorkProfile();
@@ -247,8 +252,8 @@
}
@Test
- public void startManuallyBinding_profileBecomesUnavailable_unbinds() {
- sender.startManuallyBinding();
+ public void addConnectionHolder_profileBecomesUnavailable_unbinds() {
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
testUtilities.turnOffWorkProfile();
@@ -257,8 +262,8 @@
}
@Test
- public void startManuallyBinding_profileBecomesUnavailable_callsConnectionListener() {
- sender.startManuallyBinding();
+ public void addConnectionHolder_profileBecomesUnavailable_callsConnectionListener() {
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
connectionListener.resetConnectionChangedCount();
@@ -268,8 +273,8 @@
}
@Test
- public void startManuallyBinding_profileBecomesAvailableAgain_rebinds() {
- sender.startManuallyBinding();
+ public void addConnectionHolder_profileBecomesAvailableAgain_rebinds() {
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
testUtilities.turnOffWorkProfile();
@@ -279,8 +284,8 @@
}
@Test
- public void startManuallyBinding_profileBecomesAvailableAgain_callsConnectionListener() {
- sender.startManuallyBinding();
+ public void addConnectionHolder_profileBecomesAvailableAgain_callsConnectionListener() {
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
testUtilities.turnOffWorkProfile();
connectionListener.resetConnectionChangedCount();
@@ -291,41 +296,21 @@
}
@Test
- public void unbind_isNotBound() {
- sender.startManuallyBinding();
-
- sender.unbind();
-
- assertThat(sender.isBound()).isFalse();
- }
-
- @Test
public void unbind_callsConnectionListener() {
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(1);
connectionListener.resetConnectionChangedCount();
- sender.unbind();
- testUtilities.advanceTimeBySeconds(1);
+ sender.removeConnectionHolder(this);
+ testUtilities.advanceTimeBySeconds(31);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
}
@Test
- public void unbind_profileBecomesAvailable_doesNotBind() {
- testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
- sender.unbind();
-
- testUtilities.turnOnWorkProfile();
-
- assertThat(sender.isBound()).isFalse();
- }
-
- @Test
public void bind_bindingFromPersonalProfile_binds() {
testUtilities.setRunningOnPersonalProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
assertThat(sender.isBound()).isTrue();
}
@@ -333,17 +318,19 @@
@Test
public void bind_bindingFromWorkProfile_binds() {
testUtilities.setRunningOnWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
assertThat(sender.isBound()).isTrue();
}
@Test
- public void call_isNotBound_throwsUnavailableProfileException() {
+ public void call_isNotBound_throwsException() {
+ // As we can't force disconnection - we just give time for any existing connections to close
+ testUtilities.advanceTimeBySeconds(31);
int crossProfileTypeIdentifier = 1;
int methodIdentifier = 0;
- Parcel params = Parcel.obtain();
- sender.unbind();
+
+ Bundle params = new Bundle(Bundler.class.getClassLoader());
assertThrows(
UnavailableProfileException.class,
@@ -354,39 +341,39 @@
public void call_isBound_callsMethod() throws UnavailableProfileException {
int crossProfileTypeIdentifier = 1;
int methodIdentifier = 0;
- Parcel params = Parcel.obtain();
- params.writeString("value");
- sender.startManuallyBinding();
+
+ Bundle params = new Bundle(Bundler.class.getClassLoader());
+ params.putString("value", "value");
+ sender.addConnectionHolder(this);
sender.call(crossProfileTypeIdentifier, methodIdentifier, params);
assertThat(testService.lastCall().getCrossProfileTypeIdentifier())
.isEqualTo(crossProfileTypeIdentifier);
assertThat(testService.lastCall().getMethodIdentifier()).isEqualTo(methodIdentifier);
- assertThat(testService.lastCall().getParams().readString()).isEqualTo("value");
+ assertThat(testService.lastCall().getParams().getString("value")).isEqualTo("value");
}
@Test
public void call_isBound_returnsResponse() throws UnavailableProfileException {
int crossProfileTypeIdentifier = 1;
int methodIdentifier = 0;
- Parcel params = Parcel.obtain();
- Parcel expectedResponseParcel = Parcel.obtain();
- expectedResponseParcel.writeInt(0); // No error
- expectedResponseParcel.writeString("value");
- testService.setResponseParcel(expectedResponseParcel);
- sender.startManuallyBinding();
+ Bundle params = new Bundle(Bundler.class.getClassLoader());
+ Bundle expectedResponseBundle = new Bundle(Bundler.class.getClassLoader());
+ expectedResponseBundle.putString("value", "value");
+ testService.setResponseBundle(expectedResponseBundle);
+ sender.addConnectionHolder(this);
- Parcel actualResponseParcel = sender.call(crossProfileTypeIdentifier, methodIdentifier, params);
+ Bundle actualResponseBundle = sender.call(crossProfileTypeIdentifier, methodIdentifier, params);
- assertThat(actualResponseParcel.readString()).isEqualTo("value");
+ assertThat(actualResponseBundle.getString("value")).isEqualTo("value");
}
@Test
public void bind_usingDpcBinding_otherProfileIsAvailable_binds() {
initWithDpcBinding();
testUtilities.turnOnWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
assertThat(sender.isBound()).isTrue();
}
@@ -395,7 +382,7 @@
public void bind_usingDpcBinding_binds_callsConnectionListener() {
initWithDpcBinding();
testUtilities.turnOnWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(1);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
@@ -406,7 +393,7 @@
initWithDpcBinding();
shadowOf(devicePolicyManager).setBindDeviceAdminTargetUsers(ImmutableList.of());
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
assertThat(sender.isBound()).isFalse();
@@ -416,7 +403,7 @@
public void bind_usingDpcBinding_otherProfileIsCreated_binds() {
initWithDpcBinding();
shadowOf(devicePolicyManager).setBindDeviceAdminTargetUsers(ImmutableList.of());
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
shadowOf(devicePolicyManager)
@@ -430,7 +417,7 @@
public void bind_usingDpcBinding_otherProfileBecomesAvailable_binds() {
initWithDpcBinding();
testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
testUtilities.turnOnWorkProfile();
@@ -442,7 +429,7 @@
public void bind_usingDpcBinding_otherProfileBecomesAvailable_callsConnectionListener() {
initWithDpcBinding();
testUtilities.turnOffWorkProfile();
- sender.startManuallyBinding();
+ sender.addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(10);
testUtilities.turnOnWorkProfile();
@@ -470,6 +457,57 @@
assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(1);
}
+ @Test
+ public void createMultipleSenders_workProfileBecomesAvailable_callsAvailabilityListenerForEachSender() {
+ CrossProfileSender sender2 =
+ new CrossProfileSender(
+ context,
+ TEST_SERVICE_CLASS_NAME,
+ new DefaultProfileBinder(),
+ connectionListener,
+ availabilityListener2,
+ scheduledExecutorService,
+ AvailabilityRestrictions.DEFAULT);
+
+ testUtilities.turnOffWorkProfile();
+ availabilityListener.reset();
+ availabilityListener2.reset();
+
+ testUtilities.turnOnWorkProfile();
+
+ assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(1);
+ assertThat(availabilityListener2.availabilityChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ // Regression test for b/195910311.
+ // Do not ignore if this test turns flaky, this likely highlights a real race condition.
+ public void concurrentDisconnectionCall_doesntCrash() throws Exception {
+ int crossProfileTypeIdentifier = 1;
+ int methodIdentifier = 0;
+ Bundle params = new Bundle(Bundler.class.getClassLoader());
+ params.putString("value", "value");
+ sender.addConnectionHolder(this);
+ Object connectionHolderAlias = new Object();
+
+ tryForceRaceCondition(
+ 10000,
+ () ->
+ sender.callAsync(
+ crossProfileTypeIdentifier,
+ methodIdentifier,
+ params,
+ new LocalCallback() {
+ @Override
+ public void onResult(int methodIdentifier, Bundle params) {}
+
+ @Override
+ public void onException(Bundle exception) {}
+ },
+ connectionHolderAlias),
+ testUtilities::simulateDisconnectingServiceConnection);
+ }
+
private void initWithDpcBinding() {
shadowOf(devicePolicyManager)
.setBindDeviceAdminTargetUsers(ImmutableList.of(getWorkUserHandle()));
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorTest.java
index a6b5b17..0e74616 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorTest.java
@@ -32,6 +32,7 @@
import com.google.android.enterprise.connectedapps.testapp.connector.DirectBootAwareConnector;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnectorWithCustomServiceClass;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,6 +71,11 @@
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
}
+ @After
+ public void teardown() {
+ testProfileConnector.clearConnectionHolders();
+ }
+
@Test
public void construct_nullConnector_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> TestProfileConnector.create(null));
@@ -79,21 +85,34 @@
// handle the multiple threads very well
@Test
public void connect_callingFromUIThread_throwsIllegalStateException() {
- assertThrows(IllegalStateException.class, testProfileConnector::connect);
+ assertThrows(IllegalStateException.class, () -> testProfileConnector.connect(this));
}
@Test
- public void startConnecting_fromPersonalProfile_binds() {
+ public void addConnectionHolder_null_throwsException() {
+ assertThrows(NullPointerException.class, () -> testProfileConnector.addConnectionHolder(null));
+ }
+
+ @Test
+ public void removeConnectionHolder_null_throwsException() {
+ assertThrows(NullPointerException.class,
+ () -> testProfileConnector.removeConnectionHolder(null));
+ }
+
+ @Test
+ public void addConnectionHolder_fromPersonalProfile_binds() {
testUtilities.setRunningOnPersonalProfile();
- testUtilities.startConnectingAndWait();
+
+ testProfileConnector.addConnectionHolder(this);
assertThat(testProfileConnector.isConnected()).isTrue();
}
@Test
- public void startConnecting_fromWorkProfile_binds() {
+ public void addConnectionHolder_fromWorkProfile_binds() {
testUtilities.setRunningOnWorkProfile();
- testUtilities.startConnectingAndWait();
+
+ testProfileConnector.addConnectionHolder(this);
assertThat(testProfileConnector.isConnected()).isTrue();
}
@@ -116,7 +135,7 @@
@Test
public void disconnect_isBound_unbinds() {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.disconnect();
@@ -124,26 +143,26 @@
}
@Test
- public void startConnecting_callsConnectionListener() {
- testProfileConnector.registerConnectionListener(connectionListener);
- testUtilities.startConnectingAndWait();
+ public void addConnectionHolder_callsConnectionListener() {
+ testProfileConnector.addConnectionListener(connectionListener);
+ testUtilities.addDefaultConnectionHolderAndWait();
assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
}
@Test
- public void startConnecting_doesNotCallUnregisteredConnectionListener() {
- testProfileConnector.registerConnectionListener(connectionListener);
- testProfileConnector.unregisterConnectionListener(connectionListener);
- testUtilities.startConnectingAndWait();
+ public void addConnectionHolder_doesNotCallUnregisteredConnectionListener() {
+ testProfileConnector.addConnectionListener(connectionListener);
+ testProfileConnector.removeConnectionListener(connectionListener);
+ testUtilities.addDefaultConnectionHolderAndWait();
assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
}
@Test
public void disconnect_callsConnectionListener() {
- testProfileConnector.registerConnectionListener(connectionListener);
- testUtilities.startConnectingAndWait();
+ testProfileConnector.addConnectionListener(connectionListener);
+ testUtilities.addDefaultConnectionHolderAndWait();
connectionListener.resetConnectionChangedCount();
testUtilities.disconnect();
@@ -153,8 +172,8 @@
@Test
public void bindingDies_callsConnectionListener() {
- testProfileConnector.registerConnectionListener(connectionListener);
- testUtilities.startConnectingAndWait();
+ testProfileConnector.addConnectionListener(connectionListener);
+ testUtilities.addDefaultConnectionHolderAndWait();
connectionListener.resetConnectionChangedCount();
testUtilities.turnOffWorkProfile();
@@ -163,9 +182,9 @@
}
@Test
- public void startConnecting_profileConnectorWithCustomServiceClass() {
+ public void addConnectionHolder_profileConnectorWithCustomServiceClass() {
TestProfileConnectorWithCustomServiceClass.create(context, scheduledExecutorService)
- .startConnecting();
+ .addConnectionHolder(this);
testUtilities.advanceTimeBySeconds(1); // Allow connection
assertThat(shadowOf(context).getNextStartedService().getComponent().getClassName())
@@ -232,23 +251,13 @@
}
@Test
- public void isManuallyManagingConnection_returnsFalse() {
- assertThat(testProfileConnector.isManuallyManagingConnection()).isFalse();
- }
+ public void addConnectionHolder_autocloseReturnedConnectionHolder_unbinds() {
+ try (ProfileConnectionHolder p = testProfileConnector.addConnectionHolder(this)) {
+ // Intentionally empty
+ }
- @Test
- public void isManuallyManagingConnection_hasManuallyConnected_returnsTrue() {
- testUtilities.startConnectingAndWait();
+ testUtilities.advanceTimeBySeconds(31);
- assertThat(testProfileConnector.isManuallyManagingConnection()).isTrue();
- }
-
- @Test
- public void isManuallyManagingConnection_hasCalledStopManualConnectionManagement_returnsFalse() {
- testUtilities.startConnectingAndWait();
-
- testProfileConnector.stopManualConnectionManagement();
-
- assertThat(testProfileConnector.isManuallyManagingConnection()).isFalse();
+ assertThat(testProfileConnector.isConnected()).isFalse();
}
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorUnsupportedTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorUnsupportedTest.java
index d6bb9ca..d97179b 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorUnsupportedTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/ProfileConnectorUnsupportedTest.java
@@ -37,8 +37,18 @@
private final TestProfileConnector testProfileConnector = TestProfileConnector.create(context);
@Test
- public void startConnecting_doesNotCrash() {
- testProfileConnector.startConnecting();
+ public void addConnectionHolder_doesNotCrash() {
+ testProfileConnector.addConnectionHolder(this);
+ }
+
+ @Test
+ public void removeConnectionHolder_doesNotCrash() {
+ testProfileConnector.removeConnectionHolder(this);
+ }
+
+ @Test
+ public void clearConnectionHolderS_doesNotCrash() {
+ testProfileConnector.clearConnectionHolders();
}
@Test
@@ -57,33 +67,28 @@
}
@Test
- public void stopManualConnectionManagement_doesNotCrash() {
- testProfileConnector.stopManualConnectionManagement();
- }
-
- @Test
public void crossProfileSender_returnsNull() {
assertThat(testProfileConnector.crossProfileSender()).isNull();
}
@Test
- public void registerConnectionListener_doesNotCrash() {
- testProfileConnector.registerConnectionListener(() -> {});
+ public void addConnectionListener_doesNotCrash() {
+ testProfileConnector.addConnectionListener(() -> {});
}
@Test
- public void unregisterConnectionListener_doesNotCrash() {
- testProfileConnector.unregisterConnectionListener(() -> {});
+ public void removeConnectionListener_doesNotCrash() {
+ testProfileConnector.removeConnectionListener(() -> {});
}
@Test
- public void registerAvailabilityListener_doesNotCrash() {
- testProfileConnector.registerAvailabilityListener(() -> {});
+ public void addAvailabilityListener_doesNotCrash() {
+ testProfileConnector.addAvailabilityListener(() -> {});
}
@Test
- public void unregisterAvailabilityListener_doesNotCrash() {
- testProfileConnector.unregisterAvailabilityListener(() -> {});
+ public void removeAvailabilityListener_doesNotCrash() {
+ testProfileConnector.removeAvailabilityListener(() -> {});
}
@Test
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/RobolectricTestUtilities.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/RobolectricTestUtilities.java
index ae14549..cd7c0d3 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/RobolectricTestUtilities.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/RobolectricTestUtilities.java
@@ -19,6 +19,7 @@
import static android.os.Looper.getMainLooper;
import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS;
import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS_FULL;
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.getUserHandleForUserId;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -61,9 +62,8 @@
private static final int PER_USER_RANGE = 100000;
private final UserHandle personalProfileUserHandle =
- SharedTestUtilities.getUserHandleForUserId(PERSONAL_PROFILE_USER_ID);
- private final UserHandle workProfileUserHandle =
- SharedTestUtilities.getUserHandleForUserId(WORK_PROFILE_USER_ID);
+ getUserHandleForUserId(PERSONAL_PROFILE_USER_ID);
+ private final UserHandle workProfileUserHandle = getUserHandleForUserId(WORK_PROFILE_USER_ID);
private static final int WORK_UID = PER_USER_RANGE * WORK_PROFILE_USER_ID;
private static final int PERSONAL_UID = PER_USER_RANGE * PERSONAL_PROFILE_USER_ID;
private final Application context;
@@ -109,16 +109,22 @@
public void initTests() {
TestCrossProfileType.voidMethodCalls = 0;
CrossProfileSDKUtilities.clearCache();
+ CrossProfileSender.clearStaticState();
createPersonalUser();
}
- public void startConnectingAndWait() {
- connector.startConnecting();
+ public void addDefaultConnectionHolderAndWait() {
+ connector.addConnectionHolder(this);
+ advanceTimeBySeconds(1);
+ }
+
+ public void addDefaultConnectionHolderAndWait(UserConnector connector, UserHandle handle) {
+ connector.addConnectionHolder(handle, this);
advanceTimeBySeconds(1);
}
public void disconnect() {
- connector.stopManualConnectionManagement();
+ connector.clearConnectionHolders();
advanceTimeBySeconds(31); // Give time to timeout connection
}
@@ -138,6 +144,13 @@
.addProfile(WORK_PROFILE_USER_ID, PERSONAL_PROFILE_USER_ID, "Personal Profile", 0);
}
+ public UserHandle createCustomUser(int id) {
+ UserHandle handle = getUserHandleForUserId(id);
+ shadowOf(userManager).addUser(id, "Custom User", /* flags= */ 0);
+ shadowOf(userManager).setUserState(handle, UserState.STATE_RUNNING_UNLOCKED);
+ return handle;
+ }
+
public void turnOnWorkProfileWithoutUnlocking() {
shadowOf(userManager).setUserState(workProfileUserHandle, UserState.STATE_RUNNING_LOCKED);
tryAddTargetUserProfile(workProfileUserHandle);
@@ -169,7 +182,7 @@
advanceTimeBySeconds(10);
}
- private void tryAddTargetUserProfile(UserHandle userHandle) {
+ public void tryAddTargetUserProfile(UserHandle userHandle) {
try {
addTargetUserProfile(userHandle);
} catch (IllegalArgumentException e) {
@@ -184,7 +197,7 @@
shadowOf(crossProfileApps).addTargetUserProfile(userHandle);
}
- private void tryRemoveTargetUserProfile(UserHandle userHandle) {
+ public void tryRemoveTargetUserProfile(UserHandle userHandle) {
try {
removeTargetUserProfile(userHandle);
} catch (IllegalArgumentException e) {
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestICrossProfileCallback.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestICrossProfileCallback.java
index 461cc9a..9fa2182 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestICrossProfileCallback.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestICrossProfileCallback.java
@@ -15,9 +15,11 @@
*/
package com.google.android.enterprise.connectedapps;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
/**
* An implementation of {@link ICrossProfileCallback} which just redirects call to a given {@link
@@ -38,13 +40,18 @@
throws RemoteException {}
@Override
+ public void prepareBundle(long callId, int bundleId, Bundle bundle) {}
+
+ @Override
public void onResult(long callId, int blockId, int methodIdentifier, byte[] params)
throws RemoteException {
Parcel p = Parcel.obtain(); // Recycled in this method
p.unmarshall(params, 0, params.length);
p.setDataPosition(0);
- localCallback.onResult(methodIdentifier, p);
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ bundle.readFromParcel(p);
p.recycle();
+ localCallback.onResult(methodIdentifier, bundle);
}
@Override
@@ -52,8 +59,10 @@
Parcel p = Parcel.obtain(); // Recycled in this method
p.unmarshall(params, 0, params.length);
p.setDataPosition(0);
- localCallback.onException(p);
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ bundle.readFromParcel(p);
p.recycle();
+ localCallback.onException(bundle);
}
@Override
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestScheduledExecutorService.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestScheduledExecutorService.java
index f237e66..06b2b4c 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestScheduledExecutorService.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestScheduledExecutorService.java
@@ -17,11 +17,13 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
-import java.util.Queue;
+import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Delayed;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -37,7 +39,9 @@
public class TestScheduledExecutorService extends AbstractExecutorService implements ScheduledExecutorService {
private long millisPast = 0;
- private final Queue<SimpleScheduledFuture<?>> executeQueue = new ConcurrentLinkedQueue<>();
+ private final Set<SimpleScheduledFuture<?>> executeQueue =
+ Collections.newSetFromMap(new ConcurrentHashMap<>());
+
public TestScheduledExecutorService() {}
@Override
@@ -107,8 +111,14 @@
private void advanceTimeByMillis(long timeoutMillis) throws Exception {
millisPast += timeoutMillis;
- while (!executeQueue.isEmpty() && executeQueue.peek().getDelay(MILLISECONDS) <= millisPast) {
- executeQueue.remove().complete();
+ Iterator<SimpleScheduledFuture<?>> scheduledFutures = executeQueue.iterator();
+
+ while (scheduledFutures.hasNext()) {
+ SimpleScheduledFuture<?> next = scheduledFutures.next();
+ if (next.getDelay(MILLISECONDS) <= millisPast) {
+ scheduledFutures.remove();
+ next.complete();
+ }
}
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestService.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestService.java
index cb11f3d..701a30f 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestService.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestService.java
@@ -15,8 +15,10 @@
*/
package com.google.android.enterprise.connectedapps;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
import com.google.android.enterprise.connectedapps.internal.ByteUtilities;
import com.google.auto.value.AutoValue;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -29,7 +31,7 @@
abstract long getMethodIdentifier();
- abstract Parcel getParams();
+ abstract Bundle getParams();
@Nullable
abstract ICrossProfileCallback callback();
@@ -37,7 +39,7 @@
static LoggedCrossProfileMethodCall create(
long crossProfileTypeIdentifier,
long methodIdentifier,
- Parcel params,
+ Bundle params,
ICrossProfileCallback callback) {
return new AutoValue_TestService_LoggedCrossProfileMethodCall(
crossProfileTypeIdentifier, methodIdentifier, params, callback);
@@ -45,26 +47,24 @@
}
private LoggedCrossProfileMethodCall lastCall;
- private Parcel responseParcel = Parcel.obtain(); // Recycled in #setResponseParcel
+ private Bundle responseBundle = new Bundle(Bundler.class.getClassLoader());
LoggedCrossProfileMethodCall lastCall() {
return lastCall;
}
- /**
- * Set the parcel to be returned from a call to this service.
- *
- * <p>The previously set parcel will be recycled.
- */
- void setResponseParcel(Parcel responseParcel) {
- this.responseParcel.recycle();
- this.responseParcel = responseParcel;
+ /** Set the bundle to be returned from a call to this service. */
+ void setResponseBundle(Bundle responseBundle) {
+ this.responseBundle = responseBundle;
}
@Override
public void prepareCall(long callId, int blockId, int numBytes, byte[] paramsBytes) {}
@Override
+ public void prepareBundle(long callId, int blockId, Bundle bundle) {}
+
+ @Override
public byte[] call(
long callId,
int blockId,
@@ -77,16 +77,18 @@
Parcel parcel = Parcel.obtain(); // Recycled by this method on next call
parcel.unmarshall(paramsBytes, 0, paramsBytes.length);
parcel.setDataPosition(0);
-
- if (lastCall != null) {
- lastCall.getParams().recycle();
- }
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ bundle.readFromParcel(parcel);
lastCall =
LoggedCrossProfileMethodCall.create(
- crossProfileTypeIdentifier, methodIdentifier, parcel, callback);
+ crossProfileTypeIdentifier, methodIdentifier, bundle, callback);
+ Parcel responseParcel = Parcel.obtain();
+ responseBundle.writeToParcel(responseParcel, /* flags= */ 0);
byte[] parcelBytes = responseParcel.marshall();
+ responseParcel.recycle();
+
return prepareResponse(parcelBytes);
}
@@ -99,4 +101,9 @@
public byte[] fetchResponse(long callId, int blockId) {
return null;
}
+
+ @Override
+ public Bundle fetchResponseBundle(long callId, int bundleId) {
+ return null;
+ }
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestStringCrossProfileCallback.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestStringCrossProfileCallback.java
index a88c222..d5085fa 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestStringCrossProfileCallback.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/TestStringCrossProfileCallback.java
@@ -15,39 +15,48 @@
*/
package com.google.android.enterprise.connectedapps;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
-import com.google.android.enterprise.connectedapps.internal.ParcelUtilities;
+import com.google.android.enterprise.connectedapps.internal.BundleUtilities;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
public class TestStringCrossProfileCallback implements ICrossProfileCallback {
public int lastReceivedMethodIdentifier = -1;
- public String lastReceivedMethodParam;
+ public String lastReceivedMethodValueParam;
public Throwable lastReceivedException;
@Override
public void prepareResult(long callId, int blockId, int numBytes, byte[] params) {}
@Override
+ public void prepareBundle(long callId, int bundleId, Bundle bundle) {}
+
+ @Override
public void onResult(long callId, int blockId, int methodIdentifier, byte[] paramsBytes)
throws RemoteException {
lastReceivedMethodIdentifier = methodIdentifier;
- Parcel parcel = Parcel.obtain(); // Recycled in this method
+ Parcel parcel = Parcel.obtain();
parcel.unmarshall(paramsBytes, 0, paramsBytes.length);
parcel.setDataPosition(0);
- lastReceivedMethodParam = parcel.readString();
+ Bundle params = new Bundle(Bundler.class.getClassLoader());
+ params.readFromParcel(parcel);
parcel.recycle();
+ lastReceivedMethodValueParam = params.getString("value");
}
@Override
public void onException(long callId, int blockId, byte[] paramsBytes) throws RemoteException {
- Parcel parcel = Parcel.obtain(); // Recycled in this method
+ Parcel parcel = Parcel.obtain();
parcel.unmarshall(paramsBytes, 0, paramsBytes.length);
parcel.setDataPosition(0);
-
- lastReceivedException = ParcelUtilities.readThrowableFromParcel(parcel);
+ Bundle bundle = new Bundle(Bundler.class.getClassLoader());
+ bundle.readFromParcel(parcel);
parcel.recycle();
+
+ lastReceivedException = BundleUtilities.readThrowableFromBundle(bundle, "throwable");
}
@Override
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/UserConnectorTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/UserConnectorTest.java
new file mode 100644
index 0000000..dd252b0
--- /dev/null
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/UserConnectorTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps;
+
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS_FULL;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Build.VERSION_CODES;
+import android.os.IBinder;
+import android.os.UserHandle;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.annotations.AvailabilityRestrictions;
+import com.google.android.enterprise.connectedapps.exceptions.MissingApiException;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.AppCrossUserConfiguration;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.AppCrossUserConnector;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.UserTestCrossUserType;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = VERSION_CODES.O)
+public final class UserConnectorTest {
+
+ private static final class TestBinder extends DefaultUserBinder {
+
+ TestBinder(UserHandle userHandle) {
+ super(userHandle);
+ }
+
+ static int tryBindCalls = 0;
+
+ @Override
+ public boolean tryBind(
+ Context context,
+ ComponentName bindToService,
+ ServiceConnection connection,
+ AvailabilityRestrictions availabilityRestrictions)
+ throws MissingApiException {
+ ++tryBindCalls;
+ return super.tryBind(context, bindToService, connection, availabilityRestrictions);
+ }
+ }
+
+ private static final String STRING = "hello";
+ private static final int TARGET_USER_ID = 5;
+
+ private final Application context = ApplicationProvider.getApplicationContext();
+ private final TestScheduledExecutorService scheduledExecutorService =
+ new TestScheduledExecutorService();
+ private final RobolectricTestUtilities utilities =
+ new RobolectricTestUtilities(context, scheduledExecutorService);
+
+ private final UserHandle targetUserHandle = utilities.createCustomUser(TARGET_USER_ID);
+
+ @Before
+ public void setUp() {
+ Service profileAwareService = Robolectric.setupService(AppCrossUserConfiguration.getService());
+ IBinder binder = profileAwareService.onBind(/* intent= */ null);
+ utilities.setBinding(binder, AppCrossUserConnector.class.getName());
+ utilities.setRequestsPermissions(INTERACT_ACROSS_USERS_FULL);
+ utilities.grantPermissions(INTERACT_ACROSS_USERS_FULL);
+ }
+
+ @Test
+ public void defaultBinder_works() throws UnavailableProfileException {
+ AppCrossUserConnector connector =
+ AppCrossUserConnector.create(context, scheduledExecutorService);
+ UserTestCrossUserType testCrossUserType = UserTestCrossUserType.create(connector);
+
+ utilities.addDefaultConnectionHolderAndWait(connector, targetUserHandle);
+
+ assertThat(testCrossUserType.user(targetUserHandle).identityStringMethod(STRING))
+ .isEqualTo(STRING);
+ }
+
+ @Test
+ public void testBinderFactory_isUsed_tryBindCallsIncremented() {
+ TestBinder.tryBindCalls = 0;
+ AppCrossUserConnector connector =
+ AppCrossUserConnector.create(context, scheduledExecutorService, TestBinder::new);
+
+ utilities.addDefaultConnectionHolderAndWait(connector, targetUserHandle);
+
+ assertThat(TestBinder.tryBindCalls).isEqualTo(1);
+ }
+}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/BundleCallSenderTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/BundleCallSenderTest.java
new file mode 100644
index 0000000..75a2b0b
--- /dev/null
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/BundleCallSenderTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.internal;
+
+import static com.google.android.enterprise.connectedapps.StringUtilities.randomString;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.TransactionTooLargeException;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BundleCallSenderTest {
+
+ static class TestBundleCallSender extends BundleCallSender {
+
+ int failPrepareCalls = 0;
+ int failCalls = 0;
+ int failFetchResponse = 0;
+ int failPrepareBundleCalls = 0;
+ int failFetchResponseBundle = 0;
+
+ private final BundleCallReceiver bundleCallReceiver = new BundleCallReceiver();
+
+ @Override
+ void prepareCall(long callId, int blockId, int totalBytes, byte[] bytes)
+ throws RemoteException {
+ if (failPrepareCalls-- > 0) {
+ throw new TransactionTooLargeException();
+ }
+
+ bundleCallReceiver.prepareCall(callId, blockId, totalBytes, bytes);
+ }
+
+ @Override
+ void prepareBundle(long callId, int bundleId, Bundle bundle) throws RemoteException {
+ if (failPrepareBundleCalls-- > 0) {
+ throw new TransactionTooLargeException();
+ }
+
+ bundleCallReceiver.prepareBundle(callId, bundleId, bundle);
+ }
+
+ @Override
+ byte[] call(long callId, int blockId, byte[] bytes) throws RemoteException {
+ if (failCalls-- > 0) {
+ throw new TransactionTooLargeException();
+ }
+
+ return bundleCallReceiver.prepareResponse(
+ callId, bundleCallReceiver.getPreparedCall(callId, blockId, bytes));
+ }
+
+ @Override
+ byte[] fetchResponse(long callId, int blockId) throws RemoteException {
+ if (failFetchResponse-- > 0) {
+ throw new TransactionTooLargeException();
+ }
+
+ return bundleCallReceiver.getPreparedResponse(callId, blockId);
+ }
+
+ @Override
+ Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException {
+ if (failFetchResponseBundle-- > 0) {
+ throw new TransactionTooLargeException();
+ }
+
+ return bundleCallReceiver.getPreparedResponseBundle(callId, bundleId);
+ }
+ }
+
+ private final TestBundleCallSender bundleCallSender = new TestBundleCallSender();
+ private static final String LARGE_STRING = randomString(1500000); // 3Mb
+ private static final Bundle LARGE_BUNDLE = new Bundle(Bundler.class.getClassLoader());
+ private final Binder binder = new Binder();
+ private final Bundle bundleContainingBinder = new Bundle();
+
+ static {
+ LARGE_BUNDLE.putString("value", LARGE_STRING);
+ }
+
+ public BundleCallSenderTest() {
+ bundleContainingBinder.putBinder("binder", binder);
+ }
+
+ @Test
+ public void makeBundleCall_prepareCallHasError_retriesUntilSuccess()
+ throws UnavailableProfileException {
+ bundleCallSender.failPrepareCalls = 5;
+
+ assertThat(bundleCallSender.makeBundleCall(LARGE_BUNDLE).getString("value"))
+ .isEqualTo(LARGE_STRING);
+ }
+
+ @Test
+ public void makeBundleCall_prepareCallHasError_failsAfter10Retries() {
+ bundleCallSender.failPrepareCalls = 11;
+
+ assertThrows(
+ UnavailableProfileException.class, () -> bundleCallSender.makeBundleCall(LARGE_BUNDLE));
+ }
+
+ @Test
+ public void makeBundleCall_callHasError_retriesUntilSuccess() throws UnavailableProfileException {
+ bundleCallSender.failCalls = 5;
+
+ assertThat(bundleCallSender.makeBundleCall(LARGE_BUNDLE).getString("value"))
+ .isEqualTo(LARGE_STRING);
+ }
+
+ @Test
+ public void makeBundleCall_callHasError_failsAfter10Retries() {
+ bundleCallSender.failCalls = 11;
+
+ assertThrows(
+ UnavailableProfileException.class, () -> bundleCallSender.makeBundleCall(LARGE_BUNDLE));
+ }
+
+ @Test
+ public void makeBundleCall_fetchResponseHasError_retriesUntilSuccess()
+ throws UnavailableProfileException {
+ bundleCallSender.failFetchResponse = 5;
+
+ assertThat(bundleCallSender.makeBundleCall(LARGE_BUNDLE).getString("value"))
+ .isEqualTo(LARGE_STRING);
+ }
+
+ @Test
+ public void makeBundleCall_fetchResponseHasError_failsAfter10Retries() {
+ bundleCallSender.failFetchResponse = 11;
+
+ assertThrows(
+ UnavailableProfileException.class, () -> bundleCallSender.makeBundleCall(LARGE_BUNDLE));
+ }
+
+ @Test
+ public void makeBundleCall_bundleContainsBinder_succeeds() throws UnavailableProfileException {
+ assertThat(bundleCallSender.makeBundleCall(bundleContainingBinder).getBinder("binder"))
+ .isEqualTo(binder);
+ }
+
+ @Test
+ public void makeBundleCall_prepareBundleHasError_retriesUntilSuccess()
+ throws UnavailableProfileException {
+ bundleCallSender.failPrepareBundleCalls = 5;
+
+ assertThat(bundleCallSender.makeBundleCall(bundleContainingBinder).getBinder("binder"))
+ .isEqualTo(binder);
+ }
+
+ @Test
+ public void makeBundleCall_prepareBundleHasError_failsAfter10Retries()
+ throws UnavailableProfileException {
+ bundleCallSender.failPrepareBundleCalls = 11;
+
+ assertThrows(
+ UnavailableProfileException.class,
+ () -> bundleCallSender.makeBundleCall(bundleContainingBinder));
+ }
+
+ @Test
+ public void makeBundleCall_fetchResponseBundleHasError_retriesUntilSuccess()
+ throws UnavailableProfileException {
+ bundleCallSender.failFetchResponseBundle = 5;
+
+ assertThat(bundleCallSender.makeBundleCall(bundleContainingBinder).getBinder("binder"))
+ .isEqualTo(binder);
+ }
+
+ @Test
+ public void makeBundleCall_fetchResponseBundleHasError_failsAfter10Retries()
+ throws UnavailableProfileException {
+ bundleCallSender.failFetchResponseBundle = 11;
+
+ assertThrows(
+ UnavailableProfileException.class,
+ () -> bundleCallSender.makeBundleCall(bundleContainingBinder));
+ }
+}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMergerTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMergerTest.java
index b7dbc95..088622e 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMergerTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/CrossProfileCallbackMultiMergerTest.java
@@ -16,11 +16,18 @@
package com.google.android.enterprise.connectedapps.internal;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import android.os.Build.VERSION_CODES;
import com.google.android.enterprise.connectedapps.Profile;
import com.google.android.enterprise.connectedapps.internal.CrossProfileCallbackMultiMerger.CrossProfileCallbackMultiMergerCompleteListener;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -188,10 +195,88 @@
public void construct_noExpectedResults_reportsResultImmediately() {
int expectedResults = 0;
- CrossProfileCallbackMultiMerger<String> ignoredMerger =
+ CrossProfileCallbackMultiMerger<String> unusedMerger =
new CrossProfileCallbackMultiMerger<>(expectedResults, stringListener);
assertThat(stringListener.timesResultsPosted).isEqualTo(1);
assertThat(stringListener.results).isEmpty();
}
+
+ @Test
+ // Do not ignore if this test turns flaky or times out, this likely highlights a real race
+ // condition.
+ public void listenableFuturesCompletingOnSeparateThreads() throws Exception {
+ Executor executorWithThread1 = Executors.newSingleThreadExecutor();
+ Executor executorWithThread2 = Executors.newSingleThreadExecutor();
+ int aboutThatManyIterationsToBeRacy = 1000;
+
+ for (int i = 0; i < aboutThatManyIterationsToBeRacy; i++) {
+ ListenableFuture<String> future1 = Futures.submit(() -> "Hello", executorWithThread1);
+ ListenableFuture<String> future2 = Futures.submit(() -> "World", executorWithThread2);
+ int expectedResults = 2;
+ SettableFuture<Map<Profile, String>> settableFuture = SettableFuture.create();
+ CrossProfileCallbackMultiMerger<String> merger =
+ new CrossProfileCallbackMultiMerger<>(expectedResults, settableFuture::set);
+ Futures.addCallback(
+ future1, new MergerFutureCallback<String>(profile0, merger), directExecutor());
+ Futures.addCallback(
+ future2, new MergerFutureCallback<String>(profile1, merger), directExecutor());
+
+ Map<Profile, String> results = settableFuture.get();
+
+ assertThat(results).containsExactly(profile0, "Hello", profile1, "World");
+ }
+ }
+
+ @Test
+ // Do not ignore if this test turns flaky or times out, this likely highlights a real race
+ // condition.
+ public void listenableFuturesCompletingWithErrorsOnSeparateThreads() throws Exception {
+ Executor executorWithThread1 = Executors.newSingleThreadExecutor();
+ Executor executorWithThread2 = Executors.newSingleThreadExecutor();
+ int aboutThatManyIterationsToBeRacy = 1000;
+
+ for (int i = 0; i < aboutThatManyIterationsToBeRacy; i++) {
+ ListenableFuture<String> future1 = Futures.submit(() -> "Hello", executorWithThread1);
+ ListenableFuture<String> future2 =
+ Futures.submit(
+ () -> {
+ throw new RuntimeException("Whoopsies");
+ },
+ executorWithThread2);
+ int expectedResults = 2;
+ SettableFuture<Map<Profile, String>> settableFuture = SettableFuture.create();
+ CrossProfileCallbackMultiMerger<String> merger =
+ new CrossProfileCallbackMultiMerger<>(expectedResults, settableFuture::set);
+ Futures.addCallback(
+ future1, new MergerFutureCallback<String>(profile0, merger), directExecutor());
+ Futures.addCallback(
+ future2, new MergerFutureCallback<String>(profile1, merger), directExecutor());
+
+ Map<Profile, String> results = settableFuture.get();
+
+ assertThat(results).containsExactly(profile0, "Hello");
+ }
+ }
+
+ private static class MergerFutureCallback<E> implements FutureCallback<E> {
+
+ private final Profile profileId;
+ private final CrossProfileCallbackMultiMerger<E> merger;
+
+ MergerFutureCallback(Profile profileId, CrossProfileCallbackMultiMerger<E> merger) {
+ this.profileId = profileId;
+ this.merger = merger;
+ }
+
+ @Override
+ public void onSuccess(E result) {
+ merger.onResult(profileId, result);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ merger.missingResult(profileId);
+ }
+ }
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/ParcelCallSenderTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/ParcelCallSenderTest.java
deleted file mode 100644
index dd39fa1..0000000
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/internal/ParcelCallSenderTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.enterprise.connectedapps.internal;
-
-import static com.google.android.enterprise.connectedapps.StringUtilities.randomString;
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.TransactionTooLargeException;
-import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class ParcelCallSenderTest {
-
- static class TestParcelCallSender extends ParcelCallSender {
-
- int failPrepareCalls = 0;
- int failCalls = 0;
- int failFetchResponse = 0;
-
- private final ParcelCallReceiver parcelCallReceiver = new ParcelCallReceiver();
-
- @Override
- void prepareCall(long callId, int blockId, int totalBytes, byte[] bytes)
- throws RemoteException {
- if (failPrepareCalls-- > 0) {
- throw new TransactionTooLargeException();
- }
-
- parcelCallReceiver.prepareCall(callId, blockId, totalBytes, bytes);
- }
-
- @Override
- byte[] call(long callId, int blockId, byte[] bytes) throws RemoteException {
- if (failCalls-- > 0) {
- throw new TransactionTooLargeException();
- }
-
- return parcelCallReceiver.prepareResponse(
- callId, parcelCallReceiver.getPreparedCall(callId, blockId, bytes));
- }
-
- @Override
- byte[] fetchResponse(long callId, int blockId) throws RemoteException {
- if (failFetchResponse-- > 0) {
- throw new TransactionTooLargeException();
- }
-
- return parcelCallReceiver.getPreparedResponse(callId, blockId);
- }
- }
-
- private final TestParcelCallSender parcelCallSender = new TestParcelCallSender();
- private static final String LARGE_STRING = randomString(1500000); // 3Mb
- private static final Parcel LARGE_PARCEL = Parcel.obtain();
-
- static {
- LARGE_PARCEL.writeString(LARGE_STRING);
- }
-
- @Test
- public void makeParcelCall_prepareCallHasError_retriesUntilSuccess()
- throws UnavailableProfileException {
- parcelCallSender.failPrepareCalls = 5;
-
- assertThat(parcelCallSender.makeParcelCall(LARGE_PARCEL).readString()).isEqualTo(LARGE_STRING);
- }
-
- @Test
- public void makeParcelCall_prepareCallHasError_failsAfter10Retries() {
- parcelCallSender.failPrepareCalls = 11;
-
- assertThrows(
- UnavailableProfileException.class, () -> parcelCallSender.makeParcelCall(LARGE_PARCEL));
- }
-
- @Test
- public void makeParcelCall_callHasError_retriesUntilSuccess() throws UnavailableProfileException {
- parcelCallSender.failCalls = 5;
-
- assertThat(parcelCallSender.makeParcelCall(LARGE_PARCEL).readString()).isEqualTo(LARGE_STRING);
- }
-
- @Test
- public void makeParcelCall_callHasError_failsAfter10Retries() {
- parcelCallSender.failCalls = 11;
-
- assertThrows(
- UnavailableProfileException.class, () -> parcelCallSender.makeParcelCall(LARGE_PARCEL));
- }
-
- @Test
- public void makeParcelCall_fetchResponseHasError_retriesUntilSuccess()
- throws UnavailableProfileException {
- parcelCallSender.failFetchResponse = 5;
-
- assertThat(parcelCallSender.makeParcelCall(LARGE_PARCEL).readString()).isEqualTo(LARGE_STRING);
- }
-
- @Test
- public void makeParcelCall_fetchResponseHasError_failsAfter10Retries() {
- parcelCallSender.failFetchResponse = 11;
-
- assertThrows(
- UnavailableProfileException.class, () -> parcelCallSender.makeParcelCall(LARGE_PARCEL));
- }
-}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AutomaticConnectionManagementTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AutomaticConnectionManagementTest.java
index 3f43c16..6e6a7a8 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AutomaticConnectionManagementTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AutomaticConnectionManagementTest.java
@@ -25,22 +25,24 @@
import android.os.Build.VERSION_CODES;
import android.os.IBinder;
import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
import com.google.android.enterprise.connectedapps.TestExceptionCallbackListener;
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
+import com.google.android.enterprise.connectedapps.TestStringCallbackListenerImpl;
import com.google.android.enterprise.connectedapps.TestVoidCallbackListenerImpl;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
-
import org.robolectric.annotation.LooperMode;
@LooperMode(LooperMode.Mode.LEGACY)
@@ -51,6 +53,8 @@
private final Application context = ApplicationProvider.getApplicationContext();
private final TestVoidCallbackListenerImpl testVoidCallbackListener =
new TestVoidCallbackListenerImpl();
+ private final TestStringCallbackListenerImpl testStringCallbackListener =
+ new TestStringCallbackListenerImpl();
private final TestExceptionCallbackListener testExceptionCallbackListener =
new TestExceptionCallbackListener();
private final TestScheduledExecutorService scheduledExecutorService =
@@ -61,6 +65,7 @@
new RobolectricTestUtilities(testProfileConnector, scheduledExecutorService);
private final ProfileTestCrossProfileType profileTestCrossProfileType =
ProfileTestCrossProfileType.create(testProfileConnector);
+ private final Object connectionHolder = new Object();
@Before
public void setUp() {
@@ -73,7 +78,11 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testProfileConnector.stopManualConnectionManagement();
+ }
+
+ @After
+ public void teardown() {
+ testProfileConnector.clearConnectionHolders();
}
@Test
@@ -104,7 +113,7 @@
.other()
.asyncVoidMethod(testVoidCallbackListener, testExceptionCallbackListener);
testUtilities.advanceTimeBySeconds(29);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.advanceTimeBySeconds(31);
@@ -145,7 +154,7 @@
public void callWhichTakesALongTime_doesNotDisconnectDuringCall() {
profileTestCrossProfileType
.other()
- .asyncVoidMethodWithNonBlockingDelayWith50SecondTimeout(
+ .asyncVoidMethodWithNonBlockingDelay(
testVoidCallbackListener, /* secondsDelay= */ 40, testExceptionCallbackListener);
testUtilities.advanceTimeBySeconds(31);
@@ -157,7 +166,7 @@
public void lessThanThirtySecondsAfterCallWhichTakesALongTime_doesNotDisconnect() {
profileTestCrossProfileType
.other()
- .asyncVoidMethodWithNonBlockingDelayWith50SecondTimeout(
+ .asyncVoidMethodWithNonBlockingDelay(
testVoidCallbackListener, /* secondsDelay= */ 40, testExceptionCallbackListener);
testUtilities.advanceTimeBySeconds(69);
@@ -169,7 +178,7 @@
public void thirtySecondsAfterCallWhichTakesALongTime_disconnects() {
profileTestCrossProfileType
.other()
- .asyncVoidMethodWithNonBlockingDelayWith50SecondTimeout(
+ .asyncVoidMethodWithNonBlockingDelay(
testVoidCallbackListener, /* secondsDelay= */ 40, testExceptionCallbackListener);
testUtilities.advanceTimeBySeconds(70);
@@ -193,9 +202,9 @@
}
@Test
- public void stopManualConnectionManagement_lessThan30SecondsLater_doesNotDisconnect() {
- testUtilities.startConnectingAndWait();
- testProfileConnector.stopManualConnectionManagement();
+ public void clearConnectionHolders_lessThan30SecondsLater_doesNotDisconnect() {
+ testUtilities.addDefaultConnectionHolderAndWait();
+ testProfileConnector.clearConnectionHolders();
testUtilities.advanceTimeBySeconds(29);
@@ -203,9 +212,9 @@
}
@Test
- public void stopManualConnectionManagement_moreThan30SecondsLater_disconnects() {
- testUtilities.startConnectingAndWait();
- testProfileConnector.stopManualConnectionManagement();
+ public void clearConnectionHolders_moreThan30SecondsLater_disconnects() {
+ testUtilities.addDefaultConnectionHolderAndWait();
+ testProfileConnector.clearConnectionHolders();
testUtilities.advanceTimeBySeconds(29);
@@ -213,6 +222,90 @@
}
@Test
+ public void addConnectionHolder_moreThan30SecondsLater_doesNotDisconnect() {
+ testProfileConnector.addConnectionHolder(this);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_lessThan30SecondsLater_doesNotDisconnect() {
+ testProfileConnector.addConnectionHolder(this);
+ testProfileConnector.removeConnectionHolder(this);
+
+ testUtilities.advanceTimeBySeconds(29);
+
+ assertThat(testProfileConnector.isConnected()).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_moreThan30SecondsLater_disconnects() {
+ testProfileConnector.addConnectionHolder(this);
+ testProfileConnector.removeConnectionHolder(this);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingAlias_moreThan30SecondsLater_disconnects() {
+ testProfileConnector.addConnectionHolder(this);
+ testProfileConnector.addConnectionHolderAlias(connectionHolder, this);
+ testProfileConnector.removeConnectionHolder(connectionHolder);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingWrapper_moreThan30SecondsLater_disconnects() {
+ ProfileConnectionHolder connectionHolder = testProfileConnector.addConnectionHolder(this);
+ testProfileConnector.removeConnectionHolder(connectionHolder);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void
+ removeConnectionHolder_stillAnotherConnectionHolder_moreThan30SecondsLater_doesNotDisconnect() {
+ testProfileConnector.addConnectionHolder(this);
+ testProfileConnector.addConnectionHolder(connectionHolder);
+ testProfileConnector.removeConnectionHolder(this);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingCallback_noResultOnCallback_moreThan30SecondsLater_disconnects() {
+ profileTestCrossProfileType.other()
+ .asyncMethodWhichNeverCallsBack(testStringCallbackListener, testExceptionCallbackListener);
+ testProfileConnector.removeConnectionHolder(testStringCallbackListener);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingFuture_noResultOnFuture_moreThan30SecondsLater_disconnects() {
+ ListenableFuture<Void> future = profileTestCrossProfileType.other()
+ .listenableFutureMethodWhichNeverSetsTheValue();
+ testProfileConnector.removeConnectionHolder(future);
+
+ testUtilities.advanceTimeBySeconds(31);
+
+ assertThat(testProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
public void asyncCall_doesNotHavePermission_failsImmediately() {
testUtilities.denyPermissions(INTERACT_ACROSS_USERS);
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AvailabilityListenerTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AvailabilityListenerTest.java
index d999d22..54e77ca 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AvailabilityListenerTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/AvailabilityListenerTest.java
@@ -63,7 +63,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
}
@Test
@@ -71,7 +71,7 @@
throws InterruptedException, ExecutionException {
testUtilities.turnOnWorkProfile();
TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
- testProfileConnector.registerAvailabilityListener(availabilityListener);
+ testProfileConnector.addAvailabilityListener(availabilityListener);
ListenableFuture<Void> unusedFuture = type.other().listenableFutureVoidMethod();
testUtilities.advanceTimeBySeconds(1);
@@ -85,7 +85,7 @@
public void temporaryConnectionError_inProgressCall_availabilityListenerFires() {
testUtilities.turnOnWorkProfile();
TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
- testProfileConnector.registerAvailabilityListener(availabilityListener);
+ testProfileConnector.addAvailabilityListener(availabilityListener);
ListenableFuture<Void> unusedFuture =
type.other().listenableFutureVoidMethodWithNonBlockingDelay(/* secondsDelay= */ 5);
@@ -95,18 +95,4 @@
assertThat(availabilityListener.availabilityChangedCount()).isGreaterThan(0);
assertThat(testProfileConnector.isAvailable()).isTrue();
}
-
- @Test
- public void temporaryConnectionError_noInProgressCall_availabilityListenerDoesNotFire() {
- testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
- TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
- testProfileConnector.registerAvailabilityListener(availabilityListener);
-
- testUtilities.simulateDisconnectingServiceConnection();
- testUtilities.advanceTimeBySeconds(1);
-
- assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(0);
- assertThat(testProfileConnector.isAvailable()).isTrue();
- }
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesAsyncTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesAsyncTest.java
index cb89aad..6905053 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesAsyncTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesAsyncTest.java
@@ -31,6 +31,7 @@
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
import com.google.android.enterprise.connectedapps.TestStringCallbackListenerMultiImpl;
import com.google.android.enterprise.connectedapps.TestVoidCallbackListenerMultiImpl;
+import com.google.android.enterprise.connectedapps.testapp.CustomError;
import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
@@ -82,7 +83,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
}
@Test
@@ -177,36 +178,21 @@
}
@Test
- public void both_async_timeoutSet_doesTimeout() {
+ public void both_async_doesNotTimeout() {
profileTestCrossProfileType
.both()
- .asyncIdentityStringMethodWithNonBlockingDelayWith3SecondTimeout(
- STRING, stringCallback, /* secondsDelay= */ 5);
-
- testUtilities.advanceTimeBySeconds(6);
-
- assertThat(stringCallback.stringCallbackValues.get(currentProfileIdentifier)).isEqualTo(STRING);
- assertThat(stringCallback.stringCallbackValues).doesNotContainKey(otherProfileIdentifier);
- }
-
- @Test
- public void both_async_timeoutSetByCaller_doesTimeout() {
- profileTestCrossProfileType
- .both()
- .timeout(3000)
.asyncIdentityStringMethodWithNonBlockingDelay(
- STRING, stringCallback, /* secondsDelay= */ 5);
+ STRING, stringCallback, /* secondsDelay= */ 100);
- testUtilities.advanceTimeBySeconds(6);
+ testUtilities.advanceTimeBySeconds(99);
- assertThat(stringCallback.stringCallbackValues.get(currentProfileIdentifier)).isEqualTo(STRING);
- assertThat(stringCallback.stringCallbackValues).doesNotContainKey(otherProfileIdentifier);
+ assertThat(stringCallback.stringCallbackValues).isNull();
}
@Test
public void both_async_throwsRuntimeException_exceptionThrownOnCurrentProfileIsThrown() {
assertThrows(
- CustomRuntimeException.class,
+ Exception.class,
() ->
profileTestCrossProfileType
.both()
@@ -214,10 +200,30 @@
}
@Test
+ public void both_async_throwsError_errorThrownOnCurrentProfileIsThrown() {
+ assertThrows(
+ CustomError.class,
+ () ->
+ profileTestCrossProfileType
+ .both()
+ .asyncStringMethodWhichThrowsError(stringCallback));
+ }
+
+ @Test
public void both_async_contextArgument_works() {
profileTestCrossProfileType.both().asyncIsContextArgumentPassed(booleanCallback);
assertThat(booleanCallback.booleanCallbackValues.get(currentProfileIdentifier)).isTrue();
assertThat(booleanCallback.booleanCallbackValues.get(otherProfileIdentifier)).isTrue();
}
+
+ @Test
+ public void both_async_passesMultipleValues_onlyReceivesFirstValues() {
+ stringCallback.numberOfCallbacks = 0;
+
+ profileTestCrossProfileType.both()
+ .asyncIdentityStringMethodWhichCallsBackTwice(STRING, stringCallback);
+
+ assertThat(stringCallback.numberOfCallbacks).isEqualTo(1);
+ }
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesListenableFutureTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesListenableFutureTest.java
index 78b70c6..f44c9e1 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesListenableFutureTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesListenableFutureTest.java
@@ -28,7 +28,7 @@
import com.google.android.enterprise.connectedapps.Profile;
import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
-import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
+import com.google.android.enterprise.connectedapps.testapp.CustomError;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
@@ -75,7 +75,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
}
@Test
@@ -222,43 +222,24 @@
}
@Test
- public void both_listenableFuture_timeoutSet_doesTimeout()
+ public void both_listenableFuture_doesNotTimeout()
throws ExecutionException, InterruptedException {
ListenableFuture<Map<Profile, String>> future =
profileTestCrossProfileType
.both()
- .listenableFutureIdentityStringMethodWithNonBlockingDelayWith3SecondTimeout(
- STRING, /* secondsDelay= */ 5);
-
- testUtilities.advanceTimeBySeconds(6);
-
- Map<Profile, String> results = future.get();
- assertThat(results.get(currentProfileIdentifier)).isEqualTo(STRING);
- assertThat(results).doesNotContainKey(otherProfileIdentifier);
- }
-
- @Test
- public void both_listenableFuture_timeoutSetByCaller_doesTimeout()
- throws ExecutionException, InterruptedException {
- ListenableFuture<Map<Profile, String>> future =
- profileTestCrossProfileType
- .both()
- .timeout(3000)
.listenableFutureIdentityStringMethodWithNonBlockingDelay(
- STRING, /* secondsDelay= */ 5);
+ STRING, /* secondsDelay= */ 100);
- testUtilities.advanceTimeBySeconds(6);
+ testUtilities.advanceTimeBySeconds(99);
- Map<Profile, String> results = future.get();
- assertThat(results.get(currentProfileIdentifier)).isEqualTo(STRING);
- assertThat(results).doesNotContainKey(otherProfileIdentifier);
+ assertThat(future.isDone()).isFalse();
}
@Test
public void
both_listenableFuture_throwsRuntimeException_exceptionThrownOnCurrentProfileIsThrown() {
assertThrows(
- CustomRuntimeException.class,
+ Exception.class,
() ->
profileTestCrossProfileType
.both()
@@ -266,6 +247,17 @@
}
@Test
+ public void
+ both_listenableFuture_throwsError_errorThrownOnCurrentProfileIsThrown() {
+ assertThrows(
+ CustomError.class,
+ () ->
+ profileTestCrossProfileType
+ .both()
+ .listenableFutureVoidMethodWhichThrowsError());
+ }
+
+ @Test
public void both_listenableFuture_contextArgument_works() throws Exception {
ListenableFuture<Map<Profile, Boolean>> resultFuture =
profileTestCrossProfileType.both().futureIsContextArgumentPassed();
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualAsyncTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualAsyncTest.java
index 66b7ef1..feaeb6c 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualAsyncTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualAsyncTest.java
@@ -77,13 +77,13 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
public void both_async_manualConnection_isBound_calledOnBothProfiles() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.both().asyncVoidMethod(voidCallback);
@@ -94,7 +94,7 @@
@Test
public void both_async_manualConnection_isBound_resultContainsBothProfilesResults() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.both().asyncIdentityStringMethod(STRING, stringCallback);
@@ -105,7 +105,7 @@
@Test // This behaviour is expected right now but will change
public void both_async_manualConnection_isBound_blockingMethod_blocks() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.both()
@@ -117,7 +117,7 @@
@Test
public void both_async_manualConnection_isBound_nonblockingMethod_doesNotBlock() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.both()
@@ -129,7 +129,7 @@
@Test
public void both_async_manualConnection_isBound_nonblockingMethod_doesCallback() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.both()
@@ -141,7 +141,7 @@
@Test
public void both_async_manualConnection_isNotBound_calledOnOnlyCurrentProfile() {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOffWorkProfile();
profileTestCrossProfileType.both().asyncVoidMethod(voidCallback);
@@ -164,7 +164,7 @@
@Test
public void both_async_manualConnection_isBound_becomesUnbound_calledOnBothProfiles() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.both()
.asyncVoidMethodWithNonBlockingDelay(voidCallback, /* secondsDelay= */ 5);
@@ -182,7 +182,7 @@
@Test
public void both_async_manualConnection_isBound_becomesUnbound_callbackFires() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.both()
.asyncVoidMethodWithNonBlockingDelay(voidCallback, /* secondsDelay= */ 5);
@@ -197,7 +197,7 @@
public void
both_async_manualConnection_connectionDropsDuringCall_resultContainsOnlyCurrentProfilesResult() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.both()
.asyncIdentityStringMethodWithNonBlockingDelay(
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualListenableFutureTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualListenableFutureTest.java
index a3ca7eb..2e2ed24 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualListenableFutureTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesManualListenableFutureTest.java
@@ -71,15 +71,15 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
public void both_listenableFuture_manualConnection_isBound_calledOnBothProfiles()
throws ExecutionException, InterruptedException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.both().listenableFutureVoidMethod().get();
@@ -90,9 +90,9 @@
@Test
public void both_listenableFuture_manualConnection_isBound_resultContainsBothProfilesResults()
throws ExecutionException, InterruptedException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
Map<Profile, String> results =
profileTestCrossProfileType.both().listenableFutureIdentityStringMethod(STRING).get();
@@ -103,9 +103,9 @@
@Test // This behaviour is expected right now but will change
public void both_listenableFuture_manualConnection_isBound_blockingMethod_blocks() {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, Void>> future =
profileTestCrossProfileType
@@ -118,7 +118,7 @@
@Test
public void both_listenableFuture_manualConnection_isBound_nonblockingMethod_doesNotBlock() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, Void>> future =
profileTestCrossProfileType
@@ -131,7 +131,7 @@
@Test
public void both_listenableFuture_manualConnection_isBound_nonblockingMethod_doesCallback() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, Void>> future =
profileTestCrossProfileType
@@ -145,7 +145,7 @@
@Test
public void both_listenableFuture_manualConnection_isNotBound_calledOnOnlyCurrentProfile()
throws ExecutionException, InterruptedException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOffWorkProfile();
profileTestCrossProfileType.both().listenableFutureVoidMethod().get();
@@ -158,7 +158,7 @@
public void
both_listenableFuture_manualConnection_isNotBound_resultContainsOnlyCurrentProfilesResult()
throws ExecutionException, InterruptedException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOffWorkProfile();
Map<Profile, String> results =
@@ -171,7 +171,7 @@
@Test
public void both_listenableFuture_manualConnection_isBound_becomesUnbound_calledOnBothProfiles() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, Void>> unusedFuture =
profileTestCrossProfileType
.both()
@@ -189,7 +189,7 @@
@Test
public void both_listenableFuture_manualConnection_isBound_becomesUnbound_callbackFires() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, Void>> future =
profileTestCrossProfileType
.both()
@@ -203,7 +203,7 @@
@Test
public void both_listenableFuture_manualConnection_profilesWithExceptionsAreNotIncludedInResults()
throws ExecutionException, InterruptedException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, Void>> future =
profileTestCrossProfileType
.both()
@@ -216,7 +216,7 @@
public void
both_listenableFuture_manualConnection_connectionDropsDuringCall_resultContainsOnlyCurrentProfilesResult()
throws ExecutionException, InterruptedException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
ListenableFuture<Map<Profile, String>> future =
profileTestCrossProfileType
.both()
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesSynchronousTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesSynchronousTest.java
index 20ee748..5289c4f 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesSynchronousTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/BothProfilesSynchronousTest.java
@@ -29,6 +29,7 @@
import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
+import com.google.android.enterprise.connectedapps.testapp.CustomError;
import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
@@ -73,13 +74,13 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
public void both_synchronous_isBound_resultContainsBothProfileResults() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
Map<Profile, String> result = profileTestCrossProfileType.both().identityStringMethod(STRING);
@@ -112,6 +113,20 @@
}
@Test
+ public void both_synchronous_throwsError_errorThrownOnCurrentProfileIsThrown() {
+ // Since the exception is thrown on both sides, which is thrown first is not deterministic.
+ // This test just confirms one of the two is thrown
+ try {
+ profileTestCrossProfileType.both().methodWhichThrowsError();
+ fail();
+ } catch (CustomError expected) {
+
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void both_synchronous_contextArgument_works() {
Map<Profile, Boolean> result = profileTestCrossProfileType.both().isContextArgumentPassed();
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackReceiverTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackReceiverTest.java
index b4118b1..f7a7029 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackReceiverTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackReceiverTest.java
@@ -20,7 +20,7 @@
import com.google.android.enterprise.connectedapps.TestStringCrossProfileCallback;
import com.google.android.enterprise.connectedapps.internal.Bundler;
import com.google.android.enterprise.connectedapps.testapp.Profile_TestStringCallbackListener_Receiver;
-import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType_Bundler;
+import com.google.android.enterprise.connectedapps.testapp.types.TestCrossProfileType_Bundler;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -31,7 +31,7 @@
private static final String STRING = "String";
private final TestStringCrossProfileCallback callback = new TestStringCrossProfileCallback();
- private final Bundler bundler = new ProfileTestCrossProfileType_Bundler();
+ private final Bundler bundler = new TestCrossProfileType_Bundler();
private final Profile_TestStringCallbackListener_Receiver receiver =
new Profile_TestStringCallbackListener_Receiver(callback, bundler);
@@ -46,6 +46,6 @@
public void asyncCallbackListenerReceiver_bundlesParams() {
receiver.stringCallback(STRING);
- assertThat(callback.lastReceivedMethodParam).isEqualTo(STRING);
+ assertThat(callback.lastReceivedMethodValueParam).isEqualTo(STRING);
}
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackSenderTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackSenderTest.java
index 5bd46b2..248b821 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackSenderTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileCallbackSenderTest.java
@@ -24,7 +24,7 @@
import com.google.android.enterprise.connectedapps.testapp.Profile_TestStringCallbackListener_Receiver;
import com.google.android.enterprise.connectedapps.testapp.Profile_TestStringCallbackListener_Sender;
import com.google.android.enterprise.connectedapps.testapp.TestStringCallbackListener;
-import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType_Bundler;
+import com.google.android.enterprise.connectedapps.testapp.types.TestCrossProfileType_Bundler;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -44,7 +44,7 @@
private final TestExceptionCallbackListener exceptionCallback =
new TestExceptionCallbackListener();
private final TestStringCallbackListenerImpl callback = new TestStringCallbackListenerImpl();
- private final Bundler bundler = new ProfileTestCrossProfileType_Bundler();
+ private final Bundler bundler = new TestCrossProfileType_Bundler();
private final Profile_TestStringCallbackListener_Sender sender =
new Profile_TestStringCallbackListener_Sender(callback, exceptionCallback, bundler);
private final Profile_TestStringCallbackListener_Receiver receiver =
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileInterfaceTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileInterfaceTest.java
index eb44ea0..7f6d249 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileInterfaceTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileInterfaceTest.java
@@ -72,7 +72,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileServiceTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileServiceTest.java
index f9af23b..5414300 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileServiceTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileServiceTest.java
@@ -16,8 +16,6 @@
package com.google.android.enterprise.connectedapps.robotests;
import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS;
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
@@ -28,7 +26,6 @@
import androidx.test.core.app.ApplicationProvider;
import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
-import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
@@ -69,7 +66,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@After
@@ -79,7 +76,7 @@
@Test
public void crossProfileMethodCall_doesNotThrowException() throws UnavailableProfileException {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.other().voidMethod();
}
@@ -90,23 +87,8 @@
ShadowBinder.setCallingUid(10);
shadowOf(context.getPackageManager())
.setPackagesForUid(10, DIFFERENT_PACKAGE_NAME, context.getPackageName());
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.other().voidMethod();
}
-
- @Test
- public void crossProfileMethodCall_callingFromInvalidPackage_throwsWrappedIllegalStateException()
- throws UnavailableProfileException {
- testUtilities.startConnectingAndWait();
- ShadowBinder.setCallingUid(10);
- shadowOf(context.getPackageManager()).setPackagesForUid(10, DIFFERENT_PACKAGE_NAME);
-
- try {
- profileTestCrossProfileType.other().voidMethod();
- fail();
- } catch (ProfileRuntimeException expected) {
- assertThat(expected).hasCauseThat().isInstanceOf(IllegalStateException.class);
- }
- }
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileTypeProfileTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileTypeProfileTest.java
index 3adf174..1ffe662 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileTypeProfileTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossProfileTypeProfileTest.java
@@ -80,7 +80,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossUserTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossUserTest.java
deleted file mode 100644
index 10b7d1f..0000000
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossUserTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.enterprise.connectedapps.robotests;
-
-import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.Application;
-import android.app.Service;
-import android.os.Build.VERSION_CODES;
-import android.os.IBinder;
-import androidx.test.core.app.ApplicationProvider;
-import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
-import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
-import com.google.android.enterprise.connectedapps.testapp.crossuser.ProfileTestCrossUserType;
-import com.google.android.enterprise.connectedapps.testapp.crossuser.TestCrossUserConfiguration;
-import com.google.android.enterprise.connectedapps.testapp.crossuser.TestCrossUserConnector;
-import com.google.android.enterprise.connectedapps.testapp.crossuser.TestCrossUserStringCallbackListenerImpl;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(minSdk = VERSION_CODES.O)
-public class CrossUserTest {
-
- private static final String STRING = "String";
-
- private final Application context = ApplicationProvider.getApplicationContext();
- private final TestScheduledExecutorService scheduledExecutorService =
- new TestScheduledExecutorService();
- private final TestCrossUserConnector testCrossUserConnector =
- TestCrossUserConnector.create(context, scheduledExecutorService);
- private final RobolectricTestUtilities testUtilities =
- new RobolectricTestUtilities(testCrossUserConnector, scheduledExecutorService);
- private final ProfileTestCrossUserType profileCrossUserType =
- ProfileTestCrossUserType.create(testCrossUserConnector);
- private final TestCrossUserStringCallbackListenerImpl crossUserStringCallback =
- new TestCrossUserStringCallbackListenerImpl();
-
- @Before
- public void setUp() {
- Service profileAwareService = Robolectric.setupService(TestCrossUserConfiguration.getService());
- testUtilities.initTests();
- IBinder binder = profileAwareService.onBind(/* intent= */ null);
- testUtilities.setBinding(binder, TestCrossUserConnector.class.getName());
- testUtilities.createWorkUser();
- testUtilities.turnOnWorkProfile();
- testUtilities.setRunningOnPersonalProfile();
- testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
- testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testCrossUserConnector.stopManualConnectionManagement();
- }
-
- @Test
- // This test covers all CrossUser annotations
- public void passArgumentToCallback_works() {
- testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
-
- profileCrossUserType.current().passString(STRING, crossUserStringCallback);
-
- assertThat(crossUserStringCallback.stringCallbackValue).isEqualTo(STRING);
- }
-}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossUserTestTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossUserTestTest.java
new file mode 100644
index 0000000..80bd1d5
--- /dev/null
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CrossUserTestTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.robotests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.app.Application;
+import android.os.UserHandle;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
+import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.AppCrossUserConfiguration;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.FakeAppCrossUserConnector;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.FakeUserNotesManager;
+import com.google.android.enterprise.connectedapps.testapp.crossuser.NotesManager;
+import com.google.android.enterprise.connectedapps.testing.annotations.CrossUserTest;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@CrossUserTest(configuration = AppCrossUserConfiguration.class)
+@RunWith(RobolectricTestRunner.class)
+public class CrossUserTestTest {
+
+ private static final String NOTE_1 = "I stayed at home today";
+ private static final String NOTE_2 = "I am hungry";
+ private static final String NOTE_3 = "I should eat something, probably";
+
+ private final Application context = ApplicationProvider.getApplicationContext();
+ private final TestScheduledExecutorService executor = new TestScheduledExecutorService();
+ private final RobolectricTestUtilities utilities =
+ new RobolectricTestUtilities(context, executor);
+
+ private final UserHandle user4Handle = utilities.createCustomUser(4);
+ private final UserHandle user5Handle = utilities.createCustomUser(5);
+
+ private final NotesManager user4NotesManager = new NotesManager();
+ private final NotesManager user5NotesManager = new NotesManager();
+
+ private final FakeAppCrossUserConnector connector = new FakeAppCrossUserConnector(context);
+ private final FakeUserNotesManager defaultFakeUserNotesManager =
+ FakeUserNotesManager.builder()
+ .user(user4Handle, user4NotesManager)
+ .user(user5Handle, user5NotesManager)
+ .connector(connector)
+ .build();
+
+ @Before
+ public void setUp() {
+ connector.setRunningOnUser(user4Handle);
+ connector.turnOnUser(user5Handle);
+ }
+
+ @Test
+ public void currentCall_returnsCorrectNotesManager()
+ throws ExecutionException, InterruptedException {
+ user4NotesManager.addNote(NOTE_1);
+ user4NotesManager.addNote(NOTE_2);
+ user5NotesManager.addNote(NOTE_3);
+
+ ListenableFuture<Set<String>> currentUserNotesFuture =
+ defaultFakeUserNotesManager.current().getNotesFuture();
+
+ assertThat(currentUserNotesFuture.get()).containsExactly(NOTE_1, NOTE_2);
+ }
+
+ @Test
+ public void userCall_returnsCorrectNotesManager()
+ throws ExecutionException, InterruptedException {
+ user4NotesManager.addNote(NOTE_1);
+ user5NotesManager.addNote(NOTE_2);
+ user5NotesManager.addNote(NOTE_3);
+
+ ListenableFuture<Set<String>> user5NotesFuture =
+ defaultFakeUserNotesManager.user(user5Handle).getNotesFuture();
+
+ assertThat(user5NotesFuture.get()).containsExactly(NOTE_2, NOTE_3);
+ }
+
+ @Test
+ public void currentCall_runningUserNull_throwsException() {
+ connector.setRunningOnUser(null);
+
+ assertThrows(UnsupportedOperationException.class, defaultFakeUserNotesManager::current);
+ }
+
+ @Test
+ public void currentCall_noTargetType_throwsException() {
+ FakeUserNotesManager noTargetTypeNotesManager =
+ FakeUserNotesManager.builder().connector(connector).build();
+
+ assertThrows(UnsupportedOperationException.class, noTargetTypeNotesManager::current);
+ }
+
+ @Test
+ public void userCall_userHandleNull_throwsException() {
+ assertThrows(IllegalArgumentException.class, () -> defaultFakeUserNotesManager.user(null));
+ }
+
+ @Test
+ public void userCall_noTargetType_throwsException() {
+ FakeUserNotesManager noTargetTypeNotesManager =
+ FakeUserNotesManager.builder().connector(connector).build();
+
+ assertThrows(
+ UnsupportedOperationException.class, () -> noTargetTypeNotesManager.user(user4Handle));
+ }
+
+ @Test
+ public void userCall_userTurnedOff_throwsException() {
+ connector.turnOffUser(user5Handle);
+
+ assertThrows(
+ ExecutionException.class,
+ () -> defaultFakeUserNotesManager.user(user5Handle).getNotesFuture().get());
+ }
+
+ @Test
+ public void builder_noConnector_throws() {
+ assertThrows(IllegalStateException.class, () -> FakeUserNotesManager.builder().build());
+ }
+}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CurrentProfileTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CurrentProfileTest.java
index 7693a54..cd26001 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CurrentProfileTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/CurrentProfileTest.java
@@ -95,13 +95,13 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
public void current_isBound_callsMethod() throws UnavailableProfileException {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
assertThat(profileTestCrossProfileType.current().identityStringMethod(STRING))
.isEqualTo(STRING);
@@ -118,7 +118,7 @@
@Test
public void current_async_isBound_callsMethod() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.current().asyncVoidMethod(voidCallbackListener);
@@ -128,7 +128,7 @@
@Test
public void current_synchronous_isBound_automaticConnectionManagement_callsMethod() {
testUtilities.turnOnWorkProfile();
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
ListenableFuture<Void> ignored =
profileTestCrossProfileType.other().listenableFutureVoidMethod(); // Causes it to bind
@@ -191,7 +191,7 @@
public void current_listenableFuture_isBound_callsMethod()
throws ExecutionException, InterruptedException {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.current().listenableFutureVoidMethod().get();
@@ -268,9 +268,7 @@
@Test
public void current_listenableFuture_doesNotTimeout() {
ListenableFuture<Void> future =
- profileTestCrossProfileType
- .current()
- .listenableFutureMethodWhichNeverSetsTheValueWith5SecondTimeout();
+ profileTestCrossProfileType.current().listenableFutureMethodWhichNeverSetsTheValue();
testUtilities.advanceTimeBySeconds(10);
assertFutureDoesNotHaveException(future, UnavailableProfileException.class);
@@ -278,10 +276,8 @@
@Test
public void current_async_doesNotTimeout() {
- profileTestCrossProfileType
- .current()
- .asyncMethodWhichNeverCallsBackWith5SecondTimeout(stringCallbackListener);
- testUtilities.advanceTimeBySeconds(10);
+ profileTestCrossProfileType.current().asyncMethodWhichNeverCallsBack(stringCallbackListener);
+ testUtilities.advanceTimeBySeconds(100);
assertThat(stringCallbackListener.callbackMethodCalls).isEqualTo(0);
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/DefaultUserBinderTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/DefaultUserBinderTest.java
new file mode 100644
index 0000000..c22ca3a
--- /dev/null
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/DefaultUserBinderTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.robotests;
+
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS;
+import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS_FULL;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.os.Build.VERSION_CODES;
+import android.os.UserHandle;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.DefaultUserBinder;
+import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
+import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = VERSION_CODES.O)
+public final class DefaultUserBinderTest {
+
+ private static final int OTHER_USER_ID = 5;
+
+ private final Application context = ApplicationProvider.getApplicationContext();
+ private final TestScheduledExecutorService scheduledExecutorService =
+ new TestScheduledExecutorService();
+ private final RobolectricTestUtilities utilities =
+ new RobolectricTestUtilities(context, scheduledExecutorService);
+
+ private final UserHandle otherUserHandle = utilities.createCustomUser(OTHER_USER_ID);
+ private final DefaultUserBinder binder = new DefaultUserBinder(otherUserHandle);
+
+ @Before
+ public void setUp() {
+ utilities.denyPermissions(
+ INTERACT_ACROSS_PROFILES, INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL);
+ }
+
+ @Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void hasPermissionToBind_isTargetUserProfile_hasInteractAcrossProfiles_true() {
+ utilities.tryAddTargetUserProfile(otherUserHandle);
+ utilities.setRequestsPermissions(INTERACT_ACROSS_PROFILES);
+ utilities.grantPermissions(INTERACT_ACROSS_PROFILES);
+
+ assertThat(binder.hasPermissionToBind(context)).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void hasPermissionToBind_isNotTargetUserProfile_hasInteractAcrossProfiles_false() {
+ utilities.tryRemoveTargetUserProfile(otherUserHandle);
+ utilities.setRequestsPermissions(INTERACT_ACROSS_PROFILES);
+ utilities.grantPermissions(INTERACT_ACROSS_PROFILES);
+
+ assertThat(binder.hasPermissionToBind(context)).isFalse();
+ }
+
+ @Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void hasPermissionToBind_isTargetUserProfile_noInteractAcrossProfiles_false() {
+ utilities.tryAddTargetUserProfile(otherUserHandle);
+ utilities.denyPermissions(INTERACT_ACROSS_PROFILES);
+
+ assertThat(binder.hasPermissionToBind(context)).isFalse();
+ }
+
+ @Test
+ public void hasPermissionToBind_interactAcrossUsersOnly_false() {
+ utilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
+ utilities.grantPermissions(INTERACT_ACROSS_USERS);
+
+ assertThat(binder.hasPermissionToBind(context)).isFalse();
+ }
+
+ @Test
+ public void hasPermissionToBind_interactAcrossUsersFull_true() {
+ utilities.setRequestsPermissions(INTERACT_ACROSS_USERS_FULL);
+ utilities.grantPermissions(INTERACT_ACROSS_USERS_FULL);
+
+ assertThat(binder.hasPermissionToBind(context)).isTrue();
+ }
+}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/IfAvailableTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/IfAvailableTest.java
index 6106125..ac5f21a 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/IfAvailableTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/IfAvailableTest.java
@@ -77,36 +77,20 @@
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
}
- @Test
- public void synchronous_notConnected_returnsDefaultValue() {
- testUtilities.disconnect();
-
- assertThat(
- profileTestCrossProfileType
- .other()
- .ifAvailable()
- .identityStringMethod(STRING1, /* defaultValue= */ STRING2))
- .isEqualTo(STRING2);
- }
+ // We cannot test synchronous calls when not connected as robolectric automatically connects
+ // if we add a connection holder
@Test
public void synchronous_connected_makesCall() throws Exception {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
assertThat(profileTestCrossProfileType.other().identityStringMethod(STRING1))
.isEqualTo(STRING1);
}
@Test
- public void synchronousVoid_notConnected_doesNotThrowException() {
- testUtilities.disconnect();
-
- profileTestCrossProfileType.other().ifAvailable().voidMethod();
- }
-
- @Test
public void synchronousVoid_connected_doesNotThrowException() {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.other().ifAvailable().voidMethod();
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ManualConnectionManagementTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ManualConnectionManagementTest.java
index bb1845e..8544934 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ManualConnectionManagementTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ManualConnectionManagementTest.java
@@ -63,7 +63,7 @@
public void connect_doesNotHavePermission_doesNotConnect() throws Exception {
testUtilities.denyPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.advanceTimeBySeconds(60);
assertThat(testProfileConnector.isConnected()).isFalse();
@@ -72,7 +72,7 @@
@Test
public void connect_getsPermissionAfterStartingConnecting_connects() throws Exception {
testUtilities.denyPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
testUtilities.advanceTimeBySeconds(5);
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/MessageSizeTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/MessageSizeTest.java
index 759d0c2..7fa793f 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/MessageSizeTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/MessageSizeTest.java
@@ -75,7 +75,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileAsyncTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileAsyncTest.java
index 92cd481..c7679cf 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileAsyncTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileAsyncTest.java
@@ -34,7 +34,6 @@
import com.google.android.enterprise.connectedapps.TestVoidCallbackListenerImpl;
import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileAnnotation;
import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
@@ -95,18 +94,19 @@
}
@Test
- public void other_async_callbackTriggeredMultipleTimes_isOnlyReceivedOnce() {
+ public void other_async_callbackTriggeredMultipleTimes_isReceivedTwice() {
profileTestCrossProfileType
.other()
- .asyncVoidMethodWhichCallsBackTwice(voidCallbackListener, exceptionCallbackListener);
+ .asyncIdentityStringMethodWhichCallsBackTwice(
+ STRING, stringCallbackListener, exceptionCallbackListener);
- assertThat(voidCallbackListener.callbackMethodCalls).isEqualTo(1);
+ assertThat(stringCallbackListener.callbackMethodCalls).isEqualTo(2);
}
@Test
public void
other_async_automaticConnection_workProfileIsTurnedOff_doesReceiveUnavailableProfileExceptionImmediately() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOffWorkProfile();
profileTestCrossProfileType
@@ -120,7 +120,7 @@
@Test
public void
other_async_automaticConnection_workProfileIsTurnedOn_doesNotSetUnavailableProfileException() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
profileTestCrossProfileType
@@ -134,7 +134,7 @@
@Test
public void other_async_automaticConnection_callsMethod() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
profileTestCrossProfileType
@@ -146,7 +146,7 @@
@Test
public void other_async_automaticConnection_resultIsSet() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
profileTestCrossProfileType
@@ -159,7 +159,7 @@
@Test
public void
other_async_automaticConnection_connectionIsDroppedDuringCall_setUnavailableProfileException() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
profileTestCrossProfileType
.other()
@@ -173,120 +173,12 @@
}
@Test
- public void other_async_timeoutSetOnMethod_doesNotTimeoutEarly() {
- profileTestCrossProfileType
- .other()
- .asyncMethodWhichNeverCallsBackWith5SecondTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- testUtilities.advanceTimeBySeconds(4);
-
- assertThat(exceptionCallbackListener.lastException).isNull();
- }
-
- @Test
- public void other_async_timeoutSetOnMethod_timesOut() {
- profileTestCrossProfileType
- .other()
- .asyncMethodWhichNeverCallsBackWith5SecondTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- testUtilities.advanceTimeBySeconds(6);
-
- assertThat(exceptionCallbackListener.lastException)
- .isInstanceOf(UnavailableProfileException.class);
- }
-
- @Test
- public void other_async_timeoutSetOnType_doesNotTimeoutEarly() {
- profileTestCrossProfileType
- .other()
- .asyncMethodWhichNeverCallsBackWith7SecondTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- testUtilities.advanceTimeBySeconds(6);
-
- assertThat(exceptionCallbackListener.lastException).isNull();
- }
-
- @Test
- public void other_async_timeoutSetOnType_timesOut() {
- profileTestCrossProfileType
- .other()
- .asyncMethodWhichNeverCallsBackWith7SecondTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- testUtilities.advanceTimeBySeconds(8);
-
- assertThat(exceptionCallbackListener.lastException)
- .isInstanceOf(UnavailableProfileException.class);
- }
-
- @Test
- public void other_async_timeoutSetByDefault_doesNotTimeoutEarly() throws Exception {
+ public void other_async_doesNotTimeOut() throws Exception {
profileTestCrossProfileTypeWhichNeedsContext
.other()
- .asyncMethodWhichNeverCallsBackWithDefaultTimeout(
- stringCallbackListener, exceptionCallbackListener);
+ .asyncMethodWhichNeverCallsBack(stringCallbackListener, exceptionCallbackListener);
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS - 1, TimeUnit.MILLISECONDS);
-
- assertThat(exceptionCallbackListener.lastException).isNull();
- }
-
- @Test
- public void other_async_timeoutSetByDefault_timesOut() throws Exception {
- profileTestCrossProfileTypeWhichNeedsContext
- .other()
- .asyncMethodWhichNeverCallsBackWithDefaultTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS + 1, TimeUnit.MILLISECONDS);
-
- assertThat(exceptionCallbackListener.lastException)
- .isInstanceOf(UnavailableProfileException.class);
- }
-
- @Test
- public void other_async_timeoutSetByCaller_doesNotTimeoutEarly() throws Exception {
- long timeoutMillis = 5000;
- profileTestCrossProfileTypeWhichNeedsContext
- .other()
- .timeout(timeoutMillis)
- .asyncMethodWhichNeverCallsBackWithDefaultTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- scheduledExecutorService.advanceTimeBy(timeoutMillis - 1, TimeUnit.MILLISECONDS);
-
- assertThat(exceptionCallbackListener.lastException).isNull();
- }
-
- @Test
- public void other_async_timeoutSetByCaller_timesOut() throws Exception {
- long timeoutMillis = 5000;
- profileTestCrossProfileTypeWhichNeedsContext
- .other()
- .timeout(timeoutMillis)
- .asyncMethodWhichNeverCallsBackWithDefaultTimeout(
- stringCallbackListener, exceptionCallbackListener);
-
- scheduledExecutorService.advanceTimeBy(timeoutMillis + 1, TimeUnit.MILLISECONDS);
-
- assertThat(exceptionCallbackListener.lastException)
- .isInstanceOf(UnavailableProfileException.class);
- }
-
- @Test
- public void other_async_doesNotTimeoutAfterCompletion() throws Exception {
- // We would expect an exception if the timeout continued after completion
- profileTestCrossProfileType
- .other()
- .asyncVoidMethod(voidCallbackListener, exceptionCallbackListener);
-
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS + 1, TimeUnit.MILLISECONDS);
+ scheduledExecutorService.advanceTimeBy(10, TimeUnit.MINUTES);
assertThat(exceptionCallbackListener.lastException).isNull();
}
@@ -294,7 +186,7 @@
@Test
public void other_async_throwsException_exceptionIsWrapped() {
// The exception is only catchable when the connection is already established.
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
try {
profileTestCrossProfileType
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileListenableFutureTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileListenableFutureTest.java
index 7d71ade..964126c 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileListenableFutureTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileListenableFutureTest.java
@@ -31,7 +31,6 @@
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
-import com.google.android.enterprise.connectedapps.processor.annotationdiscovery.interfaces.CrossProfileAnnotation;
import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
@@ -83,7 +82,7 @@
@Test
public void
other_listenableFuture_automaticConnection_workProfileIsTurnedOff_doesSetUnavailableProfileExceptionImmediately() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOffWorkProfile();
ListenableFuture<Void> future =
@@ -95,7 +94,7 @@
@Test
public void
other_listenableFuture_automaticConnection_workProfileIsTurnedOn_doesNotSetUnavailableProfileException() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
ListenableFuture<Void> future =
@@ -110,7 +109,7 @@
@Test
public void other_listenableFuture_automaticConnection_callsMethod()
throws ExecutionException, InterruptedException {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
profileTestCrossProfileType.other().listenableFutureVoidMethod().get();
@@ -121,7 +120,7 @@
@Test
public void other_listenableFuture_automaticConnection_setsFuture()
throws ExecutionException, InterruptedException {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
// This would throw an exception if it wasn't set
@@ -131,7 +130,7 @@
@Test
public void
other_listenableFuture_automaticConnection_connectionIsDroppedDuringCall_setUnavailableProfileException() {
- testProfileConnector.stopManualConnectionManagement();
+ testProfileConnector.clearConnectionHolders();
testUtilities.turnOnWorkProfile();
ListenableFuture<Void> future =
profileTestCrossProfileType.other().listenableFutureMethodWhichNeverSetsTheValue();
@@ -143,138 +142,21 @@
}
@Test
- public void other_listenableFuture_timeoutSetOnMethod_doesNotTimeoutEarly() throws Exception {
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileType
- .other()
- .listenableFutureMethodWhichNeverSetsTheValueWith5SecondTimeout();
-
- scheduledExecutorService.advanceTimeBy(4, TimeUnit.SECONDS);
-
- assertFutureDoesNotHaveException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_timeoutSetOnMethod_timesOut() throws Exception {
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileType
- .other()
- .listenableFutureMethodWhichNeverSetsTheValueWith5SecondTimeout();
-
- scheduledExecutorService.advanceTimeBy(6, TimeUnit.SECONDS);
-
- assertFutureHasException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_timeoutSetOnType_doesNotTimeoutEarly() throws Exception {
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileType
- .other()
- .listenableFutureMethodWhichNeverSetsTheValueWith7SecondTimeout();
-
- scheduledExecutorService.advanceTimeBy(6, TimeUnit.SECONDS);
-
- assertFutureDoesNotHaveException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_timeoutSetOnType_timesOut() throws Exception {
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileType
- .other()
- .listenableFutureMethodWhichNeverSetsTheValueWith7SecondTimeout();
-
- scheduledExecutorService.advanceTimeBy(8, TimeUnit.SECONDS);
-
- assertFutureHasException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_timeoutSetByDefault_doesNotTimeoutEarly() throws Exception {
+ public void other_listenableFuture_doesNotTimeout() throws Exception {
ListenableFuture<Void> listenableFuture =
profileTestCrossProfileTypeWhichNeedsContext
.other()
- .listenableFutureMethodWhichNeverSetsTheValueWithDefaultTimeout();
+ .listenableFutureMethodWhichNeverSetsTheValue();
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS - 1, TimeUnit.MILLISECONDS);
+ scheduledExecutorService.advanceTimeBy(10, TimeUnit.MINUTES);
assertFutureDoesNotHaveException(listenableFuture, UnavailableProfileException.class);
}
@Test
- public void other_listenableFuture_timeoutSetByDefault_timesOut() throws Exception {
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileTypeWhichNeedsContext
- .other()
- .listenableFutureMethodWhichNeverSetsTheValueWithDefaultTimeout();
-
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS + 1, TimeUnit.MILLISECONDS);
-
- assertFutureHasException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_timeoutSetByCaller_doesNotTimeoutEarly() throws Exception {
- long timeoutMillis = 5000;
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileTypeWhichNeedsContext
- .other()
- .timeout(timeoutMillis)
- .listenableFutureMethodWhichNeverSetsTheValueWithDefaultTimeout();
-
- scheduledExecutorService.advanceTimeBy(timeoutMillis - 1, TimeUnit.MILLISECONDS);
-
- assertFutureDoesNotHaveException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_timeoutSetByCaller_timesOut() throws Exception {
- long timeoutMillis = 5000;
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileTypeWhichNeedsContext
- .other()
- .timeout(timeoutMillis)
- .listenableFutureMethodWhichNeverSetsTheValueWithDefaultTimeout();
-
- scheduledExecutorService.advanceTimeBy(timeoutMillis + 1, TimeUnit.MILLISECONDS);
-
- assertFutureHasException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_doesNotTimeoutAfterCompletion() throws Exception {
- // We would expect an exception if the timeout continued after completion
- ListenableFuture<Void> listenableFuture =
- profileTestCrossProfileType.other().listenableFutureVoidMethod();
-
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS + 1, TimeUnit.MILLISECONDS);
-
- assertFutureDoesNotHaveException(listenableFuture, UnavailableProfileException.class);
- }
-
- @Test
- public void other_listenableFuture_doesNotTimeoutAfterException() throws Exception {
- // We would expect an exception if the timeout continued after completion
- ListenableFuture<Void> unusedFuture =
- profileTestCrossProfileType
- .other()
- .listenableFutureVoidMethodWhichSetsIllegalStateException();
-
- scheduledExecutorService.advanceTimeBy(
- CrossProfileAnnotation.DEFAULT_TIMEOUT_MILLIS + 1, TimeUnit.MILLISECONDS);
-
- // We expect there would be an exception thrown due to setting the future twice if it timed out
- // now
- }
-
- @Test
public void other_listenableFuture_throwsException_exceptionIsWrapped() {
// The exception is only catchable when the connection is already established.
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
try {
ListenableFuture<Void> unusedFuture =
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualAsyncTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualAsyncTest.java
index cac6278..fb494ed 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualAsyncTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualAsyncTest.java
@@ -82,7 +82,7 @@
@Test
public void other_async_manualConnection_isBound_callsMethod() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
@@ -94,7 +94,7 @@
@Test
public void other_async_manualConnection_isBound_firesCallback() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
@@ -106,7 +106,7 @@
@Test
public void other_async_manualConnection_isBound_unbundlesCorrectly() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
@@ -118,7 +118,7 @@
@Test // This behaviour is expected right now but will change
public void other_async_manualConnection_isBound_blockingMethod_blocks() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
@@ -131,7 +131,7 @@
@Test
public void other_async_manualConnection_isBound_nonBlockingMethod_doesNotBlock() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
@@ -144,7 +144,7 @@
@Test
public void other_async_manualConnection_isBound_nonBlockingMethod_doesCallback() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
@@ -182,7 +182,7 @@
public void
other_async_manualConnection_connectionIsDroppedDuringCall_setUnavailableProfileException() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType
.other()
.asyncMethodWhichNeverCallsBack(stringCallbackListener, exceptionCallbackListener);
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualListenableFutureTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualListenableFutureTest.java
index ae6aafb..5af23a2 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualListenableFutureTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileManualListenableFutureTest.java
@@ -74,7 +74,7 @@
@Test
public void
other_listenableFuture_manualConnection_workProfileIsTurnedOff_doesSetUnavailableProfileExceptionImmediately() {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
testUtilities.turnOffWorkProfile();
testUtilities.assertFutureHasException(
@@ -86,7 +86,7 @@
public void other_listenableFuture_manualConnection_isBound_callsMethod()
throws ExecutionException, InterruptedException {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
profileTestCrossProfileType.other().listenableFutureVoidMethod().get();
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileSynchronousTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileSynchronousTest.java
index 5b40c0f..18ff21d 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileSynchronousTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/OtherProfileSynchronousTest.java
@@ -18,6 +18,7 @@
import static com.google.android.enterprise.connectedapps.SharedTestUtilities.INTERACT_ACROSS_USERS;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.app.Application;
@@ -27,6 +28,7 @@
import androidx.test.core.app.ApplicationProvider;
import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
+import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
import com.google.android.enterprise.connectedapps.testapp.NotReallySerializableObject;
import com.google.android.enterprise.connectedapps.testapp.ParcelableObject;
@@ -75,13 +77,13 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
public void other_synchronous_isBound_callsMethod() throws UnavailableProfileException {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
assertThat(profileTestCrossProfileType.other().identityStringMethod(STRING)).isEqualTo(STRING);
}
@@ -106,19 +108,6 @@
}
@Test
- public void
- other_synchronous_isBound_automaticConnectionManagement_throwsUnavailableProfileException() {
- testUtilities.turnOnWorkProfile();
- testProfileConnector.stopManualConnectionManagement();
- ListenableFuture<Void> ignored =
- profileTestCrossProfileType.other().listenableFutureVoidMethod(); // Causes it to bind
-
- assertThrows(
- UnavailableProfileException.class,
- () -> profileTestCrossProfileType.other().voidMethod());
- }
-
- @Test
public void other_serializableObjectParameterIsNotReallySerializable_throwsException() {
assertThrows(
RuntimeException.class,
@@ -143,6 +132,18 @@
}
@Test
+ public void other_synchronous_declaresExceptionButThrowsRuntimeException_wrapsException() throws Exception {
+ try {
+ profileTestCrossProfileType
+ .other()
+ .methodWhichThrowsRuntimeExceptionAndDeclaresException();
+ fail();
+ } catch (ProfileRuntimeException expected) {
+ // Expected
+ }
+ }
+
+ @Test
public void other_synchronous_throwsException_works() {
assertThrows(
IOException.class,
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProfilesTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProfilesTest.java
index 4150353..1b945b7 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProfilesTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProfilesTest.java
@@ -71,13 +71,13 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
public void profiles_isBound_resultContainsAllProfileResults() {
testUtilities.turnOnWorkProfile();
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
Map<Profile, String> result =
profileTestCrossProfileType
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProviderTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProviderTest.java
index e66c09a..128116e 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProviderTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/ProviderTest.java
@@ -43,7 +43,7 @@
@Before
public void setup() {
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/StaticTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/StaticTest.java
index 5ad5534..0b1dce7 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/StaticTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/StaticTest.java
@@ -89,7 +89,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
@Test
@@ -113,7 +113,7 @@
@Test
public void staticCrossProfileMethod_fake_blocking_other_works() throws Exception {
fakeConnector.turnOnWorkProfile();
- fakeConnector.startConnecting();
+ fakeConnector.addConnectionHolder(this);
assertThat(fakeType.other().staticIdentityStringMethod(STRING)).isEqualTo(STRING);
}
@@ -126,7 +126,7 @@
@Test
public void staticCrossProfileMethod_fake_blocking_both_works() {
fakeConnector.turnOnWorkProfile();
- fakeConnector.startConnecting();
+ fakeConnector.addConnectionHolder(this);
Map<Profile, String> result = fakeType.both().staticIdentityStringMethod(STRING);
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/TypesTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/TypesTest.java
index ff5d66c..717a6e9 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/TypesTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/robotests/TypesTest.java
@@ -22,17 +22,23 @@
import android.app.Service;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Build.VERSION_CODES;
import android.os.IBinder;
+import android.os.Parcelable;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
import com.google.android.enterprise.connectedapps.TestCustomWrapperCallbackListenerImpl;
import com.google.android.enterprise.connectedapps.TestExceptionCallbackListener;
+import com.google.android.enterprise.connectedapps.TestParcelableCallbackListenerImpl;
import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
import com.google.android.enterprise.connectedapps.testapp.CustomWrapper;
import com.google.android.enterprise.connectedapps.testapp.CustomWrapper2;
+import com.google.android.enterprise.connectedapps.testapp.ParcelableContainingBinder;
import com.google.android.enterprise.connectedapps.testapp.ParcelableObject;
import com.google.android.enterprise.connectedapps.testapp.SerializableObject;
import com.google.android.enterprise.connectedapps.testapp.SimpleFuture;
@@ -40,12 +46,13 @@
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType;
-import com.google.android.enterprise.connectedapps.testapp.types.ProfileTestCrossProfileType_SingleSenderCanThrow;
import com.google.android.enterprise.connectedapps.testapp.types.TestCrossProfileType;
+import com.google.android.enterprise.connectedapps.testapp.types.TestCrossProfileType_SingleSenderCanThrow;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -68,20 +75,38 @@
private static final String STRING = "string";
private static final byte BYTE = 1;
+ private static final byte[] BYTE_ARRAY = new byte[]{BYTE};
+ private static final byte[][][] MULTIDIMENSIONAL_BYTE_ARRAY = new byte[][][] {{BYTE_ARRAY}};
private static final Byte BYTE_BOXED = 1;
private static final short SHORT = 1;
+ private static final short[] SHORT_ARRAY = new short[]{SHORT};
+ private static final short[][][] MULTIDIMENSIONAL_SHORT_ARRAY = new short[][][] {{SHORT_ARRAY}};
private static final Short SHORT_BOXED = 1;
private static final int INT = 1;
+ private static final int[] INT_ARRAY = new int[]{INT};
+ private static final int[][][] MULTIDIMENSIONAL_INT_ARRAY = new int[][][] {{INT_ARRAY}};
private static final Integer INTEGER = 1;
private static final long LONG = 1;
+ private static final long[] LONG_ARRAY = new long[]{LONG};
+ private static final long[][][] MULTIDIMENSIONAL_LONG_ARRAY = new long[][][] {{LONG_ARRAY}};
private static final Long LONG_BOXED = 1L;
private static final float FLOAT = 1;
+ private static final float[] FLOAT_ARRAY = new float[]{FLOAT};
+ private static final float[][][] MULTIDIMENSIONAL_FLOAT_ARRAY = new float[][][] {{FLOAT_ARRAY}};
private static final Float FLOAT_BOXED = 1f;
private static final double DOUBLE = 1;
+ private static final double[] DOUBLE_ARRAY = new double[]{DOUBLE};
+ private static final double[][][] MULTIDIMENSIONAL_DOUBLE_ARRAY =
+ new double[][][] {{DOUBLE_ARRAY}};
private static final Double DOUBLE_BOXED = 1d;
private static final char CHAR = 1;
+ private static final char[] CHAR_ARRAY = new char[]{CHAR};
+ private static final char[][][] MULTIDIMENSIONAL_CHAR_ARRAY = new char[][][] {{CHAR_ARRAY}};
private static final Character CHARACTER = 1;
private static final boolean BOOLEAN = true;
+ private static final boolean[] BOOLEAN_ARRAY = new boolean[]{BOOLEAN};
+ private static final boolean[][][] MULTIDIMENSIONAL_BOOLEAN_ARRAY =
+ new boolean[][][] {{BOOLEAN_ARRAY}};
private static final Boolean BOOLEAN_BOXED = true;
private static final ParcelableObject PARCELABLE = new ParcelableObject("test");
private static final SerializableObject SERIALIZABLE = new SerializableObject("test");
@@ -112,11 +137,16 @@
private static final StringWrapper STRING_WRAPPER = new StringWrapper(STRING);
private static final Optional<ParcelableObject> GUAVA_OPTIONAL = Optional.of(PARCELABLE);
private static final int[] BITMAP_PIXELS = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ private static final CharSequence CHAR_SEQUENCE = STRING;
private final Application context = ApplicationProvider.getApplicationContext();
// Android type can't be static due to Robolectric
private final Pair<String, Integer> pair = new Pair<>(STRING, INTEGER);
private final Bitmap bitmap = Bitmap.createBitmap(BITMAP_PIXELS, 3, 3, Bitmap.Config.ARGB_8888);
+ private final ParcelableContainingBinder parcelableContainingBinder =
+ new ParcelableContainingBinder();
+ private final Drawable drawable = new BitmapDrawable(context.getResources(), bitmap);
+
private final TestScheduledExecutorService scheduledExecutorService =
new TestScheduledExecutorService();
@@ -126,7 +156,7 @@
new RobolectricTestUtilities(testProfileConnector, scheduledExecutorService);
private interface SenderProvider {
- ProfileTestCrossProfileType_SingleSenderCanThrow provide(
+ TestCrossProfileType_SingleSenderCanThrow provide(
Context context, TestProfileConnector testProfileConnector);
}
@@ -135,7 +165,7 @@
SenderProvider currentProfileSenderProvider =
(Context context, TestProfileConnector testProfileConnector) ->
- (ProfileTestCrossProfileType_SingleSenderCanThrow)
+ (TestCrossProfileType_SingleSenderCanThrow)
ProfileTestCrossProfileType.create(testProfileConnector).current();
SenderProvider otherProfileSenderProvider =
(Context context, TestProfileConnector testProfileConnector) ->
@@ -159,7 +189,7 @@
testUtilities.setRunningOnPersonalProfile();
testUtilities.setRequestsPermissions(INTERACT_ACROSS_USERS);
testUtilities.grantPermissions(INTERACT_ACROSS_USERS);
- testUtilities.startConnectingAndWait();
+ testUtilities.addDefaultConnectionHolderAndWait();
}
private SenderProvider senderProvider;
@@ -399,7 +429,7 @@
@Test
public void collectionOfArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
- ProfileTestCrossProfileType_SingleSenderCanThrow sender =
+ TestCrossProfileType_SingleSenderCanThrow sender =
senderProvider.provide(context, testProfileConnector);
List<String[]> originalAsList = new ArrayList<>(collectionOfStringArray);
@@ -436,7 +466,7 @@
@Test
public void collectionOfParcelableArrayReturnTypeAndArgument_bothWork()
throws UnavailableProfileException {
- ProfileTestCrossProfileType_SingleSenderCanThrow sender =
+ TestCrossProfileType_SingleSenderCanThrow sender =
senderProvider.provide(context, testProfileConnector);
List<ParcelableObject[]> originalAsList = new ArrayList<>(collectionOfParcelableArray);
@@ -453,7 +483,7 @@
@Test
public void collectionOfSerializableArrayReturnTypeAndArgument_bothWork()
throws UnavailableProfileException {
- ProfileTestCrossProfileType_SingleSenderCanThrow sender =
+ TestCrossProfileType_SingleSenderCanThrow sender =
senderProvider.provide(context, testProfileConnector);
List<SerializableObject[]> originalAsList = new ArrayList<>(collectionOfSerializableArray);
@@ -627,12 +657,6 @@
assertThat(getBitmapPixels(returnBitmap)).isEqualTo(BITMAP_PIXELS);
}
- @Test
- public void nullBitmap_works() throws UnavailableProfileException {
- assertThat(senderProvider.provide(context, testProfileConnector).identityBitmapMethod(null))
- .isNull();
- }
-
private static int[] getBitmapPixels(Bitmap bitmap) {
int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
@@ -640,8 +664,212 @@
}
@Test
+ public void nullBitmap_works() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityBitmapMethod(null))
+ .isNull();
+ }
+
+ @Test
public void contextArgument_works() throws UnavailableProfileException {
assertThat(senderProvider.provide(context, testProfileConnector).isContextArgumentPassed())
.isTrue();
}
+
+ @Test
+ public void parcelableArgumentAndReturnType_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector)
+ .identityParcelableMethod((Parcelable) PARCELABLE)).isEqualTo(PARCELABLE);
+ }
+
+ @Test
+ public void parcelableContainingBinderArgumentAndReturnType_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityParcelableMethod(parcelableContainingBinder))
+ .isEqualTo(parcelableContainingBinder);
+ }
+
+ @Test
+ public void parcelableContainingBinderAsyncMethod_works() {
+ TestParcelableCallbackListenerImpl callbackListener = new TestParcelableCallbackListenerImpl();
+ TestExceptionCallbackListener exceptionListener = new TestExceptionCallbackListener();
+
+ senderProvider
+ .provide(context, testProfileConnector)
+ .asyncIdentityParcelableMethod(
+ parcelableContainingBinder, callbackListener, exceptionListener);
+
+ assertThat(callbackListener.parcelableCallbackValue).isEqualTo(parcelableContainingBinder);
+ }
+
+ @Test
+ public void futureParcelableContainingBinder_works() throws Exception {
+ ListenableFuture<Parcelable> future =
+ senderProvider
+ .provide(context, testProfileConnector)
+ .futureIdentityParcelableMethod(parcelableContainingBinder);
+
+ assertThat(future.get()).isEqualTo(parcelableContainingBinder);
+ }
+
+ @Test
+ public void charSequenceReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector)
+ .identityCharSequenceMethod(CHAR_SEQUENCE).toString())
+ .isEqualTo(CHAR_SEQUENCE.toString());
+ }
+
+ @Test
+ public void floatArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityFloatArrayMethod(FLOAT_ARRAY))
+ .isEqualTo(FLOAT_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalFloatArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalFloatArrayMethod(MULTIDIMENSIONAL_FLOAT_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_FLOAT_ARRAY);
+ }
+
+ @Test
+ public void byteArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityByteArrayMethod(BYTE_ARRAY))
+ .isEqualTo(BYTE_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalByteArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalByteArrayMethod(MULTIDIMENSIONAL_BYTE_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_BYTE_ARRAY);
+ }
+
+ @Test
+ public void shortArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityShortArrayMethod(SHORT_ARRAY))
+ .isEqualTo(SHORT_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalShortArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalShortArrayMethod(MULTIDIMENSIONAL_SHORT_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_SHORT_ARRAY);
+ }
+
+ @Test
+ public void intArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityIntArrayMethod(INT_ARRAY))
+ .isEqualTo(INT_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalIntArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalIntArrayMethod(MULTIDIMENSIONAL_INT_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_INT_ARRAY);
+ }
+
+ @Test
+ public void longArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityLongArrayMethod(LONG_ARRAY))
+ .isEqualTo(LONG_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalLongArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalLongArrayMethod(MULTIDIMENSIONAL_LONG_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_LONG_ARRAY);
+ }
+
+ @Test
+ public void doubleArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityDoubleArrayMethod(DOUBLE_ARRAY))
+ .isEqualTo(DOUBLE_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalDoubleArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalDoubleArrayMethod(MULTIDIMENSIONAL_DOUBLE_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_DOUBLE_ARRAY);
+ }
+
+ @Test
+ public void charArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityCharArrayMethod(CHAR_ARRAY))
+ .isEqualTo(CHAR_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalCharArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalCharArrayMethod(MULTIDIMENSIONAL_CHAR_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_CHAR_ARRAY);
+ }
+
+ @Test
+ public void booleanArrayReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ assertThat(senderProvider.provide(context, testProfileConnector).identityBooleanArrayMethod(BOOLEAN_ARRAY))
+ .isEqualTo(BOOLEAN_ARRAY);
+ }
+
+ @Test
+ public void multidimensionalBooleanArrayReturnTypeAndArgument_bothWork()
+ throws UnavailableProfileException {
+ assertThat(
+ senderProvider
+ .provide(context, testProfileConnector)
+ .identityMultidimensionalBooleanArrayMethod(MULTIDIMENSIONAL_BOOLEAN_ARRAY))
+ .isEqualTo(MULTIDIMENSIONAL_BOOLEAN_ARRAY);
+ }
+
+ @Test
+ public void drawableReturnTypeAndArgument_bothWork() throws UnavailableProfileException {
+ Drawable returnDrawable =
+ senderProvider.provide(context, testProfileConnector).identityDrawableMethod(drawable);
+
+ assertThat(returnDrawable.getIntrinsicHeight()).isEqualTo(drawable.getIntrinsicHeight());
+ assertThat(returnDrawable.getIntrinsicWidth()).isEqualTo(drawable.getIntrinsicWidth());
+ assertThat(getDrawablePixels(returnDrawable)).isEqualTo(BITMAP_PIXELS);
+ }
+
+ private static int[] getDrawablePixels(Drawable drawable) {
+ Bitmap bitmap = Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ drawable.draw(new Canvas(bitmap));
+
+ int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
+ bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+ return pixels;
+ }
}
+
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnectorTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnectorTest.java
index f42d89b..cbdf717 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnectorTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeProfileConnectorTest.java
@@ -22,10 +22,15 @@
import android.os.Build.VERSION_CODES;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.enterprise.connectedapps.ConnectedAppsUtils;
+import com.google.android.enterprise.connectedapps.ProfileConnectionHolder;
import com.google.android.enterprise.connectedapps.TestAvailabilityListener;
import com.google.android.enterprise.connectedapps.TestConnectionListener;
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -45,43 +50,101 @@
new FakeProfileConnector(context, /* primaryProfileType= */ ProfileType.NONE);
private final TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
private final TestConnectionListener connectionListener = new TestConnectionListener();
+ private final Object connectionHolder = new Object();
+
+ @After
+ public void teardown() {
+ fakeProfileConnector.clearConnectionHolders();
+ }
@Test
- public void startConnecting_connectionIsAvailable_isConnected() {
+ public void addConnectionHolder_connectionIsAvailable_isConnected() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.startConnecting();
+ fakeProfileConnector.addConnectionHolder(this);
assertThat(fakeProfileConnector.isConnected()).isTrue();
}
@Test
- public void startConnecting_connectionIsAvailable_notifiesConnectionChanged() {
- fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.registerConnectionListener(connectionListener);
+ public void addConnectionHolder_connectionIsNotAvailable_doesNotConnect() {
+ fakeProfileConnector.turnOffWorkProfile();
+ fakeProfileConnector.addConnectionListener(connectionListener);
- fakeProfileConnector.startConnecting();
+ fakeProfileConnector.addConnectionHolder(this);
+
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void addConnectionHolder_doesNotHavePermission_doesNotConnect() {
+ fakeProfileConnector.setHasPermissionToMakeCrossProfileCalls(false);
+ fakeProfileConnector.addConnectionListener(connectionListener);
+
+ fakeProfileConnector.addConnectionHolder(this);
+
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void addConnectionHolder_notifiesConnectionChanged() {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.addConnectionListener(connectionListener);
+
+ fakeProfileConnector.addConnectionHolder(this);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
}
@Test
- public void startConnecting_unregisteredConnectionListener_doesNotNotifyConnectionChanged() {
+ public void addConnectionHolder_doesNotConnectIfConnectionHandlerReturnsFalse() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.registerConnectionListener(connectionListener);
- fakeProfileConnector.unregisterConnectionListener(connectionListener);
+ fakeProfileConnector.setConnectionHandler(() -> false);
+ fakeProfileConnector.addConnectionListener(connectionListener);
- fakeProfileConnector.startConnecting();
+ fakeProfileConnector.addConnectionHolder(this);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void addConnectionHolder_usesPassedExecutorForAutomaticConnection() {
+ fakeProfileConnector.turnOnWorkProfile();
+ QueueingExecutor fakeExecutor = new QueueingExecutor();
+ fakeProfileConnector.setExecutor(fakeExecutor);
+ fakeProfileConnector.addConnectionListener(connectionListener);
+
+ fakeProfileConnector.addConnectionHolder(this);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
+
+ fakeExecutor.runNext();
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
+ assertThat(fakeProfileConnector.isConnected()).isTrue();
+
+ assertThat(fakeExecutor.isQueueEmpty()).isTrue();
+ }
+
+ @Test
+ public void addConnectionHolder_unregisteredConnectionListener_doesNotNotifyConnectionChanged() {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.addConnectionListener(connectionListener);
+ fakeProfileConnector.removeConnectionListener(connectionListener);
+
+ fakeProfileConnector.addConnectionHolder(this);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
}
@Test
- public void startConnecting_connectionIsNotAvailable_doesNotNotifyOfConnectionChanged() {
+ public void addConnectionHolder_connectionIsNotAvailable_doesNotNotifyOfConnectionChanged() {
fakeProfileConnector.removeWorkProfile();
- fakeProfileConnector.registerConnectionListener(connectionListener);
+ fakeProfileConnector.addConnectionListener(connectionListener);
- fakeProfileConnector.startConnecting();
+ fakeProfileConnector.addConnectionHolder(this);
assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
}
@@ -98,7 +161,7 @@
@Test
public void connect_connectionIsAvailable_notifiesConnectionChanged() throws Exception {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.registerConnectionListener(connectionListener);
+ fakeProfileConnector.addConnectionListener(connectionListener);
fakeProfileConnector.connect();
@@ -109,8 +172,8 @@
public void connect_unregisteredConnectionListener_doesNotNotifyConnectionChanged()
throws Exception {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.registerConnectionListener(connectionListener);
- fakeProfileConnector.unregisterConnectionListener(connectionListener);
+ fakeProfileConnector.addConnectionListener(connectionListener);
+ fakeProfileConnector.removeConnectionListener(connectionListener);
fakeProfileConnector.connect();
@@ -120,14 +183,45 @@
@Test
public void connect_connectionIsNotAvailable_throwsUnavailableProfileException() {
fakeProfileConnector.removeWorkProfile();
- fakeProfileConnector.registerConnectionListener(connectionListener);
+ fakeProfileConnector.addConnectionListener(connectionListener);
assertThrows(UnavailableProfileException.class, fakeProfileConnector::connect);
}
@Test
+ public void connect_doesNotHavePermission_throwsUnavailableProfileException() {
+ fakeProfileConnector.setHasPermissionToMakeCrossProfileCalls(false);
+ fakeProfileConnector.addConnectionListener(connectionListener);
+
+ assertThrows(UnavailableProfileException.class, fakeProfileConnector::connect);
+ }
+
+ @Test
+ public void connect_doesNotConnectIfHandlerReturnsFalse() throws Exception {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.setConnectionHandler(() -> false);
+ fakeProfileConnector.addConnectionListener(connectionListener);
+
+ fakeProfileConnector.connect();
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void connect_doesNotUsePassedExecutor() throws Exception {
+ fakeProfileConnector.turnOnWorkProfile();
+ // Noop executor which we expect won't be used.
+ fakeProfileConnector.setExecutor(task -> {});
+ fakeProfileConnector.addConnectionListener(connectionListener);
+
+ fakeProfileConnector.connect();
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
+ }
+
+ @Test
public void turnOnWorkProfile_workProfileWasOff_notifiesAvailabilityChange() {
- fakeProfileConnector.registerAvailabilityListener(availabilityListener);
+ fakeProfileConnector.addAvailabilityListener(availabilityListener);
fakeProfileConnector.turnOnWorkProfile();
@@ -137,7 +231,7 @@
@Test
public void turnOnWorkProfile_workProfileWasOn_doesNotNotifyAvailabilityChange() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.registerAvailabilityListener(availabilityListener);
+ fakeProfileConnector.addAvailabilityListener(availabilityListener);
fakeProfileConnector.turnOnWorkProfile();
@@ -147,7 +241,7 @@
@Test
public void turnOffWorkProfile_workProfileWasOn_notifiesAvailabilityChange() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.registerAvailabilityListener(availabilityListener);
+ fakeProfileConnector.addAvailabilityListener(availabilityListener);
fakeProfileConnector.turnOffWorkProfile();
@@ -157,7 +251,7 @@
@Test
public void turnOffWorkProfile_workProfileWasOff_doesNotNotifyAvailabilityChange() {
fakeProfileConnector.turnOffWorkProfile();
- fakeProfileConnector.registerAvailabilityListener(availabilityListener);
+ fakeProfileConnector.addAvailabilityListener(availabilityListener);
fakeProfileConnector.turnOffWorkProfile();
@@ -168,7 +262,7 @@
public void turnOffWorkProfile_wasConnected_notifiesConnectionChange() throws Exception {
fakeProfileConnector.turnOnWorkProfile();
fakeProfileConnector.connect();
- fakeProfileConnector.registerConnectionListener(connectionListener);
+ fakeProfileConnector.addConnectionListener(connectionListener);
fakeProfileConnector.turnOffWorkProfile();
@@ -357,38 +451,28 @@
}
@Test
- public void isManuallyManagingConnection_returnsFalse() {
- assertThat(fakeProfileConnector.isManuallyManagingConnection()).isFalse();
- }
-
- @Test
- public void isManuallyManagingConnection_hasStartedManuallyConnecting_returnsTrue() {
- fakeProfileConnector.startConnecting();
-
- assertThat(fakeProfileConnector.isManuallyManagingConnection()).isTrue();
- }
-
- @Test
- public void isManuallyManagingConnection_hasManuallyConnected_returnsTrue() throws Exception {
+ public void timeoutConnection_hasNoConnectionHolders_disconnects() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.connect();
+ fakeProfileConnector.clearConnectionHolders();
- assertThat(fakeProfileConnector.isManuallyManagingConnection()).isTrue();
+ fakeProfileConnector.timeoutConnection();
+
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
}
@Test
- public void isManuallyManagingConnection_hasCalledStopManualConnectionManagement_returnsFalse() {
- fakeProfileConnector.startConnecting();
-
- fakeProfileConnector.stopManualConnectionManagement();
-
- assertThat(fakeProfileConnector.isManuallyManagingConnection()).isFalse();
- }
-
- @Test
- public void timeoutConnection_isManuallyManagingConnection_doesNotDisconnect() throws Exception {
+ public void addConnectionHolder_connects() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.connect();
+
+ fakeProfileConnector.addConnectionHolder(this);
+
+ assertThat(fakeProfileConnector.isConnected()).isTrue();
+ }
+
+ @Test
+ public void timeoutConnection_hasConnectionHolder_doesNotDisconnect() {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.addConnectionHolder(this);
fakeProfileConnector.timeoutConnection();
@@ -396,12 +480,76 @@
}
@Test
- public void timeoutConnection_isNotManuallyManagingConnection_disconnects() {
+ public void removeConnectionHolder_lastConnectionHolder_doesNotDisconnect() {
fakeProfileConnector.turnOnWorkProfile();
- fakeProfileConnector.stopManualConnectionManagement();
+ fakeProfileConnector.addConnectionHolder(this);
+
+ fakeProfileConnector.removeConnectionHolder(this);
+
+ assertThat(fakeProfileConnector.isConnected()).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_timeout_disconnects() {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.addConnectionHolder(this);
+ fakeProfileConnector.removeConnectionHolder(this);
fakeProfileConnector.timeoutConnection();
assertThat(fakeProfileConnector.isConnected()).isFalse();
}
+
+ @Test
+ public void removeConnectionHolder_stillAnotherConnectionHolder_timeout_doesNotDisconnect() {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.addConnectionHolder(this);
+ fakeProfileConnector.addConnectionHolder(connectionHolder);
+ fakeProfileConnector.removeConnectionHolder(this);
+
+ fakeProfileConnector.timeoutConnection();
+
+ assertThat(fakeProfileConnector.isConnected()).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingAlias_timeout_disconnects() {
+ fakeProfileConnector.turnOnWorkProfile();
+ fakeProfileConnector.addConnectionHolder(this);
+ fakeProfileConnector.addConnectionHolderAlias(connectionHolder, this);
+ fakeProfileConnector.removeConnectionHolder(connectionHolder);
+
+ fakeProfileConnector.timeoutConnection();
+
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingWrapper_timeout_disconnects() {
+ fakeProfileConnector.turnOnWorkProfile();
+ ProfileConnectionHolder connectionHolder = fakeProfileConnector.addConnectionHolder(this);
+ fakeProfileConnector.removeConnectionHolder(connectionHolder);
+
+ fakeProfileConnector.timeoutConnection();
+
+ assertThat(fakeProfileConnector.isConnected()).isFalse();
+ }
+}
+
+class QueueingExecutor implements Executor {
+
+ private final Queue<Runnable> commands = new ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable command) {
+ commands.add(command);
+ }
+
+ public void runNext() {
+ commands.remove().run();
+ }
+
+ public boolean isQueueEmpty() {
+ return commands.isEmpty();
+ }
}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeUserConnectorTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeUserConnectorTest.java
new file mode 100644
index 0000000..413d00f
--- /dev/null
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/AbstractFakeUserConnectorTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.os.UserHandle;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.enterprise.connectedapps.RobolectricTestUtilities;
+import com.google.android.enterprise.connectedapps.TestAvailabilityListener;
+import com.google.android.enterprise.connectedapps.TestConnectionListener;
+import com.google.android.enterprise.connectedapps.TestScheduledExecutorService;
+import com.google.android.enterprise.connectedapps.UserConnectionHolder;
+import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = VERSION_CODES.O)
+public class AbstractFakeUserConnectorTest {
+ static class FakeUserConnector extends AbstractFakeUserConnector {
+ FakeUserConnector(Context context) {
+ super(context);
+ }
+ }
+
+ private static final int CURRENT_USER_ID = 4;
+ private static final int TARGET_USER_ID = 5;
+
+ private final Application context = ApplicationProvider.getApplicationContext();
+ private final TestScheduledExecutorService scheduledExecutorService =
+ new TestScheduledExecutorService();
+ private final RobolectricTestUtilities utilities =
+ new RobolectricTestUtilities(context, scheduledExecutorService);
+ private final UserHandle currentUser = utilities.createCustomUser(CURRENT_USER_ID);
+ private final UserHandle targetUser = utilities.createCustomUser(TARGET_USER_ID);
+ private final FakeUserConnector fakeUserConnector = new FakeUserConnector(context);
+ private final TestAvailabilityListener availabilityListener = new TestAvailabilityListener();
+ private final TestConnectionListener connectionListener = new TestConnectionListener();
+ private final Object connectionHolder = new Object();
+
+ @Test
+ public void addConnectionHolder_connectionIsAvailable_isConnected() {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void addConnectionHolder_connectionIsAvailable_notifiesConnectionChanged() {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void addConnectionHolder_unregisteredConnectionListener_doesNotNotifyConnectionChanged() {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+ fakeUserConnector.removeConnectionListener(targetUser, connectionListener);
+
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void addConnectionHolder_connectionIsNotAvailable_doesNotNotifyOfConnectionChanged() {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOffUser(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void connect_connectionIsAvailable_isConnected() throws Exception {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+
+ fakeUserConnector.connect(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void connect_connectionIsAvailable_notifiesConnectionChanged() throws Exception {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+
+ fakeUserConnector.connect(targetUser);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void connect_unregisteredConnectionListener_doesNotNotifyConnectionChanged()
+ throws Exception {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+ fakeUserConnector.removeConnectionListener(targetUser, connectionListener);
+
+ fakeUserConnector.connect(targetUser);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void connect_connectionIsNotAvailable_throwsUnavailableProfileException() {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOffUser(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+
+ assertThrows(UnavailableProfileException.class, () -> fakeUserConnector.connect(targetUser));
+ }
+
+ @Test
+ public void turnOnTargetUser_targetUserWasOff_notifiesAvailabilityChange() {
+ fakeUserConnector.turnOffUser(targetUser);
+ fakeUserConnector.addAvailabilityListener(targetUser, availabilityListener);
+
+ fakeUserConnector.turnOnUser(targetUser);
+
+ assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void turnOnTargetUser_targetUserWasOn_doesNotNotifyAvailabilityChange() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addAvailabilityListener(targetUser, availabilityListener);
+
+ fakeUserConnector.turnOnUser(targetUser);
+
+ assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void turnOffTargetUser_targetUserWasOn_notifiesAvailabilityChange() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addAvailabilityListener(targetUser, availabilityListener);
+
+ fakeUserConnector.turnOffUser(targetUser);
+
+ assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void turnOffTargetUser_targetUserWasOff_doesNotNotifyAvailabilityChange() {
+ fakeUserConnector.turnOffUser(targetUser);
+ fakeUserConnector.addAvailabilityListener(targetUser, availabilityListener);
+
+ fakeUserConnector.turnOffUser(targetUser);
+
+ assertThat(availabilityListener.availabilityChangedCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void turnOffTargetUser_wasConnected_notifiesConnectionChange() throws Exception {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.connect(targetUser);
+ fakeUserConnector.addConnectionListener(targetUser, connectionListener);
+
+ fakeUserConnector.turnOffUser(targetUser);
+
+ assertThat(connectionListener.connectionChangedCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void setRunningOnUser_setsRunningOnUser() {
+ fakeUserConnector.setRunningOnUser(targetUser);
+
+ assertThat(fakeUserConnector.runningOnUser()).isEqualTo(targetUser);
+ }
+
+ @Test
+ public void setRunningOnTargetUser_startsTargetUser() {
+ fakeUserConnector.setRunningOnUser(targetUser);
+ fakeUserConnector.setRunningOnUser(currentUser);
+
+ assertThat(fakeUserConnector.isAvailable(targetUser)).isTrue();
+ }
+
+ @Test
+ public void removeTargetUser_targetUserBecomesUnavailable() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.removeUser(targetUser);
+
+ assertThat(fakeUserConnector.isAvailable(targetUser)).isFalse();
+ }
+
+ @Test
+ public void isConnected_isConnected_returnsTrue() throws Exception {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+
+ fakeUserConnector.connect(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void isConnected_isNotConnected_returnsFalse() {
+ fakeUserConnector.setRunningOnUser(currentUser);
+ fakeUserConnector.turnOnUser(targetUser);
+
+ fakeUserConnector.disconnect(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isFalse();
+ }
+
+ @Test
+ public void canMakeCrossProfileCalls_defaultsToTrue() {
+ assertThat(fakeUserConnector.permissions(targetUser).canMakeCrossProfileCalls()).isTrue();
+ }
+
+ @Test
+ public void canMakeCrossProfileCalls_setToFalse_returnsFalse() {
+ fakeUserConnector.setHasPermissionToMakeCrossProfileCalls(targetUser, false);
+
+ assertThat(fakeUserConnector.permissions(targetUser).canMakeCrossProfileCalls()).isFalse();
+ }
+
+ @Test
+ public void canMakeCrossProfileCalls_setToTrue_returnsTrue() {
+ fakeUserConnector.setHasPermissionToMakeCrossProfileCalls(targetUser, false);
+ fakeUserConnector.setHasPermissionToMakeCrossProfileCalls(targetUser, true);
+
+ assertThat(fakeUserConnector.permissions(targetUser).canMakeCrossProfileCalls()).isTrue();
+ }
+
+ @Test
+ public void timeoutConnection_hasNoConnectionHolders_disconnects() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.clearConnectionHolders(targetUser);
+
+ fakeUserConnector.timeoutConnection(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isFalse();
+ }
+
+ @Test
+ public void addConnectionHolder_connects() {
+ fakeUserConnector.turnOnUser(targetUser);
+
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void timeoutConnection_hasConnectionHolder_doesNotDisconnect() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ fakeUserConnector.timeoutConnection(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_lastConnectionHolder_doesNotDisconnect() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+
+ fakeUserConnector.removeConnectionHolder(targetUser, this);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_timeout_disconnects() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+ fakeUserConnector.removeConnectionHolder(targetUser, this);
+
+ fakeUserConnector.timeoutConnection(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isFalse();
+ }
+
+ @Test
+ public void removeConnectionHolder_stillAnotherConnectionHolder_timeout_doesNotDisconnect() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+ fakeUserConnector.addConnectionHolder(targetUser, connectionHolder);
+ fakeUserConnector.removeConnectionHolder(targetUser, this);
+
+ fakeUserConnector.timeoutConnection(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isTrue();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingAlias_timeout_disconnects() {
+ fakeUserConnector.turnOnUser(targetUser);
+ fakeUserConnector.addConnectionHolder(targetUser, this);
+ fakeUserConnector.addConnectionHolderAlias(targetUser, connectionHolder, this);
+ fakeUserConnector.removeConnectionHolder(targetUser, connectionHolder);
+
+ fakeUserConnector.timeoutConnection(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isFalse();
+ }
+
+ @Test
+ public void removeConnectionHolder_removingWrapper_timeout_disconnects() {
+ fakeUserConnector.turnOnUser(targetUser);
+ UserConnectionHolder connectionHolder = fakeUserConnector.addConnectionHolder(targetUser, this);
+ fakeUserConnector.removeConnectionHolder(targetUser, connectionHolder);
+
+ fakeUserConnector.timeoutConnection(targetUser);
+
+ assertThat(fakeUserConnector.isConnected(targetUser)).isFalse();
+ }
+}
diff --git a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfileTypeTest.java b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfileTypeTest.java
index 3599dd6..821cb63 100644
--- a/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfileTypeTest.java
+++ b/tests/robotests/src/test/java/com/google/android/enterprise/connectedapps/testing/FakeCrossProfileTypeTest.java
@@ -34,6 +34,7 @@
import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
+import com.google.android.enterprise.connectedapps.testapp.CustomError;
import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
import com.google.android.enterprise.connectedapps.testapp.configuration.TestApplication;
import com.google.android.enterprise.connectedapps.testapp.connector.FakeTestProfileConnector;
@@ -45,6 +46,7 @@
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ExecutionException;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,8 +91,13 @@
public void setUp() {
connector.setRunningOnProfile(ProfileType.PERSONAL);
connector.turnOnWorkProfile();
- connector.startConnecting();
- connector.registerConnectionListener(connectionListener);
+ connector.addConnectionHolder(this);
+ connector.addConnectionListener(connectionListener);
+ }
+
+ @After
+ public void teardown() {
+ connector.clearConnectionHolders();
}
@Test
@@ -290,9 +297,9 @@
}
@Test
- public void blockingCall_notManuallyManagingConnection_throwsUnavailableProfileException()
+ public void blockingCall_noConnectionHolders_throwsUnavailableProfileException()
throws Exception {
- connector.stopManualConnectionManagement();
+ connector.clearConnectionHolders();
connector.turnOnWorkProfile();
fakeCrossProfileType.other().listenableFutureVoidMethod().get(); // Force connection
@@ -324,17 +331,6 @@
}
@Test
- public void asyncCall_notConnected_doesNotStartManualConnectionManagement() {
- connector.turnOnWorkProfile();
- connector.stopManualConnectionManagement();
- connector.disconnect();
-
- fakeCrossProfileType.work().asyncVoidMethod(voidCallbackListener, exceptionCallbackListener);
-
- assertThat(connector.isManuallyManagingConnection()).isFalse();
- }
-
- @Test
public void asyncCall_workProfileUnavailable_callsWithUnavailableProfileException() {
connector.removeWorkProfile();
@@ -369,17 +365,6 @@
}
@Test
- public void futureCall_notConnected_doesNotStartManualConnectionManagement() {
- connector.turnOnWorkProfile();
- connector.stopManualConnectionManagement();
- connector.disconnect();
-
- ListenableFuture<Void> unusedFuture = fakeCrossProfileType.work().listenableFutureVoidMethod();
-
- assertThat(connector.isManuallyManagingConnection()).isFalse();
- }
-
- @Test
public void futureCall_workProfileUnavailable_setsUnavailableProfileException() {
connector.removeWorkProfile();
@@ -438,6 +423,11 @@
}
@Test
+ public void current_synchronous_throwsError_errorIsThrown() {
+ assertThrows(CustomError.class, () -> fakeCrossProfileType.current().methodWhichThrowsError());
+ }
+
+ @Test
public void other_synchronous_throwsRuntimeException_exceptionIsWrapped()
throws UnavailableProfileException {
try {
@@ -449,6 +439,17 @@
}
@Test
+ public void other_synchronous_throwsError_exceptionIsWrapped()
+ throws UnavailableProfileException {
+ try {
+ fakeCrossProfileType.other().methodWhichThrowsError();
+ fail();
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void current_async_throwsRuntimeException_runtimeExceptionIsThrown() {
assertThrows(
CustomRuntimeException.class,
@@ -460,6 +461,14 @@
}
@Test
+ public void current_async_throwsError_errorIsThrown() {
+ assertThrows(
+ CustomError.class,
+ () ->
+ fakeCrossProfileType.current().asyncStringMethodWhichThrowsError(/* callback= */ null));
+ }
+
+ @Test
public void other_async_throwsRuntimeException_exceptionIsWrapped() {
try {
fakeCrossProfileType
@@ -473,6 +482,19 @@
}
@Test
+ public void other_async_throwsError_errorIsWrapped() {
+ try {
+ fakeCrossProfileType
+ .other()
+ .asyncStringMethodWhichThrowsError(
+ /* callback= */ null, /* exceptionCallback= */ null);
+ fail();
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void current_future_throwsRuntimeException_runtimeExceptionIsThrown() {
assertThrows(
CustomRuntimeException.class,
@@ -482,6 +504,13 @@
}
@Test
+ public void current_future_throwsError_errorIsThrown() {
+ assertThrows(
+ CustomError.class,
+ () -> fakeCrossProfileType.current().listenableFutureVoidMethodWhichThrowsError());
+ }
+
+ @Test
public void other_future_throwsRuntimeException_exceptionIsWrapped() {
try {
fakeCrossProfileType.other().listenableFutureVoidMethodWhichThrowsRuntimeException();
@@ -492,6 +521,16 @@
}
@Test
+ public void other_future_throwsError_errorIsWrapped() {
+ try {
+ fakeCrossProfileType.other().listenableFutureVoidMethodWhichThrowsError();
+ fail();
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void both_synchronous_throwsRuntimeException_exceptionIsThrown() {
// Which one is thrown when both throw exceptions is not specified
try {
@@ -505,6 +544,19 @@
}
@Test
+ public void both_synchronous_throwsError_errorIsThrown() {
+ // Which one is thrown when both throw exceptions is not specified
+ try {
+ fakeCrossProfileType.both().methodWhichThrowsError();
+ fail();
+ } catch (CustomError expected) {
+
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void both_async_throwsRuntimeException_exceptionIsThrown() {
// Which one is thrown when both throw exceptions is not specified
try {
@@ -520,6 +572,21 @@
}
@Test
+ public void both_async_throwsError_errorIsThrown() {
+ // Which one is thrown when both throw exceptions is not specified
+ try {
+ fakeCrossProfileType
+ .both()
+ .asyncStringMethodWhichThrowsError(/* callback= */ null);
+ fail();
+ } catch (CustomError expected) {
+
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void both_future_throwsRuntimeException_exceptionIsThrown() {
// Which one is thrown when both throw exceptions is not specified
try {
@@ -533,6 +600,19 @@
}
@Test
+ public void both_future_throwsError_errorIsThrown() {
+ // Which one is thrown when both throw exceptions is not specified
+ try {
+ fakeCrossProfileType.both().listenableFutureVoidMethodWhichThrowsError();
+ fail();
+ } catch (CustomError expected) {
+
+ } catch (ProfileRuntimeException expected) {
+ assertThat(expected).hasCauseThat().isInstanceOf(CustomError.class);
+ }
+ }
+
+ @Test
public void ifAvailable_synchronous_notConnected_returnsDefaultValue() {
connector.disconnect();
@@ -556,7 +636,7 @@
@Test
public void ifAvailable_synchronous_connected_returnsCorrectValue() {
- connector.startConnecting();
+ connector.addConnectionHolder(this);
assertThat(
fakeCrossProfileType
@@ -568,7 +648,7 @@
@Test
public void ifAvailable_synchronousVoid_connected_callsMethod() {
- connector.startConnecting();
+ connector.addConnectionHolder(this);
connector.setRunningOnProfile(ProfileType.PERSONAL);
fakeCrossProfileType.other().ifAvailable().voidMethod();
diff --git a/tests/shared/additional_types/build.gradle b/tests/shared/additional_types/build.gradle
index 342d739..920a418 100644
--- a/tests/shared/additional_types/build.gradle
+++ b/tests/shared/additional_types/build.gradle
@@ -38,7 +38,7 @@
java.srcDirs = [file('../src/main/java')]
java.includes = [
"com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileInterface.java",
- "com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java",
+ "com/google/android/enterprise/connectedapps/testapp/TestInterfaceProvider.java",
]
manifest.srcFile 'AndroidManifest.xml'
}
diff --git a/tests/shared/basictypes/build.gradle b/tests/shared/basictypes/build.gradle
index f00a2ac..cd8b697 100644
--- a/tests/shared/basictypes/build.gradle
+++ b/tests/shared/basictypes/build.gradle
@@ -35,6 +35,7 @@
java.srcDirs = [file('../src/main/java')]
java.includes = [
"com/google/android/enterprise/connectedapps/testapp/CustomRuntimeException.java",
+ "com/google/android/enterprise/connectedapps/testapp/CustomError.java",
"com/google/android/enterprise/connectedapps/testapp/CustomWrapper.java",
"com/google/android/enterprise/connectedapps/testapp/CustomWrapper2.java",
"com/google/android/enterprise/connectedapps/testapp/NonSimpleCallbackListener.java",
@@ -48,6 +49,8 @@
"com/google/android/enterprise/connectedapps/testapp/TestNotReallySerializableObjectCallbackListener.java",
"com/google/android/enterprise/connectedapps/testapp/TestStringCallbackListener.java",
"com/google/android/enterprise/connectedapps/testapp/TestVoidCallbackListener.java",
+ "com/google/android/enterprise/connectedapps/testapp/TestParcelableCallbackListener.java",
+ "com/google/android/enterprise/connectedapps/testapp/ParcelableContainingBinder.java"
]
manifest.srcFile 'AndroidManifest.xml'
}
diff --git a/tests/shared/build.gradle b/tests/shared/build.gradle
index 4642f3b..370f8a9 100644
--- a/tests/shared/build.gradle
+++ b/tests/shared/build.gradle
@@ -11,8 +11,9 @@
dependencies {
api deps.checkerFramework
api project(path: ':connectedapps-testapp')
+ api project(path: ':connectedapps-testapp_crossuser')
implementation project(path: ':connectedapps-annotations')
- implementation 'org.robolectric:robolectric:4.4'
+ implementation deps.robolectric
implementation 'junit:junit:4.13.1'
implementation 'com.google.truth:truth:1.1.2'
implementation 'androidx.test:core:1.3.0'
diff --git a/tests/shared/crossuser/build.gradle b/tests/shared/crossuser/build.gradle
index 4cceaba..acd19d2 100644
--- a/tests/shared/crossuser/build.gradle
+++ b/tests/shared/crossuser/build.gradle
@@ -13,6 +13,7 @@
implementation project(path: ':connectedapps-annotations')
implementation project(path: ':connectedapps-processor')
annotationProcessor project(path: ':connectedapps-processor')
+ implementation project(path: ':connectedapps-testapp_basictypes')
}
android {
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/SharedTestUtilities.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/SharedTestUtilities.java
index 5ff3b0b..012c576 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/SharedTestUtilities.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/SharedTestUtilities.java
@@ -20,8 +20,10 @@
import android.os.UserHandle;
import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -92,5 +94,23 @@
assertThat(didThrow.get()).isFalse();
}
+ /**
+ * Repeatedly call {@code a} and {@code b} on different threads to attempt to force a race
+ * condition.
+ */
+ public static void tryForceRaceCondition(int iterations, Runnable a, Runnable b)
+ throws Exception {
+ ExecutorService executorA = Executors.newSingleThreadExecutor();
+ ExecutorService executorB = Executors.newSingleThreadExecutor();
+
+ for (int i = 0; i < iterations; i++) {
+ ListenableFuture<?> aFuture = Futures.submit(a, executorA);
+ ListenableFuture<?> bFuture = Futures.submit(b, executorB);
+
+ aFuture.get();
+ bFuture.get();
+ }
+ }
+
private SharedTestUtilities() {}
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestParcelableCallbackListenerImpl.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestParcelableCallbackListenerImpl.java
new file mode 100644
index 0000000..3184dcd
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestParcelableCallbackListenerImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps;
+
+import android.os.Parcelable;
+import com.google.android.enterprise.connectedapps.testapp.TestParcelableCallbackListener;
+
+public class TestParcelableCallbackListenerImpl implements TestParcelableCallbackListener {
+
+ public int callbackMethodCalls = 0;
+ public Parcelable parcelableCallbackValue;
+
+ @Override
+ public void parcelableCallback(Parcelable s) {
+ callbackMethodCalls++;
+ parcelableCallbackValue = s;
+ }
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestStringCallbackListenerMultiImpl.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestStringCallbackListenerMultiImpl.java
index 5f3043e..e81f581 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestStringCallbackListenerMultiImpl.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/TestStringCallbackListenerMultiImpl.java
@@ -19,11 +19,13 @@
import java.util.Map;
public class TestStringCallbackListenerMultiImpl implements TestStringCallbackListener_Multi {
+ public int numberOfCallbacks = 0;
public int numberOfResults = 0;
public Map<Profile, String> stringCallbackValues;
@Override
public void stringCallback(Map<Profile, String> s) {
+ numberOfCallbacks++;
numberOfResults = s.size();
stringCallbackValues = s;
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/CustomError.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/CustomError.java
new file mode 100644
index 0000000..9a1dddd
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/CustomError.java
@@ -0,0 +1,5 @@
+package com.google.android.enterprise.connectedapps.testapp;
+
+public class CustomError extends Error {
+
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/ParcelableContainingBinder.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/ParcelableContainingBinder.java
new file mode 100644
index 0000000..57575aa
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/ParcelableContainingBinder.java
@@ -0,0 +1,42 @@
+package com.google.android.enterprise.connectedapps.testapp;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ParcelableContainingBinder implements Parcelable {
+
+ IBinder binder;
+
+ public ParcelableContainingBinder() {
+ binder = new Binder();
+ }
+
+ private ParcelableContainingBinder(Parcel in) {
+ binder = in.readStrongBinder();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(binder);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ParcelableContainingBinder> CREATOR =
+ new Creator<ParcelableContainingBinder>() {
+ @Override
+ public ParcelableContainingBinder createFromParcel(Parcel in) {
+ return new ParcelableContainingBinder(in);
+ }
+
+ @Override
+ public ParcelableContainingBinder[] newArray(int size) {
+ return new ParcelableContainingBinder[size];
+ }
+ };
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestInterfaceProvider.java
similarity index 84%
rename from tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
rename to tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestInterfaceProvider.java
index 06c01ba..e9b7675 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestInterfaceProvider.java
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.enterprise.connectedapps.testapp.types;
+package com.google.android.enterprise.connectedapps.testapp;
import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
+import com.google.android.enterprise.connectedapps.testapp.types.TestCrossProfileInterface;
public class TestInterfaceProvider {
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestParcelableCallbackListener.java
similarity index 72%
copy from tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
copy to tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestParcelableCallbackListener.java
index 06c01ba..690c9a0 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestParcelableCallbackListener.java
@@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.enterprise.connectedapps.testapp.types;
+package com.google.android.enterprise.connectedapps.testapp;
-import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
+import android.os.Parcelable;
+import com.google.android.enterprise.connectedapps.annotations.CrossProfileCallback;
-public class TestInterfaceProvider {
-
- @CrossProfileProvider
- public TestCrossProfileInterface provideCrossProfileInterface() {
- return s -> s;
- }
+@CrossProfileCallback
+public interface TestParcelableCallbackListener {
+ void parcelableCallback(Parcelable value);
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestStringCallbackListener.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestStringCallbackListener.java
index dc25651..c8531f9 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestStringCallbackListener.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/TestStringCallbackListener.java
@@ -19,5 +19,5 @@
@CrossProfileCallback
public interface TestStringCallbackListener {
- void stringCallback(String s);
+ void stringCallback(String value);
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/ExceptionsSuppressingApplication.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/ExceptionsSuppressingApplication.java
new file mode 100644
index 0000000..d1dc8ef
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/ExceptionsSuppressingApplication.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testapp.configuration;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
+import com.google.android.enterprise.connectedapps.testapp.connector.ExceptionsSuppressingConnector;
+import com.google.android.enterprise.connectedapps.testapp.types.ExceptionsSuppressingTestProvider;
+
+@CrossProfileConfiguration(
+ providers = {ExceptionsSuppressingTestProvider.class},
+ connector = ExceptionsSuppressingConnector.class)
+public abstract class ExceptionsSuppressingApplication {
+
+ private ExceptionsSuppressingApplication() {}
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/TestApplication.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/TestApplication.java
index de1d8f5..6e6d608 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/TestApplication.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/configuration/TestApplication.java
@@ -19,7 +19,7 @@
import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector_Service;
import com.google.android.enterprise.connectedapps.testapp.types.SeparateBuildTargetProvider;
-import com.google.android.enterprise.connectedapps.testapp.types.TestInterfaceProvider;
+import com.google.android.enterprise.connectedapps.testapp.TestInterfaceProvider;
import com.google.android.enterprise.connectedapps.testapp.types.TestProvider;
@CrossProfileConfiguration(providers = {
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/connector/ExceptionsSuppressingConnector.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/connector/ExceptionsSuppressingConnector.java
new file mode 100644
index 0000000..930579f
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/connector/ExceptionsSuppressingConnector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testapp.connector;
+
+import android.content.Context;
+import com.google.android.enterprise.connectedapps.ProfileConnector;
+import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector;
+import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
+import com.google.android.enterprise.connectedapps.annotations.GeneratedProfileConnector;
+import com.google.android.enterprise.connectedapps.annotations.UncaughtExceptionsPolicy;
+import com.google.android.enterprise.connectedapps.testapp.wrappers.ParcelableCustomWrapper;
+import com.google.android.enterprise.connectedapps.testapp.wrappers.SimpleFutureWrapper;
+import java.util.concurrent.ScheduledExecutorService;
+
+@GeneratedProfileConnector
+@CustomProfileConnector(
+ primaryProfile = ProfileType.WORK,
+ uncaughtExceptionsPolicy = UncaughtExceptionsPolicy.NOTIFY_SUPPRESS,
+ parcelableWrappers = {ParcelableCustomWrapper.class},
+ futureWrappers = {SimpleFutureWrapper.class})
+public interface ExceptionsSuppressingConnector extends ProfileConnector {
+ static ExceptionsSuppressingConnector create(Context context) {
+ return GeneratedExceptionsSuppressingConnector.builder(context).build();
+ }
+
+ static ExceptionsSuppressingConnector create(
+ Context context, ScheduledExecutorService scheduledExecutorService) {
+ return GeneratedExceptionsSuppressingConnector.builder(context)
+ .setScheduledExecutorService(scheduledExecutorService)
+ .build();
+ }
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserConfiguration.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/AppCrossUserConfiguration.java
similarity index 79%
rename from tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserConfiguration.java
rename to tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/AppCrossUserConfiguration.java
index f8ac4e8..6423923 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserConfiguration.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/AppCrossUserConfiguration.java
@@ -19,13 +19,15 @@
import com.google.android.enterprise.connectedapps.annotations.CrossUserConfiguration;
import com.google.android.enterprise.connectedapps.annotations.CrossUserConfigurations;
-@CrossUserConfigurations(@CrossUserConfiguration(providers = TestCrossUserProvider.class))
-public abstract class TestCrossUserConfiguration {
+@CrossUserConfigurations({
+ @CrossUserConfiguration(providers = {TestCrossUserProvider.class, NotesManagerProvider.class})
+})
+public abstract class AppCrossUserConfiguration {
// This is available so the test targets can access the generated Service class.
public static Class<? extends Service> getService() {
- return TestCrossUserConnector_Service.class;
+ return AppCrossUserConnector_Service.class;
}
- private TestCrossUserConfiguration() {}
+ private AppCrossUserConfiguration() {}
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/AppCrossUserConnector.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/AppCrossUserConnector.java
new file mode 100644
index 0000000..887f889
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/AppCrossUserConnector.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testapp.crossuser;
+
+import android.content.Context;
+import com.google.android.enterprise.connectedapps.UserBinderFactory;
+import com.google.android.enterprise.connectedapps.UserConnector;
+import com.google.android.enterprise.connectedapps.annotations.CustomUserConnector;
+import com.google.android.enterprise.connectedapps.annotations.GeneratedUserConnector;
+import java.util.concurrent.ScheduledExecutorService;
+
+@GeneratedUserConnector
+@CustomUserConnector
+public interface AppCrossUserConnector extends UserConnector {
+ static AppCrossUserConnector create(Context context) {
+ return GeneratedAppCrossUserConnector.builder(context).build();
+ }
+
+ static AppCrossUserConnector create(
+ Context context, ScheduledExecutorService scheduledExecutorService) {
+ return GeneratedAppCrossUserConnector.builder(context)
+ .setScheduledExecutorService(scheduledExecutorService)
+ .build();
+ }
+
+ static AppCrossUserConnector create(
+ Context context,
+ ScheduledExecutorService scheduledExecutorService,
+ UserBinderFactory binderFactory) {
+ return GeneratedAppCrossUserConnector.builder(context)
+ .setBinderFactory(binderFactory)
+ .setScheduledExecutorService(scheduledExecutorService)
+ .build();
+ }
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/NotesManager.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/NotesManager.java
new file mode 100644
index 0000000..f7f31d2
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/NotesManager.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.enterprise.connectedapps.testapp.crossuser;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossUser;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.HashSet;
+import java.util.Set;
+
+public final class NotesManager {
+
+ private final Set<String> notes = new HashSet<>();
+
+ public void addNote(String note) {
+ notes.add(note);
+ }
+
+ @CrossUser
+ public ListenableFuture<Set<String>> getNotesFuture() {
+ return Futures.immediateFuture(notes);
+ }
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/NotesManagerProvider.java
similarity index 73%
copy from tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
copy to tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/NotesManagerProvider.java
index 06c01ba..60bf65b 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestInterfaceProvider.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/NotesManagerProvider.java
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.enterprise.connectedapps.testapp.types;
+package com.google.android.enterprise.connectedapps.testapp.crossuser;
-import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
+import com.google.android.enterprise.connectedapps.annotations.CrossUserProvider;
-public class TestInterfaceProvider {
+public class NotesManagerProvider {
- @CrossProfileProvider
- public TestCrossProfileInterface provideCrossProfileInterface() {
- return s -> s;
+ @CrossUserProvider
+ public NotesManager provideNotesManager() {
+ return new NotesManager();
}
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserConnector.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserConnector.java
deleted file mode 100644
index 72c3a8c..0000000
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserConnector.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.enterprise.connectedapps.testapp.crossuser;
-
-import android.content.Context;
-import com.google.android.enterprise.connectedapps.ProfileConnector;
-import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector;
-import com.google.android.enterprise.connectedapps.annotations.CustomProfileConnector.ProfileType;
-import com.google.android.enterprise.connectedapps.annotations.GeneratedProfileConnector;
-import java.util.concurrent.ScheduledExecutorService;
-
-@GeneratedProfileConnector
-@CustomProfileConnector(primaryProfile = ProfileType.WORK)
-public interface TestCrossUserConnector extends ProfileConnector {
- static TestCrossUserConnector create(Context context) {
- return GeneratedTestCrossUserConnector.builder(context).build();
- }
-
- static TestCrossUserConnector create(
- Context context, ScheduledExecutorService scheduledExecutorService) {
- return GeneratedTestCrossUserConnector.builder(context)
- .setScheduledExecutorService(scheduledExecutorService)
- .build();
- }
-}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserType.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserType.java
index 9be071d..4d8a6c4 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserType.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/crossuser/TestCrossUserType.java
@@ -16,12 +16,26 @@
package com.google.android.enterprise.connectedapps.testapp.crossuser;
import com.google.android.enterprise.connectedapps.annotations.CrossUser;
+import com.google.android.enterprise.connectedapps.testapp.TestStringCallbackListener;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
-@CrossUser(connector = TestCrossUserConnector.class, timeoutMillis = 7000)
+@CrossUser(connector = AppCrossUserConnector.class)
public class TestCrossUserType {
@CrossUser
- public void passString(String string, TestCrossUserStringCallbackListener callbackListener) {
+ public String identityStringMethod(String string) {
+ return string;
+ }
+
+ @CrossUser
+ public ListenableFuture<String> listenableFutureIdentityStringMethod(String string) {
+ return Futures.immediateFuture(string);
+ }
+
+ @CrossUser
+ public void asyncIdentityStringMethod(
+ String string, TestStringCallbackListener callbackListener) {
callbackListener.stringCallback(string);
}
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/ExceptionsSuppressingCrossProfileType.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/ExceptionsSuppressingCrossProfileType.java
new file mode 100644
index 0000000..618269d
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/ExceptionsSuppressingCrossProfileType.java
@@ -0,0 +1,13 @@
+package com.google.android.enterprise.connectedapps.testapp.types;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
+import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
+import com.google.android.enterprise.connectedapps.testapp.connector.ExceptionsSuppressingConnector;
+
+@CrossProfile(connector = ExceptionsSuppressingConnector.class)
+public class ExceptionsSuppressingCrossProfileType {
+ @CrossProfile
+ public String methodWhichThrowsRuntimeException() {
+ throw new CustomRuntimeException("Exception");
+ }
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/ExceptionsSuppressingTestProvider.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/ExceptionsSuppressingTestProvider.java
new file mode 100644
index 0000000..30c21d0
--- /dev/null
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/ExceptionsSuppressingTestProvider.java
@@ -0,0 +1,12 @@
+package com.google.android.enterprise.connectedapps.testapp.types;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
+
+@CrossProfileProvider
+public class ExceptionsSuppressingTestProvider {
+
+ @CrossProfileProvider
+ public ExceptionsSuppressingCrossProfileType provideExceptionsSuppressingCrossProfileType() {
+ return new ExceptionsSuppressingCrossProfileType();
+ }
+}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileType.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileType.java
index ddadc81..1466ffd 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileType.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileType.java
@@ -20,9 +20,13 @@
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
import android.util.Pair;
import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
+import com.google.android.enterprise.connectedapps.testapp.CustomError;
import com.google.android.enterprise.connectedapps.testapp.CustomRuntimeException;
import com.google.android.enterprise.connectedapps.testapp.CustomWrapper;
import com.google.android.enterprise.connectedapps.testapp.CustomWrapper2;
@@ -35,6 +39,7 @@
import com.google.android.enterprise.connectedapps.testapp.TestBooleanCallbackListener;
import com.google.android.enterprise.connectedapps.testapp.TestCustomWrapperCallbackListener;
import com.google.android.enterprise.connectedapps.testapp.TestNotReallySerializableObjectCallbackListener;
+import com.google.android.enterprise.connectedapps.testapp.TestParcelableCallbackListener;
import com.google.android.enterprise.connectedapps.testapp.TestStringCallbackListener;
import com.google.android.enterprise.connectedapps.testapp.TestVoidCallbackListener;
import com.google.android.enterprise.connectedapps.testapp.connector.TestProfileConnector;
@@ -55,7 +60,6 @@
@CrossProfile(
connector = TestProfileConnector.class,
- timeoutMillis = 7000,
parcelableWrappers = {ParcelableCustomWrapper2.class, ParcelableStringWrapper.class})
public class TestCrossProfileType {
@@ -74,11 +78,21 @@
}
@CrossProfile
+ public String methodWhichThrowsError() {
+ throw new CustomError();
+ }
+
+ @CrossProfile
public String methodWhichThrowsRuntimeException() {
throw new CustomRuntimeException("Exception");
}
@CrossProfile
+ public String methodWhichThrowsRuntimeExceptionAndDeclaresException() throws IOException {
+ throw new CustomRuntimeException("Exception");
+ }
+
+ @CrossProfile
public ListenableFuture<Void> listenableFutureVoidMethod() {
voidMethod();
return immediateFuture(null);
@@ -89,14 +103,9 @@
return SettableFuture.create();
}
- @CrossProfile // Timeout is inherited
- public ListenableFuture<Void> listenableFutureMethodWhichNeverSetsTheValueWith7SecondTimeout() {
- return SettableFuture.create();
- }
-
- @CrossProfile(timeoutMillis = 5000)
- public ListenableFuture<Void> listenableFutureMethodWhichNeverSetsTheValueWith5SecondTimeout() {
- return SettableFuture.create();
+ @CrossProfile
+ public ListenableFuture<Void> listenableFutureVoidMethodWhichThrowsError() {
+ throw new CustomError();
}
@CrossProfile
@@ -123,7 +132,7 @@
public ListenableFuture<Void> listenableFutureVoidMethodWithNonBlockingDelay(int secondsDelay) {
SettableFuture<Void> v = SettableFuture.create();
- new Handler()
+ new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
voidMethod();
@@ -138,18 +147,14 @@
String s, int secondsDelay) {
SettableFuture<String> v = SettableFuture.create();
- new Handler().postDelayed(() -> v.set(s), TimeUnit.SECONDS.toMillis(secondsDelay));
+ new Handler(Looper.getMainLooper())
+ .postDelayed(() -> v.set(s), TimeUnit.SECONDS.toMillis(secondsDelay));
return v;
}
- @CrossProfile(timeoutMillis = 3000)
- public ListenableFuture<String>
- listenableFutureIdentityStringMethodWithNonBlockingDelayWith3SecondTimeout(
- String s, int secondsDelay) {
- SettableFuture<String> v = SettableFuture.create();
-
- new Handler().postDelayed(() -> v.set(s), TimeUnit.SECONDS.toMillis(secondsDelay));
- return v;
+ @CrossProfile
+ public void asyncStringMethodWhichThrowsError(TestStringCallbackListener callback) {
+ throw new CustomError();
}
@CrossProfile
@@ -158,13 +163,29 @@
}
@CrossProfile
- public void asyncVoidMethodWhichCallsBackTwice(TestVoidCallbackListener callback) {
+ public void asyncIdentityStringMethodWhichCallsBackTwice(
+ String s, TestStringCallbackListener callback) {
voidMethod();
- callback.callback();
- callback.callback();
+ callback.stringCallback(s);
+ callback.stringCallback(s);
}
@CrossProfile
+ public void asyncIdentityStringMethodWhichCallsBackTwiceWithNonBlockingDelay(
+ String s, TestStringCallbackListener callback, long secondsDelay) {
+ voidMethod();
+ callback.stringCallback(s);
+
+ new Handler(Looper.getMainLooper())
+ .postDelayed(
+ () -> {
+ callback.stringCallback(s);
+ },
+ TimeUnit.SECONDS.toMillis(secondsDelay));
+ }
+
+
+ @CrossProfile
public void asyncVoidMethod(TestVoidCallbackListener callback) {
voidMethod();
callback.callback();
@@ -173,14 +194,6 @@
@CrossProfile
public void asyncMethodWhichNeverCallsBack(TestStringCallbackListener callback) {}
- @CrossProfile // Timeout is inherited
- public void asyncMethodWhichNeverCallsBackWith7SecondTimeout(
- TestStringCallbackListener callback) {}
-
- @CrossProfile(timeoutMillis = 5000)
- public void asyncMethodWhichNeverCallsBackWith5SecondTimeout(
- TestStringCallbackListener callback) {}
-
@CrossProfile
public void asyncVoidMethodWithDelay(TestVoidCallbackListener callback, int secondsDelay) {
try {
@@ -194,29 +207,14 @@
@CrossProfile
public void asyncVoidMethodWithNonBlockingDelay(
TestVoidCallbackListener callback, int secondsDelay) {
- new Handler()
+ new Handler(Looper.getMainLooper())
.postDelayed(() -> asyncVoidMethod(callback), TimeUnit.SECONDS.toMillis(secondsDelay));
}
- @CrossProfile(timeoutMillis = 50000)
- public void asyncVoidMethodWithNonBlockingDelayWith50SecondTimeout(
- TestVoidCallbackListener callback, int secondsDelay) {
- new Handler()
- .postDelayed(() -> asyncVoidMethod(callback), TimeUnit.SECONDS.toMillis(secondsDelay));
- }
-
- @CrossProfile(timeoutMillis = 3000)
- public void asyncIdentityStringMethodWithNonBlockingDelayWith3SecondTimeout(
- String s, TestStringCallbackListener callback, int secondsDelay) {
- new Handler()
- .postDelayed(
- () -> asyncIdentityStringMethod(s, callback), TimeUnit.SECONDS.toMillis(secondsDelay));
- }
-
@CrossProfile
public void asyncIdentityStringMethodWithNonBlockingDelay(
String s, TestStringCallbackListener callback, int secondsDelay) {
- new Handler()
+ new Handler(Looper.getMainLooper())
.postDelayed(
() -> asyncIdentityStringMethod(s, callback), TimeUnit.SECONDS.toMillis(secondsDelay));
}
@@ -358,11 +356,31 @@
}
@CrossProfile
+ public CharSequence identityCharSequenceMethod(CharSequence c) {
+ return c;
+ }
+
+ @CrossProfile
public ParcelableObject identityParcelableMethod(ParcelableObject p) {
return p;
}
@CrossProfile
+ public Parcelable identityParcelableMethod(Parcelable p) {
+ return p;
+ }
+
+ @CrossProfile
+ public void asyncIdentityParcelableMethod(Parcelable p, TestParcelableCallbackListener callback) {
+ callback.parcelableCallback(p);
+ }
+
+ @CrossProfile
+ public ListenableFuture<Parcelable> futureIdentityParcelableMethod(Parcelable p) {
+ return immediateFuture(p);
+ }
+
+ @CrossProfile
public SerializableObject identitySerializableObjectMethod(SerializableObject s) {
return s;
}
@@ -533,7 +551,8 @@
String s, int secondsDelay) {
SimpleFuture<String> future = new SimpleFuture<>();
- new Handler().postDelayed(() -> future.set(s), TimeUnit.SECONDS.toMillis(secondsDelay));
+ new Handler(Looper.getMainLooper())
+ .postDelayed(() -> future.set(s), TimeUnit.SECONDS.toMillis(secondsDelay));
return future;
}
@@ -599,4 +618,89 @@
NonSimpleCallbackListener callback, String s1, String s2) {
callback.callback2(s1, s2);
}
+
+ @CrossProfile
+ public float[] identityFloatArrayMethod(float[] f) {
+ return f;
+ }
+
+ @CrossProfile
+ public float[][][] identityMultidimensionalFloatArrayMethod(float[][][] f) {
+ return f;
+ }
+
+ @CrossProfile
+ public int[] identityIntArrayMethod(int[] i) {
+ return i;
+ }
+
+ @CrossProfile
+ public int[][][] identityMultidimensionalIntArrayMethod(int[][][] i) {
+ return i;
+ }
+
+ @CrossProfile
+ public byte[] identityByteArrayMethod(byte[] b) {
+ return b;
+ }
+
+ @CrossProfile
+ public byte[][][] identityMultidimensionalByteArrayMethod(byte[][][] b) {
+ return b;
+ }
+
+ @CrossProfile
+ public short[] identityShortArrayMethod(short[] s) {
+ return s;
+ }
+
+ @CrossProfile
+ public short[][][] identityMultidimensionalShortArrayMethod(short[][][] s) {
+ return s;
+ }
+
+ @CrossProfile
+ public long[] identityLongArrayMethod(long[] l) {
+ return l;
+ }
+
+ @CrossProfile
+ public long[][][] identityMultidimensionalLongArrayMethod(long[][][] l) {
+ return l;
+ }
+
+ @CrossProfile
+ public double[] identityDoubleArrayMethod(double[] d) {
+ return d;
+ }
+
+ @CrossProfile
+ public double[][][] identityMultidimensionalDoubleArrayMethod(double[][][] d) {
+ return d;
+ }
+
+ @CrossProfile
+ public boolean[] identityBooleanArrayMethod(boolean[] b) {
+ return b;
+ }
+
+ @CrossProfile
+ public boolean[][][] identityMultidimensionalBooleanArrayMethod(boolean[][][] b) {
+ return b;
+ }
+
+ @CrossProfile
+ public char[] identityCharArrayMethod(char[] c) {
+ return c;
+ }
+
+ @CrossProfile
+ public char[][][] identityMultidimensionalCharArrayMethod(char[][][] c) {
+ return c;
+ }
+
+ @CrossProfile
+ public Drawable identityDrawableMethod(Drawable d) {
+ return d;
+ }
}
diff --git a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileTypeWhichNeedsContext.java b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileTypeWhichNeedsContext.java
index f98a5b1..c63edbd 100644
--- a/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileTypeWhichNeedsContext.java
+++ b/tests/shared/src/main/java/com/google/android/enterprise/connectedapps/testapp/types/TestCrossProfileTypeWhichNeedsContext.java
@@ -45,14 +45,13 @@
ProfileTestCrossProfileType.create(ConnectorSingleton.getConnector(context));
}
- @CrossProfile // Timeout is not specified on type or method so will be default
- public ListenableFuture<Void> listenableFutureMethodWhichNeverSetsTheValueWithDefaultTimeout() {
+ @CrossProfile
+ public ListenableFuture<Void> listenableFutureMethodWhichNeverSetsTheValue() {
return SettableFuture.create();
}
- @CrossProfile // Timeout is not specified on type or method so will be default
- public void asyncMethodWhichNeverCallsBackWithDefaultTimeout(
- TestStringCallbackListener callback) {}
+ @CrossProfile
+ public void asyncMethodWhichNeverCallsBack(TestStringCallbackListener callback) {}
@CrossProfile
public void voidMethod() {
@@ -63,7 +62,11 @@
public void connectToOtherProfile() {
// This, when called cross-profile, causes the other profile to create a connection back to the
// original profile
- ConnectorSingleton.getConnector(context).startConnecting();
+ try {
+ ConnectorSingleton.getConnector(context).connect(this);
+ } catch (UnavailableProfileException e) {
+ throw new IllegalStateException("Error connecting", e);
+ }
}
@CrossProfile
diff --git a/tests/shared/testapp/build.gradle b/tests/shared/testapp/build.gradle
index 16da3a4..ccbd938 100644
--- a/tests/shared/testapp/build.gradle
+++ b/tests/shared/testapp/build.gradle
@@ -16,7 +16,6 @@
api project(path: ':connectedapps-testapp_types')
api project(path: ':connectedapps-testapp_types_providers')
api project(path: ':connectedapps-testapp_wrappers')
- api project(path: ':connectedapps-testapp_crossuser')
}
android {