1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.test;
18
19import android.app.Activity;
20import android.content.Intent;
21
22import java.lang.reflect.Method;
23
24/**
25 * This class provides functional testing of a single activity.  The activity under test will
26 * be created using the system infrastructure (by calling InstrumentationTestCase.launchActivity())
27 * and you will then be able to manipulate your Activity directly.
28 *
29 * <p>Other options supported by this test case include:
30 * <ul>
31 * <li>You can run any test method on the UI thread (see {@link android.test.UiThreadTest}).</li>
32 * <li>You can inject custom Intents into your Activity (see
33 * {@link #setActivityIntent(Intent)}).</li>
34 * </ul>
35 *
36 * <p>This class replaces {@link android.test.ActivityInstrumentationTestCase}, which is deprecated.
37 * New tests should be written using this base class.
38 *
39 * <p>If you prefer an isolated unit test, see {@link android.test.ActivityUnitTestCase}.
40 *
41 * <div class="special reference">
42 * <h3>Developer Guides</h3>
43 * <p>For more information about application testing, read the
44 * <a href="{@docRoot}guide/topics/testing/index.html">Testing</a> developer guide.</p>
45 * </div>
46 */
47public abstract class ActivityInstrumentationTestCase2<T extends Activity>
48        extends ActivityTestCase {
49    Class<T> mActivityClass;
50    boolean mInitialTouchMode = false;
51    Intent mActivityIntent = null;
52
53    /**
54     * Creates an {@link ActivityInstrumentationTestCase2}.
55     *
56     * @param pkg ignored - no longer in use.
57     * @param activityClass The activity to test. This must be a class in the instrumentation
58     * targetPackage specified in the AndroidManifest.xml
59     *
60     * @deprecated use {@link #ActivityInstrumentationTestCase2(Class)} instead
61     */
62    @Deprecated
63    public ActivityInstrumentationTestCase2(String pkg, Class<T> activityClass) {
64        this(activityClass);
65    }
66
67    /**
68     * Creates an {@link ActivityInstrumentationTestCase2}.
69     *
70     * @param activityClass The activity to test. This must be a class in the instrumentation
71     * targetPackage specified in the AndroidManifest.xml
72     */
73    public ActivityInstrumentationTestCase2(Class<T> activityClass) {
74        mActivityClass = activityClass;
75    }
76
77    /**
78     * Get the Activity under test, starting it if necessary.
79     *
80     * For each test method invocation, the Activity will not actually be created until the first
81     * time this method is called.
82     *
83     * <p>If you wish to provide custom setup values to your Activity, you may call
84     * {@link #setActivityIntent(Intent)} and/or {@link #setActivityInitialTouchMode(boolean)}
85     * before your first call to getActivity().  Calling them after your Activity has
86     * started will have no effect.
87     *
88     * <p><b>NOTE:</b> Activities under test may not be started from within the UI thread.
89     * If your test method is annotated with {@link android.test.UiThreadTest}, then your Activity
90     * will be started automatically just before your test method is run.  You still call this
91     * method in order to get the Activity under test.
92     *
93     * @return the Activity under test
94     */
95    @Override
96    public T getActivity() {
97        Activity a = super.getActivity();
98        if (a == null) {
99            // set initial touch mode
100            getInstrumentation().setInTouchMode(mInitialTouchMode);
101            final String targetPackage = getInstrumentation().getTargetContext().getPackageName();
102            // inject custom intent, if provided
103            if (mActivityIntent == null) {
104                a = launchActivity(targetPackage, mActivityClass, null);
105            } else {
106                a = launchActivityWithIntent(targetPackage, mActivityClass, mActivityIntent);
107            }
108            setActivity(a);
109        }
110        return (T) a;
111    }
112
113    /**
114     * Call this method before the first call to {@link #getActivity} to inject a customized Intent
115     * into the Activity under test.
116     *
117     * <p>If you do not call this, the default intent will be provided.  If you call this after
118     * your Activity has been started, it will have no effect.
119     *
120     * <p><b>NOTE:</b> Activities under test may not be started from within the UI thread.
121     * If your test method is annotated with {@link android.test.UiThreadTest}, then you must call
122     * {@link #setActivityIntent(Intent)} from {@link #setUp()}.
123     *
124     * <p>The default Intent (if this method is not called) is:
125     *  action = {@link Intent#ACTION_MAIN}
126     *  flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
127     * All other fields are null or empty.
128     *
129     * @param i The Intent to start the Activity with, or null to reset to the default Intent.
130     */
131    public void setActivityIntent(Intent i) {
132        mActivityIntent = i;
133    }
134
135    /**
136     * Call this method before the first call to {@link #getActivity} to set the initial touch
137     * mode for the Activity under test.
138     *
139     * <p>If you do not call this, the touch mode will be false.  If you call this after
140     * your Activity has been started, it will have no effect.
141     *
142     * <p><b>NOTE:</b> Activities under test may not be started from within the UI thread.
143     * If your test method is annotated with {@link android.test.UiThreadTest}, then you must call
144     * {@link #setActivityInitialTouchMode(boolean)} from {@link #setUp()}.
145     *
146     * @param initialTouchMode true if the Activity should be placed into "touch mode" when started
147     */
148    public void setActivityInitialTouchMode(boolean initialTouchMode) {
149        mInitialTouchMode = initialTouchMode;
150    }
151
152    @Override
153    protected void setUp() throws Exception {
154        super.setUp();
155
156        mInitialTouchMode = false;
157        mActivityIntent = null;
158    }
159
160    @Override
161    protected void tearDown() throws Exception {
162        // Finish the Activity off (unless was never launched anyway)
163        Activity a = super.getActivity();
164        if (a != null) {
165            a.finish();
166            setActivity(null);
167        }
168
169        // Scrub out members - protects against memory leaks in the case where someone
170        // creates a non-static inner class (thus referencing the test case) and gives it to
171        // someone else to hold onto
172        scrubClass(ActivityInstrumentationTestCase2.class);
173
174        super.tearDown();
175    }
176
177    /**
178     * Runs the current unit test. If the unit test is annotated with
179     * {@link android.test.UiThreadTest}, force the Activity to be created before switching to
180     * the UI thread.
181     */
182    @Override
183    protected void runTest() throws Throwable {
184        try {
185            Method method = getClass().getMethod(getName(), (Class[]) null);
186            if (method.isAnnotationPresent(UiThreadTest.class)) {
187                getActivity();
188            }
189        } catch (Exception e) {
190            // eat the exception here; super.runTest() will catch it again and handle it properly
191        }
192        super.runTest();
193    }
194
195}
196