Add code generation for RemoteDPC.
This removes a load of boilerplate.
Test: atest RemoteDPCTest
Bug: 177663307
Change-Id: I9d0b87a32997ca08beb3fceffebee5e814daa576
diff --git a/common/device-side/bedstead/remotedpc/Android.bp b/common/device-side/bedstead/remotedpc/Android.bp
index e80fcf7..b4c6259 100644
--- a/common/device-side/bedstead/remotedpc/Android.bp
+++ b/common/device-side/bedstead/remotedpc/Android.bp
@@ -12,11 +12,12 @@
"Nene",
"ConnectedAppsSDK",
"ConnectedAppsSDK_Annotations",
- "androidx.annotation_annotation"
+ "androidx.annotation_annotation",
+ "RemoteDPC_Annotations"
],
manifest: "src/communication/main/AndroidManifest.xml",
min_sdk_version: "27",
- plugins: ["ConnectedAppsSDK_Processor"],
+ plugins: ["ConnectedAppsSDK_Processor", "RemoteDPC_Processor"],
}
android_library {
@@ -75,4 +76,28 @@
],
manifest: "src/library/test/AndroidManifest.xml",
min_sdk_version: "27"
+}
+
+java_library {
+ name: "RemoteDPC_Annotations",
+ srcs: [
+ "src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/*.java"
+ ],
+ host_supported: true
+}
+
+java_plugin {
+ name: "RemoteDPC_Processor",
+ processor_class: "com.android.bedstead.remotedpc.processor.Processor",
+ static_libs: [
+ "javapoet",
+ "auto_service_annotations",
+ "RemoteDPC_Annotations",
+ "ConnectedAppsSDK_Annotations"
+ ],
+ srcs: [
+ "src/processor/main/java/com/android/bedstead/remotedpc/processor/Processor.java"
+ ],
+ plugins: ["auto_service_plugin"],
+ generates_api: true,
}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/Configuration.java b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/Configuration.java
new file mode 100644
index 0000000..28007c9
--- /dev/null
+++ b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/Configuration.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remotedpc;
+
+import android.content.ComponentName;
+
+/**
+ * Utilities and configuration for RemoteDPC Communication.
+ */
+public final class Configuration {
+
+ private Configuration() {
+
+ }
+
+ public static final ComponentName DPC_COMPONENT = new ComponentName(
+ "com.android.bedstead.remotedpc.dpc",
+ "com.android.eventlib.premade.EventLibDeviceAdminReceiver"
+ );
+}
diff --git a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/connected/Configuration.java b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/connected/Configuration.java
deleted file mode 100644
index 930c596..0000000
--- a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/connected/Configuration.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bedstead.remotedpc.connected;
-
-import com.android.bedstead.remotedpc.managers.Provider;
-
-import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
-
-/** Connected Apps SDK Configuration for RemoteDPC. */
-@CrossProfileConfiguration(providers = Provider.class)
-public class Configuration {
-}
diff --git a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/Provider.java b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/Provider.java
deleted file mode 100644
index ba08fee..0000000
--- a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/Provider.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bedstead.remotedpc.managers;
-
-import android.content.Context;
-
-import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
-
-/** Connected Apps SDK Provider for RemoteDPC. */
-public class Provider {
-
- /** Provide instance of {@link RemoteDevicePolicyManager} to Connected Apps SDK. */
- @CrossProfileProvider
- public RemoteDevicePolicyManager provideRemoteDevicePolicyManager(Context context) {
- return new RemoteDevicePolicyManagerImpl(context);
- }
-}
diff --git a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManager.java b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManager.java
index f33209e..f1b79bb 100644
--- a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManager.java
+++ b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManager.java
@@ -21,20 +21,33 @@
import androidx.annotation.NonNull;
-import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
+import com.android.bedstead.remotedpc.processor.annotations.RemoteDpcAutomaticAdmin;
+import com.android.bedstead.remotedpc.processor.annotations.RemoteDpcManager;
-/** See {@link DevicePolicyManager}. */
+/**
+ * Wrapper of {@link DevicePolicyManager} methods for use with Remote DPC
+ *
+ * <p>Methods called on this interface will behave as if they were called directly by the
+ * RemoteDPC instance. Return values and exceptions will behave as expected.
+ *
+ * <p>Methods on this interface must match exactly the methods declared by
+ * {@link DevicePolicyManager}, or else must be identical to a method declared by
+ * {@link DevicePolicyManager} except that it excludes a {@code ComponentName admin} first argument
+ * and must be annotated {@link RemoteDpcAutomaticAdmin}.
+ *
+ * <p>When using {@link RemoteDpcAutomaticAdmin}, there must also exist an identical method on the
+ * interface which includes the {@code ComponentName admin} argument. The RemoteDPC component name
+ * will be automatically provided when the {@link RemoteDpcAutomaticAdmin} annotated method is
+ * called.
+ */
+@RemoteDpcManager(managerClass = DevicePolicyManager.class)
public interface RemoteDevicePolicyManager {
- /** See {@link DevicePolicyManager#isUsingUnifiedPassword(android.content.ComponentName)}. */
- @CrossProfile
+ /** See {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}. */
boolean isUsingUnifiedPassword(@NonNull ComponentName admin);
-
- /** See {@link DevicePolicyManager#isUsingUnifiedPassword(android.content.ComponentName)}. */
- @CrossProfile
- boolean isUsingUnifiedPassword();
+ /** See {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}. */
+ @RemoteDpcAutomaticAdmin boolean isUsingUnifiedPassword();
/** See {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()}. */
- @CrossProfile
int getCurrentFailedPasswordAttempts();
}
diff --git a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManagerImpl.java b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManagerImpl.java
deleted file mode 100644
index 5bd0979..0000000
--- a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManagerImpl.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bedstead.remotedpc.managers;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-
-/** Implementation of {@link com.android.bedstead.remotedpc.managers.RemoteDevicePolicyManager}. */
-@SuppressWarnings("NewApi")
-public class RemoteDevicePolicyManagerImpl implements RemoteDevicePolicyManager {
-
- private static final ComponentName DPC_COMPONENT = new ComponentName(
- "com.android.bedstead.remotedpc.dpc",
- "com.android.eventlib.premade.EventLibDeviceAdminReceiver"
- );
-
- private final DevicePolicyManager mDevicePolicyManager;
-
- public RemoteDevicePolicyManagerImpl(Context context) {
- mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
- }
-
- @Override
- public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) {
- return mDevicePolicyManager.isUsingUnifiedPassword(admin);
- }
-
- @Override
- public boolean isUsingUnifiedPassword() {
- return isUsingUnifiedPassword(DPC_COMPONENT);
- }
-
- @Override
- public int getCurrentFailedPasswordAttempts() {
- return mDevicePolicyManager.getCurrentFailedPasswordAttempts();
- }
-}
diff --git a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManagerWrapper.java b/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManagerWrapper.java
deleted file mode 100644
index 775a8d4..0000000
--- a/common/device-side/bedstead/remotedpc/src/communication/main/java/com/android/bedstead/remotedpc/managers/RemoteDevicePolicyManagerWrapper.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bedstead.remotedpc.managers;
-
-import android.content.ComponentName;
-
-import androidx.annotation.NonNull;
-
-import com.android.bedstead.nene.exceptions.NeneException;
-
-import com.google.android.enterprise.connectedapps.CrossProfileConnector;
-import com.google.android.enterprise.connectedapps.exceptions.ProfileRuntimeException;
-import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
-
-
-/**
- * Implementation of {@link RemoteDevicePolicyManager} which uses the Connected Apps SDK internally.
- */
-public class RemoteDevicePolicyManagerWrapper implements RemoteDevicePolicyManager {
-
- private final ProfileRemoteDevicePolicyManager mProfileRemoteDevicePolicyManager;
- private final CrossProfileConnector mConnector;
-
- public RemoteDevicePolicyManagerWrapper(CrossProfileConnector connector) {
- mConnector = connector;
- mProfileRemoteDevicePolicyManager = ProfileRemoteDevicePolicyManager.create(connector);
- }
-
- @Override
- public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) {
- try {
- mConnector.connect();
- return mProfileRemoteDevicePolicyManager.other().isUsingUnifiedPassword(admin);
- } catch (UnavailableProfileException e) {
- e.printStackTrace();
- throw new NeneException("Error connecting to RemoteDPC", e);
- } catch (ProfileRuntimeException e) {
- throw (RuntimeException) e.getCause();
- }
- }
-
- @Override
- public boolean isUsingUnifiedPassword() {
- try {
- mConnector.connect();
- return mProfileRemoteDevicePolicyManager.other().isUsingUnifiedPassword();
- } catch (UnavailableProfileException e) {
- e.printStackTrace();
- throw new NeneException("Error connecting to RemoteDPC", e);
- } catch (ProfileRuntimeException e) {
- throw (RuntimeException) e.getCause();
- }
- }
-
- @Override
- public int getCurrentFailedPasswordAttempts() {
- try {
- mConnector.connect();
- return mProfileRemoteDevicePolicyManager.other().getCurrentFailedPasswordAttempts();
- } catch (UnavailableProfileException e) {
- e.printStackTrace();
- throw new NeneException("Error connecting to RemoteDPC", e);
- } catch (ProfileRuntimeException e) {
- throw (RuntimeException) e.getCause();
- }
- }
-}
diff --git a/common/device-side/bedstead/remotedpc/src/library/main/java/com/android/bedstead/remotedpc/RemoteDpc.java b/common/device-side/bedstead/remotedpc/src/library/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
index 2212ff4..106a35d 100644
--- a/common/device-side/bedstead/remotedpc/src/library/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
+++ b/common/device-side/bedstead/remotedpc/src/library/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
@@ -16,10 +16,10 @@
package com.android.bedstead.remotedpc;
+import static com.android.bedstead.remotedpc.Configuration.DPC_COMPONENT;
import static com.android.compatibility.common.util.FileUtils.readInputStreamFully;
import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
@@ -33,7 +33,7 @@
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.remotedpc.connected.RemoteDPCBinder;
import com.android.bedstead.remotedpc.managers.RemoteDevicePolicyManager;
-import com.android.bedstead.remotedpc.managers.RemoteDevicePolicyManagerWrapper;
+import com.android.bedstead.remotedpc.managers.RemoteDevicePolicyManager_Wrapper;
import com.google.android.enterprise.connectedapps.CrossProfileConnector;
@@ -45,10 +45,6 @@
private static final TestApis sTestApis = new TestApis();
private static final Context sContext = sTestApis.context().instrumentedContext();
- private static final ComponentName DPC_COMPONENT = new ComponentName(
- "com.android.bedstead.remotedpc.dpc",
- "com.android.eventlib.premade.EventLibDeviceAdminReceiver"
- );
/**
* Get the {@link RemoteDpc} instance for the Device Owner.
@@ -273,6 +269,6 @@
* this RemoteDPC.
*/
public RemoteDevicePolicyManager devicePolicyManager() {
- return new RemoteDevicePolicyManagerWrapper(mConnector);
+ return new RemoteDevicePolicyManager_Wrapper(mConnector);
}
}
diff --git a/common/device-side/bedstead/remotedpc/src/library/test/java/com/android/bedstead/remotedpc/RemoteDpcTest.java b/common/device-side/bedstead/remotedpc/src/library/test/java/com/android/bedstead/remotedpc/RemoteDpcTest.java
index e59e4733..7e3e889 100644
--- a/common/device-side/bedstead/remotedpc/src/library/test/java/com/android/bedstead/remotedpc/RemoteDpcTest.java
+++ b/common/device-side/bedstead/remotedpc/src/library/test/java/com/android/bedstead/remotedpc/RemoteDpcTest.java
@@ -703,7 +703,7 @@
}
@Test
- public void frameworkCall_notProfileOwner_throwsSecurityException() {
+ public void frameworkCallRequiresProfileOwner_notProfileOwner_throwsSecurityException() {
RemoteDpc remoteDPC = RemoteDpc.setAsDeviceOwner(sUser);
try {
diff --git a/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/Processor.java b/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/Processor.java
new file mode 100644
index 0000000..dd61bce
--- /dev/null
+++ b/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/Processor.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remotedpc.processor;
+
+import com.android.bedstead.remotedpc.processor.annotations.RemoteDpcAutomaticAdmin;
+import com.android.bedstead.remotedpc.processor.annotations.RemoteDpcManager;
+
+import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
+import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
+import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
+import com.google.auto.service.AutoService;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.TypeSpec;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeKind;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/** Processor for generating RemoteDPC API for framework manager classes. */
+@SupportedAnnotationTypes({
+ "com.android.bedstead.remotedpc.processor.annotations.RemoteDpcManager",
+})
+@AutoService(javax.annotation.processing.Processor.class)
+public final class Processor extends AbstractProcessor {
+ // TODO(scottjonathan): Add more verification before generating - and add processor tests
+ private static final ClassName CONTEXT_CLASSNAME =
+ ClassName.get("android.content", "Context");
+ private static final ClassName CONFIGURATION_CLASSNAME =
+ ClassName.get("com.android.bedstead.remotedpc", "Configuration");
+ private static final ClassName CROSS_PROFILE_CONNECTOR_CLASSNAME =
+ ClassName.get("com.google.android.enterprise.connectedapps", "CrossProfileConnector");
+ private static final ClassName UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME =
+ ClassName.get(
+ "com.google.android.enterprise.connectedapps.exceptions",
+ "UnavailableProfileException");
+ private static final ClassName PROFILE_RUNTIME_EXCEPTION_CLASSNAME =
+ ClassName.get(
+ "com.google.android.enterprise.connectedapps.exceptions",
+ "ProfileRuntimeException");
+ private static final ClassName NENE_EXCEPTION_CLASSNAME =
+ ClassName.get(
+ "com.android.bedstead.nene.exceptions", "NeneException");
+ public static final String MANAGER_PACKAGE_NAME = "com.android.bedstead.remotedpc.managers";
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations,
+ RoundEnvironment roundEnv) {
+
+ Set<TypeElement> interfaces = new HashSet<>();
+
+ for (Element e : roundEnv.getElementsAnnotatedWith(RemoteDpcManager.class)) {
+ TypeElement interfaceClass = (TypeElement) e;
+ interfaces.add(interfaceClass);
+ processRemoteDpcManagerInterface(interfaceClass);
+ }
+
+ if (interfaces.isEmpty()) {
+ // We only want to generate the provider and configuration once, not on every iteration
+ return true;
+ }
+
+ generateProvider(interfaces);
+ generateConfiguration();
+
+ return true;
+ }
+
+ private void processRemoteDpcManagerInterface(TypeElement interfaceClass) {
+ RemoteDpcManager r = interfaceClass.getAnnotation(RemoteDpcManager.class);
+ TypeElement managerClass = extractClassFromAnnotation(r::managerClass);
+
+ if (!interfaceClass.getKind().isInterface()) {
+ showError("@RemoteDpcManager can only be applied to interfaces", interfaceClass);
+ }
+
+ generateCrossProfileInterface(interfaceClass);
+ generateImplClass(interfaceClass, managerClass);
+ generateWrapperClass(interfaceClass);
+ }
+
+ /** Generate Impl which wraps the manager class. */
+ private void generateImplClass(TypeElement interfaceClass, TypeElement managerClass) {
+ ClassName managerClassName = ClassName.get(managerClass);
+
+ TypeSpec.Builder classBuilder = TypeSpec.classBuilder(implName(interfaceClass))
+ .addSuperinterface(crossProfileInterfaceName(interfaceClass))
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+
+ classBuilder.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "{\"NewApi\", \"OldTargetApi\"}")
+ .build());
+
+ classBuilder.addField(managerClassName,
+ "mManager", Modifier.PRIVATE, Modifier.FINAL);
+
+ classBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addCode("mManager = context.getSystemService($T.class);",
+ managerClassName)
+ .build()
+ );
+
+ for (ExecutableElement method : getMethods(interfaceClass)) {
+ MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(
+ method.getSimpleName().toString())
+ .returns(ClassName.get(method.getReturnType()))
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class);
+
+ for (VariableElement param : method.getParameters()) {
+ ParameterSpec parameterSpec =
+ ParameterSpec.builder(ClassName.get(param.asType()),
+ param.getSimpleName().toString()).build();
+
+ methodBuilder.addParameter(parameterSpec);
+ }
+
+ String parametersString = method.getParameters().stream()
+ .map(VariableElement::getSimpleName)
+ .collect(Collectors.joining(", "));
+ CodeBlock methodCall;
+
+ if (method.getAnnotation(RemoteDpcAutomaticAdmin.class) != null) {
+ // We just redirect to the other method, adding in the component
+ if (parametersString.isEmpty()) {
+ methodCall = CodeBlock.of("$L($T.DPC_COMPONENT);", method.getSimpleName(),
+ CONFIGURATION_CLASSNAME);
+ } else {
+ methodCall = CodeBlock.of("$L($T.DPC_COMPONENT, $L);", method.getSimpleName(),
+ CONFIGURATION_CLASSNAME, parametersString);
+ }
+ } else {
+ // We call through to the wrapped manager class
+ methodCall = CodeBlock.of("mManager.$L($L);",
+ method.getSimpleName(), parametersString);
+ }
+
+ if (!method.getReturnType().getKind().equals(TypeKind.VOID)) {
+ methodCall = CodeBlock.of("return $L", methodCall);
+ }
+ methodBuilder.addCode(methodCall);
+
+ classBuilder.addMethod(methodBuilder.build());
+ }
+
+ PackageElement packageElement = (PackageElement) interfaceClass.getEnclosingElement();
+
+ writeClassToFile(packageElement.getQualifiedName().toString(), classBuilder.build());
+ }
+
+ /** Generate wrapper which wraps the cross-profile class. */
+ private void generateWrapperClass(TypeElement interfaceClass) {
+ TypeSpec.Builder classBuilder =
+ TypeSpec.classBuilder(
+ wrapperName(interfaceClass))
+ .addSuperinterface(crossProfileInterfaceName(interfaceClass))
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+
+
+ classBuilder.addField(CROSS_PROFILE_CONNECTOR_CLASSNAME,
+ "mConnector", Modifier.PRIVATE, Modifier.FINAL);
+ classBuilder.addField(profileTypeName(interfaceClass),
+ "mProfileType", Modifier.PRIVATE, Modifier.FINAL);
+
+ classBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(CROSS_PROFILE_CONNECTOR_CLASSNAME, "connector")
+ .addCode("mConnector = connector;")
+ .addCode("mProfileType = $T.create(connector);",
+ profileTypeName(interfaceClass))
+ .build()
+ );
+
+ for (ExecutableElement method : getMethods(interfaceClass)) {
+ MethodSpec.Builder methodBuilder =
+ MethodSpec.methodBuilder(method.getSimpleName().toString())
+ .returns(ClassName.get(method.getReturnType()))
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class);
+
+ for (VariableElement param : method.getParameters()) {
+ ParameterSpec parameterSpec =
+ ParameterSpec.builder(ClassName.get(param.asType()),
+ param.getSimpleName().toString()).build();
+
+ methodBuilder.addParameter(parameterSpec);
+ }
+
+ String parametersString = method.getParameters().stream()
+ .map(VariableElement::getSimpleName)
+ .collect(Collectors.joining(", "));
+
+ CodeBlock methodCall = CodeBlock.of("mProfileType.other().$L($L);",
+ method.getSimpleName().toString(), parametersString);
+ if (!method.getReturnType().getKind().equals(TypeKind.VOID)) {
+ methodCall = CodeBlock.of("return $L", methodCall);
+ }
+
+ methodBuilder.beginControlFlow("try")
+ .addCode("mConnector.connect();")
+ .addCode(methodCall)
+ .nextControlFlow("catch ($T e)",
+ UNAVAILABLE_PROFILE_EXCEPTION_CLASSNAME)
+ .addCode("throw new $T(\"Error connecting\", e);", NENE_EXCEPTION_CLASSNAME)
+ .nextControlFlow("catch ($T e)",
+ PROFILE_RUNTIME_EXCEPTION_CLASSNAME)
+ .addCode("throw ($T) e.getCause();", RuntimeException.class)
+ .endControlFlow();
+
+ classBuilder.addMethod(methodBuilder.build());
+ }
+
+ PackageElement packageElement = (PackageElement) interfaceClass.getEnclosingElement();
+ writeClassToFile(packageElement.getQualifiedName().toString(), classBuilder.build());
+ }
+
+ /** Generate sub-interface which is annotated @CrossProfile. */
+ private void generateCrossProfileInterface(TypeElement interfaceClass) {
+ TypeSpec.Builder classBuilder =
+ TypeSpec.interfaceBuilder(
+ crossProfileInterfaceName(interfaceClass))
+ .addSuperinterface(ClassName.get(interfaceClass))
+ .addModifiers(Modifier.PUBLIC);
+
+ for (ExecutableElement method : getMethods(interfaceClass)) {
+ MethodSpec.Builder methodBuilder =
+ MethodSpec.methodBuilder(method.getSimpleName().toString())
+ .returns(ClassName.get(method.getReturnType()))
+ .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+ .addAnnotation(CrossProfile.class)
+ .addAnnotation(Override.class);
+
+ for (VariableElement param : method.getParameters()) {
+ ParameterSpec parameterSpec =
+ ParameterSpec.builder(ClassName.get(param.asType()),
+ param.getSimpleName().toString()).build();
+
+ methodBuilder.addParameter(parameterSpec);
+ }
+
+ classBuilder.addMethod(methodBuilder.build());
+ }
+
+ PackageElement packageElement = (PackageElement) interfaceClass.getEnclosingElement();
+ writeClassToFile(packageElement.getQualifiedName().toString(), classBuilder.build());
+ }
+
+ private void generateProvider(Set<TypeElement> interfaces) {
+ TypeSpec.Builder classBuilder =
+ TypeSpec.classBuilder(
+ "Provider")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+
+ for (TypeElement i : interfaces) {
+ MethodSpec.Builder methodBuilder =
+ MethodSpec.methodBuilder("provide_" + i.getSimpleName())
+ .returns(crossProfileInterfaceName(i))
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(CONTEXT_CLASSNAME, "context")
+ .addAnnotation(CrossProfileProvider.class)
+ .addCode("return new $T(context);", implName(i));
+
+ classBuilder.addMethod(methodBuilder.build());
+ }
+
+ writeClassToFile(MANAGER_PACKAGE_NAME, classBuilder.build());
+ }
+
+ private void generateConfiguration() {
+ TypeSpec.Builder classBuilder =
+ TypeSpec.classBuilder(
+ "Configuration")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addAnnotation(AnnotationSpec.builder(CrossProfileConfiguration.class)
+ .addMember("providers", "Provider.class")
+ .build());
+
+ writeClassToFile(MANAGER_PACKAGE_NAME, classBuilder.build());
+ }
+
+ private TypeElement extractClassFromAnnotation(Runnable runnable) {
+ try {
+ runnable.run();
+ } catch (MirroredTypeException e) {
+ return e.getTypeMirrors().stream()
+ .map(t -> (TypeElement) processingEnv.getTypeUtils().asElement(t))
+ .findFirst()
+ .get();
+ }
+ throw new AssertionError("Could not extract class from annotation");
+ }
+
+ private void writeClassToFile(String packageName, TypeSpec clazz) {
+ String qualifiedClassName =
+ packageName.isEmpty() ? clazz.name : packageName + "." + clazz.name;
+
+ JavaFile javaFile = JavaFile.builder(packageName, clazz).build();
+ try {
+ JavaFileObject builderFile =
+ processingEnv.getFiler().createSourceFile(qualifiedClassName);
+ try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
+ javaFile.writeTo(out);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Error writing " + qualifiedClassName + " to file", e);
+ }
+ }
+
+ private ClassName crossProfileInterfaceName(TypeElement interfaceClass) {
+ return ClassName.bestGuess(interfaceClass.getQualifiedName().toString() + "_Internal");
+ }
+
+ private ClassName implName(TypeElement interfaceClass) {
+ return ClassName.bestGuess(interfaceClass.getQualifiedName().toString() + "_Impl");
+ }
+
+ private ClassName wrapperName(TypeElement interfaceClass) {
+ return ClassName.bestGuess(interfaceClass.getQualifiedName().toString() + "_Wrapper");
+ }
+
+ private ClassName profileTypeName(TypeElement interfaceClass) {
+ ClassName crossProfileInterfaceName = crossProfileInterfaceName(interfaceClass);
+ return ClassName.get(crossProfileInterfaceName.packageName(),
+ "Profile" + crossProfileInterfaceName.simpleName());
+ }
+
+ private Set<ExecutableElement> getMethods(TypeElement interfaceClass) {
+ return interfaceClass.getEnclosedElements().stream()
+ .filter(e -> e instanceof ExecutableElement)
+ .map(e -> (ExecutableElement) e)
+ .collect(Collectors.toSet());
+ }
+
+ private void showError(String errorText, Element errorElement) {
+ processingEnv
+ .getMessager()
+ .printMessage(Diagnostic.Kind.ERROR, errorText, errorElement);
+ }
+}
diff --git a/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/RemoteDpcAutomaticAdmin.java b/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/RemoteDpcAutomaticAdmin.java
new file mode 100644
index 0000000..b609a0e
--- /dev/null
+++ b/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/RemoteDpcAutomaticAdmin.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remotedpc.processor.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Applied to methods which map to an identically named method on the same interface but needs the
+ * admin {@link android.content.ComponentName} added as the first argument.
+ *
+ * <p>This should always be paired with another method which accepts the
+ * {@link android.content.ComponentName}.
+ *
+ * <p>For example:
+ * <pre><code>
+ * boolean isUsingUnifiedPassword(@NonNull ComponentName admin);
+ * @RemoteDpcAutomaticAdmin boolean isUsingUnifiedPassword();
+ * <code></pre>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface RemoteDpcAutomaticAdmin {
+}
diff --git a/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/RemoteDpcManager.java b/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/RemoteDpcManager.java
new file mode 100644
index 0000000..8d767a33
--- /dev/null
+++ b/common/device-side/bedstead/remotedpc/src/processor/main/java/com/android/bedstead/remotedpc/processor/annotations/RemoteDpcManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remotedpc.processor.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark an interface as wrapping a framework manager class for RemoteDPC.
+ *
+ * <p>Every method in the interface must have a matching method on the {@code managerClass} or else
+ * be annotated {@link RemoteDpcAutomaticAdmin}.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface RemoteDpcManager {
+
+ /**
+ * The manager class being wrapped.
+ *
+ * <p>For example, {@link android.app.admin.DevicePolicyManager}. */
+ Class<?> managerClass();
+}