1/*
2 * Copyright (C) 2016 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 */
16package com.android.contacts;
17
18import android.app.Activity;
19import android.app.Instrumentation;
20import android.content.Context;
21import android.os.Bundle;
22import android.os.Debug;
23import android.support.test.InstrumentationRegistry;
24import android.util.Log;
25
26import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Method;
28
29/**
30 * Runs a single static method specified via the arguments.
31 *
32 * Useful for manipulating the app state during manual testing. If the class argument is omitted
33 * this class will attempt to invoke a method in
34 * {@link com.android.contacts.tests.AdbHelpers}
35 *
36 * Valid signatures: void f(Context, Bundle), void f(Context), void f()
37 *
38 * Example usage:
39 * $ adb shell am instrument -e class com.android.contacts.Foo -e method bar -e someArg someValue\
40 *   -w com.google.android.contacts.tests/com.android.contacts.RunMethodInstrumentation
41 */
42public class RunMethodInstrumentation extends Instrumentation {
43
44    private static final String TAG = "RunMethod";
45
46    private static final String DEFAULT_CLASS = "AdbHelpers";
47
48    private String className;
49    private String methodName;
50    private Bundle args;
51
52    @Override
53    public void onCreate(Bundle arguments) {
54        super.onCreate(arguments);
55
56        InstrumentationRegistry.registerInstance(this, arguments);
57
58        className = arguments.getString("class", getContext().getPackageName() + "." +
59                DEFAULT_CLASS);
60        methodName = arguments.getString("method");
61        args = arguments;
62
63        if (Log.isLoggable(TAG, Log.DEBUG)) {
64            Log.d(TAG, "Running " + className + "." + methodName);
65            Log.d(TAG, "args=" + args);
66        }
67
68        if (arguments.containsKey("debug") && Boolean.parseBoolean(arguments.getString("debug"))) {
69            Debug.waitForDebugger();
70        }
71        start();
72    }
73
74    @Override
75    public void onStart() {
76        if (Log.isLoggable(TAG, Log.DEBUG)) {
77            Log.d(TAG, "onStart");
78        }
79        super.onStart();
80
81        if (className == null || methodName == null) {
82            Log.e(TAG, "Must supply class and method");
83            finish(Activity.RESULT_CANCELED, null);
84            return;
85        }
86
87        // Wait for the Application to finish creating.
88        runOnMainSync(new Runnable() {
89            @Override
90            public void run() {
91                if (Log.isLoggable(TAG, Log.DEBUG)) {
92                    Log.d(TAG, "acquired main thread from instrumentation");
93                }
94            }
95        });
96
97        try {
98            invokeMethod(args, className, methodName);
99        } catch (Exception e) {
100            e.printStackTrace();
101            finish(Activity.RESULT_CANCELED, null);
102            return;
103        }
104        // Maybe should let the method determine when this is called.
105        finish(Activity.RESULT_OK, null);
106    }
107
108    private void invokeMethod(Bundle args, String className, String methodName) throws
109            InvocationTargetException, IllegalAccessException, NoSuchMethodException,
110            ClassNotFoundException {
111        Context context;
112        Class<?> clazz = null;
113        try {
114            // Try to load from App's code
115            clazz = getTargetContext().getClassLoader().loadClass(className);
116            context = getTargetContext();
117        } catch (Exception e) {
118            // Try to load from Test App's code
119            clazz = getContext().getClassLoader().loadClass(className);
120            context = getContext();
121        }
122
123        Object[] methodArgs = null;
124        Method method = null;
125
126        try {
127            method = clazz.getMethod(methodName, Context.class, Bundle.class);
128            methodArgs = new Object[] { context, args };
129        } catch (NoSuchMethodException e) {
130        }
131
132        if (method != null) {
133            method.invoke(clazz, methodArgs);
134            return;
135        }
136
137        try {
138            method = clazz.getMethod(methodName, Context.class);
139            methodArgs = new Object[] { context };
140        } catch (NoSuchMethodException e) {
141        }
142
143        if (method != null) {
144            method.invoke(clazz, methodArgs);
145            return;
146        }
147
148        method = clazz.getMethod(methodName);
149        method.invoke(clazz);
150    }
151}
152