Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,69 @@
*/
package org.androidannotations.api.builder;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public abstract class ActivityIntentBuilder<I extends ActivityIntentBuilder<I>> extends IntentBuilder<I> {
/**
* Base class for generated {@link android.app.Activity Activity} {@link Intent}
* builders, which provide a fluent API to build {@link Intent}s and start the
* generated {@link android.app.Activity Activity}.
*
* @param <I>
* The actual class, so method chain can return the generated class
* and provide generated methods
*/
public abstract class ActivityIntentBuilder<I extends ActivityIntentBuilder<I>> extends IntentBuilder<I> implements ActivityStarter {

protected Bundle lastOptions;

/**
* Creates a builder for a given {@link android.app.Activity Activity}
* class.
*
* @param context
* A {@link Context} of the application package implementing this
* class.
* @param clazz
* The component class that is to be used for the {@link Intent}.
*/
public ActivityIntentBuilder(Context context, Class<?> clazz) {
super(context, clazz);
}

/**
* Creates a builder which will append to a previously created
* {@link android.content.Intent Intent}.
*
* @param context
* A {@link Context} of the application package implementing this
* class.
* @param intent
* The previously created {@link Intent} to append to.
*
*/
public ActivityIntentBuilder(Context context, Intent intent) {
super(context, intent);
}

public void start() {
context.startActivity(intent);
@Override
public final void start() {
startForResult(-1);
}

public void startForResult(int requestCode) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
context.startActivity(intent);
}
@Override
public abstract void startForResult(int requestCode);

/**
* Adds additional options {@link Bundle} to the start method.
*
* @param options
* the {@link android.app.Activity Activity} options
* @return an {@link ActivityStarter} instance to provide starter methods
*/
public ActivityStarter withOptions(Bundle options) {
lastOptions = options;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.androidannotations.api.builder;

/**
* Provides methods for starting an {@link android.app.Activity Activity}.
*/
public interface ActivityStarter {

/**
* Starts the {@link android.app.Activity Activity}, by calling
* {@link android.app.Activity#startActivity(android.content.Intent)
* Activity#startActivity(android.content.Intent)} for the previously given
* {@link android.content.Context Context} or Fragment or support Fragment
* objects. It also passes the given extras, the options
* {@link android.os.Bundle Bundle}, if new methods are available which
* accept that.
*/
void start();

/**
* Starts the {@link android.app.Activity Activity} for result, by calling
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)
* Activity#startActivityForResult(android.content.Intent, int)} for the
* previously given {@link android.content.Context Context} or Fragment or
* support Fragment objects. It also passes the given extras, the options
* {@link android.os.Bundle Bundle}, if new methods are available which
* accept that.
*
* @param requestCode
* this code will be returned in onActivityResult() when the
* activity exits.
*/
void startForResult(int requestCode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
*/
package org.androidannotations.api.builder;

/**
* Base class for fluent builders.
*/
public abstract class Builder {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@
package org.androidannotations.helper;

import static com.sun.codemodel.JExpr._new;
import static com.sun.codemodel.JExpr._super;
import static com.sun.codemodel.JExpr.ref;
import static com.sun.codemodel.JMod.PRIVATE;
import static com.sun.codemodel.JMod.PUBLIC;
import static com.sun.codemodel.JMod.STATIC;

import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

import org.androidannotations.holder.HasIntentBuilder;

import com.sun.codemodel.JBlock;
Expand All @@ -29,7 +37,9 @@
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JVar;
Expand All @@ -38,16 +48,23 @@ public class ActivityIntentBuilder extends IntentBuilder {

private static final int MIN_SDK_WITH_FRAGMENT_SUPPORT = 11;

private static final int MIN_SDK_WITH_ACTIVITY_OPTIONS = 16;

private JFieldVar fragmentField;
private JFieldVar fragmentSupportField;

private JFieldRef optionsField;

public ActivityIntentBuilder(HasIntentBuilder holder, AndroidManifest androidManifest) {
super(holder, androidManifest);
}

@Override
public void build() throws JClassAlreadyExistsException {
super.build();

optionsField = ref("lastOptions");

createAdditionalConstructor(); // See issue #541
createAdditionalIntentMethods();
overrideStartForResultMethod();
Expand Down Expand Up @@ -97,9 +114,6 @@ private JFieldVar addFragmentConstructor(JClass fragmentClass, String fieldName)
}

private void overrideStartForResultMethod() {
if (fragmentSupportField == null && fragmentField == null) {
return;
}
JMethod method = holder.getIntentBuilderClass().method(PUBLIC, holder.codeModel().VOID, "startForResult");
method.annotate(Override.class);
JVar requestCode = method.param(holder.codeModel().INT, "requestCode");
Expand All @@ -117,10 +131,73 @@ private void overrideStartForResultMethod() {
} else {
condition = condition._elseif(fragmentField.ne(JExpr._null()));
}
condition._then() //

JBlock fragmentStartForResultInvocationBlock;

if (hasActivityOptionsInFragment() && shouldGuardActivityOptions()) {
fragmentStartForResultInvocationBlock = createCallWithIfGuard(requestCode, condition._then(), fragmentField);
} else {
fragmentStartForResultInvocationBlock = condition._then();
}
JInvocation invocation = fragmentStartForResultInvocationBlock //
.invoke(fragmentField, "startActivityForResult").arg(intentField).arg(requestCode);
if (hasActivityOptionsInFragment()) {
invocation.arg(optionsField);
}
}

JBlock activityStartInvocationBlock = null;

if (condition != null) {
activityStartInvocationBlock = condition._else();
} else {
activityStartInvocationBlock = method.body();
}

JConditional activityCondition = activityStartInvocationBlock._if(contextField._instanceof(holder.classes().ACTIVITY));
JBlock thenBlock = activityCondition._then();
JVar activityVar = thenBlock.decl(holder.classes().ACTIVITY, "activity", JExpr.cast(holder.classes().ACTIVITY, contextField));

if (hasActivityCompatInClasspath() && hasActivityOptionsInActivityCompat()) {
thenBlock.staticInvoke(holder.classes().ACTIVITY_COMPAT, "startActivityForResult") //
.arg(activityVar).arg(intentField).arg(requestCode).arg(optionsField);
} else if (hasActivityOptionsInFragment()) {
JBlock startForResultInvocationBlock;
if (shouldGuardActivityOptions()) {
startForResultInvocationBlock = createCallWithIfGuard(requestCode, thenBlock, activityVar);
} else {
startForResultInvocationBlock = thenBlock;
}

startForResultInvocationBlock.invoke(activityVar, "startActivityForResult") //
.arg(intentField).arg(requestCode).arg(optionsField);
} else {
thenBlock.invoke(activityVar, "startActivityForResult").arg(intentField).arg(requestCode);
}

if (hasActivityOptionsInFragment()) {
JBlock startInvocationBlock;
if (shouldGuardActivityOptions()) {
startInvocationBlock = createCallWithIfGuard(null, activityCondition._else(), contextField);
} else {
startInvocationBlock = activityCondition._else();
}
startInvocationBlock.invoke(contextField, "startActivity").arg(intentField).arg(optionsField);
} else {
activityCondition._else().invoke(contextField, "startActivity").arg(intentField);
}
}

private JBlock createCallWithIfGuard(JVar requestCode, JBlock thenBlock, JExpression invocationTarget) {
JConditional guardIf = thenBlock._if(holder.classes().BUILD_VERSION.staticRef("SDK_INT").gte(holder.classes().BUILD_VERSION_CODES.staticRef("JELLY_BEAN")));
JBlock startInvocationBlock = guardIf._then();
String methodName = requestCode != null ? "startActivityForResult" : "startActivity";

JInvocation invocation = guardIf._else().invoke(invocationTarget, methodName).arg(intentField);
if (requestCode != null) {
invocation.arg(requestCode);
}
condition._else().invoke(_super(), "startForResult").arg(requestCode);
return startInvocationBlock;
}

protected boolean hasFragmentInClasspath() {
Expand All @@ -131,4 +208,50 @@ protected boolean hasFragmentInClasspath() {
protected boolean hasFragmentSupportInClasspath() {
return elementUtils.getTypeElement(CanonicalNameConstants.SUPPORT_V4_FRAGMENT) != null;
}

protected boolean hasActivityCompatInClasspath() {
return elementUtils.getTypeElement(CanonicalNameConstants.ACTIVITY_COMPAT) != null;
}

protected boolean hasActivityOptionsInFragment() {
if (!hasFragmentInClasspath()) {
return false;
}

TypeElement fragment = elementUtils.getTypeElement(CanonicalNameConstants.FRAGMENT);

return hasActivityOptions(fragment, 1);
}

protected boolean hasActivityOptionsInActivityCompat() {
TypeElement activityCompat = elementUtils.getTypeElement(CanonicalNameConstants.ACTIVITY_COMPAT);

return hasActivityOptions(activityCompat, 2);
}

private boolean hasActivityOptions(TypeElement type, int optionsParamPosition) {
if (type == null) {
return false;
}

for (Element element : type.getEnclosedElements()) {
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
if (executableElement.getSimpleName().contentEquals("startActivity")) {
List<? extends VariableElement> parameters = executableElement.getParameters();
if (parameters.size() == optionsParamPosition + 1) {
VariableElement parameter = parameters.get(optionsParamPosition);
if (parameter.asType().toString().equals(CanonicalNameConstants.BUNDLE)) {
return true;
}
}
}
}
}
return false;
}

protected boolean shouldGuardActivityOptions() {
return androidManifest.getMinSdkVersion() < MIN_SDK_WITH_ACTIVITY_OPTIONS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public final class CanonicalNameConstants {
public static final String LOOPER = "android.os.Looper";
public static final String POWER_MANAGER = "android.os.PowerManager";
public static final String WAKE_LOCK = "android.os.PowerManager.WakeLock";
public static final String BUILD_VERSION = "android.os.Build.VERSION";
public static final String BUILD_VERSION_CODES = "android.os.Build.VERSION_CODES";
public static final String ACTIVITY_COMPAT = "android.support.v4.app.ActivityCompat";

/*
* Android permission
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.io.InputStream;
import java.io.Serializable;
import java.sql.SQLException;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -119,6 +118,9 @@ public class Classes {
public final JClass LOOPER = refClass(CanonicalNameConstants.LOOPER);
public final JClass POWER_MANAGER = refClass(CanonicalNameConstants.POWER_MANAGER);
public final JClass WAKE_LOCK = refClass(CanonicalNameConstants.WAKE_LOCK);
public final JClass BUILD_VERSION = refClass(CanonicalNameConstants.BUILD_VERSION);
public final JClass BUILD_VERSION_CODES = refClass(CanonicalNameConstants.BUILD_VERSION_CODES);
public final JClass ACTIVITY_COMPAT = refClass(CanonicalNameConstants.ACTIVITY_COMPAT);

/*
* Sherlock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ public void setUp() {
@Test
public void activityIntentFragmentMinSdkFroyoCompilesWithFroyo() {
addManifestProcessorParameter(ActivityIntentFragmentTest.class, "AndroidManifestMinFroyo.xml");
CompileResult result = compileFiles(ActivityInManifest.class);
// we need android.os.Build in the classpath
CompileResult result = compileFiles(ActivityInManifest.class /*
* ,toPath(
* ActivityIntentFragmentTest
* .class,
* "Build.java"
* )
*/);
File generatedFile = toGeneratedFile(ActivityInManifest.class);

assertCompilationSuccessful(result);
Expand Down Expand Up @@ -68,9 +75,25 @@ public void activityIntentFragmentMinSdkJBCompileWithJB() {
@Test
public void activityIntentFragmentCompilesWithSupport() {
// To simulate android support v4 in classpath, we add
// android.support.v4.Fragment in classpath
// android.support.v4.Fragment and android.support.v4.app.ActivityCompat
// in classpath
addManifestProcessorParameter(ActivityIntentFragmentTest.class, "AndroidManifestMinFroyo.xml");
CompileResult result = compileFiles(toPath(ActivityIntentFragmentTest.class, "support/Fragment.java"), ActivityInManifest.class);
CompileResult result = compileFiles(toPath(ActivityIntentFragmentTest.class, "support/Fragment.java"), //
toPath(ActivityIntentFragmentTest.class, "support/ActivityCompat.java"), ActivityInManifest.class);
File generatedFile = toGeneratedFile(ActivityInManifest.class);

assertCompilationSuccessful(result);
assertGeneratedClassMatches(generatedFile, INTENT_FRAGMENT_SUPPORT_SIGNATURE);
}

@Test
public void activityIntentFragmentCompilesWithSupportContainingBundleOptions() {
// To simulate android support v4 in classpath, we add
// android.support.v4.Fragment and android.support.v4.app.ActivityCompat
// in classpath
addManifestProcessorParameter(ActivityIntentFragmentTest.class, "AndroidManifestMinFroyo.xml");
CompileResult result = compileFiles(toPath(ActivityIntentFragmentTest.class, "support/Fragment.java"), //
toPath(ActivityIntentFragmentTest.class, "ActivityCompat.java"), ActivityInManifest.class);
File generatedFile = toGeneratedFile(ActivityInManifest.class);

assertCompilationSuccessful(result);
Expand Down
Loading