• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.platform.test.helpers;
18 
19 import android.app.Instrumentation;
20 import android.content.Context;
21 import android.util.Log;
22 import dalvik.system.DexFile;
23 import java.io.File;
24 import java.io.IOException;
25 import java.lang.ClassLoader;
26 import java.lang.ClassNotFoundException;
27 import java.lang.IllegalAccessException;
28 import java.lang.InstantiationException;
29 import java.lang.NoSuchMethodException;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.InvocationTargetException;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.Enumeration;
36 import java.util.List;
37 
38 /**
39  * The HelperManager class is used to remove any explicit or hard-coded dependencies on app helper
40  * implementations. Instead, it provides a method for abstracting helper instantiation by specifying
41  * a base class for which you require an implementation. The manager effectively searches for a
42  * suitable implementation using runtime class loading.
43  * <p>
44  * The class provides two means for finding the necessary classes:
45  * <ol>
46  * <li> Static inclusion - if all the code is included in the final APK, the Context can be used to
47  * generate a HelperManager and to instantiate implementations.
48  * <li> Dexed file inclusion - if this manager and the helper implementations are bundled into dex
49  * files and loaded from a single class loader, then the files can be used to generate a
50  * HelperManager and to instantiate implementations.
51  * </ol>
52  * <p>
53  * Including and using this strategy will prune the explicit dependency tree for the App Helper
54  * Library and provide a more robust library for use across the Android source tree.
55  */
56 public class HelperManager {
57     private static final String LOG_TAG = HelperManager.class.getSimpleName();
58     private static HelperManager sInstance;
59 
60     /**
61      * Returns an instance of the HelperManager that searches the supplied application context for
62      * classes to instantiate to helper implementations.
63      *
64      * @param context the application context to search
65      * @param instr the active instrumentation
66      * @return a new instance of the HelperManager class
67      */
getInstance(Context context, Instrumentation instr)68     public static HelperManager getInstance(Context context, Instrumentation instr) {
69         if (sInstance == null) {
70             // Input checks
71             if (context == null) {
72                 throw new NullPointerException("Cannot pass in a null context.");
73             }
74             if (instr == null) {
75                 throw new NullPointerException(
76                         String.format("Cannot pass in null instrumentation."));
77             }
78             // Instantiation
79             List<String> paths = Arrays.asList(context.getPackageCodePath());
80             sInstance = new HelperManager(paths, instr);
81         }
82         return sInstance;
83     }
84 
85     /**
86      * Returns an instance of the HelperManager that searches the supplied locations for classes to
87      * instantiate to helper implementations.
88      *
89      * @param paths the dex files where the classes are included
90      * @param instr the active instrumentation
91      * @throws IllegalArgumentException if the path is not a valid file
92      * @return a new instance of the HelperManager class
93      */
getInstance(List<String> paths, Instrumentation instr)94     public static HelperManager getInstance(List<String> paths, Instrumentation instr) {
95         if (sInstance == null) {
96             // Input checks
97             for (String path : paths) {
98                 if (!(new File(path)).exists()) {
99                     throw new IllegalArgumentException(
100                             String.format("No file found at path: %s.", path));
101                 }
102             }
103             if (instr == null) {
104                 throw new NullPointerException(
105                         String.format("Cannot pass in null instrumentation."));
106             }
107             // Instantiation
108             sInstance = new HelperManager(paths, instr);
109         }
110         return sInstance;
111     }
112 
113     private Instrumentation mInstrumentation;
114     private List<String> mClasses;
115 
HelperManager(List<String> paths, Instrumentation instr)116     private HelperManager(List<String> paths, Instrumentation instr) {
117         mInstrumentation = instr;
118         // Collect all of the available classes
119         mClasses = new ArrayList<String>();
120         try {
121             for (String path : paths) {
122                 DexFile dex = new DexFile(path);
123                 mClasses.addAll(Collections.list(dex.entries()));
124             }
125         } catch (IOException e) {
126             throw new RuntimeException("Failed to retrieve the dex file.");
127         }
128     }
129 
130     /**
131      * Returns a concrete implementation of the helper interface supplied, if available.
132      *
133      * @param base the interface base class to find an implementation for
134      * @throws RuntimeException if no implementation is found
135      * @return a concrete implementation of base
136      */
get(Class<T> base)137     public <T extends AbstractStandardAppHelper> T get(Class<T> base) {
138         ClassLoader loader = HelperManager.class.getClassLoader();
139         // Iterate and search for the implementation
140         for (String className : mClasses) {
141             Class<?> clazz = null;
142             try {
143                 clazz = loader.loadClass(className);
144             } catch (ClassNotFoundException e) {
145                 Log.w(LOG_TAG, String.format("Class not found: %s", className));
146             }
147             if (base.isAssignableFrom(clazz) && !clazz.equals(base)) {
148                 // Instantiate the implementation class and return
149                 try {
150                     Constructor<?> constructor = clazz.getConstructor(Instrumentation.class);
151                     return (T)constructor.newInstance(mInstrumentation);
152                 } catch (NoSuchMethodException e) {
153                     Log.w(LOG_TAG, String.format("Failed to find a matching constructor for %s",
154                             className), e);
155                 } catch (IllegalAccessException e) {
156                     Log.w(LOG_TAG, String.format("Failed to access the constructor %s",
157                             className), e);
158                 } catch (InstantiationException e) {
159                     Log.w(LOG_TAG, String.format("Failed to instantiate %s",
160                             className), e);
161                 } catch (InvocationTargetException e) {
162                     Log.w(LOG_TAG, String.format("Exception encountered instantiating %s",
163                             className), e);
164                 }
165             }
166         }
167         throw new RuntimeException(
168                 String.format("Failed to find an implementation for %s", base.toString()));
169     }
170 }
171