diff options
Diffstat (limited to 'tools')
25 files changed, 784 insertions, 225 deletions
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index a516a5a..0a4c68b 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -332,8 +332,8 @@ static status_t do_9patch(const char* imageName, image_info* image) int H = image->height; int i, j; - int maxSizeXDivs = (W / 2 + 1) * sizeof(int32_t); - int maxSizeYDivs = (H / 2 + 1) * sizeof(int32_t); + int maxSizeXDivs = W * sizeof(int32_t); + int maxSizeYDivs = H * sizeof(int32_t); int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs); int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs); uint8_t numXDivs = 0; @@ -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,8 +857,16 @@ 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); + + // If the image is a 9-patch, we need to preserve it as a ARGB file to make + // sure the pixels will not be pre-dithered/clamped until we decide they are + if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) { + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + switch (color_type) { case PNG_COLOR_TYPE_PALETTE: NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n", @@ -910,21 +930,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/Package.cpp b/tools/aapt/Package.cpp index 5d9e140..eb7d6f5 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -5,6 +5,7 @@ // #include "Main.h" #include "AaptAssets.h" +#include "ResourceTable.h" #include <utils.h> #include <utils/ZipFile.h> @@ -190,11 +191,20 @@ bail: ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets) { + ResourceFilter filter; + status_t status = filter.parse(bundle->getConfigurations()); + if (status != NO_ERROR) { + return -1; + } + ssize_t count = 0; const size_t N = assets->getGroupEntries().size(); for (size_t i=0; i<N; i++) { const AaptGroupEntry& ge = assets->getGroupEntries()[i]; + if (!filter.match(ge.toParams())) { + continue; + } ssize_t res = processAssets(bundle, zip, assets, ge); if (res < 0) { return res; 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/aidl/aidl.cpp b/tools/aidl/aidl.cpp index dc61567..fc658f5 100644 --- a/tools/aidl/aidl.cpp +++ b/tools/aidl/aidl.cpp @@ -610,6 +610,62 @@ generate_dep_file(const Options& options) } // ========================================================== +static string +generate_outputFileName(const Options& options, const document_item_type* items) +{ + string result; + + // items has already been checked to have only one interface. + if (items->item_type == INTERFACE_TYPE) { + interface_type* type = (interface_type*)items; + + // create the path to the destination folder based on the + // interface package name + result = options.outputBaseFolder; + result += OS_PATH_SEPARATOR; + + string package = type->package; + size_t len = package.length(); + for (size_t i=0; i<len; i++) { + if (package[i] == '.') { + package[i] = OS_PATH_SEPARATOR; + } + } + + result += package; + + // add the filename by replacing the .aidl extension to .java + const char* p = strchr(type->name.data, '.'); + len = p ? p-type->name.data : strlen(type->name.data); + + result += OS_PATH_SEPARATOR; + result.append(type->name.data, len); + result += ".java"; + } + + return result; +} + +// ========================================================== +static void +check_outputFileName(const string& path) { + size_t len = path.length(); + for (size_t i=0; i<len ; i++) { + if (path[i] == OS_PATH_SEPARATOR) { + string p = path.substr(0, i); + if (access(path.data(), F_OK) != 0) { +#ifdef HAVE_MS_C_RUNTIME + _mkdir(p.data()); +#else + mkdir(p.data(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); +#endif + } + } + } +} + + +// ========================================================== static int parse_preprocessed_file(const string& filename) { @@ -617,7 +673,7 @@ parse_preprocessed_file(const string& filename) FILE* f = fopen(filename.c_str(), "rb"); if (f == NULL) { - fprintf(stderr, "aidl: can't open preprocessd file: %s\n", + fprintf(stderr, "aidl: can't open preprocessed file: %s\n", filename.c_str()); return 1; } @@ -804,7 +860,17 @@ compile_aidl(const Options& options) generate_dep_file(options); } - err = generate_java(options.outputFileName, options.inputFileName.c_str(), + // if needed, generate the outputFileName from the outputBaseFolder + string outputFileName = options.outputFileName; + if (outputFileName.length() == 0 && + options.outputBaseFolder.length() > 0) { + outputFileName = generate_outputFileName(options, mainDoc); + } + + // make sure the folders of the output file all exists + check_outputFileName(outputFileName); + + err = generate_java(outputFileName, options.inputFileName.c_str(), (interface_type*)mainDoc); return err; diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp index 57b10ae..0aa7db2 100644 --- a/tools/aidl/options.cpp +++ b/tools/aidl/options.cpp @@ -13,16 +13,19 @@ usage() " aidl --preprocess OUTPUT INPUT...\n" "\n" "OPTIONS:\n" - " -I<DIR> search path for import statements.\n" - " -d<FILE> generate dependency file.\n" - " -p<FILE> file created by --preprocess to import.\n" - " -b fail when trying to compile a parcelable.\n" + " -I<DIR> search path for import statements.\n" + " -d<FILE> generate dependency file.\n" + " -p<FILE> file created by --preprocess to import.\n" + " -o<FOLDER> base output folder for generated files.\n" + " -b fail when trying to compile a parcelable.\n" "\n" "INPUT:\n" " An aidl interface file.\n" "\n" "OUTPUT:\n" - " The generated interface files. If omitted, the input filename is used, with the .aidl extension changed to a .java extension.\n" + " The generated interface files.\n" + " If omitted and the -o option is not used, the input filename is used, with the .aidl extension changed to a .java extension.\n" + " If the -o option is used, the generated files will be placed in the base output folder, under their package folder\n" ); return 1; } @@ -78,6 +81,14 @@ parse_options(int argc, const char* const* argv, Options *options) return usage(); } } + else if (s[1] == 'o') { + if (len > 2) { + options->outputBaseFolder = s+2; + } else { + fprintf(stderr, "-o option (%d) requires a path.\n", i); + return usage(); + } + } else if (len == 2 && s[1] == 'b') { options->failOnParcelable = true; } @@ -111,7 +122,7 @@ parse_options(int argc, const char* const* argv, Options *options) if (i < argc) { options->outputFileName = argv[i]; i++; - } else { + } else if (options->outputBaseFolder.length() == 0) { // copy input into output and change the extension from .aidl to .java options->outputFileName = options->inputFileName; string::size_type pos = options->outputFileName.size()-5; diff --git a/tools/aidl/options.h b/tools/aidl/options.h index e7e62ec..d88d988 100644 --- a/tools/aidl/options.h +++ b/tools/aidl/options.h @@ -21,6 +21,7 @@ struct Options vector<string> preprocessedFiles; string inputFileName; string outputFileName; + string outputBaseFolder; string depFileName; vector<string> filesToPreprocess; diff --git a/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java b/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java index 0810d29..df1876d 100644 --- a/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java +++ b/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java @@ -24,28 +24,35 @@ import java.util.Map; * <p/> * <p/>{@link #getApiLevel()} gives the ability to know which methods are available. * <p/> + * Changes in API level 3: + * <ul> + * <li>{@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * <li> deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * </ul> * Changes in API level 2: * <ul> * <li>{@link #getApiLevel()}</li> - * <li>{@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}, - * deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, String, Map, Map, IProjectCallback, ILayoutLog)}.</li> + * <li>{@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * <li>deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, String, Map, Map, IProjectCallback, ILayoutLog)}</li> * </ul> */ public interface ILayoutBridge { - final int API_CURRENT = 2; + final int API_CURRENT = 3; /** * Returns the API level of the layout library. * While no methods will ever be removed, some may become deprecated, and some new ones * will appear. + * <p/>If calling this method throws an {@link AbstractMethodError}, then the API level + * should be considered to be 1. */ int getApiLevel(); /** * Initializes the Bridge object. * @param fontOsLocation the location of the fonts. - * @param enumValueMap map attrName => { map enumFlagName => Integer value }. + * @param enumValueMap map attrName => { map enumFlagName => Integer value }. * @return true if success. * @since 1 */ @@ -56,8 +63,11 @@ public interface ILayoutBridge { * @param layoutDescription the {@link IXmlPullParser} letting the LayoutLib Bridge visit the * layout file. * @param projectKey An Object identifying the project. This is used for the cache mechanism. - * @param screenWidth - * @param screenHeight + * @param screenWidth the screen width + * @param screenHeight the screen height + * @param density the density factor for the screen. + * @param xdpi the screen actual dpi in X + * @param ydpi the screen actual dpi in Y * @param themeName The name of the theme to use. * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme. * @param projectResources the resources of the project. The map contains (String, map) pairs @@ -72,8 +82,41 @@ public interface ILayoutBridge { * the project. * @param logger the object responsible for displaying warning/errors to the user. * @return an {@link ILayoutResult} object that contains the result of the layout. + * @since 3 + */ + ILayoutResult computeLayout(IXmlPullParser layoutDescription, + Object projectKey, + int screenWidth, int screenHeight, int density, float xdpi, float ydpi, + String themeName, boolean isProjectTheme, + Map<String, Map<String, IResourceValue>> projectResources, + Map<String, Map<String, IResourceValue>> frameworkResources, + IProjectCallback projectCallback, ILayoutLog logger); + + /** + * Computes and renders a layout + * @param layoutDescription the {@link IXmlPullParser} letting the LayoutLib Bridge visit the + * layout file. + * @param projectKey An Object identifying the project. This is used for the cache mechanism. + * @param screenWidth the screen width + * @param screenHeight the screen height + * @param themeName The name of the theme to use. + * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme. + * @param projectResources the resources of the project. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the + * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, + * and the value is the resource value. + * @param frameworkResources the framework resources. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the map + * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the + * value is the resource value. + * @param projectCallback The {@link IProjectCallback} object to get information from + * the project. + * @param logger the object responsible for displaying warning/errors to the user. + * @return an {@link ILayoutResult} object that contains the result of the layout. + * @deprecated Use {@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)} * @since 2 */ + @Deprecated ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, int screenWidth, int screenHeight, String themeName, boolean isProjectTheme, @@ -102,7 +145,7 @@ public interface ILayoutBridge { * the project. * @param logger the object responsible for displaying warning/errors to the user. * @return an {@link ILayoutResult} object that contains the result of the layout. - * @deprecated Use {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}. + * @deprecated Use {@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)} * @since 1 */ @Deprecated 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..47a7ec0 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; @@ -40,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.BridgeInflater; import android.view.IWindow; @@ -55,6 +56,7 @@ import android.view.View.MeasureSpec; import android.view.WindowManager.LayoutParams; import android.widget.FrameLayout; +import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; @@ -72,6 +74,8 @@ public final class Bridge implements ILayoutBridge { private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; public static class StaticMethodNotImplementedException extends RuntimeException { + private static final long serialVersionUID = 1L; + public StaticMethodNotImplementedException(String msg) { super(msg); } @@ -91,14 +95,15 @@ public final class Bridge implements ILayoutBridge { private final static Map<String, Map<String, Integer>> sRFullMap = new HashMap<String, Map<String,Integer>>(); - private final static Map<Object, Map<String, Bitmap>> sProjectBitmapCache = - new HashMap<Object, Map<String, Bitmap>>(); - private final static Map<Object, Map<String, NinePatch>> sProject9PatchCache = - new HashMap<Object, Map<String, NinePatch>>(); - - private final static Map<String, Bitmap> sFrameworkBitmapCache = new HashMap<String, Bitmap>(); - private final static Map<String, NinePatch> sFramework9PatchCache = - new HashMap<String, NinePatch>(); + private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = + new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); + private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = + new HashMap<Object, Map<String, SoftReference<NinePatch>>>(); + + private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = + new HashMap<String, SoftReference<Bitmap>>(); + private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = + new HashMap<String, SoftReference<NinePatch>>(); private static Map<String, Map<String, Integer>> sEnumValueMap; @@ -277,20 +282,40 @@ public final class Bridge implements ILayoutBridge { isProjectTheme = true; } - return computeLayout(layoutDescription, projectKey, screenWidth, screenHeight, themeName, isProjectTheme, + return computeLayout(layoutDescription, projectKey, + screenWidth, screenHeight, DisplayMetrics.DEFAULT_DENSITY, + DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY, + themeName, isProjectTheme, projectResources, frameworkResources, customViewLoader, logger); } /* + * For compatilibty purposes, we implement the old deprecated version of computeLayout. * (non-Javadoc) * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) */ - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, - Object projectKey, + public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, int screenWidth, int screenHeight, String themeName, boolean isProjectTheme, Map<String, Map<String, IResourceValue>> projectResources, Map<String, Map<String, IResourceValue>> frameworkResources, IProjectCallback customViewLoader, ILayoutLog logger) { + return computeLayout(layoutDescription, projectKey, + screenWidth, screenHeight, DisplayMetrics.DEFAULT_DENSITY, + DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY, + themeName, isProjectTheme, + projectResources, frameworkResources, customViewLoader, logger); + } + + /* + * (non-Javadoc) + * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) + */ + public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, + int screenWidth, int screenHeight, int density, float xdpi, float ydpi, + String themeName, boolean isProjectTheme, + Map<String, Map<String, IResourceValue>> projectResources, + Map<String, Map<String, IResourceValue>> frameworkResources, + IProjectCallback customViewLoader, ILayoutLog logger) { if (logger == null) { logger = sDefaultLogger; } @@ -298,7 +323,7 @@ public final class Bridge implements ILayoutBridge { synchronized (sDefaultLogger) { sLogger = logger; } - + // find the current theme and compute the style inheritance map Map<IStyleResourceValue, IStyleResourceValue> styleParentMap = new HashMap<IStyleResourceValue, IStyleResourceValue>(); @@ -307,32 +332,42 @@ 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 { + // setup the display Metrics. + DisplayMetrics metrics = new DisplayMetrics(); + metrics.density = density / (float) DisplayMetrics.DEFAULT_DENSITY; + metrics.scaledDensity = metrics.density; + metrics.widthPixels = screenWidth; + metrics.heightPixels = screenHeight; + metrics.xdpi = xdpi; + metrics.ydpi = ydpi; + + context = new BridgeContext(projectKey, metrics, 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 +416,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; @@ -680,15 +719,21 @@ public final class Bridge implements ILayoutBridge { */ static Bitmap getCachedBitmap(String value, Object projectKey) { if (projectKey != null) { - Map<String, Bitmap> map = sProjectBitmapCache.get(projectKey); + Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); if (map != null) { - return map.get(value); + SoftReference<Bitmap> ref = map.get(value); + if (ref != null) { + return ref.get(); + } + } + } else { + SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); + if (ref != null) { + return ref.get(); } - - return null; } - - return sFrameworkBitmapCache.get(value); + + return null; } /** @@ -699,17 +744,17 @@ public final class Bridge implements ILayoutBridge { */ static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { if (projectKey != null) { - Map<String, Bitmap> map = sProjectBitmapCache.get(projectKey); + Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); if (map == null) { - map = new HashMap<String, Bitmap>(); + map = new HashMap<String, SoftReference<Bitmap>>(); sProjectBitmapCache.put(projectKey, map); } - map.put(value, bmp); + map.put(value, new SoftReference<Bitmap>(bmp)); + } else { + sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp)); } - - sFrameworkBitmapCache.put(value, bmp); } /** @@ -721,16 +766,22 @@ public final class Bridge implements ILayoutBridge { */ static NinePatch getCached9Patch(String value, Object projectKey) { if (projectKey != null) { - Map<String, NinePatch> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); if (map != null) { - return map.get(value); + SoftReference<NinePatch> ref = map.get(value); + if (ref != null) { + return ref.get(); + } + } + } else { + SoftReference<NinePatch> ref = sFramework9PatchCache.get(value); + if (ref != null) { + return ref.get(); } - - return null; } - return sFramework9PatchCache.get(value); + return null; } /** @@ -741,19 +792,19 @@ public final class Bridge implements ILayoutBridge { */ static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) { if (projectKey != null) { - Map<String, NinePatch> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); if (map == null) { - map = new HashMap<String, NinePatch>(); + map = new HashMap<String, SoftReference<NinePatch>>(); sProject9PatchCache.put(projectKey, map); } - map.put(value, ninePatch); + map.put(value, new SoftReference<NinePatch>(ninePatch)); + } else { + sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); } - - sFramework9PatchCache.put(value, ninePatch); } - + /** * Implementation of {@link IWindowSession} so that mSession is not null in * the {@link SurfaceView}. @@ -784,6 +835,12 @@ public final class Bridge implements ILayoutBridge { } @SuppressWarnings("unused") + public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { + // pass for now. + return false; + } + + @SuppressWarnings("unused") public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { // pass for now. return null; 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..baa3d53 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java @@ -84,16 +84,24 @@ public final class BridgeContext extends Context { private final IProjectCallback mProjectCallback; private final ILayoutLog mLogger; + private BridgeContentResolver mContentResolver; /** - * @param projectKey - * @param currentTheme - * @param projectResources - * @param frameworkResources + * @param projectKey An Object identifying the project. This is used for the cache mechanism. + * @param metrics the {@link DisplayMetrics}. + * @param themeName The name of the theme to use. + * @param projectResources the resources of the project. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the + * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, + * and the value is the resource value. + * @param frameworkResources the framework resources. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the map + * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the + * value is the resource value. * @param styleInheritanceMap * @param customViewLoader */ - public BridgeContext(Object projectKey, + public BridgeContext(Object projectKey, DisplayMetrics metrics, IStyleResourceValue currentTheme, Map<String, Map<String, IResourceValue>> projectResources, Map<String, Map<String, IResourceValue>> frameworkResources, @@ -104,12 +112,10 @@ public final class BridgeContext extends Context { mLogger = logger; Configuration config = new Configuration(); - 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 +174,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 +911,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/Android.mk b/tools/preload/Android.mk index d3457fe..e6fa103 100644 --- a/tools/preload/Android.mk +++ b/tools/preload/Android.mk @@ -2,7 +2,19 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := *.java +LOCAL_SRC_FILES := \ + ClassRank.java \ + Compile.java \ + LoadedClass.java \ + MemoryUsage.java \ + Operation.java \ + Policy.java \ + PrintCsv.java \ + PrintPsTree.java \ + Proc.java \ + Record.java \ + Root.java \ + WritePreloadedClassFile.java LOCAL_MODULE:= preload 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); } } |