diff options
Diffstat (limited to 'tools')
19 files changed, 518 insertions, 165 deletions
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index a516a5a..4c776fb 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -600,10 +600,22 @@ static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) { return true; } -static void dump_image(int w, int h, png_bytepp rows, int bpp) +static void dump_image(int w, int h, png_bytepp rows, int color_type) { int i, j, rr, gg, bb, aa; + int bpp; + if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { + bpp = 1; + } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + bpp = 2; + } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + // We use a padding byte even when there is no alpha + bpp = 4; + } else { + printf("Unknown color type %d.\n", color_type); + } + for (j = 0; j < h; j++) { png_bytep row = rows[j]; for (i = 0; i < w; i++) { @@ -640,7 +652,7 @@ static void dump_image(int w, int h, png_bytepp rows, int bpp) #define MAX(a,b) ((a)>(b)?(a):(b)) #define ABS(a) ((a)<0?-(a):(a)) -static void analyze_image(image_info &imageInfo, int grayscaleTolerance, +static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance, png_colorp rgbPalette, png_bytep alphaPalette, int *paletteEntries, bool *hasTransparency, int *colorType, png_bytepp outRows) @@ -662,7 +674,7 @@ static void analyze_image(image_info &imageInfo, int grayscaleTolerance, // 3. There are no more than 256 distinct RGBA colors // NOISY(printf("Initial image data:\n")); - // dump_image(w, h, imageInfo.rows, 4); + // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA); for (j = 0; j < h; j++) { png_bytep row = imageInfo.rows[j]; @@ -763,7 +775,7 @@ static void analyze_image(image_info &imageInfo, int grayscaleTolerance, *colorType = PNG_COLOR_TYPE_PALETTE; } else { if (maxGrayDeviation <= grayscaleTolerance) { - NOISY(printf("Forcing image to gray (max deviation = %d)\n", maxGrayDeviation)); + printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation); *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; } else { *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; @@ -845,7 +857,7 @@ static void write_png(const char* imageName, bool hasTransparency; int paletteEntries; - analyze_image(imageInfo, grayscaleTolerance, rgbPalette, alphaPalette, + analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette, &paletteEntries, &hasTransparency, &color_type, outRows); switch (color_type) { case PNG_COLOR_TYPE_PALETTE: @@ -910,21 +922,8 @@ static void write_png(const char* imageName, } png_write_image(write_ptr, rows); -// int bpp; -// if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { -// bpp = 1; -// } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { -// bpp = 2; -// } else if (color_type == PNG_COLOR_TYPE_RGB) { -// bpp = 4; -// } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { -// bpp = 4; -// } else { -// printf("Uknknown color type %d, exiting.\n", color_type); -// exit(1); -// } // NOISY(printf("Final image data:\n")); -// dump_image(imageInfo.width, imageInfo.height, rows, bpp); +// dump_image(imageInfo.width, imageInfo.height, rows, color_type); png_write_end(write_ptr, write_info); diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index ee0dbad..71b1a3c 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -146,9 +146,11 @@ int handleCommand(Bundle* bundle) */ int main(int argc, char* const argv[]) { + char *prog = argv[0]; Bundle bundle; bool wantUsage = false; int result = 1; // pessimistically assume an error. + int tolerance = 0; /* default to compression */ bundle.setCompressionMethod(ZipEntry::kCompressDeflated); @@ -214,7 +216,9 @@ int main(int argc, char* const argv[]) wantUsage = true; goto bail; } - bundle.setGrayscaleTolerance(atoi(argv[0])); + tolerance = atoi(argv[0]); + bundle.setGrayscaleTolerance(tolerance); + printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance); break; case 'm': bundle.setMakePackageDirs(true); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index c438366..6f71a1e 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -644,6 +644,7 @@ status_t compileResourceFile(Bundle* bundle, const String16 bool16("bool"); const String16 integer16("integer"); const String16 dimen16("dimen"); + const String16 fraction16("fraction"); const String16 style16("style"); const String16 plurals16("plurals"); const String16 array16("array"); @@ -1022,6 +1023,10 @@ status_t compileResourceFile(Bundle* bundle, curTag = &dimen16; curType = dimen16; curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION; + } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) { + curTag = &fraction16; + curType = fraction16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION; } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) { curTag = &bag16; curIsBag = true; diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index 2ea453c..d476567 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -220,9 +220,9 @@ moveon: spanStack.pop(); if (empty) { - fprintf(stderr, "%s:%d: WARNING: empty '%s' span found for at text '%s'\n", + fprintf(stderr, "%s:%d: WARNING: empty '%s' span found in text '%s'\n", fileName, inXml->getLineNumber(), - String8(*outString).string(), String8(spanTag).string()); + String8(spanTag).string(), String8(*outString).string()); } } else if (code == ResXMLTree::START_NAMESPACE) { diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java index 13cc62d..ade07d6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java @@ -380,7 +380,6 @@ public class Paint extends _Original_Paint { int filterNative = 0; if (filter != null) filterNative = filter.native_instance; - native_setColorFilter(mNativePaint, filterNative); mColorFilter = filter; return filter; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index f5087d9..39200a1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -27,8 +27,8 @@ import com.android.layoutlib.api.IXmlPullParser; import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; import com.android.layoutlib.bridge.LayoutResult.LayoutViewInfo; import com.android.ninepatch.NinePatch; -import com.android.tools.layoutlib.create.OverrideMethod; import com.android.tools.layoutlib.create.MethodAdapter; +import com.android.tools.layoutlib.create.OverrideMethod; import android.graphics.Bitmap; import android.graphics.Rect; @@ -307,32 +307,33 @@ public final class Bridge implements ILayoutBridge { projectResources.get(BridgeConstants.RES_STYLE), frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap); - BridgeContext context = new BridgeContext(projectKey, currentTheme, projectResources, - frameworkResources, styleParentMap, customViewLoader, logger); - BridgeInflater inflater = new BridgeInflater(context, customViewLoader); - context.setBridgeInflater(inflater); - - IResourceValue windowBackground = null; - int screenOffset = 0; - if (currentTheme != null) { - windowBackground = context.findItemInStyle(currentTheme, "windowBackground"); - windowBackground = context.resolveResValue(windowBackground); - - screenOffset = getScreenOffset(currentTheme, context); - } - - // we need to make sure the Looper has been initialized for this thread. - // this is required for View that creates Handler objects. - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription, - context, false /* platformResourceFlag */); - - ViewGroup root = new FrameLayout(context); - + BridgeContext context = null; try { + context = new BridgeContext(projectKey, currentTheme, projectResources, + frameworkResources, styleParentMap, customViewLoader, logger); + BridgeInflater inflater = new BridgeInflater(context, customViewLoader); + context.setBridgeInflater(inflater); + + IResourceValue windowBackground = null; + int screenOffset = 0; + if (currentTheme != null) { + windowBackground = context.findItemInStyle(currentTheme, "windowBackground"); + windowBackground = context.resolveResValue(windowBackground); + + screenOffset = getScreenOffset(currentTheme, context); + } + + // we need to make sure the Looper has been initialized for this thread. + // this is required for View that creates Handler objects. + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription, + context, false /* platformResourceFlag */); + + ViewGroup root = new FrameLayout(context); + View view = inflater.inflate(parser, root); // set the AttachInfo on the root view. @@ -381,6 +382,10 @@ public final class Bridge implements ILayoutBridge { return new LayoutResult(ILayoutResult.ERROR, t.getClass().getSimpleName() + ": " + t.getMessage()); } finally { + // Make sure to remove static references, otherwise we could not unload the lib + BridgeResources.clearSystem(); + BridgeAssetManager.clearSystem(); + // Remove the global logger synchronized (sDefaultLogger) { sLogger = sDefaultLogger; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java index 02545af..1fa11af 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java @@ -24,9 +24,36 @@ import java.util.Locale; public class BridgeAssetManager extends AssetManager { /** - * Change the configuation used when retrieving resources. Not for use by - * applications. - * {@hide} + * This initializes the static field {@link AssetManager#mSystem} which is used + * by methods who get a global asset manager using {@link AssetManager#getSystem()}. + * <p/> + * They will end up using our bridge asset manager. + * <p/> + * {@link Bridge} calls this method after setting up a new bridge. + */ + /*package*/ static AssetManager initSystem() { + if (!(AssetManager.mSystem instanceof BridgeAssetManager)) { + // Note that AssetManager() creates a system AssetManager and we override it + // with our BridgeAssetManager. + AssetManager.mSystem = new BridgeAssetManager(); + AssetManager.mSystem.makeStringBlocks(false); + } + return AssetManager.mSystem; + } + + /** + * Clears the static {@link AssetManager#mSystem} to make sure we don't leave objects + * around that would prevent us from unloading the library. + */ + /*package*/ static void clearSystem() { + AssetManager.mSystem = null; + } + + private BridgeAssetManager() { + } + + /** + * Change the configuration used when retrieving resources. Not for use by applications. */ @Override public void setConfiguration(int mcc, int mnc, String locale, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java new file mode 100644 index 0000000..727d6f2 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge; + +import android.content.ContentResolver; +import android.content.ContentServiceNative; +import android.content.Context; +import android.content.IContentProvider; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; + +/** + * A mock content resolver for the LayoutLib Bridge. + * <p/> + * It won't serve any actual data but it's good enough for all + * the widgets which expect to have a content resolver available via + * {@link BridgeContext#getContentResolver()}. + */ +public class BridgeContentResolver extends ContentResolver { + + public BridgeContentResolver(Context context) { + super(context); + } + + @Override + public IContentProvider acquireProvider(Context c, String name) { + // ignore + return null; + } + + @Override + public boolean releaseProvider(IContentProvider icp) { + // ignore + return false; + } + + /** + * Stub for the layoutlib bridge content resolver. + * <p/> + * The super implementation accesses the {@link ContentServiceNative#getDefault()} + * which returns null and would make the call crash. Instead we do nothing. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + * <p/> + * The super implementation accesses the {@link ContentServiceNative#getDefault()} + * which returns null and would make the call crash. Instead we do nothing. + */ + @Override + public void unregisterContentObserver(ContentObserver observer) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + * <p/> + * The super implementation accesses the {@link ContentServiceNative#getDefault()} + * which returns null and would make the call crash. Instead we do nothing. + */ + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + * <p/> + * The super implementation accesses the {@link ContentServiceNative#getDefault()} + * which returns null and would make the call crash. Instead we do nothing. + */ + @Override + public void startSync(Uri uri, Bundle extras) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + * <p/> + * The super implementation accesses the {@link ContentServiceNative#getDefault()} + * which returns null and would make the call crash. Instead we do nothing. + */ + @Override + public void cancelSync(Uri uri) { + // pass + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java index cb9509b..0df20e4 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java @@ -84,6 +84,7 @@ public final class BridgeContext extends Context { private final IProjectCallback mProjectCallback; private final ILayoutLog mLogger; + private BridgeContentResolver mContentResolver; /** * @param projectKey @@ -107,9 +108,11 @@ public final class BridgeContext extends Context { DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - mResources = new BridgeResources( + + AssetManager assetManager = BridgeAssetManager.initSystem(); + mResources = BridgeResources.initSystem( this, - new BridgeAssetManager(), + assetManager, metrics, config, customViewLoader); @@ -168,6 +171,12 @@ public final class BridgeContext extends Context { if (LAYOUT_INFLATER_SERVICE.equals(service)) { return mInflater; } + + // AutoCompleteTextView and MultiAutoCompleteTextView want a window + // service. We don't have any but it's not worth an exception. + if (WINDOW_SERVICE.equals(service)) { + return null; + } throw new UnsupportedOperationException("Unsupported Service: " + service); } @@ -899,8 +908,10 @@ public final class BridgeContext extends Context { @Override public ContentResolver getContentResolver() { - // TODO Auto-generated method stub - return null; + if (mContentResolver == null) { + mContentResolver = new BridgeContentResolver(this); + } + return mContentResolver; } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java index 6ab6e32..0bcc7fd 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java @@ -51,11 +51,46 @@ public final class BridgeResources extends Resources { private IProjectCallback mProjectCallback; private boolean[] mPlatformResourceFlag = new boolean[1]; - public BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics, - Configuration config, IProjectCallback projetCallback) { + /** + * This initializes the static field {@link Resources#mSystem} which is used + * by methods who get global resources using {@link Resources#getSystem()}. + * <p/> + * They will end up using our bridge resources. + * <p/> + * {@link Bridge} calls this method after setting up a new bridge. + */ + /*package*/ static Resources initSystem(BridgeContext context, + AssetManager assets, + DisplayMetrics metrics, + Configuration config, + IProjectCallback projectCallback) { + if (!(Resources.mSystem instanceof BridgeResources)) { + Resources.mSystem = new BridgeResources(context, + assets, + metrics, + config, + projectCallback); + } + return Resources.mSystem; + } + + /** + * Clears the static {@link Resources#mSystem} to make sure we don't leave objects + * around that would prevent us from unloading the library. + */ + /*package*/ static void clearSystem() { + if (Resources.mSystem instanceof BridgeResources) { + ((BridgeResources)(Resources.mSystem)).mContext = null; + ((BridgeResources)(Resources.mSystem)).mProjectCallback = null; + } + Resources.mSystem = null; + } + + private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics, + Configuration config, IProjectCallback projectCallback) { super(assets, metrics, config); mContext = context; - mProjectCallback = projetCallback; + mProjectCallback = projectCallback; } public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java index ca3c8b0..1ca3182 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java @@ -24,7 +24,8 @@ import android.widget.TextView; /** * Base class for mocked views. * - * TODO: implement onDraw and draw a rectangle in a random color with the name of the class (or better the id of the view). + * TODO: implement onDraw and draw a rectangle in a random color with the name of the class + * (or better the id of the view). */ public class MockView extends TextView { diff --git a/tools/preload/Compile.java b/tools/preload/Compile.java index 8388b12..67258ef 100644 --- a/tools/preload/Compile.java +++ b/tools/preload/Compile.java @@ -15,26 +15,22 @@ */ import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.IOException; import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.ObjectOutputStream; -import java.io.BufferedOutputStream; -import java.util.List; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.ArrayList; -import java.lang.reflect.InvocationTargetException; +import java.util.List; /** * Parses and analyzes a log, pulling our PRELOAD information. If you have * an emulator or device running in the background, this class will use it * to measure and record the memory usage of each class. + * + * TODO: Should analyze lines and select substring dynamically (instead of hardcoded 19) */ public class Compile { - public static void main(String[] args) - throws IOException, NoSuchMethodException, IllegalAccessException, - InvocationTargetException { + public static void main(String[] args) throws IOException { if (args.length != 2) { System.err.println("Usage: Compile [log file] [output file]"); System.exit(0); @@ -48,10 +44,17 @@ public class Compile { new FileInputStream(args[0]))); String line; + int lineNumber = 0; while ((line = in.readLine()) != null) { + lineNumber++; if (line.startsWith("I/PRELOAD")) { - line = line.substring(19); - records.add(new Record(line)); + try { + String clipped = line.substring(19); + records.add(new Record(clipped, lineNumber)); + } catch (RuntimeException e) { + throw new RuntimeException( + "Exception while recording line " + lineNumber + ": " + line, e); + } } } diff --git a/tools/preload/LoadedClass.java b/tools/preload/LoadedClass.java index 6b3d345..5782807 100644 --- a/tools/preload/LoadedClass.java +++ b/tools/preload/LoadedClass.java @@ -14,12 +14,11 @@ * limitations under the License. */ -import java.util.List; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Set; -import java.util.HashSet; -import java.io.Serializable; /** * A loaded class. @@ -134,6 +133,7 @@ class LoadedClass implements Serializable, Comparable<LoadedClass> { return name.compareTo(o.name); } + @Override public String toString() { return name; } diff --git a/tools/preload/MemoryUsage.java b/tools/preload/MemoryUsage.java index 89717eb..e5dfb2a 100644 --- a/tools/preload/MemoryUsage.java +++ b/tools/preload/MemoryUsage.java @@ -32,6 +32,9 @@ class MemoryUsage implements Serializable { private static final long serialVersionUID = 0; static final MemoryUsage NOT_AVAILABLE = new MemoryUsage(); + + static int errorCount = 0; + static final int MAXIMUM_ERRORS = 10; // give up after this many fails final int nativeSharedPages; final int javaSharedPages; @@ -160,6 +163,13 @@ class MemoryUsage implements Serializable { * Measures memory usage for the given class. */ static MemoryUsage forClass(String className) { + + // This is a coarse approximation for determining that no device is connected, + // or that the communication protocol has changed, but we'll keep going and stop whining. + if (errorCount >= MAXIMUM_ERRORS) { + return NOT_AVAILABLE; + } + MeasureWithTimeout measurer = new MeasureWithTimeout(className); new Thread(measurer).start(); @@ -237,6 +247,7 @@ class MemoryUsage implements Serializable { if (line == null || !line.startsWith("DECAFBAD,")) { System.err.println("Got bad response for " + className + ": " + line); + errorCount += 1; return NOT_AVAILABLE; } diff --git a/tools/preload/Operation.java b/tools/preload/Operation.java index 65c9a03..4f1938e 100644 --- a/tools/preload/Operation.java +++ b/tools/preload/Operation.java @@ -115,4 +115,22 @@ class Operation implements Serializable { } return microsInt; } + + /** + * Primarily for debugger support + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(type.toString()); + sb.append(' '); + sb.append(loadedClass.toString()); + if (subops.size() > 0) { + sb.append(" ("); + sb.append(subops.size()); + sb.append(" sub ops)"); + } + return sb.toString(); + } + } diff --git a/tools/preload/Policy.java b/tools/preload/Policy.java new file mode 100644 index 0000000..554966b --- /dev/null +++ b/tools/preload/Policy.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * This is not instantiated - we just provide data for other classes to use + */ +public class Policy { + + /** + * This location (in the build system) of the preloaded-classes file. + */ + private static final String PRELOADED_CLASS_FILE = "frameworks/base/preloaded-classes"; + + /** + * The internal process name of the system process. Note, this also shows up as + * "system_process", e.g. in ddms. + */ + private static final String SYSTEM_SERVER_PROCESS_NAME = "system_server"; + + /** + * Names of non-application processes - these will not be checked for preloaded classes. + * + * TODO: Replace this hardcoded list with a walk up the parent chain looking for zygote. + */ + private static final Set<String> NOT_FROM_ZYGOTE = new HashSet<String>(Arrays.asList( + "zygote", + "dexopt", + "unknown", + SYSTEM_SERVER_PROCESS_NAME, + "com.android.development", + "app_process" // am & other shell commands + )); + + /** + * Long running services. These are restricted in their contribution to the preloader + * because their launch time is less critical. + */ + private static final Set<String> SERVICES = new HashSet<String>(Arrays.asList( + SYSTEM_SERVER_PROCESS_NAME, + "com.android.acore", + // Commented out to make sure DefaultTimeZones gets preloaded. + // "com.android.phone", + "com.google.process.content", + "android.process.media" + )); + + /** + * Classes which we shouldn't load from the Zygote. + */ + private static final Set<String> EXCLUDED_CLASSES = new HashSet<String>(Arrays.asList( + // Binders + "android.app.AlarmManager", + "android.app.SearchManager", + "android.os.FileObserver", + "com.android.server.PackageManagerService$AppDirObserver", + + // Threads + "android.os.AsyncTask", + "android.pim.ContactsAsyncHelper", + "java.lang.ProcessManager" + + )); + + /** + * No constructor - use static methods only + */ + private Policy() {} + + /** + * Returns the path/file name of the preloaded classes file that will be written + * by WritePreloadedClassFile. + */ + public static String getPreloadedClassFileName() { + return PRELOADED_CLASS_FILE; + } + + /** + * Reports if a given process name was created from zygote + */ + public static boolean isFromZygote(String processName) { + return !NOT_FROM_ZYGOTE.contains(processName); + } + + /** + * Reports if the given process name is a "long running" process or service + */ + public static boolean isService(String processName) { + return SERVICES.contains(processName); + } + + /** + * Reports if the given class should never be preloaded + */ + public static boolean isPreloadableClass(String className) { + return !EXCLUDED_CLASSES.contains(className); + } +} diff --git a/tools/preload/Proc.java b/tools/preload/Proc.java index 0b27a51..22697f8 100644 --- a/tools/preload/Proc.java +++ b/tools/preload/Proc.java @@ -43,49 +43,6 @@ class Proc implements Serializable { */ static final int MAX_TO_PRELOAD = 100; - /** Name of system server process. */ - private static final String SYSTEM_SERVER = "system_server"; - - /** Names of non-application processes. */ - private static final Set<String> NOT_FROM_ZYGOTE - = new HashSet<String>(Arrays.asList( - "zygote", - "dexopt", - "unknown", - SYSTEM_SERVER, - "com.android.development", - "app_process" // am - )); - - /** Long running services. */ - private static final Set<String> SERVICES - = new HashSet<String>(Arrays.asList( - SYSTEM_SERVER, - "com.android.home", -// Commented out to make sure DefaultTimeZones gets preloaded. -// "com.android.phone", - "com.google.process.content", - "com.android.process.media" - )); - - /** - * Classes which we shouldn't load from the Zygote. - */ - static final Set<String> EXCLUDED_CLASSES - = new HashSet<String>(Arrays.asList( - // Binders - "android.app.AlarmManager", - "android.app.SearchManager", - "android.os.FileObserver", - "com.android.server.PackageManagerService$AppDirObserver", - - // Threads - "java.lang.ProcessManager", - - // This class was deleted. - "java.math.Elementary" - )); - /** Parent process. */ final Proc parent; @@ -139,17 +96,12 @@ class Proc implements Serializable { } /** - * Is this a long running process? - */ - boolean isService() { - return SERVICES.contains(this.name); - } - - /** * Returns a list of classes which should be preloaded. + * + * @param takeAllClasses forces all classes to be taken (irrespective of ranking) */ - List<LoadedClass> highestRankedClasses() { - if (NOT_FROM_ZYGOTE.contains(this.name)) { + List<LoadedClass> highestRankedClasses(boolean takeAllClasses) { + if (!isApplication()) { return Collections.emptyList(); } @@ -162,23 +114,30 @@ class Proc implements Serializable { int timeToSave = totalTimeMicros() * percentageToPreload() / 100; int timeSaved = 0; - boolean service = isService(); + boolean service = Policy.isService(this.name); List<LoadedClass> highest = new ArrayList<LoadedClass>(); for (Operation operation : ranked) { - if (highest.size() >= MAX_TO_PRELOAD) { - System.out.println(name + " got " - + (timeSaved * 100 / timeToSave) + "% through"); - - break; + + // These are actual ranking decisions, which can be overridden + if (!takeAllClasses) { + if (highest.size() >= MAX_TO_PRELOAD) { + System.out.println(name + " got " + + (timeSaved * 100 / timeToSave) + "% through"); + break; + } + + if (timeSaved >= timeToSave) { + break; + } } - if (timeSaved >= timeToSave) { - break; + // The remaining rules apply even to wired-down processes + if (!Policy.isPreloadableClass(operation.loadedClass.name)) { + continue; } - - if (EXCLUDED_CLASSES.contains(operation.loadedClass.name) - || !operation.loadedClass.systemClass) { + + if (!operation.loadedClass.systemClass) { continue; } @@ -205,9 +164,13 @@ class Proc implements Serializable { return totalTime; } - /** Returns true if this process is an app. */ + /** + * Returns true if this process is an app. + * + * TODO: Replace the hardcoded list with a walk up the parent chain looking for zygote. + */ public boolean isApplication() { - return !NOT_FROM_ZYGOTE.contains(name); + return Policy.isFromZygote(name); } /** diff --git a/tools/preload/Record.java b/tools/preload/Record.java index 9ffab46..b2be4d4 100644 --- a/tools/preload/Record.java +++ b/tools/preload/Record.java @@ -56,11 +56,14 @@ class Record { /** Record time (ns). */ final long time; + + /** Source file line# */ + int sourceLineNumber; /** * Parses a line from the loaded-classes file. */ - Record(String line) { + Record(String line, int lineNum) { char typeChar = line.charAt(0); switch (typeChar) { case '>': type = Type.START_LOAD; break; @@ -70,6 +73,8 @@ class Record { default: throw new AssertionError("Bad line: " + line); } + sourceLineNumber = lineNum; + line = line.substring(1); String[] parts = line.split(":"); @@ -77,20 +82,51 @@ class Record { pid = Integer.parseInt(parts[1]); tid = Integer.parseInt(parts[2]); - processName = parts[3].intern(); + processName = decode(parts[3]).intern(); classLoader = Integer.parseInt(parts[4]); - className = vmTypeToLanguage(parts[5]).intern(); + className = vmTypeToLanguage(decode(parts[5])).intern(); time = Long.parseLong(parts[6]); } + + /** + * Decode any escaping that may have been written to the log line. + * + * Supports unicode-style escaping: \\uXXXX = character in hex + * + * @param rawField the field as it was written into the log + * @result the same field with any escaped characters replaced + */ + String decode(String rawField) { + String result = rawField; + int offset = result.indexOf("\\u"); + while (offset >= 0) { + String before = result.substring(0, offset); + String escaped = result.substring(offset+2, offset+6); + String after = result.substring(offset+6); + + result = String.format("%s%c%s", before, Integer.parseInt(escaped, 16), after); + + // find another but don't recurse + offset = result.indexOf("\\u", offset + 1); + } + return result; + } /** * Converts a VM-style name to a language-style name. */ - static String vmTypeToLanguage(String typeName) { + String vmTypeToLanguage(String typeName) { + // if the typename is (null), just return it as-is. This is probably in dexopt and + // will be discarded anyway. NOTE: This corresponds to the case in dalvik/vm/oo/Class.c + // where dvmLinkClass() returns false and we clean up and exit. + if ("(null)".equals(typeName)) { + return typeName; + } + if (!typeName.startsWith("L") || !typeName.endsWith(";") ) { - throw new AssertionError("Bad name: " + typeName); + throw new AssertionError("Bad name: " + typeName + " in line " + sourceLineNumber); } typeName = typeName.substring(1, typeName.length() - 1); diff --git a/tools/preload/WritePreloadedClassFile.java b/tools/preload/WritePreloadedClassFile.java index 5596a62..d87b1f0 100644 --- a/tools/preload/WritePreloadedClassFile.java +++ b/tools/preload/WritePreloadedClassFile.java @@ -14,54 +14,69 @@ * limitations under the License. */ -import java.io.IOException; -import java.io.Writer; import java.io.BufferedWriter; -import java.io.OutputStreamWriter; import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.List; /** - * Writes /java/android/preloaded-classes. Also updates LoadedClass.preloaded + * Writes /frameworks/base/preloaded-classes. Also updates LoadedClass.preloaded * fields and writes over compiled log file. */ public class WritePreloadedClassFile { - private static final String PRELOADED_CLASS_FILE - = "java/android/preloaded-classes"; - - public static void main(String[] args) - throws IOException, ClassNotFoundException { - if (args.length != 1) { - System.err.println( - "Usage: WritePreloadedClassFile [compiled log file]"); + public static void main(String[] args) throws IOException, ClassNotFoundException { + + // Process command-line arguments first + List<String> wiredProcesses = new ArrayList<String>(); + String inputFileName = null; + int argOffset = 0; + try { + while ("--preload-all-process".equals(args[argOffset])) { + argOffset++; + wiredProcesses.add(args[argOffset++]); + } + + inputFileName = args[argOffset++]; + } catch (RuntimeException e) { + System.err.println("Usage: WritePreloadedClassFile " + + "[--preload-all-process process-name] " + + "[compiled log file]"); System.exit(0); } - Root root = Root.fromFile(args[0]); + Root root = Root.fromFile(inputFileName); for (LoadedClass loadedClass : root.loadedClasses.values()) { loadedClass.preloaded = false; } Writer out = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(PRELOADED_CLASS_FILE), + new FileOutputStream(Policy.getPreloadedClassFileName()), Charset.forName("US-ASCII"))); - out.write("# Classes which are preloaded by " + - "com.android.internal.os.ZygoteInit.\n"); - out.write("# Automatically generated by /tools/preload.\n"); + out.write("# Classes which are preloaded by com.android.internal.os.ZygoteInit.\n"); + out.write("# Automatically generated by /frameworks/base/tools/preload.\n"); out.write("# percent=" + Proc.PERCENTAGE_TO_PRELOAD + ", weight=" + ClassRank.SEQUENCE_WEIGHT + ", bucket_size=" + ClassRank.BUCKET_SIZE + "\n"); + for (String wiredProcess : wiredProcesses) { + out.write("# forcing classes loaded by: " + wiredProcess + "\n"); + } Set<LoadedClass> highestRanked = new TreeSet<LoadedClass>(); for (Proc proc : root.processes.values()) { - List<LoadedClass> highestForProc = proc.highestRankedClasses(); + // test to see if this is one of the wired-down ("take all classes") processes + boolean isWired = wiredProcesses.contains(proc.name); + + List<LoadedClass> highestForProc = proc.highestRankedClasses(isWired); System.out.println(proc.name + ": " + highestForProc.size()); @@ -82,6 +97,6 @@ public class WritePreloadedClassFile { + " classes will be preloaded."); // Update data to reflect LoadedClass.preloaded changes. - root.toFile(args[0]); + root.toFile(inputFileName); } } |