Skip to content

Commit dadd194

Browse files
committed
Merge pull request androidannotations#1297 from WonderCsabo/1027_activityIntentBuilderWithOptions
Activity Intent builder with options
2 parents e608029 + ac156ed commit dadd194

File tree

9 files changed

+268
-20
lines changed

9 files changed

+268
-20
lines changed

AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/api/builder/ActivityIntentBuilder.java

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,69 @@
1515
*/
1616
package org.androidannotations.api.builder;
1717

18-
import android.app.Activity;
1918
import android.content.Context;
2019
import android.content.Intent;
20+
import android.os.Bundle;
2121

22-
public abstract class ActivityIntentBuilder<I extends ActivityIntentBuilder<I>> extends IntentBuilder<I> {
22+
/**
23+
* Base class for generated {@link android.app.Activity Activity} {@link Intent}
24+
* builders, which provide a fluent API to build {@link Intent}s and start the
25+
* generated {@link android.app.Activity Activity}.
26+
*
27+
* @param <I>
28+
* The actual class, so method chain can return the generated class
29+
* and provide generated methods
30+
*/
31+
public abstract class ActivityIntentBuilder<I extends ActivityIntentBuilder<I>> extends IntentBuilder<I> implements ActivityStarter {
2332

33+
protected Bundle lastOptions;
34+
35+
/**
36+
* Creates a builder for a given {@link android.app.Activity Activity}
37+
* class.
38+
*
39+
* @param context
40+
* A {@link Context} of the application package implementing this
41+
* class.
42+
* @param clazz
43+
* The component class that is to be used for the {@link Intent}.
44+
*/
2445
public ActivityIntentBuilder(Context context, Class<?> clazz) {
2546
super(context, clazz);
2647
}
2748

49+
/**
50+
* Creates a builder which will append to a previously created
51+
* {@link android.content.Intent Intent}.
52+
*
53+
* @param context
54+
* A {@link Context} of the application package implementing this
55+
* class.
56+
* @param intent
57+
* The previously created {@link Intent} to append to.
58+
*
59+
*/
2860
public ActivityIntentBuilder(Context context, Intent intent) {
2961
super(context, intent);
3062
}
3163

32-
public void start() {
33-
context.startActivity(intent);
64+
@Override
65+
public final void start() {
66+
startForResult(-1);
3467
}
3568

36-
public void startForResult(int requestCode) {
37-
if (context instanceof Activity) {
38-
((Activity) context).startActivityForResult(intent, requestCode);
39-
} else {
40-
context.startActivity(intent);
41-
}
69+
@Override
70+
public abstract void startForResult(int requestCode);
71+
72+
/**
73+
* Adds additional options {@link Bundle} to the start method.
74+
*
75+
* @param options
76+
* the {@link android.app.Activity Activity} options
77+
* @return an {@link ActivityStarter} instance to provide starter methods
78+
*/
79+
public ActivityStarter withOptions(Bundle options) {
80+
lastOptions = options;
81+
return this;
4282
}
4383
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.androidannotations.api.builder;
2+
3+
/**
4+
* Provides methods for starting an {@link android.app.Activity Activity}.
5+
*/
6+
public interface ActivityStarter {
7+
8+
/**
9+
* Starts the {@link android.app.Activity Activity}, by calling
10+
* {@link android.app.Activity#startActivity(android.content.Intent)
11+
* Activity#startActivity(android.content.Intent)} for the previously given
12+
* {@link android.content.Context Context} or Fragment or support Fragment
13+
* objects. It also passes the given extras, the options
14+
* {@link android.os.Bundle Bundle}, if new methods are available which
15+
* accept that.
16+
*/
17+
void start();
18+
19+
/**
20+
* Starts the {@link android.app.Activity Activity} for result, by calling
21+
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)
22+
* Activity#startActivityForResult(android.content.Intent, int)} for the
23+
* previously given {@link android.content.Context Context} or Fragment or
24+
* support Fragment objects. It also passes the given extras, the options
25+
* {@link android.os.Bundle Bundle}, if new methods are available which
26+
* accept that.
27+
*
28+
* @param requestCode
29+
* this code will be returned in onActivityResult() when the
30+
* activity exits.
31+
*/
32+
void startForResult(int requestCode);
33+
}

AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/api/builder/Builder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@
1515
*/
1616
package org.androidannotations.api.builder;
1717

18+
/**
19+
* Base class for fluent builders.
20+
*/
1821
public abstract class Builder {
1922
}

AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/ActivityIntentBuilder.java

Lines changed: 129 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@
1616
package org.androidannotations.helper;
1717

1818
import static com.sun.codemodel.JExpr._new;
19-
import static com.sun.codemodel.JExpr._super;
19+
import static com.sun.codemodel.JExpr.ref;
2020
import static com.sun.codemodel.JMod.PRIVATE;
2121
import static com.sun.codemodel.JMod.PUBLIC;
2222
import static com.sun.codemodel.JMod.STATIC;
2323

24+
import java.util.List;
25+
26+
import javax.lang.model.element.Element;
27+
import javax.lang.model.element.ElementKind;
28+
import javax.lang.model.element.ExecutableElement;
29+
import javax.lang.model.element.TypeElement;
30+
import javax.lang.model.element.VariableElement;
31+
2432
import org.androidannotations.holder.HasIntentBuilder;
2533

2634
import com.sun.codemodel.JBlock;
@@ -29,7 +37,9 @@
2937
import com.sun.codemodel.JConditional;
3038
import com.sun.codemodel.JExpr;
3139
import com.sun.codemodel.JExpression;
40+
import com.sun.codemodel.JFieldRef;
3241
import com.sun.codemodel.JFieldVar;
42+
import com.sun.codemodel.JInvocation;
3343
import com.sun.codemodel.JMethod;
3444
import com.sun.codemodel.JMod;
3545
import com.sun.codemodel.JVar;
@@ -38,16 +48,23 @@ public class ActivityIntentBuilder extends IntentBuilder {
3848

3949
private static final int MIN_SDK_WITH_FRAGMENT_SUPPORT = 11;
4050

51+
private static final int MIN_SDK_WITH_ACTIVITY_OPTIONS = 16;
52+
4153
private JFieldVar fragmentField;
4254
private JFieldVar fragmentSupportField;
4355

56+
private JFieldRef optionsField;
57+
4458
public ActivityIntentBuilder(HasIntentBuilder holder, AndroidManifest androidManifest) {
4559
super(holder, androidManifest);
4660
}
4761

4862
@Override
4963
public void build() throws JClassAlreadyExistsException {
5064
super.build();
65+
66+
optionsField = ref("lastOptions");
67+
5168
createAdditionalConstructor(); // See issue #541
5269
createAdditionalIntentMethods();
5370
overrideStartForResultMethod();
@@ -97,9 +114,6 @@ private JFieldVar addFragmentConstructor(JClass fragmentClass, String fieldName)
97114
}
98115

99116
private void overrideStartForResultMethod() {
100-
if (fragmentSupportField == null && fragmentField == null) {
101-
return;
102-
}
103117
JMethod method = holder.getIntentBuilderClass().method(PUBLIC, holder.codeModel().VOID, "startForResult");
104118
method.annotate(Override.class);
105119
JVar requestCode = method.param(holder.codeModel().INT, "requestCode");
@@ -117,10 +131,73 @@ private void overrideStartForResultMethod() {
117131
} else {
118132
condition = condition._elseif(fragmentField.ne(JExpr._null()));
119133
}
120-
condition._then() //
134+
135+
JBlock fragmentStartForResultInvocationBlock;
136+
137+
if (hasActivityOptionsInFragment() && shouldGuardActivityOptions()) {
138+
fragmentStartForResultInvocationBlock = createCallWithIfGuard(requestCode, condition._then(), fragmentField);
139+
} else {
140+
fragmentStartForResultInvocationBlock = condition._then();
141+
}
142+
JInvocation invocation = fragmentStartForResultInvocationBlock //
121143
.invoke(fragmentField, "startActivityForResult").arg(intentField).arg(requestCode);
144+
if (hasActivityOptionsInFragment()) {
145+
invocation.arg(optionsField);
146+
}
147+
}
148+
149+
JBlock activityStartInvocationBlock = null;
150+
151+
if (condition != null) {
152+
activityStartInvocationBlock = condition._else();
153+
} else {
154+
activityStartInvocationBlock = method.body();
155+
}
156+
157+
JConditional activityCondition = activityStartInvocationBlock._if(contextField._instanceof(holder.classes().ACTIVITY));
158+
JBlock thenBlock = activityCondition._then();
159+
JVar activityVar = thenBlock.decl(holder.classes().ACTIVITY, "activity", JExpr.cast(holder.classes().ACTIVITY, contextField));
160+
161+
if (hasActivityCompatInClasspath() && hasActivityOptionsInActivityCompat()) {
162+
thenBlock.staticInvoke(holder.classes().ACTIVITY_COMPAT, "startActivityForResult") //
163+
.arg(activityVar).arg(intentField).arg(requestCode).arg(optionsField);
164+
} else if (hasActivityOptionsInFragment()) {
165+
JBlock startForResultInvocationBlock;
166+
if (shouldGuardActivityOptions()) {
167+
startForResultInvocationBlock = createCallWithIfGuard(requestCode, thenBlock, activityVar);
168+
} else {
169+
startForResultInvocationBlock = thenBlock;
170+
}
171+
172+
startForResultInvocationBlock.invoke(activityVar, "startActivityForResult") //
173+
.arg(intentField).arg(requestCode).arg(optionsField);
174+
} else {
175+
thenBlock.invoke(activityVar, "startActivityForResult").arg(intentField).arg(requestCode);
176+
}
177+
178+
if (hasActivityOptionsInFragment()) {
179+
JBlock startInvocationBlock;
180+
if (shouldGuardActivityOptions()) {
181+
startInvocationBlock = createCallWithIfGuard(null, activityCondition._else(), contextField);
182+
} else {
183+
startInvocationBlock = activityCondition._else();
184+
}
185+
startInvocationBlock.invoke(contextField, "startActivity").arg(intentField).arg(optionsField);
186+
} else {
187+
activityCondition._else().invoke(contextField, "startActivity").arg(intentField);
188+
}
189+
}
190+
191+
private JBlock createCallWithIfGuard(JVar requestCode, JBlock thenBlock, JExpression invocationTarget) {
192+
JConditional guardIf = thenBlock._if(holder.classes().BUILD_VERSION.staticRef("SDK_INT").gte(holder.classes().BUILD_VERSION_CODES.staticRef("JELLY_BEAN")));
193+
JBlock startInvocationBlock = guardIf._then();
194+
String methodName = requestCode != null ? "startActivityForResult" : "startActivity";
195+
196+
JInvocation invocation = guardIf._else().invoke(invocationTarget, methodName).arg(intentField);
197+
if (requestCode != null) {
198+
invocation.arg(requestCode);
122199
}
123-
condition._else().invoke(_super(), "startForResult").arg(requestCode);
200+
return startInvocationBlock;
124201
}
125202

126203
protected boolean hasFragmentInClasspath() {
@@ -131,4 +208,50 @@ protected boolean hasFragmentInClasspath() {
131208
protected boolean hasFragmentSupportInClasspath() {
132209
return elementUtils.getTypeElement(CanonicalNameConstants.SUPPORT_V4_FRAGMENT) != null;
133210
}
211+
212+
protected boolean hasActivityCompatInClasspath() {
213+
return elementUtils.getTypeElement(CanonicalNameConstants.ACTIVITY_COMPAT) != null;
214+
}
215+
216+
protected boolean hasActivityOptionsInFragment() {
217+
if (!hasFragmentInClasspath()) {
218+
return false;
219+
}
220+
221+
TypeElement fragment = elementUtils.getTypeElement(CanonicalNameConstants.FRAGMENT);
222+
223+
return hasActivityOptions(fragment, 1);
224+
}
225+
226+
protected boolean hasActivityOptionsInActivityCompat() {
227+
TypeElement activityCompat = elementUtils.getTypeElement(CanonicalNameConstants.ACTIVITY_COMPAT);
228+
229+
return hasActivityOptions(activityCompat, 2);
230+
}
231+
232+
private boolean hasActivityOptions(TypeElement type, int optionsParamPosition) {
233+
if (type == null) {
234+
return false;
235+
}
236+
237+
for (Element element : type.getEnclosedElements()) {
238+
if (element.getKind() == ElementKind.METHOD) {
239+
ExecutableElement executableElement = (ExecutableElement) element;
240+
if (executableElement.getSimpleName().contentEquals("startActivity")) {
241+
List<? extends VariableElement> parameters = executableElement.getParameters();
242+
if (parameters.size() == optionsParamPosition + 1) {
243+
VariableElement parameter = parameters.get(optionsParamPosition);
244+
if (parameter.asType().toString().equals(CanonicalNameConstants.BUNDLE)) {
245+
return true;
246+
}
247+
}
248+
}
249+
}
250+
}
251+
return false;
252+
}
253+
254+
protected boolean shouldGuardActivityOptions() {
255+
return androidManifest.getMinSdkVersion() < MIN_SDK_WITH_ACTIVITY_OPTIONS;
256+
}
134257
}

AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/CanonicalNameConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public final class CanonicalNameConstants {
107107
public static final String LOOPER = "android.os.Looper";
108108
public static final String POWER_MANAGER = "android.os.PowerManager";
109109
public static final String WAKE_LOCK = "android.os.PowerManager.WakeLock";
110+
public static final String BUILD_VERSION = "android.os.Build.VERSION";
111+
public static final String BUILD_VERSION_CODES = "android.os.Build.VERSION_CODES";
112+
public static final String ACTIVITY_COMPAT = "android.support.v4.app.ActivityCompat";
110113

111114
/*
112115
* Android permission

AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/process/ProcessHolder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.InputStream;
2020
import java.io.Serializable;
2121
import java.sql.SQLException;
22-
2322
import java.util.Arrays;
2423
import java.util.Collections;
2524
import java.util.HashMap;
@@ -119,6 +118,9 @@ public class Classes {
119118
public final JClass LOOPER = refClass(CanonicalNameConstants.LOOPER);
120119
public final JClass POWER_MANAGER = refClass(CanonicalNameConstants.POWER_MANAGER);
121120
public final JClass WAKE_LOCK = refClass(CanonicalNameConstants.WAKE_LOCK);
121+
public final JClass BUILD_VERSION = refClass(CanonicalNameConstants.BUILD_VERSION);
122+
public final JClass BUILD_VERSION_CODES = refClass(CanonicalNameConstants.BUILD_VERSION_CODES);
123+
public final JClass ACTIVITY_COMPAT = refClass(CanonicalNameConstants.ACTIVITY_COMPAT);
122124

123125
/*
124126
* Sherlock

AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/generation/ActivityIntentFragmentTest.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,14 @@ public void setUp() {
3636
@Test
3737
public void activityIntentFragmentMinSdkFroyoCompilesWithFroyo() {
3838
addManifestProcessorParameter(ActivityIntentFragmentTest.class, "AndroidManifestMinFroyo.xml");
39-
CompileResult result = compileFiles(ActivityInManifest.class);
39+
// we need android.os.Build in the classpath
40+
CompileResult result = compileFiles(ActivityInManifest.class /*
41+
* ,toPath(
42+
* ActivityIntentFragmentTest
43+
* .class,
44+
* "Build.java"
45+
* )
46+
*/);
4047
File generatedFile = toGeneratedFile(ActivityInManifest.class);
4148

4249
assertCompilationSuccessful(result);
@@ -68,9 +75,25 @@ public void activityIntentFragmentMinSdkJBCompileWithJB() {
6875
@Test
6976
public void activityIntentFragmentCompilesWithSupport() {
7077
// To simulate android support v4 in classpath, we add
71-
// android.support.v4.Fragment in classpath
78+
// android.support.v4.Fragment and android.support.v4.app.ActivityCompat
79+
// in classpath
7280
addManifestProcessorParameter(ActivityIntentFragmentTest.class, "AndroidManifestMinFroyo.xml");
73-
CompileResult result = compileFiles(toPath(ActivityIntentFragmentTest.class, "support/Fragment.java"), ActivityInManifest.class);
81+
CompileResult result = compileFiles(toPath(ActivityIntentFragmentTest.class, "support/Fragment.java"), //
82+
toPath(ActivityIntentFragmentTest.class, "support/ActivityCompat.java"), ActivityInManifest.class);
83+
File generatedFile = toGeneratedFile(ActivityInManifest.class);
84+
85+
assertCompilationSuccessful(result);
86+
assertGeneratedClassMatches(generatedFile, INTENT_FRAGMENT_SUPPORT_SIGNATURE);
87+
}
88+
89+
@Test
90+
public void activityIntentFragmentCompilesWithSupportContainingBundleOptions() {
91+
// To simulate android support v4 in classpath, we add
92+
// android.support.v4.Fragment and android.support.v4.app.ActivityCompat
93+
// in classpath
94+
addManifestProcessorParameter(ActivityIntentFragmentTest.class, "AndroidManifestMinFroyo.xml");
95+
CompileResult result = compileFiles(toPath(ActivityIntentFragmentTest.class, "support/Fragment.java"), //
96+
toPath(ActivityIntentFragmentTest.class, "ActivityCompat.java"), ActivityInManifest.class);
7497
File generatedFile = toGeneratedFile(ActivityInManifest.class);
7598

7699
assertCompilationSuccessful(result);

0 commit comments

Comments
 (0)