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