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