/talkback_preics/src/com/google/android/marvin/talkback/ClassLoadingManager.java

http://eyes-free.googlecode.com/ · Java · 212 lines · 111 code · 21 blank · 80 comment · 13 complexity · ab883adfaccf03cf87957702dab642c3 MD5 · raw file

  1. /*
  2. * Copyright (C) 2010 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.google.android.marvin.talkback;
  17. import com.google.android.marvin.talkback.TalkBackService.InfrastructureStateListener;
  18. import android.content.Context;
  19. import android.content.pm.PackageInfo;
  20. import android.content.pm.PackageManager.NameNotFoundException;
  21. import android.util.Log;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. /**
  26. * This class manages efficient loading of classes.
  27. *
  28. * @author svetoslavganov@google.com (Svetoslav R. Ganov)
  29. */
  30. public class ClassLoadingManager implements InfrastructureStateListener {
  31. /**
  32. * Tag used for logging.
  33. */
  34. private static final String LOG_TAG = "ClassLoadingManager";
  35. /**
  36. * The singleton instance of this class.
  37. */
  38. private static ClassLoadingManager sInstance;
  39. /**
  40. * Mapping from class names to classes form outside packages.
  41. */
  42. private final HashMap<String, Class<?>> mClassNameToOutsidePackageClassMap = new HashMap<String, Class<?>>();
  43. /**
  44. * A set of classes not found to be loaded. Used to avoid multiple attempts
  45. * that will fail.
  46. */
  47. private final HashSet<String> mNotFoundClassesSet = new HashSet<String>();
  48. /**
  49. * Cache of installed packages to avoid class loading attempts
  50. */
  51. private final HashSet<String> mInstalledPackagesSet = new HashSet<String>();
  52. /**
  53. * Lock for mutex access to the cache.
  54. */
  55. private final Object mLock = new Object();
  56. /**
  57. * {@link Context} for accessing
  58. */
  59. private final Context mContext;
  60. /**
  61. * Monitor to keep track of the installed packages;
  62. */
  63. private final BasePackageMonitor mPackageMonitor;
  64. /**
  65. * The singleton instance of this class.
  66. *
  67. * @return The singleton instance of this class.
  68. */
  69. public static ClassLoadingManager getInstance() {
  70. if (sInstance == null) {
  71. sInstance = new ClassLoadingManager();
  72. }
  73. return sInstance;
  74. }
  75. /**
  76. * Creates a new instance.
  77. */
  78. private ClassLoadingManager() {
  79. mContext = TalkBackService.asContext();
  80. mPackageMonitor = new BasePackageMonitor() {
  81. @Override
  82. protected void onPackageAdded(String packageName) {
  83. synchronized (mLock) {
  84. mInstalledPackagesSet.add(packageName);
  85. // TODO (svetoslavganov): we can be more efficient
  86. mNotFoundClassesSet.clear();
  87. }
  88. }
  89. @Override
  90. protected void onPackageRemoved(String packageName) {
  91. synchronized (mLock) {
  92. mInstalledPackagesSet.remove(packageName);
  93. }
  94. }
  95. };
  96. }
  97. @Override
  98. public void onInfrastructureStateChange(boolean isInitialized) {
  99. if (isInitialized) {
  100. buildInstalledPackagesCache();
  101. mPackageMonitor.register(mContext);
  102. } else {
  103. clearInstalledPackagesCache();
  104. mPackageMonitor.unregister();
  105. }
  106. }
  107. /**
  108. * Builds a cache of installed packages.
  109. */
  110. private void buildInstalledPackagesCache() {
  111. synchronized (mLock) {
  112. List<PackageInfo> installedPackages = mContext.getPackageManager()
  113. .getInstalledPackages(0);
  114. for (PackageInfo installedPackage : installedPackages) {
  115. mInstalledPackagesSet.add(installedPackage.packageName);
  116. }
  117. }
  118. }
  119. /**
  120. * Clears the installed package cache.
  121. */
  122. private void clearInstalledPackagesCache() {
  123. synchronized (mLock) {
  124. mInstalledPackagesSet.clear();
  125. }
  126. }
  127. /**
  128. * Returns a class by given <code>className</code>. The loading proceeds as
  129. * follows: </br> 1. Try to load with the current context class loader (it
  130. * caches loaded classes). </br> 2. If (1) fails try if we have loaded the
  131. * class before and return it if that is the cases. </br> 3. If (2) failed,
  132. * try to create a package context and load the class. </p> Note: If the
  133. * package name is null and an attempt for loading of a package context is
  134. * required the it is extracted from the class name.
  135. *
  136. * @param context The context from which to first try loading the class.
  137. * @param className The name of the class to load.
  138. * @param packageName The name of the package to which the class belongs.
  139. * @return The class if loaded successfully, null otherwise.
  140. */
  141. public Class<?> loadOrGetCachedClass(Context context, String className, String packageName) {
  142. // if we failed once loading this class - no bother trying again
  143. if (mNotFoundClassesSet.contains(className)) {
  144. return null;
  145. }
  146. try {
  147. // try the current ClassLoader first
  148. return context.getClassLoader().loadClass(className);
  149. } catch (ClassNotFoundException cnfe) {
  150. // do we have a cached class
  151. Class<?> clazz = mClassNameToOutsidePackageClassMap.get(className);
  152. if (clazz != null) {
  153. return clazz;
  154. }
  155. // no package - get it from the class name
  156. if (packageName == null) {
  157. int lastDotIndex = className.lastIndexOf(".");
  158. if (lastDotIndex > -1) {
  159. packageName = className.substring(0, lastDotIndex);
  160. } else {
  161. return null;
  162. }
  163. }
  164. // no cached class - check if the package is installed first
  165. if (!mInstalledPackagesSet.contains(packageName)) {
  166. return null;
  167. }
  168. // all failed - try via creating a package context
  169. try {
  170. int flags = (Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
  171. try {
  172. Context packageContext = context.getApplicationContext().createPackageContext(
  173. packageName, flags);
  174. clazz = packageContext.getClassLoader().loadClass(className);
  175. mClassNameToOutsidePackageClassMap.put(className, clazz);
  176. } catch (NullPointerException npe) {
  177. String s = "";
  178. }
  179. return clazz;
  180. } catch (NameNotFoundException nnfe) {
  181. Log.e(LOG_TAG, "Error during loading an event source class: " + className + " "
  182. + nnfe);
  183. } catch (ClassNotFoundException cnfe2) {
  184. Log.e(LOG_TAG, "Error during loading an event source class: " + className + " "
  185. + cnfe);
  186. mNotFoundClassesSet.add(className);
  187. }
  188. return null;
  189. }
  190. }
  191. }